using Bro.Common.Helper;
|
using Bro.UI.Model.Winform;
|
using Newtonsoft.Json;
|
using Newtonsoft.Json.Linq;
|
using System;
|
using System.Configuration;
|
using System.IO;
|
using WeifenLuo.WinFormsUI.Docking;
|
using static Bro.Common.Helper.EnumHelper;
|
|
namespace Bro.UI.Config.MenuForms
|
{
|
[DockOption(DockState.DockRight, 230, 0)]
|
[MenuNode("Statistic", "生产信息", 2, "View1", true)]
|
public partial class FrmStatistic : MenuFrmBase
|
{
|
#region Properties
|
private int qty_OK = 0;
|
private int qty_NG = 0;
|
private int qty_All = 0;
|
|
private float qty_ok_rate = 0;
|
private float qty_ng_rate = 0;
|
|
private float ct = 0;
|
private int uph = 0;
|
|
private DateTime? startTime = null;
|
private TimeSpan runTime = new TimeSpan();
|
private TimeSpan idleTime = new TimeSpan();
|
private TimeSpan downTime = new TimeSpan();
|
private TimeSpan availableTime = new TimeSpan();
|
private int qty_OEE = 0;
|
private int qty_OEE_OK = 0;
|
private float oee = 0;
|
|
public int Qty_OK
|
{
|
get => qty_OK;
|
set
|
{
|
qty_OK = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblOKNum.Text = qty_OK.ToString();
|
}));
|
|
Qty_All = Qty_OK + Qty_NG;
|
Qty_OK_Rate = Qty_All == 0 ? 0 : (float)Qty_OK / (float)Qty_All;
|
Qty_NG_Rate = Qty_All == 0 ? 0 : (float)Qty_NG / (float)Qty_All;
|
}
|
}
|
|
public int Qty_NG
|
{
|
get => qty_NG;
|
set
|
{
|
qty_NG = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblNGNum.Text = qty_NG.ToString();
|
}));
|
|
Qty_All = Qty_OK + Qty_NG;
|
Qty_OK_Rate = Qty_All == 0 ? 0 : (float)Qty_OK / (float)Qty_All;
|
Qty_NG_Rate = Qty_All == 0 ? 0 : (float)Qty_NG / (float)Qty_All;
|
}
|
}
|
|
public int Qty_All
|
{
|
get => qty_All;
|
set
|
{
|
qty_All = value;
|
this.Invoke(new Action(() =>
|
{
|
lblTotalNum.Text = qty_All.ToString();
|
}));
|
}
|
}
|
|
public float Qty_OK_Rate
|
{
|
get => qty_ok_rate;
|
set
|
{
|
qty_ok_rate = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblOKRate.Text = $"{(qty_ok_rate * 100).ToString("f2")}%";
|
}));
|
}
|
}
|
|
public float Qty_NG_Rate
|
{
|
get => qty_ng_rate;
|
set
|
{
|
qty_ng_rate = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblNGRate.Text = $"{(qty_ng_rate * 100).ToString("f2")}%";
|
}));
|
}
|
}
|
|
public float CT
|
{
|
get => ct;
|
set
|
{
|
ct = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblCT.Text = $"{ct.ToString("f3")} s";
|
}));
|
}
|
}
|
|
public int UPH
|
{
|
get => uph;
|
set
|
{
|
uph = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblUPH.Text = $"{uph} pcs/hr";
|
}));
|
}
|
}
|
|
public DateTime? StartTime
|
{
|
get => startTime;
|
set
|
{
|
startTime = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblStartTime.Text = (startTime != null ? startTime.Value.ToString("yyyy-MM-dd HH:mm:ss") : "");
|
}));
|
}
|
}
|
|
public TimeSpan RunTime
|
{
|
get => runTime;
|
set
|
{
|
runTime = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblRunTime.Text = runTime.ToString();
|
}));
|
}
|
}
|
|
public TimeSpan IdleTime
|
{
|
get => idleTime;
|
set
|
{
|
idleTime = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblIdleTime.Text = idleTime.ToString();
|
}));
|
GetRunTime();
|
}
|
}
|
|
public TimeSpan DownTime
|
{
|
get => downTime;
|
set
|
{
|
downTime = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblDownTime.Text = downTime.ToString();
|
}));
|
GetRunTime();
|
}
|
}
|
|
private void GetRunTime()
|
{
|
RunTime = AvailableTime + IdleTime + DownTime;
|
}
|
|
public TimeSpan AvailableTime
|
{
|
get => availableTime;
|
set
|
{
|
availableTime = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblAvailableTime.Text = availableTime.ToString();
|
}));
|
GetRunTime();
|
}
|
}
|
|
public int Qty_OEE
|
{
|
get => qty_OEE;
|
set
|
{
|
qty_OEE = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblOEE_Total.Text = qty_OEE.ToString();
|
}));
|
}
|
}
|
|
public int Qty_OEE_OK
|
{
|
get => qty_OEE_OK;
|
set
|
{
|
qty_OEE_OK = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblOEE_OK.Text = qty_OEE_OK.ToString();
|
}));
|
}
|
}
|
|
public float OEE
|
{
|
get => oee;
|
set
|
{
|
oee = value;
|
|
this.Invoke(new Action(() =>
|
{
|
lblOEE_Rate.Text = $"{(oee * 100).ToString("f2")}%";
|
}));
|
}
|
}
|
|
public int IdealUPH
|
{
|
get => (int)nudDemandUPH.Value;
|
set
|
{
|
this.Invoke(new Action(() =>
|
{
|
nudDemandUPH.Value = value;
|
}));
|
}
|
}
|
#endregion
|
|
#region Timer
|
System.Threading.Timer _availableTimer = null;
|
System.Threading.Timer _idleTimer = null;
|
System.Threading.Timer _downTimer = null;
|
System.Threading.Timer _checkIdleTimer = null;
|
System.Threading.Timer _calculateTimer = null;
|
|
private RunState? currentState = null;
|
public RunState? CurrentState
|
{
|
get => currentState;
|
set
|
{
|
if (value != null)
|
{
|
if (currentState != value.Value)
|
{
|
switch (value.Value)
|
{
|
case RunState.Stop:
|
_availableTimer.Change(-1, -1);
|
_idleTimer.Change(-1, -1);
|
_downTimer.Change(-1, -1);
|
break;
|
case RunState.Running:
|
_idleTimer.Change(-1, -1);
|
_downTimer.Change(-1, -1);
|
_availableTimer.Change(0, 1000);
|
break;
|
case RunState.Idle:
|
_availableTimer.Change(-1, -1);
|
_downTimer.Change(-1, -1);
|
_idleTimer.Change(0, 1000);
|
break;
|
case RunState.Down:
|
_idleTimer.Change(-1, -1);
|
_availableTimer.Change(-1, -1);
|
_downTimer.Change(0, 1000);
|
break;
|
}
|
|
currentState = value;
|
}
|
}
|
}
|
}
|
#endregion
|
|
int _idleThreshold = 30;
|
public FrmStatistic()
|
{
|
InitializeComponent();
|
|
this.Load += (s, e) =>
|
{
|
ReadNumRecords();
|
|
string idealUPHStr = ConfigurationManager.AppSettings["IdealUPH"];
|
if (!string.IsNullOrWhiteSpace(idealUPHStr) && int.TryParse(idealUPHStr, out int idealUPHSetting))
|
{
|
IdealUPH = idealUPHSetting;
|
}
|
else
|
{
|
IdealUPH = 5000;
|
}
|
};
|
|
string idleThresholdStr = ConfigurationManager.AppSettings["IdleThreshold"];
|
if (!string.IsNullOrWhiteSpace(idleThresholdStr) && int.TryParse(idleThresholdStr, out int idleTTemp))
|
{
|
_idleThreshold = idleTTemp;
|
}
|
else
|
{
|
_idleThreshold = 30;
|
}
|
}
|
|
public override void OnProcessUpdated()
|
{
|
Process.OnProcessStateChanged -= Process_OnProcessStateChanged;
|
Process.OnProcessStateChanged += Process_OnProcessStateChanged;
|
|
Process.OnAlarmUpdate -= Process_OnAlarmUpdate;
|
Process.OnAlarmUpdate += Process_OnAlarmUpdate;
|
|
Process.OnUpdateResult = UpdateResultOK;
|
Process.OnUpdateCT = UpdateCT;
|
|
_availableTimer = new System.Threading.Timer(AvailableTimerCallBack, null, -1, -1);
|
_idleTimer = new System.Threading.Timer(IdleTimerCallBack, null, -1, -1);
|
_downTimer = new System.Threading.Timer(DownTimerCallBack, null, -1, -1);
|
_checkIdleTimer = new System.Threading.Timer(CheckIdle, null, -1, -1);
|
|
_calculateTimer = new System.Threading.Timer(Calculation, null, 1000 * 60, 1000 * 60);
|
}
|
|
private void Process_OnAlarmUpdate(string alarmMsg)
|
{
|
if (string.IsNullOrWhiteSpace(alarmMsg))
|
{
|
if (CurrentState == RunState.Down)
|
{
|
CurrentState = RunState.Idle;
|
}
|
}
|
else
|
{
|
if (CurrentState == RunState.Idle || CurrentState == RunState.Running)
|
{
|
CurrentState = RunState.Down;
|
}
|
}
|
}
|
|
private void Process_OnProcessStateChanged(EnumHelper.DeviceState state)
|
{
|
if (state == EnumHelper.DeviceState.DSOpen)
|
{
|
StartTime = DateTime.Now;
|
CurrentState = RunState.Idle; ;
|
}
|
else
|
{
|
CurrentState = RunState.Stop;
|
}
|
|
if (state == EnumHelper.DeviceState.DSClose)
|
{
|
SaveNumRecord();
|
}
|
}
|
|
private void CheckIdle(object state)
|
{
|
var idleBack = new TimeSpan(0, 0, _idleThreshold);
|
IdleTime = IdleTime.Add(idleBack);
|
AvailableTime = AvailableTime.Subtract(idleBack);
|
CurrentState = RunState.Idle;
|
}
|
|
private void UpdateResultOK(DateTime updateTime, int result)
|
{
|
CurrentState = RunState.Running;
|
_checkIdleTimer.Change(_idleThreshold * 1000, -1); //1分钟没有上传Load认为进入空闲时间
|
|
if (result == 1)
|
{
|
Qty_OK++;
|
Qty_OEE_OK++;
|
}
|
else
|
{
|
Qty_NG++;
|
}
|
|
Qty_OEE++;
|
}
|
|
private void UpdateCT(float ctTime)
|
{
|
CT = ctTime;
|
}
|
|
private void RefreshOEE()
|
{
|
Calculation(null);
|
}
|
|
/// <summary>
|
/// 每分钟计算OEE,平均UPH
|
/// </summary>
|
/// <param name="state"></param>
|
private void Calculation(object state)
|
{
|
if (RunTime.TotalHours == 0)
|
{
|
UPH = 0;
|
}
|
else
|
{
|
UPH = (int)(Qty_OEE / RunTime.TotalHours);
|
}
|
|
if (IdealUPH == 0 || RunTime.TotalSeconds == 0 || Qty_OEE == 0)
|
{
|
OEE = 0;
|
}
|
else
|
{
|
OEE = (float)(AvailableTime.TotalSeconds / RunTime.TotalSeconds) * ((float)UPH / (float)IdealUPH) * ((float)Qty_OEE_OK / (float)Qty_OEE);
|
}
|
}
|
|
private void ClearOEE()
|
{
|
RunTime = IdleTime = DownTime = new TimeSpan();
|
Qty_OEE_OK = Qty_OEE = 0;
|
|
if (Process.ProcessState == EnumHelper.DeviceState.DSOpen)
|
{
|
StartTime = DateTime.Now;
|
}
|
else
|
{
|
StartTime = null;
|
}
|
}
|
|
private void DownTimerCallBack(object state)
|
{
|
DownTime = DownTime.Add(new TimeSpan(0, 0, 1));
|
}
|
|
private void IdleTimerCallBack(object state)
|
{
|
IdleTime = IdleTime.Add(new TimeSpan(0, 0, 1));
|
}
|
|
private void AvailableTimerCallBack(object state)
|
{
|
AvailableTime = AvailableTime.Add(new TimeSpan(0, 0, 1));
|
}
|
|
private void OnClearQty()
|
{
|
Qty_OK = Qty_NG = 0;
|
}
|
|
string numRecordFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Statistic.json");
|
static object numLock = new object();
|
private void SaveNumRecord()
|
{
|
lock (numLock)
|
{
|
using (StreamWriter writer = new StreamWriter(numRecordFile, false, System.Text.Encoding.UTF8))
|
{
|
string dataStr = JsonConvert.SerializeObject(new { Qty_OK, Qty_NG });
|
writer.Write(dataStr);
|
writer.Flush();
|
}
|
}
|
}
|
|
private void ReadNumRecords()
|
{
|
if (File.Exists(numRecordFile))
|
{
|
lock (numLock)
|
{
|
using (StreamReader reader = new StreamReader(numRecordFile, System.Text.Encoding.UTF8))
|
{
|
string dataStr = reader.ReadToEnd();
|
|
JObject data = JsonConvert.DeserializeObject<JObject>(dataStr);
|
|
if (data != null)
|
{
|
Qty_OK = data.Value<int>("Qty_OK");
|
Qty_NG = data.Value<int>("Qty_NG");
|
}
|
}
|
}
|
}
|
}
|
|
private void btnClearStatistic_Click(object sender, EventArgs e)
|
{
|
Qty_OK = Qty_NG = 0;
|
}
|
}
|
}
|