[UI.Client]

移除PMProcessGasFlowCounterViewModel.cs。

[RT.Core]
新增ProcessDataStat相关对象,用于统计PM工艺过程中的数据。
This commit is contained in:
SL 2023-07-31 16:38:10 +08:00
parent 1a2979f0c5
commit b0c5451a43
10 changed files with 609 additions and 539 deletions

View File

@ -23,6 +23,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -32,6 +33,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugWithoutCopyFiles|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
@ -40,7 +42,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<LangVersion>latest</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
@ -124,6 +126,9 @@
<Compile Include="IoProviders\Siemens\Transfer\SoftBasic.cs" />
<Compile Include="Language\DefaultLanguage.cs" />
<Compile Include="Language\English.cs" />
<Compile Include="Managers\PDS\ProcessDataStatCounter.cs" />
<Compile Include="Managers\PDS\ProcessDataStatPerRun.cs" />
<Compile Include="Managers\PDS\ProcessDataStatManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StringResources.cs" />
<Compile Include="ThreadLock\ThreadLock.cs" />
@ -140,6 +145,10 @@
<Project>{2c9e1df3-1aba-4972-be60-41dd9b3c47a7}</Project>
<Name>MECF.Framework.UI.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Sicentury.Core\Sicentury.Core.csproj">
<Project>{A78B3F87-4601-43F7-A941-EDB339CE35A9}</Project>
<Name>Sicentury.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Page Include="Backend\BackendMainView.xaml">

View File

@ -0,0 +1,119 @@
using System;
using Aitex.Core.RT.DataCenter;
using Aitex.Core.RT.Log;
using Aitex.Core.Util;
using Sicentury.Core;
namespace MECF.Framework.RT.Core.Managers.PDS
{
/// <summary>
/// 气体流量累加器。
/// </summary>
public class ProcessDataStatCounter : BindableBase
{
#region Variables
private DateTime _lastPollingTime = DateTime.MinValue;
private readonly R_TRIG _trigDataPollingError = new();
#endregion
#region Constructors
public ProcessDataStatCounter(string name, string module)
{
Name = name;
Module = module;
DataPath = new[]
{
$"{module}.GasRealTimeFlow.{name}_Run.FeedBack",
$"{module}.GasRealTimeFlow.{name}_Vent.FeedBack",
};
}
public ProcessDataStatCounter(string name, string module, params string[] dataPath) : this(name, module)
{
DataPath = dataPath;
Total = 0;
}
public ProcessDataStatCounter(string name, string module, double initValue) : this(name, module)
{
Total = initValue;
}
public ProcessDataStatCounter(string name, string module, double initValue, params string[] dataPath) : this(name, module, dataPath)
{
Total = initValue;
}
#endregion
#region Properties
/// <summary>
/// 返回当前统计数据的名称。
/// </summary>
public string Name { get; }
/// <summary>
/// 返回当前统计数据所属的模块。
/// </summary>
public string Module { get; }
/// <summary>
/// 返回当前数据统计时所使用的数据拉取路径。
/// </summary>
public string[] DataPath { get; }
/// <summary>
/// 返回当前统计数据的累加值。
/// </summary>
public double Total { get; private set; }
#endregion
#region Methods
/// <summary>
/// 累加气体流量。
/// </summary>
public void Accumulate()
{
var ts = DateTime.Now - _lastPollingTime;
if(_lastPollingTime == DateTime.MinValue)
ts = TimeSpan.FromSeconds(1);
foreach (var pdata in DataPath)
{
var sFlow = DATA.Poll(pdata).ToString();
if (double.TryParse(sFlow, out var dFlow))
{
Total += dFlow * ts.TotalSeconds;
_trigDataPollingError.RST = true;
}
else
{
_trigDataPollingError.CLK = true;
if (_trigDataPollingError.Q)
{
LOG.Error($"Unable to polling data {DataPath} or the data({sFlow}) is not double value.");
}
break;
}
}
_lastPollingTime = DateTime.Now;
}
public override string ToString()
{
return $"{Name}: {Total:F1}";
}
#endregion
}
}

View File

