#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 : Queue, 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(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 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++; }); } } /// /// 监测Process的Status属性是否变化,以确定Process的开始和结束时机。 /// /// /// 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; } /// /// 将统计值保存到数据库。 /// /// 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); } } /// /// 从数据库加载指定的记录。 /// /// 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 } }