526 lines
16 KiB
C#
526 lines
16 KiB
C#
#define _FAKE_DATA
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.Specialized;
|
||
using System.ComponentModel;
|
||
using System.Diagnostics;
|
||
using System.Linq;
|
||
using System.Windows.Data;
|
||
using Aitex.Core.RT.Log;
|
||
using MECF.Framework.Common.DataCenter;
|
||
using MECF.Framework.UI.Client.ClientBase;
|
||
using Sicentury.Core;
|
||
using SicModules.PMs;
|
||
|
||
#if FAKE_DATA
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
#endif
|
||
|
||
|
||
namespace SicUI.Models.PMs
|
||
{
|
||
public class ObservableQueue<T> : Queue<T>, INotifyCollectionChanged
|
||
{
|
||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||
|
||
private readonly object _syncRoot = new object();
|
||
|
||
#region Constructors
|
||
|
||
public ObservableQueue()
|
||
{
|
||
LimitSize = 0;
|
||
}
|
||
|
||
public ObservableQueue(int capacity) : base(capacity)
|
||
{
|
||
LimitSize = capacity;
|
||
}
|
||
|
||
|
||
#endregion
|
||
|
||
#region Properties
|
||
|
||
public int LimitSize { get; }
|
||
|
||
public new virtual void Enqueue(T obj)
|
||
{
|
||
lock (_syncRoot)
|
||
{
|
||
base.Enqueue(obj);
|
||
|
||
if (LimitSize > 0 && base.Count > LimitSize)
|
||
base.Dequeue();
|
||
|
||
CollectionChanged?.Invoke(this,
|
||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, obj));
|
||
}
|
||
}
|
||
|
||
public new virtual T Dequeue()
|
||
{
|
||
lock (_syncRoot)
|
||
{
|
||
var obj = base.Dequeue();
|
||
CollectionChanged?.Invoke(this,
|
||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, obj));
|
||
return obj;
|
||
}
|
||
}
|
||
|
||
|
||
public new virtual void Clear()
|
||
{
|
||
lock (_syncRoot)
|
||
{
|
||
base.Clear();
|
||
CollectionChanged?.Invoke(this,
|
||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
public class GasFlowCounter : BindableBase
|
||
{
|
||
#region Variables
|
||
|
||
#endregion
|
||
|
||
#region Constructors
|
||
|
||
public GasFlowCounter(string gasName)
|
||
{
|
||
GasName = gasName;
|
||
TotalFlow = 0;
|
||
}
|
||
|
||
public GasFlowCounter(string gasName, double initValue) : this(gasName)
|
||
{
|
||
TotalFlow = initValue;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Properties
|
||
|
||
public string GasName { get; }
|
||
|
||
public double TotalFlow { get; private set; }
|
||
|
||
#endregion
|
||
|
||
#region Methods
|
||
|
||
public void AddFlow(double increment)
|
||
{
|
||
TotalFlow += increment;
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"{TotalFlow:F1}";
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
public class PmGasFlowCountInfo : BindableBase
|
||
{
|
||
#region Variables
|
||
|
||
private DateTime _statStart;
|
||
private DateTime? _statEnd;
|
||
|
||
|
||
#endregion
|
||
|
||
#region Constructors
|
||
|
||
public PmGasFlowCountInfo(int index, Guid uid, string recipeName, string waferUid, DateTime beginTime, DateTime endTime, double h2,
|
||
double ar,
|
||
double pn2, double hcl, double sih4, double cxhx, double tcs, double tma)
|
||
{
|
||
Index = index;
|
||
StatisticsUid = uid;
|
||
RecipeName = recipeName;
|
||
WaferId = waferUid;
|
||
StatisticsStart = beginTime;
|
||
StatisticsEnd = endTime;
|
||
H2 = new GasFlowCounter(nameof(H2), h2);
|
||
Ar = new GasFlowCounter(nameof(Ar), ar);
|
||
PN2 = new GasFlowCounter(nameof(PN2), pn2);
|
||
HCL = new GasFlowCounter(nameof(HCL), hcl);
|
||
SiH4 = new GasFlowCounter(nameof(SiH4), sih4);
|
||
CxHx = new GasFlowCounter(nameof(CxHx), cxhx);
|
||
TCS = new GasFlowCounter(nameof(TCS), tcs);
|
||
TMA = new GasFlowCounter(nameof(TMA), tma);
|
||
|
||
|
||
}
|
||
|
||
public PmGasFlowCountInfo()
|
||
{
|
||
StatisticsUid = Guid.NewGuid();
|
||
WaferId = string.Empty;
|
||
RecipeName = string.Empty;
|
||
StatisticsStart = DateTime.Now;
|
||
|
||
H2 = new GasFlowCounter(nameof(H2));
|
||
Ar = new GasFlowCounter(nameof(Ar));
|
||
PN2 = new GasFlowCounter(nameof(PN2));
|
||
HCL = new GasFlowCounter(nameof(HCL));
|
||
SiH4 = new GasFlowCounter(nameof(SiH4));
|
||
CxHx = new GasFlowCounter(nameof(CxHx));
|
||
TCS = new GasFlowCounter(nameof(TCS));
|
||
TMA = new GasFlowCounter(nameof(TMA));
|
||
}
|
||
|
||
public PmGasFlowCountInfo(string waferId) : this()
|
||
{
|
||
WaferId = waferId;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Properties
|
||
|
||
public int Index { get; set; }
|
||
|
||
public string WaferId { get; }
|
||
|
||
public string RecipeName { get; }
|
||
|
||
public Guid StatisticsUid { get; }
|
||
|
||
public DateTime StatisticsStart
|
||
{
|
||
get => _statStart;
|
||
set => Set(ref _statStart, value);
|
||
}
|
||
|
||
public DateTime? StatisticsEnd
|
||
{
|
||
get => _statEnd;
|
||
set => Set(ref _statEnd, value);
|
||
}
|
||
|
||
public GasFlowCounter H2 { get; }
|
||
|
||
public GasFlowCounter Ar { get; }
|
||
|
||
public GasFlowCounter PN2 { get; }
|
||
|
||
public GasFlowCounter HCL { get; }
|
||
|
||
public GasFlowCounter SiH4 { get; }
|
||
|
||
public GasFlowCounter CxHx { get; }
|
||
|
||
public GasFlowCounter TCS { get; }
|
||
|
||
public GasFlowCounter TMA { get; }
|
||
|
||
#endregion
|
||
}
|
||
|
||
public class PMProcessGasFlowCounterViewModel : UiViewModelBase
|
||
{
|
||
#region Variables
|
||
|
||
private const int MAX_ROWS = 10;
|
||
|
||
private readonly object _syncObject = new object();
|
||
|
||
private readonly PMProcessViewModel _pmProcessVm;
|
||
private string _previousStatus;
|
||
|
||
|
||
#if FAKE_DATA
|
||
|
||
private readonly Random _randomFlow;
|
||
|
||
#endif
|
||
|
||
#endregion
|
||
|
||
#region Constructors
|
||
|
||
public PMProcessGasFlowCounterViewModel(PMProcessViewModel vm)
|
||
{
|
||
_previousStatus = PMModule.STATE.Init.ToString();
|
||
_pmProcessVm = vm;
|
||
GasFlowCountCollection = new ObservableQueue<PmGasFlowCountInfo>(MAX_ROWS);
|
||
GasFlowCountCollection.CollectionChanged += GasFlowCountCollectionOnCollectionChanged;
|
||
vm.PropertyChanged += VmOnPropertyChanged;
|
||
|
||
// 加载历史记录。
|
||
ReadLastFlowCounts();
|
||
|
||
#if FAKE_DATA
|
||
|
||
_randomFlow = new Random();
|
||
|
||
//for (var i = 0; i < 10; i++)
|
||
//{
|
||
// var counter = new PmGasFlowCountInfo();
|
||
// counter.H2.AddFlow(_randomFlow.NextDouble());
|
||
// counter.Ar.AddFlow(_randomFlow.NextDouble());
|
||
// counter.PN2.AddFlow(_randomFlow.NextDouble());
|
||
// counter.HCL.AddFlow(_randomFlow.NextDouble());
|
||
// counter.SiH4.AddFlow(_randomFlow.NextDouble());
|
||
// counter.C3H8.AddFlow(_randomFlow.NextDouble());
|
||
// counter.TCS.AddFlow(_randomFlow.NextDouble());
|
||
// counter.TMA.AddFlow(_randomFlow.NextDouble());
|
||
// counter.StatisticsEnd = DateTime.Now;
|
||
|
||
// GasFlowCountCollection.Enqueue(counter);
|
||
|
||
// CurrentFlowCounter = counter;
|
||
//}
|
||
|
||
// Add new Flow Counter every 5s
|
||
Task.Run(() =>
|
||
{
|
||
while (true)
|
||
{
|
||
Thread.Sleep(5000);
|
||
if (CurrentFlowCounter != null)
|
||
{
|
||
// 结束当前的统计,随后开启新统计
|
||
CurrentFlowCounter.StatisticsEnd = DateTime.Now;
|
||
SaveFlowCount(CurrentFlowCounter);
|
||
}
|
||
|
||
var counter = new PmGasFlowCountInfo();
|
||
GasFlowCountCollection.Enqueue(counter);
|
||
CurrentFlowCounter = counter;
|
||
}
|
||
});
|
||
|
||
#endif
|
||
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Properties
|
||
|
||
public ObservableQueue<PmGasFlowCountInfo> GasFlowCountCollection { get; }
|
||
|
||
public PmGasFlowCountInfo CurrentFlowCounter { get; private set; }
|
||
|
||
public ICollectionView GasFlowDetailList
|
||
{
|
||
get
|
||
{
|
||
var view = CollectionViewSource.GetDefaultView(GasFlowCountCollection.ToList());
|
||
view.SortDescriptions.Add(new SortDescription(nameof(PmGasFlowCountInfo.Index),
|
||
ListSortDirection.Ascending));
|
||
return view;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Methods
|
||
|
||
private void GasFlowCountCollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||
{
|
||
// 列表发生变化时重新排序。
|
||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||
{
|
||
var i = 1;
|
||
GasFlowCountCollection.ToList().OrderByDescending(x => x.StatisticsStart).ToList().ForEach(x =>
|
||
{
|
||
x.Index = i;
|
||
i++;
|
||
});
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 监测Process的Status属性是否变化,以确定Process的开始和结束时机。
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void VmOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||
{
|
||
if (e.PropertyName == nameof(PMOperationViewModel.Status))
|
||
{
|
||
var currentStatus = _pmProcessVm.Status;
|
||
|
||
if (CheckIfProcessStarted(_previousStatus, currentStatus))
|
||
{
|
||
// Process启动
|
||
|
||
lock (_syncObject)
|
||
{
|
||
CurrentFlowCounter = new PmGasFlowCountInfo();
|
||
GasFlowCountCollection.Enqueue(CurrentFlowCounter);
|
||
}
|
||
|
||
}
|
||
else if (CheckIfProcessStopped(_previousStatus, currentStatus))
|
||
{
|
||
lock (_syncObject)
|
||
{
|
||
// Process结束
|
||
if (CurrentFlowCounter != null)
|
||
{
|
||
CurrentFlowCounter.StatisticsEnd = DateTime.Now;
|
||
|
||
// 写数据库
|
||
SaveFlowCount(CurrentFlowCounter);
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
Debug.WriteLineIf(_previousStatus != currentStatus,
|
||
$"PM Process Status: {_previousStatus} to {currentStatus}");
|
||
|
||
_previousStatus = currentStatus;
|
||
}
|
||
}
|
||
|
||
private bool CheckIfProcessStarted(string previousStatus, string currentStatus)
|
||
{
|
||
if (previousStatus == PMModule.STATE.ProcessIdle.ToString() &&
|
||
currentStatus == PMModule.STATE.PreProcess.ToString())
|
||
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
private bool CheckIfProcessStopped(string previousStatus, string currentStatus)
|
||
{
|
||
if ((previousStatus != PMModule.STATE.ProcessIdle.ToString() &&
|
||
currentStatus == PMModule.STATE.ProcessIdle.ToString()) ||
|
||
(previousStatus != PMModule.STATE.Error.ToString() &&
|
||
currentStatus == PMModule.STATE.Error.ToString()))
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将统计值保存到数据库。
|
||
/// </summary>
|
||
/// <param name="count"></param>
|
||
private void SaveFlowCount(PmGasFlowCountInfo count)
|
||
{
|
||
try
|
||
{
|
||
var c = count;
|
||
var sql = $"INSERT INTO process_flow_data " +
|
||
$"(process_guid, module_name, recipe_name, wafer_guid, process_begin_time, process_end_time, h2, ar, pn2, hcl, sih4, c3h8, tcs, tma) " +
|
||
$"VALUES " +
|
||
$"('{c.StatisticsUid:D}', '{_pmProcessVm.SystemName}', '{_pmProcessVm.SelectedRecipe}', '{c.WaferId:D}', " +
|
||
$"'{c.StatisticsStart:yyyy/MM/dd HH:mm:ss.fff}', '{c.StatisticsEnd:yyyy/MM/dd HH:mm:ss.fff}', " +
|
||
$"{c.H2}, {c.Ar}, {c.PN2}, {c.HCL}, {c.SiH4}, {c.CxHx}, {c.TCS}, {c.TMA})";
|
||
|
||
QueryDataClient.Instance.Service.QueryData(sql);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LOG.Error($"Unable to save total gas flow to database, {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从数据库加载指定的记录。
|
||
/// </summary>
|
||
/// <param name="limit"></param>
|
||
private void ReadLastFlowCounts(int limit = MAX_ROWS)
|
||
{
|
||
try
|
||
{
|
||
var sql =
|
||
$"SELECT * FROM process_flow_data where module_name = '{_pmProcessVm.SystemName}' " +
|
||
$" ORDER BY process_begin_time DESC LIMIT {limit}";
|
||
var dt = QueryDataClient.Instance.Service.QueryData(sql);
|
||
if (dt != null && dt.Rows.Count > 0)
|
||
{
|
||
for (var i = 0; i < dt.Rows.Count; i++)
|
||
{
|
||
try
|
||
{
|
||
var row = dt.Rows[i];
|
||
var uid = Guid.Parse(row["process_guid"].ToString());
|
||
var recipeName = row["recipe_name"].ToString();
|
||
var waferUid = row["wafer_guid"].ToString();
|
||
var beginTime = DateTime.Parse(row["process_begin_time"].ToString());
|
||
var endTime = DateTime.Parse(row["process_end_time"].ToString());
|
||
var h2 = double.Parse(row["h2"].ToString());
|
||
var ar = double.Parse(row["ar"].ToString());
|
||
var pn2 = double.Parse(row["pn2"].ToString());
|
||
var hcl = double.Parse(row["hcl"].ToString());
|
||
var sih4 = double.Parse(row["sih4"].ToString());
|
||
var cxhx = double.Parse(row["c3h8"].ToString());
|
||
var tcs = double.Parse(row["tcs"].ToString());
|
||
var tma = double.Parse(row["tma"].ToString());
|
||
var counter = new PmGasFlowCountInfo(i + 1, uid, recipeName, waferUid, beginTime, endTime, h2, ar, pn2,
|
||
hcl,
|
||
sih4, cxhx, tcs, tma);
|
||
|
||
GasFlowCountCollection.Enqueue(counter);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LOG.Error($"Unable to load process flow count history at row {i}, {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LOG.Error($"Unable to load process flow count history, {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
protected override void Poll()
|
||
{
|
||
base.Poll();
|
||
|
||
lock (_syncObject)
|
||
{
|
||
if (CurrentFlowCounter != null && CurrentFlowCounter.StatisticsEnd.HasValue == false)
|
||
{
|
||
#if FAKE_DATA
|
||
CurrentFlowCounter.H2.AddFlow(_randomFlow.NextDouble());
|
||
CurrentFlowCounter.Ar.AddFlow(_randomFlow.NextDouble());
|
||
CurrentFlowCounter.PN2.AddFlow(_randomFlow.NextDouble());
|
||
CurrentFlowCounter.HCL.AddFlow(_randomFlow.NextDouble());
|
||
CurrentFlowCounter.SiH4.AddFlow(_randomFlow.NextDouble());
|
||
CurrentFlowCounter.CxHx.AddFlow(_randomFlow.NextDouble());
|
||
CurrentFlowCounter.TCS.AddFlow(_randomFlow.NextDouble());
|
||
CurrentFlowCounter.TMA.AddFlow(_randomFlow.NextDouble());
|
||
#else
|
||
|
||
CurrentFlowCounter.H2.AddFlow(_pmProcessVm.H2Flow);
|
||
CurrentFlowCounter.Ar.AddFlow(_pmProcessVm.ArFlow);
|
||
CurrentFlowCounter.PN2.AddFlow(_pmProcessVm.PN2Flow);
|
||
CurrentFlowCounter.HCL.AddFlow(_pmProcessVm.HCLFlow);
|
||
CurrentFlowCounter.SiH4.AddFlow(_pmProcessVm.SiH4Flow);
|
||
CurrentFlowCounter.CxHx.AddFlow(_pmProcessVm.C2H4Flow);
|
||
CurrentFlowCounter.TCS.AddFlow(_pmProcessVm.TCSFlow);
|
||
CurrentFlowCounter.TMA.AddFlow(_pmProcessVm.TMAFlow);
|
||
|
||
#endif
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|