2023-04-13 11:51:03 +08:00
|
|
|
|
using System;
|
2023-08-25 16:41:46 +08:00
|
|
|
|
using System.Diagnostics;
|
2023-04-13 11:51:03 +08:00
|
|
|
|
using System.Xml;
|
|
|
|
|
using Aitex.Core.Common.DeviceData;
|
|
|
|
|
using Aitex.Core.RT.DataCenter;
|
|
|
|
|
using Aitex.Core.RT.Event;
|
|
|
|
|
using Aitex.Core.RT.IOCore;
|
|
|
|
|
using Aitex.Core.RT.OperationCenter;
|
|
|
|
|
using Aitex.Core.RT.SCCore;
|
|
|
|
|
using Aitex.Core.Util;
|
2023-04-17 17:05:49 +08:00
|
|
|
|
using MECF.Framework.Common.Aitex.Core.RT.Device;
|
2023-04-13 11:51:03 +08:00
|
|
|
|
using MECF.Framework.RT.EquipmentLibrary.HardwareUnits.MFCs;
|
|
|
|
|
|
|
|
|
|
namespace Aitex.Core.RT.Device.Devices
|
|
|
|
|
{
|
2023-04-17 17:05:49 +08:00
|
|
|
|
public class IoMFC : ErrorDetectableBaseDevice, IMfc
|
2023-04-13 11:51:03 +08:00
|
|
|
|
{
|
2023-08-25 16:05:19 +08:00
|
|
|
|
|
|
|
|
|
#region Variables
|
|
|
|
|
|
|
|
|
|
private readonly DeviceTimer _rampTimer = new();
|
|
|
|
|
private double _rampTarget;
|
|
|
|
|
private double _rampInitValue;
|
|
|
|
|
private double _rampSetMode;
|
|
|
|
|
private int _rampTime;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private readonly DIAccessor _diAlarm;
|
|
|
|
|
private readonly AIAccessor _aiFlow;
|
|
|
|
|
private readonly AIAccessor _aiActMode;
|
|
|
|
|
private readonly AOAccessor _aoFlow;
|
|
|
|
|
private readonly AOAccessor _aoSetMode;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected SCConfigItem _scGasName;
|
|
|
|
|
protected SCConfigItem _scEnable;
|
|
|
|
|
|
|
|
|
|
private readonly SCConfigItem _scN2Scale;
|
|
|
|
|
private readonly SCConfigItem _scDefaultSetPoint;
|
|
|
|
|
private readonly bool _isFloatAioType = false;
|
|
|
|
|
|
2023-11-14 09:28:10 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 当检测到前置Valve关闭时,停止并重置当前报警检测,以避免下次Valve开启式可能立即报警的问题。
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly R_TRIG _valveCloseTrigger = new ();
|
2023-11-02 14:55:25 +08:00
|
|
|
|
|
2023-08-25 16:05:19 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 当前MFC前置或后置阀门ID
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly string _valve;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 是否根据Valve的开光状态决定是否进行误差检测。
|
|
|
|
|
/// 如果该参数为true,当Valve关闭时,不进行误差检测。
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly bool _disableFbSpErrMonWhenValveClosed;
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Constructors
|
|
|
|
|
|
|
|
|
|
public IoMFC(string module, XmlElement node, string ioModule = "") : base(module, node, ioModule)
|
|
|
|
|
{
|
|
|
|
|
_aiFlow = ParseAiNode("aiFlow", node, ioModule);
|
|
|
|
|
_aoFlow = ParseAoNode("aoFlow", node, ioModule);
|
|
|
|
|
_aiActMode = ParseAiNode("aiActMode", node, ioModule);
|
|
|
|
|
_aoSetMode = ParseAoNode("aoSetMode", node, ioModule);
|
|
|
|
|
_diAlarm = ParseDiNode("diAlarm", node, ioModule);
|
|
|
|
|
|
|
|
|
|
_valve = node.HasAttribute("Valve") ? node.GetAttribute("Valve") : "";
|
|
|
|
|
_disableFbSpErrMonWhenValveClosed = node.HasAttribute("DisableFbSpErrMonWhenValveClosed") && Convert.ToBoolean(node.GetAttribute("DisableFbSpErrMonWhenValveClosed"));
|
|
|
|
|
_isFloatAioType = !string.IsNullOrEmpty(node.GetAttribute("aioType")) && (node.GetAttribute("aioType") == "float");
|
|
|
|
|
|
|
|
|
|
_scGasName = SC.GetConfigItem($"{ScBasePath}.{Name}.GasName");
|
|
|
|
|
_scN2Scale = ParseScNode("scN2Scale", node, ioModule, $"{ScBasePath}.{Name}.N2Scale");
|
|
|
|
|
_scDefaultSetPoint = ParseScNode("scDefaultSetPoint", node, ioModule, $"{ScBasePath}.{Name}.DefaultSetPoint");
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Properties
|
|
|
|
|
|
|
|
|
|
public string DisplayName
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_scGasName != null)
|
|
|
|
|
return _scGasName.StringValue;
|
|
|
|
|
return Display;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-13 11:51:03 +08:00
|
|
|
|
public double Scale
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_scN2Scale == null )
|
|
|
|
|
return 0;
|
|
|
|
|
return _scN2Scale.DoubleValue ;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public double SetPoint
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_aoFlow != null)
|
|
|
|
|
{
|
2023-12-18 16:39:16 +08:00
|
|
|
|
return _isFloatAioType ? _aoFlow.Value : _aoFlow.Value;
|
2023-04-13 11:51:03 +08:00
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (_aoFlow != null)
|
|
|
|
|
{
|
|
|
|
|
if (_isFloatAioType)
|
2023-12-18 16:39:16 +08:00
|
|
|
|
_aoFlow.Value = (float)value;
|
2023-04-13 11:51:03 +08:00
|
|
|
|
else
|
|
|
|
|
_aoFlow.Value = (short)value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public double DefaultSetPoint
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_scDefaultSetPoint != null)
|
|
|
|
|
return _scDefaultSetPoint.DoubleValue;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public double FeedBack
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_aiFlow != null)
|
|
|
|
|
{
|
2023-12-18 16:39:16 +08:00
|
|
|
|
double aiValue = _isFloatAioType ? _aiFlow.Value : _aiFlow.Value;
|
2023-04-13 11:51:03 +08:00
|
|
|
|
|
|
|
|
|
return aiValue; // (_scRegulationFactor != null && _scRegulationFactor.DoubleValue > 0.001) ? aiValue / _scRegulationFactor.DoubleValue : aiValue;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public double ActMode
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_aiActMode != null)
|
|
|
|
|
{
|
2023-12-18 16:39:16 +08:00
|
|
|
|
return _aiActMode.Value;
|
2023-04-13 11:51:03 +08:00
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public double SetMode
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_aoSetMode != null)
|
|
|
|
|
{
|
2023-12-18 16:39:16 +08:00
|
|
|
|
return _aoSetMode.Value;
|
2023-04-13 11:51:03 +08:00
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (_aoSetMode != null)
|
|
|
|
|
{
|
|
|
|
|
if (_isFloatAioType)
|
2023-12-18 16:39:16 +08:00
|
|
|
|
_aoSetMode.Value = (float)value;
|
2023-04-13 11:51:03 +08:00
|
|
|
|
else
|
|
|
|
|
_aoSetMode.Value = (short)value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private AITMfcData DeviceData
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2023-04-17 17:05:49 +08:00
|
|
|
|
//原DisplayName = DisplayName 改成DisplayName = $"{Name}-->{DisplayName}"
|
2023-04-13 11:51:03 +08:00
|
|
|
|
AITMfcData data = new AITMfcData()
|
|
|
|
|
{
|
|
|
|
|
UniqueName = $"{Module}.{Name}",
|
|
|
|
|
Type = "MFC",
|
|
|
|
|
Module = Module,
|
|
|
|
|
DeviceName = Name,
|
|
|
|
|
DeviceSchematicId = DeviceID,
|
2023-04-17 17:05:49 +08:00
|
|
|
|
DisplayName = $"{Name}-->{DisplayName}",
|
2023-04-13 11:51:03 +08:00
|
|
|
|
FeedBack = FeedBack,
|
|
|
|
|
SetPoint = SetPoint,
|
|
|
|
|
Scale = Scale,
|
2023-04-17 17:05:49 +08:00
|
|
|
|
IsWarning = ErrWarningChecker.Result,
|
|
|
|
|
IsError = ErrAlarmChecker.Result,
|
2023-04-13 11:51:03 +08:00
|
|
|
|
DefaultValue = DefaultSetPoint,
|
|
|
|
|
ActMode = ActMode,
|
|
|
|
|
SetMode = SetMode,
|
|
|
|
|
};
|
|
|
|
|
//if (Module.Equals("PM1") && Name.Equals("Mfc1"))
|
|
|
|
|
// Console.WriteLine("Mfc3:" + FeedBack);
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 16:05:19 +08:00
|
|
|
|
#endregion
|
2023-04-13 11:51:03 +08:00
|
|
|
|
|
2023-08-25 16:05:19 +08:00
|
|
|
|
#region Methods
|
2023-04-13 11:51:03 +08:00
|
|
|
|
|
2023-04-17 17:05:49 +08:00
|
|
|
|
public override bool Initialize()
|
2023-04-13 11:51:03 +08:00
|
|
|
|
{
|
|
|
|
|
DATA.Subscribe($"{Module}.{Name}.DeviceData", () => DeviceData);
|
|
|
|
|
|
|
|
|
|
DATA.Subscribe($"{Module}.{Name}.FeedBack", () => FeedBack);
|
|
|
|
|
DATA.Subscribe($"{Module}.{Name}.SetPoint", () => SetPoint);
|
|
|
|
|
DATA.Subscribe($"{Module}.{Name}.SetMode", () => SetMode);
|
|
|
|
|
|
|
|
|
|
OP.Subscribe($"{Module}.{Name}.Ramp", (out string reason, int time, object[] param) =>
|
|
|
|
|
{
|
|
|
|
|
double target = Convert.ToDouble(param[0].ToString());
|
|
|
|
|
|
|
|
|
|
if (target < 0 || target > Scale)
|
|
|
|
|
{
|
|
|
|
|
reason = $"set {Display} value {target} out of range [0, {Scale}] {Unit}";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ramp(target, time);
|
|
|
|
|
|
|
|
|
|
if (time > 0)
|
|
|
|
|
{
|
|
|
|
|
reason = $"{Display} ramp to {target} {Unit} in {time} seconds";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
reason = $"{Display} ramp to {target} {Unit}";
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
OP.Subscribe($"{Module}.{Name}.SetMode", SetContrlMode);
|
|
|
|
|
//OP.Subscribe($"{Module}.{Name}.SetMode", (function, args) =>
|
|
|
|
|
//{
|
|
|
|
|
// if (!Enum.TryParse((string)args[0], out PressureCtrlMode mode))
|
|
|
|
|
// {
|
|
|
|
|
// EV.PostWarningLog(Module, $"Argument {args[0]}not valid");
|
|
|
|
|
// return false;
|
|
|
|
|
// }
|
|
|
|
|
// SetMode();
|
|
|
|
|
// return true;
|
|
|
|
|
//});
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool SetContrlMode(out string reason, int time, object[] param)
|
|
|
|
|
{
|
|
|
|
|
return SetMfcMode((MfcCtrlMode)Enum.Parse(typeof(MfcCtrlMode), (string)param[0], true),
|
|
|
|
|
out reason);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool SetMfcMode(MfcCtrlMode mode, out string reason)
|
|
|
|
|
{
|
|
|
|
|
switch (mode)
|
|
|
|
|
{
|
|
|
|
|
case MfcCtrlMode.Normal:
|
|
|
|
|
SetMode = (double)MfcCtrlMode.Normal;
|
|
|
|
|
break;
|
|
|
|
|
case MfcCtrlMode.Close:
|
|
|
|
|
SetMode = (double)MfcCtrlMode.Close;
|
|
|
|
|
break;
|
|
|
|
|
case MfcCtrlMode.Open:
|
|
|
|
|
SetMode = (double)MfcCtrlMode.Open;
|
|
|
|
|
break;
|
|
|
|
|
case MfcCtrlMode.Hold:
|
|
|
|
|
SetMode = (double)MfcCtrlMode.Hold;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
reason = $"{Display} set to {mode}";
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-08-25 16:05:19 +08:00
|
|
|
|
protected override void HandleMonitor()
|
2023-04-13 11:51:03 +08:00
|
|
|
|
{
|
|
|
|
|
MonitorRamping();
|
2023-08-25 16:05:19 +08:00
|
|
|
|
|
|
|
|
|
var isAllowErrMon = true;
|
|
|
|
|
if (!string.IsNullOrEmpty(_valve) && _disableFbSpErrMonWhenValveClosed)
|
|
|
|
|
{
|
|
|
|
|
// 如果配置为Valve关闭时不检测Feedback-SetPoint误差,获取Valve状态
|
|
|
|
|
var devData = DATA.Poll($"{Module}.{_valve}.DeviceData");
|
|
|
|
|
if (devData is AITValveData valveData)
|
|
|
|
|
{
|
|
|
|
|
// 如果Valve关闭,则不检测。
|
|
|
|
|
isAllowErrMon = valveData.Feedback;
|
2023-11-02 14:55:25 +08:00
|
|
|
|
|
|
|
|
|
_valveCloseTrigger.CLK = !valveData.IsOpen;
|
|
|
|
|
if (_valveCloseTrigger.Q)
|
|
|
|
|
{
|
|
|
|
|
Reset();
|
|
|
|
|
}
|
2023-08-25 16:05:19 +08:00
|
|
|
|
}
|
2023-08-25 16:41:46 +08:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.Assert(false, $"Unable to poll the {Module}.{_valve}.DeviceData.");
|
|
|
|
|
}
|
2023-08-25 16:05:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isAllowErrMon)
|
|
|
|
|
{
|
|
|
|
|
// 当SetPoint大于0.01,并且没有Ramp时,允许检测误差。
|
|
|
|
|
MonitorSpFbError(SetPoint >= 0.01 && _rampTimer.IsIdle(), SetPoint, FeedBack);
|
|
|
|
|
}
|
2023-04-13 11:51:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetToDefaultByRamp(int time)
|
|
|
|
|
{
|
|
|
|
|
Ramp(DefaultSetPoint, time * 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-04-17 17:05:49 +08:00
|
|
|
|
public override void Terminate()
|
2023-04-13 11:51:03 +08:00
|
|
|
|
{
|
|
|
|
|
Ramp(DefaultSetPoint, 5000);
|
2023-04-17 17:05:49 +08:00
|
|
|
|
base.Terminate();
|
2023-04-13 11:51:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Ramp(double flowSetPoint, int time, out string reason)
|
|
|
|
|
{
|
|
|
|
|
if (HasAlarm)
|
|
|
|
|
{
|
|
|
|
|
reason = $"{DisplayName} in error status, can not flow";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flowSetPoint < 0 || flowSetPoint > Scale)
|
|
|
|
|
{
|
|
|
|
|
reason = $"{DisplayName} range is [0, {Scale}], can not flow {flowSetPoint}";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (time > 0)
|
|
|
|
|
{
|
|
|
|
|
EV.PostInfoLog(Module, $"Set {DisplayName} flow to {flowSetPoint} {Unit} in {time / 1000:F0} seconds");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
EV.PostInfoLog(Module, $"Set {DisplayName} flow to {flowSetPoint} {Unit}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ramp(flowSetPoint, time);
|
|
|
|
|
reason = string.Empty;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Ramp(int time)
|
|
|
|
|
{
|
|
|
|
|
Ramp(DefaultSetPoint, time);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-17 17:05:49 +08:00
|
|
|
|
public void Ramp(double target, int time)
|
2023-04-13 11:51:03 +08:00
|
|
|
|
{
|
|
|
|
|
_rampTimer.Stop();
|
|
|
|
|
target = Math.Max(0, target);
|
|
|
|
|
target = Math.Min(Scale, target);
|
|
|
|
|
_rampInitValue = SetPoint; //ramp 初始值取当前设定值,而非实际读取值.零漂问题
|
|
|
|
|
_rampTime = time;
|
|
|
|
|
_rampTarget = target;
|
|
|
|
|
_rampTimer.Start(_rampTime);
|
|
|
|
|
_rampSetMode = SetMode;
|
|
|
|
|
|
|
|
|
|
//EV.PostInfoLog(Module, $"{Name}.Ramp {_rampTarget} in {_rampTime} senconds,Mode {_rampSetMode/1000}");
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void StopRamp()
|
|
|
|
|
{
|
|
|
|
|
if (!_rampTimer.IsIdle())
|
|
|
|
|
{
|
|
|
|
|
if (_rampTime != 0)
|
|
|
|
|
{
|
|
|
|
|
Ramp(SetPoint, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void MonitorRamping()
|
|
|
|
|
{
|
|
|
|
|
if (!_rampTimer.IsIdle())
|
|
|
|
|
{
|
|
|
|
|
if (_rampTimer.IsTimeout() || _rampTime == 0)
|
|
|
|
|
{
|
|
|
|
|
_rampTimer.Stop();
|
|
|
|
|
SetPoint = _rampTarget;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SetPoint = _rampInitValue + (_rampTarget - _rampInitValue) * _rampTimer.GetElapseTime() / _rampTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(_rampSetMode == 0 || _rampSetMode == 1 || _rampSetMode == 2 || _rampSetMode == 3)
|
|
|
|
|
{
|
|
|
|
|
SetMode = _rampSetMode;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-25 16:05:19 +08:00
|
|
|
|
|
|
|
|
|
#endregion
|
2023-04-13 11:51:03 +08:00
|
|
|
|
}
|
|
|
|
|
}
|