@ -0,0 +1,270 @@
#define _FAKE_DATA
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
using Aitex.Core.RT.Log;
using MECF.Framework.Common.DataCenter;
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
/// <summary>
/// 工艺数据统计列表中显示的最大行数
/// </summary>
private const int DISPLAYED_ROWS_MAX = 10;
private readonly object _syncObject = new();
private bool _isCounting;
#if FAKE_DATA
private readonly Random _randomFlow;
#endif
#endregion
#region Constructors
public ProcessDataStatManager(string module)
{
Module = module;
GasFlowCountCollection = new ObservableQueue<ProcessDataStatPerRun>(DISPLAYED_ROWS_MAX);
GasFlowCountCollection.CollectionChanged += GasFlowCountCollectionOnCollectionChanged;
// 加载历史记录。
ReadLastFlowCounts();
#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);
// 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 GasFlowPerRun();
GasFlowCountCollection.Enqueue(counter);
CurrentFlowCounter = counter;
}
});
#endif
}
#endregion
#region Properties
public string Module { get; private set; }
public ObservableQueue<ProcessDataStatPerRun> GasFlowCountCollection { get; }
public ProcessDataStatPerRun CurrentFlowCounter { get; private set; }
public ICollectionView GasFlowDetailList
{
get
{
var view = CollectionViewSource.GetDefaultView(GasFlowCountCollection.ToList());
view.SortDescriptions.Add(new SortDescription(nameof(ProcessDataStatPerRun.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>
/// 将统计值保存到数据库。
/// </summary>
/// <param name="runData"></param>
private void SaveFlowCount(ProcessDataStatPerRun runData)
{
try
{
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 " +
$"('{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}, {runData.Ar}, {runData.PN2}, {runData.HCL}, {runData.SiH4}, {runData.C2H4}, {runData.TCS}, {runData.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 = DISPLAYED_ROWS_MAX)
{
try
{
var sql =
$"SELECT * FROM process_flow_data where module_name = '{Module}' " +
$" 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 heaterPowerConsumption = double.Parse(row["heater_power_consumption"].ToString());
var counter = new ProcessDataStatPerRun(i + 1, uid, Module, recipeName, waferUid, beginTime, endTime, h2, ar, pn2,
hcl, sih4, cxhx, tcs, tma, heaterPowerConsumption);
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);
}
}
public void Monitor()
{
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.Accumulate();
#endif
}
}
}
/// <summary>
/// 启动统计。
/// </summary>
public void Begin()
{
lock (_syncObject)
{
_isCounting = true;
CurrentFlowCounter = new ProcessDataStatPerRun(Module);
GasFlowCountCollection.Enqueue(CurrentFlowCounter);
}
}
/// <summary>
/// 结束上次统计。
/// </summary>
public void End()
{
lock (_syncObject)
{
// Process结束
if (CurrentFlowCounter != null)
{
CurrentFlowCounter.StatisticsEnd = DateTime.Now;
// 写数据库
SaveFlowCount(CurrentFlowCounter);
}
_isCounting = false;
}
}
#endregion
}
}

View File

@ -0,0 +1,139 @@
using System;
using Sicentury.Core;
namespace MECF.Framework.RT.Core.Managers.PDS
{
public class ProcessDataStatPerRun : BindableBase
{
#region Variables
private DateTime _statStart;
private DateTime? _statEnd;
#endregion
#region Constructors
public ProcessDataStatPerRun(int index, Guid uid, string module, string recipeName, string waferUid, DateTime beginTime, DateTime endTime, double h2,
double ar,
double pn2, double hcl, double sih4, double cxhx, double tcs, double tma, double heaterPowerConsumption)
{
Index = index;
Module = module;
StatisticsUid = uid;
RecipeName = recipeName;
WaferId = waferUid;
StatisticsStart = beginTime;
StatisticsEnd = endTime;
H2 = new ProcessDataStatCounter(nameof(H2), module, h2);
Ar = new ProcessDataStatCounter(nameof(Ar), module, ar);
PN2 = new ProcessDataStatCounter(nameof(PN2), module, pn2);
HCL = new ProcessDataStatCounter(nameof(HCL), module, hcl);
SiH4 = new ProcessDataStatCounter(nameof(SiH4), module, sih4);
C2H4 = new ProcessDataStatCounter(nameof(C2H4), module, cxhx);
TCS = new ProcessDataStatCounter(nameof(TCS), module, tcs);
TMA = new ProcessDataStatCounter(nameof(TMA), module, tma);
HeaterPowerConsumption = new ProcessDataStatCounter(nameof(HeaterPowerConsumption), module, new []
{
$"{module}.PSU1.OutputPowerFeedBack",
$"{module}.PSU2.OutputPowerFeedBack",
$"{module}.PSU3.OutputPowerFeedBack",
$"{module}.SCR1.PowerFeedBack",
$"{module}.SCR2.PowerFeedBack",
$"{module}.SCR3.PowerFeedBack"
});
}
public ProcessDataStatPerRun(string module)
{
StatisticsUid = Guid.NewGuid();
Module = module;
WaferId = string.Empty;
RecipeName = string.Empty;
StatisticsStart = DateTime.Now;
H2 = new ProcessDataStatCounter(nameof(H2), module);
Ar = new ProcessDataStatCounter(nameof(Ar), module);
PN2 = new ProcessDataStatCounter(nameof(PN2), module);
HCL = new ProcessDataStatCounter(nameof(HCL), module);
SiH4 = new ProcessDataStatCounter(nameof(SiH4), module);
C2H4 = new ProcessDataStatCounter(nameof(C2H4), module);
TCS = new ProcessDataStatCounter(nameof(TCS), module);
TMA = new ProcessDataStatCounter(nameof(TMA), module);
HeaterPowerConsumption = new ProcessDataStatCounter(nameof(HeaterPowerConsumption), module);
}
public ProcessDataStatPerRun(string module, string waferId) : this(module)
{
WaferId = waferId;
}
#endregion
#region Properties
public int Index { get; set; }
public string Module { 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 ProcessDataStatCounter H2 { get; }
public ProcessDataStatCounter Ar { get; }
public ProcessDataStatCounter PN2 { get; }
public ProcessDataStatCounter HCL { get; }
public ProcessDataStatCounter SiH4 { get; }
public ProcessDataStatCounter C2H4 { get; }
public ProcessDataStatCounter TCS { get; }
public ProcessDataStatCounter TMA { get; }
public ProcessDataStatCounter HeaterPowerConsumption { get; }
#endregion
#region Methods
/// <summary>
/// 累加工艺数据。
/// </summary>
public void Accumulate()
{
H2.Accumulate();
Ar.Accumulate();
PN2.Accumulate();
HCL.Accumulate();
SiH4.Accumulate();
C2H4.Accumulate();
TCS.Accumulate();
TMA.Accumulate();
HeaterPowerConsumption.Accumulate();
}
#endregion
}
}

View File

@ -1,526 +0,0 @@
#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.CenterViews.Modules.PM;
using MECF.Framework.UI.Client.ClientBase;
using Sicentury.Core;
#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 = "Init";
_pmProcessVm = vm;
_pmProcessVm.PropertyChanged += VmOnPropertyChanged;
GasFlowCountCollection = new ObservableQueue<PmGasFlowCountInfo>(MAX_ROWS);
GasFlowCountCollection.CollectionChanged += GasFlowCountCollectionOnCollectionChanged;
// 加载历史记录。
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(PMProcessViewModel.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 == "ProcessIdle" &&
currentStatus == "PreProcess")
return true;
return false;
}
private bool CheckIfProcessStopped(string previousStatus, string currentStatus)
{
if ((previousStatus != "ProcessIdle" &&
currentStatus == "ProcessIdle") ||
(previousStatus != "Error" &&
currentStatus == "Error"))
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
}
}

View File

@ -12,7 +12,6 @@ using Caliburn.Micro.Core;
using System.Windows.Media;
using System.Windows.Threading;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using OpenSEMI.ClientBase;
using Sicentury.Core;
@ -22,11 +21,7 @@ using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel;
using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel.Params;
using Action = System.Action;
using Newtonsoft.Json;
using System.Dynamic;
using Aitex.Core.RT.Device.PmDevices;
using SicUI.Models.PMs;
using SciChart.Core.Messaging;
using MECF.Framework.RT.EquipmentLibrary.HardwareUnits.GasFlow;
using MECF.Framework.Common.Aitex.Core.Common.DeviceData;
namespace MECF.Framework.UI.Client.CenterViews.Modules.PM
@ -53,7 +48,6 @@ namespace MECF.Framework.UI.Client.CenterViews.Modules.PM
public string title { get; set; } = "123";
public PMProcessGasFlowCounterViewModel GasFlowCounterVm { get; private set; }
public PMProcessViewModel()
{
@ -407,10 +401,6 @@ namespace MECF.Framework.UI.Client.CenterViews.Modules.PM
_progressRecipeStepChanged = new Progress<int>(RecipeStepInProcessChanged);
UpdateRecipeFormat();
GasFlowCounterVm = new PMProcessGasFlowCounterViewModel(this);
GasFlowCounterVm.EnableTimer(true);
}

View File

@ -3,7 +3,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SicUI"
mc:Ignorable="d"
Height="380" Width="600" Topmost="True" WindowStartupLocation="CenterScreen" >
<Window.Resources>

View File

@ -236,7 +236,6 @@
<DependentUpon>PMJobListView.xaml</DependentUpon>
</Compile>
<Compile Include="CenterViews\JobList\PMJobListViewModel.cs" />
<Compile Include="CenterViews\Modules\PM\PMProcessGasFlowCounterViewModel.cs" />
<Compile Include="CenterViews\Modules\PM\PMProcessView.xaml.cs">
<DependentUpon>PMProcessView.xaml</DependentUpon>
</Compile>

View File

@ -0,0 +1,70 @@
using System.Collections.Generic;
using System.Collections.Specialized;
namespace Sicentury.Core.Collections
{
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
}
}

View File

@ -60,6 +60,7 @@
<Compile Include="AttachedProperties\WatermarkAdorner.cs" />
<Compile Include="AttachedProperties\WatermarkService.cs" />
<Compile Include="BindableBase.cs" />
<Compile Include="Collections\ObservableQueue.cs" />
<Compile Include="Converters\DummyConverter.cs" />
<Compile Include="Converters\MediaColorToSolidColorBrushConverter.cs" />
<Compile Include="Converters\ParameterNodeStatisticToStringConverter.cs" />