#define _FAKE_DATA using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using Aitex.Core.Common.DeviceData.IoDevice; using Aitex.Core.RT.DataCenter; using Aitex.Core.RT.DBCore; using Aitex.Core.RT.Log; using Aitex.Core.Util; using Sicentury.Core.Collections; #if FAKE_DATA using System.Threading; using System.Threading.Tasks; #endif namespace MECF.Framework.RT.Core.Managers.PDS { public class ProcessDataStatManager { #region Variables /// /// 工艺数据统计列表中显示的最大行数 /// private const int DISPLAYED_ROWS_MAX = 10; private readonly object _syncObject = new(); /// /// 后台统计工艺数据的线程 /// private readonly PeriodicJob _tStatistic; #if FAKE_DATA private readonly Random _randomFlow; #endif #endregion #region Constructors public ProcessDataStatManager(string module) { Debug.Assert(!string.IsNullOrEmpty(module), "the module is not assigned"); Module = module; GasFlowCountCollection = new ObservableQueue(DISPLAYED_ROWS_MAX); GasFlowCountCollection.CollectionChanged += GasFlowCountCollectionOnCollectionChanged; // 加载历史记录。 ReadLastFlowCounts(); DATA.Subscribe($"{Module}.ProcessDataStat.RealtimeList", ()=> ProcessRunDataList); _tStatistic = new PeriodicJob(1000, OnTimer, "MonitorProcDataStatPerRun", false); #if FAKE_DATA _randomFlow = new Random(); //for (var i = 0; i < 10; i++) //{ // var counter = new GasFlowPerRun(); // 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); // CurrentDataStat = counter; //} // Add new Flow Counter every 5s Task.Run(() => { while (true) { Thread.Sleep(5000); if (CurrentDataStat != null) { // 结束当前的统计,随后开启新统计 CurrentDataStat.StatisticsEnd = DateTime.Now; SaveFlowCount(CurrentDataStat); } var counter = new GasFlowPerRun(); GasFlowCountCollection.Enqueue(counter); CurrentDataStat = counter; } }); #endif } #endregion #region Properties public string Module { get; private set; } public ObservableQueue GasFlowCountCollection { get; } public ProcessDataStatPerRun CurrentDataStat { get; private set; } public List ProcessRunDataList { get { var list = new List(); lock (_syncObject) { list.AddRange(GasFlowCountCollection.OrderBy(x => x.Index) .ToList() .Select(item => new AITProcessRunData { Uid = item.StatisticsUid, Index = item.Index, Module = item.Module, RecipeName = item.RecipeName.Replace("Sic\\Process\\", ""), WaferId = item.WaferId, ProcessBegin = item.StatisticsStart, ProcessEnd = item.StatisticsEnd, H2 = item.H2.Total, Ar = item.Ar.Total, PN2 = item.PN2.Total, HCL = item.HCL.Total, SiH4 = item.SiH4.Total, C2H4 = item.C2H4.Total, TCS = item.TCS.Total, TMA = item.TMA.Total, HeaterPowerConsumption = item.HeaterPowerConsumption.Total })); } return list; } } #endregion #region Methods private bool OnTimer() { Monitor(); return true; } private void GasFlowCountCollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // 列表发生变化时重新排序。 if (e.Action == NotifyCollectionChangedAction.Add) { var i = 1; GasFlowCountCollection.ToList().OrderBy(x => x.StatisticsStart).ToList().ForEach(x => { x.Index = i; i++; }); } } /// /// 将统计值保存到数据库。 /// /// private void SaveFlowCount(ProcessDataStatPerRun runData) { try { var sql = $"INSERT INTO process_data_stat_per_run " + $"(process_guid, module_name, recipe_name, wafer_guid, process_begin_time, process_end_time, h2, ar, pn2, hcl, sih4, c2h4, tcs, tma, heater_power_consumption) " + $"VALUES " + $"('{runData.StatisticsUid:D}', '{Module}', '{runData.RecipeName}', '{runData.WaferId:D}', " + $"'{runData.StatisticsStart:yyyy/MM/dd HH:mm:ss.fff}', '{runData.StatisticsEnd:yyyy/MM/dd HH:mm:ss.fff}', " + $"{runData.H2.Total}, {runData.Ar.Total}, {runData.PN2.Total}, {runData.HCL.Total}, {runData.SiH4.Total}, {runData.C2H4.Total}, {runData.TCS.Total}, {runData.TMA.Total}, {runData.HeaterPowerConsumption.Total})"; DB.ExecuteNonQuery(sql); } catch (Exception ex) { LOG.Error($"Unable to save total gas flow to database, {ex.Message}", ex); } } /// /// 从数据库加载指定的记录。 /// /// private void ReadLastFlowCounts(int limit = DISPLAYED_ROWS_MAX) { try { var sql = $"SELECT * FROM process_data_stat_per_run where module_name = '{Module}' " + $" ORDER BY process_begin_time DESC LIMIT {limit}"; var dt = DB.ExecuteDataTable(sql); if (dt is { 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["c2h4"].ToString()); var tcs = double.Parse(row["tcs"].ToString()); var tma = double.Parse(row["tma"].ToString()); var heaterPowerConsumption = double.Parse(row["heater_power_consumption"].ToString()); var run = new ProcessDataStatPerRun(i + 1, uid, Module, recipeName, waferUid, beginTime, endTime, h2, ar, pn2, hcl, sih4, cxhx, tcs, tma, heaterPowerConsumption); GasFlowCountCollection.Enqueue(run); } catch (Exception ex) { LOG.Error($"Unable to load process data statistic history at row {i}, {ex.Message}", ex); } } } } catch (Exception ex) { LOG.Error($"Unable to load process data statistic history, {ex.Message}", ex); } } public void Monitor() { lock (_syncObject) { if (CurrentDataStat != null && CurrentDataStat.StatisticsEnd.HasValue == false) { #if FAKE_DATA CurrentDataStat.H2.AddFlow(_randomFlow.NextDouble()); CurrentDataStat.Ar.AddFlow(_randomFlow.NextDouble()); CurrentDataStat.PN2.AddFlow(_randomFlow.NextDouble()); CurrentDataStat.HCL.AddFlow(_randomFlow.NextDouble()); CurrentDataStat.SiH4.AddFlow(_randomFlow.NextDouble()); CurrentDataStat.CxHx.AddFlow(_randomFlow.NextDouble()); CurrentDataStat.TCS.AddFlow(_randomFlow.NextDouble()); CurrentDataStat.TMA.AddFlow(_randomFlow.NextDouble()); #else CurrentDataStat.Accumulate(); #endif } } } /// /// 启动统计。 /// public void Begin(string recipeName) { lock (_syncObject) { CurrentDataStat = new ProcessDataStatPerRun(Module); CurrentDataStat.RecipeName = recipeName; GasFlowCountCollection.Enqueue(CurrentDataStat); // 启动数据采集线程 _tStatistic.Start(); } } /// /// 结束上次统计。 /// public void End() { lock (_syncObject) { _tStatistic.Pause(); // Process结束 if (CurrentDataStat != null) { CurrentDataStat.StatisticsEnd = DateTime.Now; // 写数据库 SaveFlowCount(CurrentDataStat); CurrentDataStat = null; } } } #endregion } }