using Aitex.Core.RT.DataCenter; using Aitex.Core.RT.Device.PmDevices; using Aitex.Core.RT.Event; using Aitex.Core.RT.IOCore; using Aitex.Core.RT.Log; using Aitex.Core.RT.OperationCenter; using Aitex.Core.RT.SCCore; using Aitex.Core.Util; using MECF.Framework.Common.MECF.Framework.Common.Utilities; using MECF.Framework.RT.EquipmentLibrary.HardwareUnits.Temps; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Xml; using static Aitex.Core.RT.Device.PmDevices.DicMode; namespace Aitex.Core.RT.Device.Devices { public class IoTC : BaseDevice, IDevice { private bool _isDBSaveTemp; private readonly string _scNamePidLimitLower; private readonly string _scNamePidLimitUpper; private readonly string _scNamePidRateLimit; private readonly bool _isFloatAioType = true; private readonly AIAccessor _aiL1WorkingOPFeedBack = null; private readonly AIAccessor _aiL2WorkingOPFeedBack = null; private readonly AIAccessor _aiL3WorkingOPFeedBack = null; //实际温度值 private readonly AIAccessor _aiL1PVFeedBack = null; private readonly AIAccessor _aiL2PVFeedBack = null; private readonly AIAccessor _aiL3PVFeedBack = null; private readonly DIAccessor _diL1TempHighAlarmFeedBack = null; private readonly DIAccessor _diL2TempHighAlarmFeedBack = null; private readonly DIAccessor _diL3TempHighAlarmFeedBack = null; private readonly DIAccessor _diL1TempLowAlarmFeedBack = null; private readonly DIAccessor _diL2TempLowAlarmFeedBack = null; private readonly DIAccessor _diL3TempLowAlarmFeedBack = null; private readonly AOAccessor _aoL1LoopModeSetPoint = null; private readonly AOAccessor _aoL2LoopModeSetPoint = null; private readonly AOAccessor _aoL3LoopModeSetPoint = null; private readonly AOAccessor _aoL1TargetSPSetPoint = null; private readonly AOAccessor _aoL2TargetSPSetPoint = null; private readonly AOAccessor _aoL3TargetSPSetPoint = null; private readonly AOAccessor _aoL1TargetOPSetPoint = null; private readonly AOAccessor _aoL2TargetOPSetPoint = null; private readonly AOAccessor _aoL3TargetOPSetPoint = null; private readonly AOAccessor _aoL1InputTempSetPoint = null; private readonly AOAccessor _aoL2InputTempSetPoint = null; private readonly AOAccessor _aoL3InputTempSetPoint = null; private readonly AOAccessor _aoL1TempHighLimitSetPoint = null; private readonly AOAccessor _aoL2TempHighLimitSetPoint = null; private readonly AOAccessor _aoL3TempHighLimitSetPoint = null; private readonly AOAccessor _aoL1TempLowLimitSetPoint = null; private readonly AOAccessor _aoL2TempLowLimitSetPoint = null; private readonly AOAccessor _aoL3TempLowLimitSetPoint = null; private readonly AOAccessor _aoHeaterModeSetPoint = null; private readonly AOAccessor _aoPowerRefSetPoint = null; private readonly AOAccessor _aoL1RatioSetPoint = null; private readonly AOAccessor _aoL2RatioSetPoint = null; private readonly AOAccessor _aoL3RatioSetPoint = null; private readonly AOAccessor _aoL1RatedSetPoint = null; private readonly AOAccessor _aoL2RatedSetPoint = null; private readonly AOAccessor _aoL3RatedSetPoint = null; private readonly AOAccessor _aoL1VoltageLimited = null; private readonly AOAccessor _aoL2VoltageLimited = null; private readonly AOAccessor _aoL3VoltageLimited = null; private readonly AIAccessor _aiTtempCtrlTCIN = null; private readonly AOAccessor _aoPSU1Y = null; private readonly AOAccessor _aoPSU2Y = null; private readonly AOAccessor _aoPSU3Y = null; //private readonly AOAccessor _aoPIDLimitLower = null; //private readonly AOAccessor _aoPIDLimitUpper = null; //private readonly AOAccessor _aoPIDRateLimit = null; private readonly AOAccessor _aoPlcTargetOpLowLimit = null; private readonly AOAccessor _aoPlcTargetOpHighLimit = null; private readonly DOAccessor _doPLCTempFilterEN = null; private readonly DOAccessor _doPLCTargetOPCtrlEN = null; private readonly DIAccessor _diPSUInnerHeaterEnable = null; private readonly DIAccessor _diPSUMiddleHeaterEnable= null; private readonly DIAccessor _diPSUOuterHeaterEnable= null; private readonly DIAccessor _diSCRUpHeaterEnable= null; private readonly DIAccessor _diSCRMiddleHeaterEnable= null; private readonly DIAccessor _diSCRDownHeaterEnable= null; private DeviceTimer _delaySetTime = new DeviceTimer(); private const int MAX_HEATER_LOOP = 3; private int _overHighTempCount = 0; private R_TRIG _overHighTempTRIG = new(); private struct ParamRampTempSp { public DeviceTimer Timer; public float FinalTemp; public float InitTemp; public int DurationMs; public AOAccessor AoTargetSetPoint; public ParamRampTempSp(AOAccessor aoTargetSetPoint) { Timer = new DeviceTimer(); AoTargetSetPoint = aoTargetSetPoint; } public void Stop() { Timer.Stop(); } } private readonly IReadOnlyList _rampParams; private readonly DeviceTimer _rampTimerSetPowerRef = new DeviceTimer(); private float _rampTargetSetPowerRefRecipe; private float _rampInitValueSetPowerRefRecipe; private int _rampTimeSetPowerRefRecipe; private readonly DeviceTimer _rampTimerL1RatiorRecipe = new DeviceTimer(); private float _rampTargetL1RatiorRecipe; private float _rampInitValueL1RatiorRecipe; private int _rampTimeL1RatiorRecipe; private readonly DeviceTimer _rampTimerL2RatiorRecipe = new DeviceTimer(); private float _rampTargetL2RatiorRecipe; private float _rampInitValueL2RatiorRecipe; private int _rampTimeL2RatiorRecipe; private readonly DeviceTimer _rampTimerL3RatiorRecipe = new DeviceTimer(); private float _rampTargetL3RatiorRecipe; private float _rampInitValueL3RatiorRecipe; private int _rampTimeL3RatiorRecipe; private string _selecetedLoop = string.Empty; private float _middleTempRatio = 100; private readonly SCConfigItem _tempRampRatio; private SCConfigItem _ampereRampRatio; private SCConfigItem _kwRampRatio; private readonly SCConfigItem _pyroWarmBaseTemp; //Pyro温度报警需要满足BaseTemp private readonly SCConfigItem _pyroWarmMaxDiff; //Pyro的MidlleTemp,OuterTemp任意两个温度大于此数值需要报警 private readonly SCConfigItem _pyroWarmEffectFromTime; //温差过大只在进入Process多久后才有效 private readonly SCConfigItem _pyroWarmEffectEndTime; //温差过大只在Process即将结束前多久才有效 //温度滤波功能块 private TempDataFilter _outerTempFilter; private TempDataFilter _innerTempFilter; private TempDataFilter _middleTempFilter; private readonly SCConfigItem _PSUHighLimit; private readonly SCConfigItem _PSULowLimit; private readonly SCConfigItem _SCRHighLimit; private readonly SCConfigItem _SCRLowLimit; private double _pidLimitLowerUnderPryo; private double _pidLimitUpperUnderPyro; private double _pidRateLimitUnderPyro; private readonly SCConfigItem _PyroWarmIsAlarm; private bool _isAlarmTempRaisingFast; private double _tempRaisingRateOuter; private double _tempRaisingRateMiddle; private string _scOuterTempValue; public string ScOuterTempValue { get => _scOuterTempValue; set => _scOuterTempValue = "TempSensor." + value; } private string _scInnerTempValue; public string ScInnerTempValue { get => _scInnerTempValue; set => _scInnerTempValue = "TempSensor." + value; } private string _scMiddleTempValue; public string ScMiddleTempValue { get => _scMiddleTempValue; set => _scMiddleTempValue = "TempSensor." + value; } private readonly R_TRIG _trigSetTempLimit = new R_TRIG(); private int _simlatorTempThreshold = 620; private DateTime _simlatorTempStartTime = DateTime.Now; private readonly Stopwatch _stopwatch = new Stopwatch(); private PeriodicJob _tReadTemp; public IoTC(string module, XmlElement node, string ioModule = "") { var attrModule = node.GetAttribute("module"); base.Module = string.IsNullOrEmpty(attrModule) ? module : attrModule; base.Name = node.GetAttribute("id"); base.Display = node.GetAttribute("display"); base.DeviceID = node.GetAttribute("schematicId"); _simlatorTempThreshold = SC.GetValue("PM.SimlatorTempThreshold"); _aiL1WorkingOPFeedBack = ParseAiNode("aiL1WorkingOPFeedBack", node, ioModule); _aiL2WorkingOPFeedBack = ParseAiNode("aiL2WorkingOPFeedBack", node, ioModule); _aiL3WorkingOPFeedBack = ParseAiNode("aiL3WorkingOPFeedBack", node, ioModule); _aiL1PVFeedBack = ParseAiNode("aiL1PVFeedBack", node, ioModule); _aiL2PVFeedBack = ParseAiNode("aiL2PVFeedBack", node, ioModule); _aiL3PVFeedBack = ParseAiNode("aiL3PVFeedBack", node, ioModule); _diL1TempHighAlarmFeedBack = ParseDiNode("diL1TempHighAlarmFeedBack", node, ioModule); _diL2TempHighAlarmFeedBack = ParseDiNode("diL2TempHighAlarmFeedBack", node, ioModule); _diL3TempHighAlarmFeedBack = ParseDiNode("diL3TempHighAlarmFeedBack", node, ioModule); _diL1TempLowAlarmFeedBack = ParseDiNode("diL1TempLowAlarmFeedBack", node, ioModule); _diL2TempLowAlarmFeedBack = ParseDiNode("diL2TempLowAlarmFeedBack", node, ioModule); _diL3TempLowAlarmFeedBack = ParseDiNode("diL3TempLowAlarmFeedBack", node, ioModule); _diPSUInnerHeaterEnable = ParseDiNode("diPSUInnerHeaterEnable", node, ioModule); _diPSUMiddleHeaterEnable = ParseDiNode("diPSUMiddleHeaterEnable", node, ioModule); _diPSUOuterHeaterEnable = ParseDiNode("diPSUOuterHeaterEnable", node, ioModule); _diSCRUpHeaterEnable = ParseDiNode("diSCRUpHeaterEnable", node, ioModule); _diSCRMiddleHeaterEnable = ParseDiNode("diSCRMiddleHeaterEnable", node, ioModule); _diSCRDownHeaterEnable = ParseDiNode("diSCRDownHeaterEnable", node, ioModule); _aoL1LoopModeSetPoint = ParseAoNode("aoL1LoopModeSetPoint", node, ioModule); _aoL2LoopModeSetPoint = ParseAoNode("aoL2LoopModeSetPoint", node, ioModule); _aoL3LoopModeSetPoint = ParseAoNode("aoL3LoopModeSetPoint", node, ioModule); _aoL1TargetSPSetPoint = ParseAoNode("aoL1TargetSPSetPoint", node, ioModule); _aoL2TargetSPSetPoint = ParseAoNode("aoL2TargetSPSetPoint", node, ioModule); _aoL3TargetSPSetPoint = ParseAoNode("aoL3TargetSPSetPoint", node, ioModule); _aoL1TargetOPSetPoint = ParseAoNode("aoL1TargetOPSetPoint", node, ioModule); _aoL2TargetOPSetPoint = ParseAoNode("aoL2TargetOPSetPoint", node, ioModule); _aoL3TargetOPSetPoint = ParseAoNode("aoL3TargetOPSetPoint", node, ioModule); _aoL1InputTempSetPoint = ParseAoNode("aoL1InputTempSetPoint", node, ioModule); _aoL2InputTempSetPoint = ParseAoNode("aoL2InputTempSetPoint", node, ioModule); _aoL3InputTempSetPoint = ParseAoNode("aoL3InputTempSetPoint", node, ioModule); _aoL1TempHighLimitSetPoint = ParseAoNode("aoL1TempHighLimitSetPoint", node, ioModule); _aoL2TempHighLimitSetPoint = ParseAoNode("aoL2TempHighLimitSetPoint", node, ioModule); _aoL3TempHighLimitSetPoint = ParseAoNode("aoL3TempHighLimitSetPoint", node, ioModule); _aoL1TempLowLimitSetPoint = ParseAoNode("aoL1TempLowLimitSetPoint", node, ioModule); _aoL2TempLowLimitSetPoint = ParseAoNode("aoL2TempLowLimitSetPoint", node, ioModule); _aoL3TempLowLimitSetPoint = ParseAoNode("aoL3TempLowLimitSetPoint", node, ioModule); _aoHeaterModeSetPoint = ParseAoNode("aoHeaterModeSetPoint", node, ioModule); _aoPowerRefSetPoint = ParseAoNode("aoPowerRefSetPoint", node, ioModule); _aoL1RatioSetPoint = ParseAoNode("aoL1RatioSetPoint", node, ioModule); _aoL2RatioSetPoint = ParseAoNode("aoL2RatioSetPoint", node, ioModule); _aoL3RatioSetPoint = ParseAoNode("aoL3RatioSetPoint", node, ioModule); _aoL1RatedSetPoint = ParseAoNode("aoL1RatedSetPoint", node, ioModule); _aoL2RatedSetPoint = ParseAoNode("aoL2RatedSetPoint", node, ioModule); _aoL3RatedSetPoint = ParseAoNode("aoL3RatedSetPoint", node, ioModule); _aoL1VoltageLimited = ParseAoNode("aoL1VoltageLimited", node, ioModule); _aoL2VoltageLimited = ParseAoNode("aoL2VoltageLimited", node, ioModule); _aoL3VoltageLimited = ParseAoNode("aoL3VoltageLimited", node, ioModule); _aoPSU1Y = ParseAoNode("aoPSU1Y", node, ioModule); _aoPSU2Y = ParseAoNode("aoPSU2Y", node, ioModule); _aoPSU3Y = ParseAoNode("aoPSU3Y", node, ioModule); _isDBSaveTemp = node.GetAttribute("isDBSaveTemp") == "true" ? true : false; _aiTtempCtrlTCIN = ParseAiNode("aiTtempCtrlTCIN", node, ioModule); _isFloatAioType = !string.IsNullOrEmpty(node.GetAttribute("aioType")) && (node.GetAttribute("aioType") == "float"); _pyroWarmEffectFromTime = ParseScNode("PyroWarmEffectFromTime", node, ioModule, $"PM.{Module}.Heater.PyroWarmEffectFromTime"); _pyroWarmEffectEndTime = ParseScNode("PyroWarmEffectFromTime", node, ioModule, $"PM.{Module}.Heater.PyroWarmEffectEndTime"); _tempRampRatio = ParseScNode("TempRampRatio", node, ioModule, $"PM.{Module}.Heater.TempRampRate"); _ampereRampRatio = ParseScNode("AmpereRampRate", node, ioModule, $"PM.{Module}.Heater.AmpereRampRate"); _kwRampRatio = ParseScNode("KWRampRate", node, ioModule, $"PM.{Module}.Heater.KWRampRate"); _pyroWarmBaseTemp = ParseScNode("PyroWarmBaseTemp", node, ioModule, $"PM.{Module}.Heater.PyroWarmBaseTemp"); _pyroWarmMaxDiff = ParseScNode("PyroWarmBaseTemp", node, ioModule, $"PM.{Module}.Heater.PyroWarmMaxDiff"); var scName = $"PM.{Module}.Heater.PSUTempHighLimit"; _PSUHighLimit = ParseScNode("PSUTempHighLimit", node, ioModule, scName); SC.RegisterValueChangedCallback(scName, val => { if (val is double dblVal) _PSUHighLimit.DoubleValue = dblVal; }); scName = $"PM.{Module}.Heater.PSUTempLowLimit"; _PSULowLimit = ParseScNode("PSUTempLowLimit", node, ioModule, $"PM.{Module}.Heater.PSUTempLowLimit"); SC.RegisterValueChangedCallback(scName, val => { if (val is double dblVal) _PSULowLimit.DoubleValue = dblVal; }); scName = $"PM.{Module}.Heater.SCRTempHighLimit"; _SCRHighLimit = ParseScNode("SCRTempHighLimit", node, ioModule, $"PM.{Module}.Heater.SCRTempHighLimit"); SC.RegisterValueChangedCallback(scName, val => { if (val is double dblVal) _SCRHighLimit.DoubleValue = dblVal; }); scName = $"PM.{Module}.Heater.SCRTempLowLimit"; _SCRLowLimit = ParseScNode("SCRTempLowLimit", node, ioModule, $"PM.{Module}.Heater.SCRTempLowLimit"); SC.RegisterValueChangedCallback(scName, val => { if (val is double dblVal) _SCRLowLimit.DoubleValue = dblVal; }); scName = $"PM.{Module}.Heater.PyroWarmIsAlarm"; _PyroWarmIsAlarm = ParseScNode("PyroWarmIsAlarm", node, ioModule, $"PM.{Module}.Heater.PyroWarmIsAlarm"); SC.RegisterValueChangedCallback(scName, val => { if (val is double dblVal) _PyroWarmIsAlarm.DoubleValue = dblVal; }); var lstRampParam = new List(); var lstAoTargetSp = new AOAccessor[MAX_HEATER_LOOP] { _aoL1TargetSPSetPoint, _aoL2TargetSPSetPoint, _aoL3TargetSPSetPoint }; var lstAoTempFromSensorToTC = new AOAccessor[MAX_HEATER_LOOP] { _aoL1InputTempSetPoint, _aoL2InputTempSetPoint, _aoL3InputTempSetPoint }; for (var i = 0; i < MAX_HEATER_LOOP; i++) lstRampParam.Add(new IoTCHeaterLoop(i + 1, lstAoTargetSp[i], lstAoTempFromSensorToTC[i])); _rampParams = lstRampParam; #region Dynamic PID Limit /* _scNamePidLimitLower = $"PM.{Module}.Heater.PIDLimitLower"; _scNamePidLimitUpper = $"PM.{Module}.Heater.PIDLimitUpper"; _scNamePidRateLimit = $"PM.{Module}.Heater.PIDRateLimit"; _pidLimitLowerUnderPryo = SC.SafeGetValue(_scNamePidLimitLower, 0.0); _pidLimitUpperUnderPyro = SC.SafeGetValue(_scNamePidLimitUpper, 100.0); _pidRateLimitUnderPyro = SC.SafeGetValue(_scNamePidRateLimit, 100.0); SC.RegisterValueChangedCallback(_scNamePidLimitLower, v => { if (double.TryParse(v.ToString(), out var dbl)) _pidLimitLowerUnderPryo = dbl.Round(1); else { _pidLimitLowerUnderPryo = 50; LOG.Error($"{_scNamePidLimitLower} is not double, set to 100.0"); } }); SC.RegisterValueChangedCallback(_scNamePidLimitUpper, v => { if (double.TryParse(v.ToString(), out var dbl)) _pidLimitUpperUnderPyro = dbl.Round(1); else { _pidLimitUpperUnderPyro = 100.0; LOG.Error($"{_scNamePidLimitUpper} is not double, set to 100.0"); } }); SC.RegisterValueChangedCallback(_scNamePidRateLimit, v => { if (double.TryParse(v.ToString(), out var dbl)) _pidRateLimitUnderPyro = dbl.Round(1); else { _pidRateLimitUnderPyro = 100.0; LOG.Error($"{_scNamePidRateLimit} is not double, set to 100.0"); } }); _aoPIDLimitLower = ParseAoNode("aoPIDLimitLower", node, ioModule); _aoPIDLimitUpper = ParseAoNode("aoPIDLimitUpper", node, ioModule); _aoPIDRateLimit = ParseAoNode("aoPIDRateLimit", node, ioModule); Debug.Assert(_aoPIDLimitLower != null); Debug.Assert(_aoPIDLimitUpper != null); Debug.Assert(_aoPIDRateLimit != null);*/ #endregion #region PLC TargetOP Limits _aoPlcTargetOpLowLimit = ParseAoNode("aoPLCTargetOPLowLimit", node, ioModule); _aoPlcTargetOpHighLimit = ParseAoNode("aoPLCTargetOPUpLimit", node, ioModule); _doPLCTempFilterEN = ParseDoNode("doPLCTempFilterEN", node, ioModule); _doPLCTargetOPCtrlEN = ParseDoNode("doPLCTargetOPCtrlEN", node, ioModule); #endregion } public bool Initialize() { DATA.Subscribe($"{Module}.{Name}.L1WorkingOPFeedBack", () => L1WorkingOPFeedBack); DATA.Subscribe($"{Module}.{Name}.L2WorkingOPFeedBack", () => L2WorkingOPFeedBack); DATA.Subscribe($"{Module}.{Name}.L3WorkingOPFeedBack", () => L3WorkingOPFeedBack); DATA.Subscribe($"{Module}.{Name}.L1PVFeedBack", () => L1PVFeedBack); DATA.Subscribe($"{Module}.{Name}.L2PVFeedBack", () => L2PVFeedBack); DATA.Subscribe($"{Module}.{Name}.L3PVFeedBack", () => L3PVFeedBack); DATA.Subscribe($"{Module}.{Name}.L1TempHighAlarmFeedBack", () => L1TempHighAlarmFeedBack); DATA.Subscribe($"{Module}.{Name}.L2TempHighAlarmFeedBack", () => L2TempHighAlarmFeedBack); DATA.Subscribe($"{Module}.{Name}.L3TempHighAlarmFeedBack", () => L3TempHighAlarmFeedBack); DATA.Subscribe($"{Module}.{Name}.L1TempLowAlarmFeedBack", () => L1TempLowAlarmFeedBack); DATA.Subscribe($"{Module}.{Name}.L2TempLowAlarmFeedBack", () => L2TempLowAlarmFeedBack); DATA.Subscribe($"{Module}.{Name}.L3TempLowAlarmFeedBack", () => L3TempLowAlarmFeedBack); DATA.Subscribe($"{Module}.{Name}.L1LoopModeSetPoint", () => (int)L1LoopModeSetPoint); DATA.Subscribe($"{Module}.{Name}.L2LoopModeSetPoint", () => (int)L2LoopModeSetPoint); DATA.Subscribe($"{Module}.{Name}.L3LoopModeSetPoint", () => (int)L3LoopModeSetPoint); DATA.Subscribe($"{Module}.{Name}.L1TargetSPSetPoint", () => L1TargetSPSetPoint); DATA.Subscribe($"{Module}.{Name}.L2TargetSPSetPoint", () => L2TargetSPSetPoint); DATA.Subscribe($"{Module}.{Name}.L3TargetSPSetPoint", () => L3TargetSPSetPoint); DATA.Subscribe($"{Module}.{Name}.L1TargetOPSetPoint", () => L1TargetOPSetPoint); DATA.Subscribe($"{Module}.{Name}.L2TargetOPSetPoint", () => L2TargetOPSetPoint); DATA.Subscribe($"{Module}.{Name}.L3TargetOPSetPoint", () => L3TargetOPSetPoint); DATA.Subscribe($"{Module}.{Name}.L1InputTempSetPoint", () => L1InputTempSetPoint); DATA.Subscribe($"{Module}.{Name}.L2InputTempSetPoint", () => L2InputTempSetPoint); DATA.Subscribe($"{Module}.{Name}.L3InputTempSetPoint", () => L3InputTempSetPoint); DATA.Subscribe($"{Module}.{Name}.L1TempHighLimitSetPoint", () => L1TempHighLimitSetPoint); DATA.Subscribe($"{Module}.{Name}.L2TempHighLimitSetPoint", () => L2TempHighLimitSetPoint); DATA.Subscribe($"{Module}.{Name}.L3TempHighLimitSetPoint", () => L3TempHighLimitSetPoint); DATA.Subscribe($"{Module}.{Name}.L1TempLowLimitSetPoint", () => L1TempLowLimitSetPoint); DATA.Subscribe($"{Module}.{Name}.L2TempLowLimitSetPoint", () => L2TempLowLimitSetPoint); DATA.Subscribe($"{Module}.{Name}.L3TempLowLimitSetPoint", () => L3TempLowLimitSetPoint); DATA.Subscribe($"{Module}.{Name}.HeaterModeSetPoint", () => (int)HeaterModeSetPoint); DATA.Subscribe($"{Module}.{Name}.PowerRefSetPoint", () => PowerRefSetPoint); DATA.Subscribe($"{Module}.{Name}.L1RatioSetPoint", () => L1RatioSetPoint); DATA.Subscribe($"{Module}.{Name}.L2RatioSetPoint", () => L2RatioSetPoint); DATA.Subscribe($"{Module}.{Name}.L3RatioSetPoint", () => L3RatioSetPoint); DATA.Subscribe($"{Module}.{Name}.L1RatedSetPoint", () => L1RatedSetPoint); DATA.Subscribe($"{Module}.{Name}.L2RatedSetPoint", () => L2RatedSetPoint); DATA.Subscribe($"{Module}.{Name}.L3RatedSetPoint", () => L3RatedSetPoint); DATA.Subscribe($"{Module}.{Name}.L1VoltageLimited", () => L1VoltageLimited); DATA.Subscribe($"{Module}.{Name}.L2VoltageLimited", () => L2VoltageLimited); DATA.Subscribe($"{Module}.{Name}.L3VoltageLimited", () => L3VoltageLimited); //DATA.Subscribe($"{Module}.{Name}.TempCtrlTCIN", () => TtempCtrlTCIN); DATA.Subscribe($"{Module}.{Name}.PyroTempMaxDiff", () => PyroTempMaxDiff); // DATA.Subscribe($"{Module}.{Name}.PSU1Y", () => PSU1Y); DATA.Subscribe($"{Module}.{Name}.PSU2Y", () => PSU2Y); DATA.Subscribe($"{Module}.{Name}.PSU3Y", () => PSU3Y); DATA.Subscribe($"{Module}.{Name}.PCPSU1Y", () => PCPSU1Y); DATA.Subscribe($"{Module}.{Name}.PCPSU2Y", () => PCPSU2Y); DATA.Subscribe($"{Module}.{Name}.PCPSU3Y", () => PCPSU3Y); DATA.Subscribe($"{Module}.{Name}.PSU1Power", () => PSU1Power); DATA.Subscribe($"{Module}.{Name}.PSU2Power", () => PSU2Power); DATA.Subscribe($"{Module}.{Name}.PSU3Power", () => PSU3Power); DATA.Subscribe($"{Module}.{Name}.HeatStrategy", () => (int)HeaterModeSetPoint); //DATA.Subscribe($"{Module}.{Name}.PIDLimitLower", () => _aoPIDLimitLower?.Value ?? double.NaN); //DATA.Subscribe($"{Module}.{Name}.PIDLimitUpper", () => _aoPIDLimitUpper?.Value ?? double.NaN); //DATA.Subscribe($"{Module}.{Name}.PIDRateLimit", () => _aoPIDRateLimit?.Value ?? double.NaN); DATA.Subscribe($"{Module}.{Name}.PlcTargetOpLowLimit", () => _aoPlcTargetOpLowLimit?.Value ?? double.NaN); DATA.Subscribe($"{Module}.{Name}.PlcTargetOpHighLimit", () => _aoPlcTargetOpHighLimit?.Value ?? double.NaN); DATA.Subscribe($"{Module}.{Name}.PlcTempFilterEN", () => _doPLCTempFilterEN?.Value ?? false); DATA.Subscribe($"{Module}.{Name}.PlcTargetOPCtrlEN", () => _doPLCTargetOPCtrlEN?.Value ?? false); if (Name == "TC1") { DATA.Subscribe($"{Module}.Temp.Outer", () => OuterTemp); DATA.Subscribe($"{Module}.Temp.Inner", () => InnerTemp); DATA.Subscribe($"{Module}.Temp.Middle", () => MiddleTemp); DATA.Subscribe($"{Module}.Temp.Chamber", () => L2InputTempSetPoint); DATA.Subscribe($"{Module}.Temp.MiddleTempRatio", () => _middleTempRatio); var pollIntervalMs = 100; _tReadTemp = new PeriodicJob(pollIntervalMs, OnTime, $"{Module}.{Name}", true); _outerTempFilter = new TempDataFilter(Module, "OuterTemp", pollIntervalMs, _simlatorTempThreshold, $"PM.{Module}.TempSensor"); _innerTempFilter = new TempDataFilter(Module, "InnerTemp", pollIntervalMs, _simlatorTempThreshold, $"PM.{Module}.TempSensor"); _middleTempFilter = new TempDataFilter(Module, "MiddleTemp", pollIntervalMs, _simlatorTempThreshold, $"PM.{Module}.TempSensor"); _outerTempFilter.Initialize(out var reason); _middleTempFilter.Initialize(out reason); _innerTempFilter.Initialize(out reason); } GetPCPSUPower(); OP.Subscribe($"{Module}.{Name}.SetL1TargetSP", (out string reason, int time, object[] args) => { _selecetedLoop = "L1"; var targetSP = Convert.ToSingle(args[0]); reason = $"{Display} {_selecetedLoop} Target SP set to {targetSP}"; EV.PostInfoLog(Module, reason); SetTargetSP(_selecetedLoop, targetSP, time); return true; }); OP.Subscribe($"{Module}.{Name}.SetL2TargetSP", (out string reason, int time, object[] args) => { _selecetedLoop = "L2"; var targetSp = Convert.ToSingle(args[0]); reason = $"{Display} {_selecetedLoop} Target SP set to {targetSp}"; EV.PostInfoLog(Module, reason); SetTargetSP(_selecetedLoop, targetSp, time); return true; }); OP.Subscribe($"{Module}.{Name}.SetL3TargetSP", (out string reason, int time, object[] args) => { _selecetedLoop = "L3"; var targetSP = Convert.ToSingle(args[0]); reason = $"{Display} {_selecetedLoop} Target SP set to {targetSP}"; EV.PostInfoLog(Module, reason); SetTargetSP(_selecetedLoop, targetSP, time); return true; }); OP.Subscribe($"{Module}.{Name}.MiddleTempRatio", (out string reason, int time, object[] args) => { var middleTempRatio = Convert.ToSingle(args[0]); reason = $"{Display} MiddleTempRatio set to {middleTempRatio}"; EV.PostInfoLog(Module, reason); _middleTempRatio = middleTempRatio; return true; }); OP.Subscribe($"{Module}.{Name}.SetHeaterMode", (out string reason, int time, object[] args) => { reason = ""; var iMode = Convert.ToInt32(args[0]); SetHeatMode(ConvertToHeatStrategy(iMode), time, TCUnits.PSU); return true; }); OP.Subscribe($"{Module}.{Name}.SetHeaterMode2", (out string reason, int time, object[] args) => { reason = ""; var iMode = Convert.ToInt32(args[0]); SetHeatMode(ConvertToHeatStrategy(iMode), time, TCUnits.SCR); return true; }); OP.Subscribe($"{Module}.{Name}.SetRatio", (function, args) => { SetRatio(Convert.ToSingle(args[0]), Convert.ToSingle(args[1]), Convert.ToSingle(args[2])); return true; }); OP.Subscribe($"{Module}.{Name}.SetRatedValue", (function, args) => { SetRatedValue(Convert.ToSingle(args[0]), Convert.ToSingle(args[1]), Convert.ToSingle(args[2])); return true; }); OP.Subscribe($"{Module}.{Name}.RecipeSetPowerRef", (out string reason, int time, object[] args) => { var PowerRef = Convert.ToSingle(args[0]); var _PowerRef = PowerRef > 0 ? (PowerRef > 100 ? 100 : PowerRef) : 0; if (time > 0) { reason = $"{Display}Recipe power Ref ramp to {_PowerRef}"; } else { reason = $"{Display}Recipe power Ref set to {_PowerRef}"; } EV.PostInfoLog(Module, reason); RecipeSetPowerRef(_PowerRef, time); return true; }); OP.Subscribe($"{Module}.{Name}.RecipeSetL1Ratio", (out string reason, int time, object[] args) => { _selecetedLoop = "L1"; var ratio = Convert.ToSingle(args[0]); reason = $"{Display} Recipe {_selecetedLoop} Ratio set to {ratio}"; EV.PostInfoLog(Module, reason); RecipeSetRatio(_selecetedLoop, ratio, time); return true; }); OP.Subscribe($"{Module}.{Name}.RecipeSetL2Ratio", (out string reason, int time, object[] args) => { _selecetedLoop = "L2"; var ratio = Convert.ToSingle(args[0]); reason = $"{Display} Recipe {_selecetedLoop} Ratio set to {ratio}"; EV.PostInfoLog(Module, reason); RecipeSetRatio(_selecetedLoop, ratio, time); return true; }); OP.Subscribe($"{Module}.{Name}.RecipeSetL3Ratio", (out string reason, int time, object[] args) => { _selecetedLoop = "L3"; var ratio = Convert.ToSingle(args[0]); reason = $"{Display} Recipe {_selecetedLoop} Ratio set to {ratio}"; EV.PostInfoLog(Module, reason); RecipeSetRatio(_selecetedLoop, ratio, time); return true; }); // OP.Subscribe($"{Module}.{Name}.SetPSUY", (function, args) => { SetPSUY(Convert.ToDouble(args[0]), Convert.ToDouble(args[1]), Convert.ToDouble(args[2])); return true; }); OP.Subscribe($"{Module}.{Name}.SetPSUPower", (function, args) => { SetPSUPower(Convert.ToDouble(args[0]), Convert.ToDouble(args[1]), Convert.ToDouble(args[2])); return true; }); OP.Subscribe($"{Module}.{Name}.SetPIDLimit", (function, args) => SetPIDLimit(args)); // 读取系统配置 SCInit(); DisturbanceSimInitialize(); return true; } private bool OnTime() { MonitorTempFilter(); MonitorTemp(); CheckHighTemp(); return true; } /// /// 读取系统配置,并注册回调。 /// private void SCInit() { ScOuterTempValue = SC.GetStringValue($"PM.{Module}.TempSensor.Outer"); ScInnerTempValue = SC.GetStringValue($"PM.{Module}.TempSensor.Inner"); ScMiddleTempValue = SC.GetStringValue($"PM.{Module}.TempSensor.Middle"); SC.RegisterValueChangedCallback($"PM.{Module}.TempSensor.Outer", (obj) => { ScOuterTempValue = obj.ToString(); }); SC.RegisterValueChangedCallback($"PM.{Module}.TempSensor.Inner", (obj) => { ScInnerTempValue = obj.ToString(); }); SC.RegisterValueChangedCallback($"PM.{Module}.TempSensor.Middle", (obj) => { ScMiddleTempValue = obj.ToString(); }); #region Temp Rising Too Fast Alarm _isAlarmTempRaisingFast = SC.SafeGetValue($"PM.{Module}.Heater.AETempRasingFastIsAlarm", true); _tempRaisingRateMiddle = SC.SafeGetValue($"PM.{Module}.Heater.AETempMiddleRasingRate", 20.0); _tempRaisingRateOuter = SC.SafeGetValue($"PM.{Module}.Heater.AETempOuterRasingRate", 20.0); SC.RegisterValueChangedCallback($"PM.{Module}.Heater.AETempRasingFastIsAlarm", val => { _isAlarmTempRaisingFast = (bool)val; }); SC.RegisterValueChangedCallback($"PM.{Module}.Heater.AETempMiddleRasingRate", val => { _tempRaisingRateMiddle = (double)val; }); SC.RegisterValueChangedCallback($"PM.{Module}.Heater.AETempOuterRasingRate", val => { _tempRaisingRateOuter = (double)val; }); #endregion } #region 手动设置比例 RatioSetPoint public bool RecipeSetRatio(string selecetedLoop, float ratio, int time) { switch (selecetedLoop) { case "L1": _rampInitValueL1RatiorRecipe = L1RatioSetPoint; _rampTimerL1RatiorRecipe.Stop(); if (time > 0) { _rampTimeL1RatiorRecipe = time; _rampTargetL1RatiorRecipe = ratio; _rampTimerL1RatiorRecipe.Start(time); } else { L1RatioSetPoint = ratio; } break; case "L2": _rampInitValueL2RatiorRecipe = L2RatioSetPoint; _rampTimerL2RatiorRecipe.Stop(); if (time > 0) { _rampTimeL2RatiorRecipe = time; _rampTargetL2RatiorRecipe = ratio; _rampTimerL2RatiorRecipe.Start(time); } else { L2RatioSetPoint = ratio; } break; case "L3": _rampInitValueL3RatiorRecipe = L3RatioSetPoint; _rampTimerL3RatiorRecipe.Stop(); if (time > 0) { _rampTimeL3RatiorRecipe = time; _rampTargetL3RatiorRecipe = ratio; _rampTimerL3RatiorRecipe.Start(time); } else { L3RatioSetPoint = ratio; } break; } if (HeaterModeSetPoint is HeatStrategy.PyroFollow or HeatStrategy.PyroAuto) //设置输出功率 { var f1 = L1RatioSetPoint > 0 ? L1RatioSetPoint : 0; var f2 = L2RatioSetPoint > 0 ? L2RatioSetPoint : 0; var f3 = L3RatioSetPoint > 0 ? L3RatioSetPoint : 0; L1TargetOPSetPoint = f1 > 100 ? 100 : f1; L2TargetOPSetPoint = f2 > 100 ? 100 : f2; L3TargetOPSetPoint = f3 > 100 ? 100 : f3; } return true; } private void MonitorL1RecipeRatioRamping() { if (!_rampTimerL1RatiorRecipe.IsIdle()) { if (_rampTimerL1RatiorRecipe.IsTimeout() || _rampTimeL1RatiorRecipe == 0) { _rampTimerL1RatiorRecipe.Stop(); L1RatioSetPoint = _rampTargetL1RatiorRecipe; } else { L1RatioSetPoint = _rampInitValueL1RatiorRecipe + (_rampTargetL1RatiorRecipe - _rampInitValueL1RatiorRecipe) * (float)_rampTimerL1RatiorRecipe.GetElapseTime() / _rampTimeL1RatiorRecipe; } } } private void MonitorL2RecipeRatioRamping() { if (!_rampTimerL2RatiorRecipe.IsIdle()) { if (_rampTimerL2RatiorRecipe.IsTimeout() || _rampTimeL2RatiorRecipe == 0) { _rampTimerL2RatiorRecipe.Stop(); L2RatioSetPoint = _rampTargetL2RatiorRecipe; } else { L2RatioSetPoint = _rampInitValueL2RatiorRecipe + (_rampTargetL2RatiorRecipe - _rampInitValueL2RatiorRecipe) * (float)_rampTimerL2RatiorRecipe.GetElapseTime() / _rampTimeL2RatiorRecipe; } } } private void MonitorL3RecipeRatioRamping() { if (!_rampTimerL3RatiorRecipe.IsIdle()) { if (_rampTimerL3RatiorRecipe.IsTimeout() || _rampTimeL3RatiorRecipe == 0) { _rampTimerL3RatiorRecipe.Stop(); L3RatioSetPoint = _rampTargetL3RatiorRecipe; } else { L3RatioSetPoint = _rampInitValueL3RatiorRecipe + (_rampTargetL3RatiorRecipe - _rampInitValueL3RatiorRecipe) * (float)_rampTimerL3RatiorRecipe.GetElapseTime() / _rampTimeL3RatiorRecipe; } } } #endregion 手动设置比例 RatioSetPoint /// /// 设置PSU加热模式。 /// /// /// /// 加热器类型 /// public bool SetHeatMode(HeatStrategy mode, int time, TCUnits tcType) { var preMode = HeaterModeSetPoint; //前一个模式 HeaterModeSetPoint = mode; switch (mode) { case HeatStrategy.Power: L1LoopModeSetPoint = TCModes.Manual; L2LoopModeSetPoint = TCModes.Manual; L3LoopModeSetPoint = TCModes.Manual; if (preMode is HeatStrategy.PyroFollow or HeatStrategy.PyroAuto)//Pyro切到Power之后设置为600 { _rampParams.ToList().ForEach(x => x.StopRamp()); } break; case HeatStrategy.PyroFollow: case HeatStrategy.PyroAuto: if (L1LoopModeSetPoint == TCModes.Manual) { _rampParams[0].StopRamp(); SetAoValue(_rampParams[0].AOTargetSP, L1InputTempSetPoint); } if (L2LoopModeSetPoint == TCModes.Manual) { _rampParams[1].StopRamp(); SetAoValue(_rampParams[1].AOTargetSP, L2InputTempSetPoint); } if (L3LoopModeSetPoint == TCModes.Manual) { _rampParams[2].StopRamp(); SetAoValue(_rampParams[2].AOTargetSP, L3InputTempSetPoint); } if (mode == HeatStrategy.PyroAuto) { L1LoopModeSetPoint = TCModes.Manual; L2LoopModeSetPoint = TCModes.Auto; L3LoopModeSetPoint = TCModes.Auto; } else if (mode == HeatStrategy.PyroFollow) { if (IsPSU(tcType)) { L1LoopModeSetPoint = TCModes.Manual; L2LoopModeSetPoint = TCModes.Auto; L3LoopModeSetPoint = TCModes.Manual; } else if (IsSCR(tcType)) { L1LoopModeSetPoint = TCModes.Manual; L2LoopModeSetPoint = TCModes.Manual; L3LoopModeSetPoint = TCModes.Auto; } else EV.PostAlarmLog(Module, $"{Display} Unknown TC when changing heat mode."); } break; } var description = $"{Display} Heat Mode set to {mode}"; EV.PostInfoLog(Module, description); return true; } #region Pyro自动模式 /// /// 都从AE温度开始Ramp /// /// /// 目标温度 /// SP爬升时间。如果为1,表示Recipe中定义为Jump模式。 /// public bool SetTargetSP(string loop, float targetTemp, int time) { var heaterLoop = _rampParams.FirstOrDefault(x => x.Name == loop); Debug.Assert(heaterLoop is not null); //如果是Power模式直接设置SP即可 if (HeaterModeSetPoint == HeatStrategy.Power) { SetAoValue(heaterLoop.AOTargetSP, targetTemp); return true; } else { var rampDuration = time; heaterLoop.StopRamp(); // 停止正在Ramp的过程 heaterLoop.RampInitTemp = GetAoValue(heaterLoop.AOTempFromSensorTC); // 保存当前温度SP if (time == 0) // 如果没有指定Ramp耗时,则根据系统配置的温度爬升速率计算当前温差所需的爬升时间 rampDuration = Math.Abs((int)((targetTemp - heaterLoop.RampInitTemp) / _tempRampRatio.DoubleValue) * 1000); heaterLoop.RampTargetTemp = targetTemp; heaterLoop.StartRamp(rampDuration); // 启动Ramp计时器 } /* switch (loop) { case "L1": //如果是Power模式直接设置SP即可 if (HeaterModeSetPoint == HeatStrategy.Power) { L1TargetSPSetPoint = targetTemp; return true; } else { _rampTimerL1.Stop(); // 停止正在Ramp的过程 _rampInitValueL1 = L1InputTempSetPoint; // 保存当前温度SP _rampTimeL1 = time; // 接下来Ramp的总耗时 if (time == 0) // 如果没有指定Ramp耗时,则根据系统配置的温度爬升速率计算当前温差所需的爬升时间 _rampTimeL1 = Math.Abs((int)((targetTemp - _rampInitValueL1) / _tempRampRatio.DoubleValue) * 1000); _rampTargetL1 = targetTemp; // 本次温度Ramp的目标值 _rampTimerL1.Start(_rampTimeL1); // 启动Ramp计时器 } break; case "L2": //如果是Power模式直接设置SP即可 if (HeaterModeSetPoint == HeatStrategy.Power) { L2TargetSPSetPoint = targetTemp; return true; } else { _rampTimerL2.Stop(); _rampInitValueL2 = L2InputTempSetPoint; _rampTimeL2 = time; if (time == 0) _rampTimeL2 = Math.Abs((int)((targetTemp - _rampInitValueL2) / _tempRampRatio.DoubleValue) * 1000); _rampTargetL2 = targetTemp; _rampTimerL2.Start(_rampTimeL2); } break; case "L3": //如果是Power模式直接设置SP即可 if (HeaterModeSetPoint == HeatStrategy.Power) { L3TargetSPSetPoint = targetTemp; return true; } else { _rampTimerL3.Stop(); _rampInitValueL3 = L3InputTempSetPoint; _rampTimeL3 = time; if (time == 0) _rampTimeL3 = Math.Abs((int)((targetTemp - _rampInitValueL3) / _tempRampRatio.DoubleValue) * 1000); _rampTargetL3 = targetTemp; _rampTimerL3.Start(_rampTimeL3); } break; }*/ return true; } private void MonitorTargetSPRamping(string loopName) { var loop = _rampParams.FirstOrDefault(x => x.Name == loopName); Debug.Assert(loop is not null); if (!loop.RampTimer.IsIdle()) { if (loop.RampTimer.IsTimeout() || loop.RampDurationMs == 0) { loop.StopRamp(); SetAoValue(loop.AOTargetSP, loop.RampTargetTemp); } else { SetAoValue(loop.AOTargetSP, loop.RampInitTemp + (loop.RampTargetTemp - loop.RampInitTemp) * (float)loop.RampTimer.GetElapseTime() / loop.RampDurationMs); } } } /* private void MonitorL1Ramping() { if (!_rampTimerL1.IsIdle()) { if (_rampTimerL1.IsTimeout() || _rampTimeL1 == 0) { _rampTimerL1.Stop(); L1TargetSPSetPoint = _rampTargetL1; } else { L1TargetSPSetPoint = _rampInitValueL1 + (_rampTargetL1 - _rampInitValueL1) * (float)_rampTimerL1.GetElapseTime() / _rampTimeL1; } } } private void MonitorL2Ramping() { if (!_rampTimerL2.IsIdle()) { if (_rampTimerL2.IsTimeout() || _rampTimeL2 == 0) { _rampTimerL2.Stop(); L2TargetSPSetPoint = _rampTargetL2; } else { L2TargetSPSetPoint = _rampInitValueL2 + (_rampTargetL2 - _rampInitValueL2) * (float)_rampTimerL2.GetElapseTime() / _rampTimeL2; } } } private void MonitorL3Ramping() { if (!_rampTimerL3.IsIdle()) { if (_rampTimerL3.IsTimeout() || _rampTimeL3 == 0) { _rampTimerL3.Stop(); L3TargetSPSetPoint = _rampTargetL3; } else { L3TargetSPSetPoint = _rampInitValueL3 + (_rampTargetL3 - _rampInitValueL3) * (float)_rampTimerL3.GetElapseTime() / _rampTimeL3; } } } */ #endregion Pyro自动模式 #region Power手动模式 //PSU SCR public bool SetRatio(float Ratio1, float Ratio2, float Ratio3) { L2RatioSetPoint = Ratio2 > 100 ? 100 : Ratio2; L1RatioSetPoint = Ratio1 > 100 ? 100 : Ratio1; L3RatioSetPoint = Ratio3 > 100 ? 100 : Ratio3; var reason = $"{Display} Set Ratio "; EV.PostInfoLog(Module, reason); return true; } //自动执行(比例和模式由其它方法设置) private void MonitorSetOP() { if (HeaterModeSetPoint == HeatStrategy.PyroFollow) { if (IsPSU(Name)) { // L1、L3输出值跟随L2;L2由2704自动调整 L2TargetOPSetPoint = L2WorkingOPFeedBack; var l1 = L2RatioSetPoint > 0 ? L2WorkingOPFeedBack * L1RatioSetPoint / L2RatioSetPoint : 0; L1TargetOPSetPoint = l1 > 100 ? 100 : l1; var l3 = L2RatioSetPoint > 0 ? L2WorkingOPFeedBack * L3RatioSetPoint / L2RatioSetPoint : 0; L3TargetOPSetPoint = l3 > 100 ? 100 : l3; } else if (IsSCR(Name)) { // L1、L2输出值跟随L3;L3由2704自动调整 L3TargetOPSetPoint = L3WorkingOPFeedBack; var l1 = L3RatioSetPoint > 0 ? L3WorkingOPFeedBack * L1RatioSetPoint / L3RatioSetPoint : 0; L1TargetOPSetPoint = l1 > 100 ? 100 : l1; var l2 = L3RatioSetPoint > 0 ? L3WorkingOPFeedBack * L2RatioSetPoint / L3RatioSetPoint : 0; L2TargetOPSetPoint = l2 > 100 ? 100 : l2; } } else if (HeaterModeSetPoint == HeatStrategy.PyroAuto) { // L1输出值跟随L2;L2、L3由2704自动调整 L2TargetOPSetPoint = L2WorkingOPFeedBack; var l1 = L2RatioSetPoint > 0 ? L2WorkingOPFeedBack * L1RatioSetPoint / L2RatioSetPoint : 0; L1TargetOPSetPoint = l1 > 100 ? 100 : l1; } else //Power { var distur = 0f; if (IsSCR(Name)) { // 扰动模拟 MonitorDisturbanceSimulation(); distur = (float)_disturbance; } var l1 = L1RatioSetPoint; if (_disturbanceScr == 1) l1 += distur; L1TargetOPSetPoint = l1 > 100 ? 100 : l1; var l2 = L2RatioSetPoint; if (_disturbanceScr == 2) l2 += distur; L2TargetOPSetPoint = l2 > 100 ? 100 : l2; var l3 = L3RatioSetPoint; if (_disturbanceScr == 3) l3 += distur; L3TargetOPSetPoint = l3 > 100 ? 100 : l3; } } #endregion protected override void HandleMonitor() { try { MonitorTargetSPRamping("L1"); MonitorTargetSPRamping("L2"); MonitorTargetSPRamping("L3"); MonitorSetOP(); MonitorL1RecipeRatioRamping(); MonitorL2RecipeRatioRamping(); MonitorL3RecipeRatioRamping(); MonitorRecipeSetPowerRefRamping(); MonitorTempLimit(); MonitorAlarm(); MonitorDynamicPIDLimit(); MonitorTempRaisingTooFastAlarm(); } catch (Exception ex) { LOG.Write(ex); } } private void CheckHighTemp() { if (OuterTemp > _PSUHighLimit.DoubleValue || MiddleTemp > _PSUHighLimit.DoubleValue) _overHighTempCount += 1; else { _overHighTempCount = 0; _overHighTempTRIG.RST = true; } if (_overHighTempCount >= 10) { _overHighTempTRIG.CLK = true; if (_overHighTempTRIG.Q) { EV.PostAlarmLog(Module, $"Alarm63 Temp High,Temp is over PSUHighLimit:{_PSUHighLimit.DoubleValue} ℃ , \r\nOuterTemp:{OuterTemp}℃ , MiddleTemp:{MiddleTemp}℃"); } } } public void StopRamp() { /*if (!_rampTimerL1.IsIdle()) { _rampTimerL1.Stop(); } if (!_rampTimerL2.IsIdle()) { _rampTimerL2.Stop(); } if (!_rampTimerL3.IsIdle()) { _rampTimerL3.Stop(); }*/ foreach (var loop in _rampParams) { loop.StopRamp(); } if (!_rampTimerL1RatiorRecipe.IsIdle()) { _rampTimerL1RatiorRecipe.Stop(); } if (!_rampTimerL2RatiorRecipe.IsIdle()) { _rampTimerL2RatiorRecipe.Stop(); } if (!_rampTimerL3RatiorRecipe.IsIdle()) { _rampTimerL3RatiorRecipe.Stop(); } } #region 不重要 //设置额定功率 private bool SetRatedValue(float Ratio1, float Ratio2, float Ratio3) { L1RatedSetPoint = Ratio1; L2RatedSetPoint = Ratio2; L3RatedSetPoint = Ratio3; var reason = $"{Display} Set RatedValue"; EV.PostInfoLog(Module, reason); return true; } public void RecipeSetPowerRef(float PowerRef, int time) { _rampTimerSetPowerRef.Stop(); if (time > 0) { _rampInitValueSetPowerRefRecipe = PowerRefSetPoint; _rampTimeSetPowerRefRecipe = time; _rampTargetSetPowerRefRecipe = PowerRef; _rampTimerSetPowerRef.Start(_rampTimeSetPowerRefRecipe); } else { PowerRefSetPoint = PowerRef; } } private void MonitorRecipeSetPowerRefRamping() { if (!_rampTimerSetPowerRef.IsIdle()) { if (_rampTimerSetPowerRef.IsTimeout() || _rampTimeSetPowerRefRecipe == 0) { _rampTimerSetPowerRef.Stop(); PowerRefSetPoint = _rampTargetSetPowerRefRecipe; } else { PowerRefSetPoint = _rampInitValueSetPowerRefRecipe + (_rampTargetSetPowerRefRecipe - _rampInitValueSetPowerRefRecipe) * (float)_rampTimerSetPowerRef.GetElapseTime() / _rampTimeSetPowerRefRecipe; } } } #endregion 不重要 #region 报警 /// /// 昂坤三个温度相互之间的最大差值 /// public double PyroTempMaxDiff { get { if (MiddleTemp > _pyroWarmBaseTemp.DoubleValue || OuterTemp > _pyroWarmBaseTemp.DoubleValue) { var tempMaxDiff = Math.Abs(OuterTemp - MiddleTemp); return tempMaxDiff; } return 0; } } private readonly R_TRIG _pyroTempMaxDiffWarm = new (); private void MonitorAlarm() { var status = DATA.Poll($"{Module}.Status") == null ? "" : DATA.Poll($"{Module}.Status").ToString(); //状态 var processCountTime = DATA.Poll($"{Module}.RecipeTotalTime") == null ? 0 : Convert.ToInt32(DATA.Poll($"{Module}.RecipeTotalTime")); //Process总时间 var recipeStepTime = DATA.Poll($"{Module}.RecipeTotalElapseTime") == null ? 0 : Convert.ToInt32(DATA.Poll($"{Module}.RecipeTotalElapseTime")); //已经运行的时间 if (status == "Process" && recipeStepTime > _pyroWarmEffectFromTime.IntValue && processCountTime - recipeStepTime > _pyroWarmEffectEndTime.IntValue) { _pyroTempMaxDiffWarm.CLK = PyroTempMaxDiff > _pyroWarmMaxDiff.DoubleValue && _pyroWarmMaxDiff.DoubleValue > 0; if (_pyroTempMaxDiffWarm.Q) { if (_PyroWarmIsAlarm.BoolValue) { EV.PostAlarmLog(Module, $"Pyro Temp Max Difference is over {_pyroWarmMaxDiff.DoubleValue} ℃"); } else { EV.PostWarningLog(Module, $"Pyro Temp Max Difference is over {_pyroWarmMaxDiff.DoubleValue} ℃"); } } } } //DateTime dtLastRecordTime = DateTime.Now; private readonly Stopwatch _swTempRaisingMonitor = new(); private double _lastInnerTemp = 0; private double _lastMiddleTemp = 0; private double _lastOuterTemp = 0; private readonly R_TRIG _rTrigMiddleTempRaisingFast = new(); private readonly R_TRIG _rTrigInnerTempRaisingFast = new(); private readonly R_TRIG _rTrigOuterTempRaisingFast = new(); private bool MonitorTempRaisingTooFastAlarm() { var objPmStatus = DATA.Poll($"{Module}.Status"); var pmStatus = objPmStatus == null ? "" : objPmStatus.ToString(); if (pmStatus != "Process") { // PM没有Process时,强制停止计时器; // 否则由于上一次工艺结束时,定时器处于运行状态,再次进入工艺时,IsRunning == true,定时器记录时间会很大,导致判断错误。 _swTempRaisingMonitor.Reset(); } else { if (!_swTempRaisingMonitor.IsRunning) { // 首次进入Process,启动1s定时,并保存当前当前温度。 // 后续整个Process过程中,定制器始终处于工作状态,除非发生异常。 _swTempRaisingMonitor.Start(); _lastInnerTemp = _innerTempRaw; _lastMiddleTemp = _middleTempRaw; _lastOuterTemp = _outerTempRaw; } else if (_swTempRaisingMonitor.Elapsed.TotalSeconds > 1) { _swTempRaisingMonitor.Stop(); var interval = _swTempRaisingMonitor.Elapsed.TotalSeconds; if (interval <= 0) return true; Debug.WriteLine($"IoTC {Name} Temp Raising Fast monitor interval: {interval:F1}", Module); Debug.WriteLine($"Is Alarm: {_isAlarmTempRaisingFast}; " + $"Middle Rate SP: {_tempRaisingRateMiddle}℃/s; " + $"Outer Rate SP: {_tempRaisingRateOuter}℃/s; ", Module); var raisingRatePerSec = 0.0; #region Inner /* raisingRatePerSec = Math.Abs(InnerTemp - _lastInnerTemp) / interval; Debug.WriteLine($"Inner Temp Raising Rate: {raisingRatePerSec:F1}", Module); _rTrigInnerTempRaisingFast.CLK = raisingPerSec > _tempRaisingRateInner; if (_rTrigInnerTempRaisingFast.Q) { var msg = $"Inner Temp raising too fast at a rate of {raisingPerSec:F1}℃/s"; if (_isAlarmTempRaisingFast) EV.PostAlarmLog(Module, msg); else EV.PostWarningLog(Module, msg); }*/ #endregion #region Middle raisingRatePerSec = Math.Abs(_middleTempRaw - _lastMiddleTemp) / interval; Debug.WriteLine($"Middle Temp Raising Rate: {raisingRatePerSec:F1}", Module); _rTrigMiddleTempRaisingFast.CLK = raisingRatePerSec > _tempRaisingRateMiddle; if (_rTrigMiddleTempRaisingFast.Q) { var msg = $"Middle Temp raising too fast at a rate of {raisingRatePerSec:F1}℃/s"; if (_isAlarmTempRaisingFast) EV.PostAlarmLog(Module, msg); else EV.PostWarningLog(Module, msg); } #endregion #region Outer raisingRatePerSec = Math.Abs(_outerTempRaw - _lastOuterTemp) / interval; Debug.WriteLine($"Outer Temp Raising Rate: {raisingRatePerSec:F1}", Module); _rTrigOuterTempRaisingFast.CLK = raisingRatePerSec > _tempRaisingRateOuter; if (_rTrigOuterTempRaisingFast.Q) { var msg = $"Outer Temp raising too fast at a rate of {raisingRatePerSec:F1}℃/s"; if (_isAlarmTempRaisingFast) EV.PostAlarmLog(Module, msg); else EV.PostWarningLog(Module, msg); } #endregion // 保存当前当前温度,并重启1s定时器 _lastInnerTemp = _innerTempRaw; _lastMiddleTemp = _middleTempRaw; _lastOuterTemp = _outerTempRaw; _swTempRaisingMonitor.Restart(); } } return true; } public void MonitorTemp() { if (IsPSU(Name)) { L1InputTempSetPoint = InnerTemp; L3InputTempSetPoint = OuterTemp; if(MiddleTemp <= _simlatorTempThreshold) { bool _simlatorTempFlag = !_diPSUInnerHeaterEnable.Value && !_diPSUMiddleHeaterEnable.Value && !_diPSUOuterHeaterEnable.Value && !_diSCRUpHeaterEnable.Value && !_diSCRMiddleHeaterEnable.Value && !_diSCRDownHeaterEnable.Value; if (_simlatorTempFlag) { _stopwatch.Start(); } else _stopwatch.Stop(); L2InputTempSetPoint = (float)(L2InputTempSetPoint > 25 ? _simlatorTempThreshold - _stopwatch.Elapsed.TotalSeconds * 3.3 / 60 : 25); } else { _stopwatch.Reset() ; float middleControlTempRatio = _middleTempRatio / 100f; float outerControlTempRatio = (100 - _middleTempRatio) / 100f; L2InputTempSetPoint = MiddleTemp * middleControlTempRatio + OuterTemp * outerControlTempRatio; } } else { L3InputTempSetPoint = InnerTemp; } } /// /// 掉线设置温度上下限 /// private void MonitorTempLimit() { if (IsPSU(Name)) { if (DoubleUtil.NotEqual(L1TempHighLimitSetPoint, _PSUHighLimit.DoubleValue)) { L1TempHighLimitSetPoint = (float)_PSUHighLimit.DoubleValue; } if (DoubleUtil.NotEqual(L2TempHighLimitSetPoint, _PSUHighLimit.DoubleValue)) { L2TempHighLimitSetPoint = (float)_PSUHighLimit.DoubleValue; } if (DoubleUtil.NotEqual(L3TempHighLimitSetPoint, _PSUHighLimit.DoubleValue)) { L3TempHighLimitSetPoint = (float)_PSUHighLimit.DoubleValue; } if (DoubleUtil.NotEqual(L1TempLowLimitSetPoint, _PSULowLimit.DoubleValue)) { L1TempLowLimitSetPoint = (float)_PSULowLimit.DoubleValue; } if (DoubleUtil.NotEqual(L2TempLowLimitSetPoint, _PSULowLimit.DoubleValue)) { L2TempLowLimitSetPoint = (float)_PSULowLimit.DoubleValue; } if (DoubleUtil.NotEqual(L3TempLowLimitSetPoint, _PSULowLimit.DoubleValue)) { L3TempLowLimitSetPoint = (float)_PSULowLimit.DoubleValue; } } else if (IsSCR(Name)) { if (DoubleUtil.NotEqual(L1TempHighLimitSetPoint, _SCRHighLimit.DoubleValue)) { L1TempHighLimitSetPoint = (float)_PSUHighLimit.DoubleValue; } if (DoubleUtil.NotEqual(L2TempHighLimitSetPoint, _SCRHighLimit.DoubleValue)) { L2TempHighLimitSetPoint = (float)_SCRHighLimit.DoubleValue; } if (DoubleUtil.NotEqual(L3TempHighLimitSetPoint, _SCRHighLimit.DoubleValue)) { L3TempHighLimitSetPoint = (float)_SCRHighLimit.DoubleValue; } if (DoubleUtil.NotEqual(L1TempLowLimitSetPoint, _SCRLowLimit.DoubleValue)) { L1TempLowLimitSetPoint = (float)_SCRLowLimit.DoubleValue; } if (DoubleUtil.NotEqual(L2TempLowLimitSetPoint, _SCRLowLimit.DoubleValue)) { L2TempLowLimitSetPoint = (float)_SCRLowLimit.DoubleValue; } if (DoubleUtil.NotEqual(L3TempLowLimitSetPoint, _SCRLowLimit.DoubleValue)) { L3TempLowLimitSetPoint = (float)_SCRLowLimit.DoubleValue; } } } #endregion 报警 public void Terminate() { } public void Reset() { _trigSetTempLimit.RST = true; _pyroTempMaxDiffWarm.RST = true; _rTrigMiddleTempRaisingFast.RST = true; _rTrigOuterTempRaisingFast.RST = true; _rTrigInnerTempRaisingFast.RST = true; _overHighTempCount = 0; _overHighTempTRIG.RST = true; } #region Dynamic PID Limit private readonly R_TRIG _rTrigHeatStrategyChanged = new(); private HeatStrategy? _prevHeatStrategy = null; private void MonitorDynamicPIDLimit() { var currHeatStrategy = HeaterModeSetPoint; _rTrigHeatStrategyChanged.CLK = HeaterModeSetPoint != _prevHeatStrategy; if (_rTrigHeatStrategyChanged.Q) { // 控温策略发生变化,设置PID Limit _prevHeatStrategy = currHeatStrategy; switch (currHeatStrategy) { case HeatStrategy.PyroAuto: case HeatStrategy.PyroFollow: SetPIDLimit(_pidLimitLowerUnderPryo, _pidLimitUpperUnderPyro, _pidRateLimitUnderPyro); break; case HeatStrategy.Power: SetPIDLimit(0, 100, 5); break; default: EV.PostWarningLog(Module, $"{Name} Unknown heat strategy to set dynamic PID limits."); break; } } } /// /// IoTC对象内部使用,直接写AO。 /// /// /// /// private void SetPIDLimit(double lower, double upper, double rate) { //SetAoValue(_aoPIDLimitLower, (float)lower); //SetAoValue(_aoPIDLimitUpper, (float)upper); //SetAoValue(_aoPIDRateLimit, (float)rate); } /// /// UI中手动设置Limit时,调用此方法。 /// /// /// /// /// UI中手动设置Limit后,暂存设定值,但不写入系统配置; /// 下次重启RT后,默认加载系统配置中的Limit设定值。 /// private bool SetPIDLimit(object[] args) { //if (args == null || args.Length != 3) //{ // EV.PostWarningLog(Module, $"{Name} Incorrect arguments of OP {nameof(SetPIDLimit)}"); // return false; //} //if (args[0] is double lower && args[1] is double upper && args[2] is double rate) //{ // SetPIDLimit(lower, upper, rate); // /*// 更新系统配置,确保重启后为 // SC.SetItemValue(_scNamePidLimitLower, lower); // SC.SetItemValue(_scNamePidLimitUpper, upper); // SC.SetItemValue(_scNamePidRateLimit, rate);*/ // // 暂存设置值,下次从Power切换到Pyro时直接使用。 // _pidLimitLowerUnderPryo = lower; // _pidLimitUpperUnderPyro = upper; // _pidRateLimitUnderPyro = rate; // return true; //} //EV.PostWarningLog(Module, $"{Name} Some of parameters of OP {nameof(SetPIDLimit)} are not numbers"); return false; } #endregion #region PSU Y Calc public void SetPCPSUY() { PSU1Y = Convert.ToInt32(PCPSU1Y); PSU2Y = Convert.ToInt32(PCPSU2Y); PSU3Y = Convert.ToInt32(PCPSU3Y); } public bool SetPSUY(double _dbPSU1Y, double _dbPSU2Y, double _dbPSU3Y) { var iPSU1Y = Convert.ToInt32(_dbPSU1Y * 10); var iPSU2Y = Convert.ToInt32(_dbPSU2Y * 10); var iPSU3Y = Convert.ToInt32(_dbPSU3Y * 10); //写入IO PSU1Y = iPSU1Y; PSU2Y = iPSU2Y; PSU3Y = iPSU3Y; //写入配置 if ((iPSU1Y > 0 && iPSU1Y < 1000) && (iPSU2Y > 0 && iPSU2Y < 1000) && (iPSU3Y > 0 && iPSU3Y < 1000) ) { SC.SetItemValue($"PM.{Module}.Heater.PSU1Y", (double)iPSU1Y); SC.SetItemValue($"PM.{Module}.Heater.PSU2Y", (double)iPSU2Y); SC.SetItemValue($"PM.{Module}.Heater.PSU3Y", (double)iPSU3Y); } var reason = $"{Display} Set PSUY values."; EV.PostInfoLog(Module, reason); return true; } public bool SetPSUPower(double psu1Power, double psu2Power, double psu3Power) { //写入配置 if (psu1Power > 0 && psu2Power > 0 && psu3Power > 0 ) { SC.SetItemValue($"PM.{Module}.Heater.PSU1Power", psu1Power); SC.SetItemValue($"PM.{Module}.Heater.PSU2Power", psu2Power); SC.SetItemValue($"PM.{Module}.Heater.PSU3Power", psu3Power); } var reason = $"{Display} Save SystemConfig PSUPower values."; EV.PostInfoLog(Module, reason); return true; } public void GetPCPSUPower() { PSU1Power = SC.GetConfigItem($"PM.{Module}.Heater.PSU1Power").DoubleValue; PSU2Power = SC.GetConfigItem($"PM.{Module}.Heater.PSU2Power").DoubleValue; PSU3Power = SC.GetConfigItem($"PM.{Module}.Heater.PSU3Power").DoubleValue; } #endregion #region Property /// /// 外部温度 /// public float OuterTemp { get; set; } public float InnerTemp { get; set; } public float MiddleTemp { get; set; } private float _innerTempRaw; private float _middleTempRaw; private float _outerTempRaw; ///// ///// 内部温度 ///// //public float InnerTemp //{ // get // { // if (ScInnerTempValue == "Disabled") // return 0; // var obj = DATA.Poll(ScInnerTempValue); // if (obj != null && float.TryParse(obj.ToString(), out var temp)) // return temp.Round(1); // return 0; // } //} ///// ///// 中间温度 ///// //public float MiddleTemp //{ // get // { // if (ScMiddleTempValue == "Disabled") // return 0; // var obj = DATA.Poll(ScMiddleTempValue); // if (obj != null && float.TryParse(obj.ToString(), out var temp)) // return temp.Round(1); // return 0; // } //} private void MonitorTempFilter() { OuterTemp = (float)FilterTempValue(_outerTempFilter, ScOuterTempValue, ref _outerTempRaw); InnerTemp = (float)FilterTempValue(_innerTempFilter, ScInnerTempValue, ref _innerTempRaw); MiddleTemp = (float)FilterTempValue(_middleTempFilter, ScMiddleTempValue, ref _middleTempRaw); } private double FilterTempValue(TempDataFilter tempFilter,string scTempSensor, ref float tempRaw) { double temp = 0; if (scTempSensor == "Disabled") return 0; var obj = DATA.Poll(scTempSensor); if (obj != null && double.TryParse(obj.ToString(), out temp)) { tempFilter.Update(temp); tempRaw = (float)temp; return temp.Round(1); } return 0; } public float L1WorkingOPFeedBack { get { return _aiL1WorkingOPFeedBack == null ? 0 : (_isFloatAioType ? _aiL1WorkingOPFeedBack.Value : _aiL1WorkingOPFeedBack.Value); } } public float L2WorkingOPFeedBack { get { return _aiL2WorkingOPFeedBack == null ? 0 : (_isFloatAioType ? _aiL2WorkingOPFeedBack.Value : _aiL2WorkingOPFeedBack.Value); } } public float L3WorkingOPFeedBack { get { return _aiL3WorkingOPFeedBack == null ? 0 : (_isFloatAioType ? _aiL3WorkingOPFeedBack.Value : _aiL3WorkingOPFeedBack.Value); } } public float L1PVFeedBack { get { return _aiL1PVFeedBack == null ? 0 : (_isFloatAioType ? _aiL1PVFeedBack.Value : _aiL1PVFeedBack.Value); } } public float L2PVFeedBack { get { return _aiL2PVFeedBack == null ? 0 : (_isFloatAioType ? _aiL2PVFeedBack.Value : _aiL2PVFeedBack.Value); } } public float L3PVFeedBack { get { return _aiL3PVFeedBack == null ? 0 : (_isFloatAioType ? _aiL3PVFeedBack.Value : _aiL3PVFeedBack.Value); } } public bool L1TempHighAlarmFeedBack { get { return _diL1TempHighAlarmFeedBack == null ? false : _diL1TempHighAlarmFeedBack.Value; } } public bool L2TempHighAlarmFeedBack { get { return _diL2TempHighAlarmFeedBack == null ? false : _diL2TempHighAlarmFeedBack.Value; } } public bool L3TempHighAlarmFeedBack { get { return _diL3TempHighAlarmFeedBack == null ? false : _diL3TempHighAlarmFeedBack.Value; } } public bool L1TempLowAlarmFeedBack { get { return _diL1TempLowAlarmFeedBack == null ? false : _diL1TempLowAlarmFeedBack.Value; } } public bool L2TempLowAlarmFeedBack { get { return _diL2TempLowAlarmFeedBack == null ? false : _diL2TempLowAlarmFeedBack.Value; } } public bool L3TempLowAlarmFeedBack { get { return _diL3TempLowAlarmFeedBack == null ? false : _diL3TempLowAlarmFeedBack.Value; } } public TCModes L1LoopModeSetPoint { get => Get2704ControlMode(_aoL1LoopModeSetPoint); set => Set2704ControlMode(_aoL1LoopModeSetPoint, value); } public TCModes L2LoopModeSetPoint { get => Get2704ControlMode(_aoL2LoopModeSetPoint); set => Set2704ControlMode(_aoL2LoopModeSetPoint, value); } public TCModes L3LoopModeSetPoint { get => Get2704ControlMode(_aoL3LoopModeSetPoint); set => Set2704ControlMode(_aoL3LoopModeSetPoint, value); } public float L1TargetSPSetPoint { get => GetAoValue(_aoL1TargetSPSetPoint); set => SetAoValue(_aoL1TargetSPSetPoint, value); } public float L2TargetSPSetPoint { get => GetAoValue(_aoL2TargetSPSetPoint); set => SetAoValue(_aoL2TargetSPSetPoint, value); } public float L3TargetSPSetPoint { get => GetAoValue(_aoL3TargetSPSetPoint); set => SetAoValue(_aoL3TargetSPSetPoint, value); } public float L1TargetOPSetPoint { get => GetAoValue(_aoL1TargetOPSetPoint); set => SetAoValue(_aoL1TargetOPSetPoint, value); } public float L2TargetOPSetPoint { get => GetAoValue(_aoL2TargetOPSetPoint); set => SetAoValue(_aoL2TargetOPSetPoint, value); } public float L3TargetOPSetPoint { get => GetAoValue(_aoL3TargetOPSetPoint); set => SetAoValue(_aoL3TargetOPSetPoint, value); } /// /// 温度传感器实时温度写入2704 /// public float L1InputTempSetPoint { get => GetAoValue(_aoL1InputTempSetPoint); set => SetAoValue(_aoL1InputTempSetPoint, value); } /// /// 温度传感器实时温度写入2704 /// public float L2InputTempSetPoint { get => GetAoValue(_aoL2InputTempSetPoint); set => SetAoValue(_aoL2InputTempSetPoint, value); } /// /// 温度传感器实时温度写入2704 /// public float L3InputTempSetPoint { get => GetAoValue(_aoL3InputTempSetPoint); set => SetAoValue(_aoL3InputTempSetPoint, value); } public float L1TempHighLimitSetPoint { get => GetAoValue(_aoL1TempHighLimitSetPoint); set => SetAoValue(_aoL1TempHighLimitSetPoint, value); } public float L2TempHighLimitSetPoint { get => GetAoValue(_aoL2TempHighLimitSetPoint); set => SetAoValue(_aoL2TempHighLimitSetPoint, value); } public float L3TempHighLimitSetPoint { get => GetAoValue(_aoL3TempHighLimitSetPoint); set => SetAoValue(_aoL3TempHighLimitSetPoint, value); } public float L1TempLowLimitSetPoint { get => GetAoValue(_aoL1TempLowLimitSetPoint); set => SetAoValue(_aoL1TempLowLimitSetPoint, value); } public float L2TempLowLimitSetPoint { get => GetAoValue(_aoL2TempLowLimitSetPoint); set => SetAoValue(_aoL2TempLowLimitSetPoint, value); } public float L3TempLowLimitSetPoint { get => GetAoValue(_aoL3TempLowLimitSetPoint); set => SetAoValue(_aoL3TempLowLimitSetPoint, value); } /// /// 读取指定AO的实时值。 /// /// /// 如果AO为null,则返回0。 /// /// 指定的AO端口。 /// 待写入的值。 /// private void SetAoValue(AOAccessor ao, float value) { if (ao == null) return; if (_isFloatAioType) ao.Value = value; else ao.Value = (short)value; } /// /// 读取指定AO的实时值。 /// /// /// 如果AO为null,则返回0。 /// /// /// private float GetAoValue(AOAccessor ao) { if (ao == null) return 0; return _isFloatAioType ? ao.Value : ao.Value; } /// /// 获取2704每通道工作模式。 /// /// /// private TCModes Get2704ControlMode(AOAccessor ao) { if (ao == null) return TCModes.Manual; var mode = (int)(ao.Value); return ConvertToTcModes(mode); } /// /// 将数字转换为枚举。 /// /// /// 如果传入的mode不在定义范围内,则默认返回HeatStrategy.Power。 /// /// /// private HeatStrategy GetHeaterMode(AOAccessor ao) { if (ao == null) return HeatStrategy.Power; var mode = (int)(ao.Value); return ConvertToHeatStrategy(mode); } private void Set2704ControlMode(AOAccessor ao, TCModes mode) { if (_isFloatAioType) { ao.Value = (float)mode; } else { ao.Value = (short)mode; } } private void SetHeatMode(AOAccessor ao, HeatStrategy mode) { if (_isFloatAioType) { ao.Value = (float)mode; } else { ao.Value = (short)mode; } } /// /// 注意:AO-203(AO203)在PLC中没有使用,仅做变量保持使用。 /// public HeatStrategy HeaterModeSetPoint { get => GetHeaterMode(_aoHeaterModeSetPoint); set => SetHeatMode(_aoHeaterModeSetPoint, value); } public float PowerRefSetPoint { get { return _aoPowerRefSetPoint == null ? 0 : (_isFloatAioType ? _aoPowerRefSetPoint.Value : _aoPowerRefSetPoint.Value); } set { if (_isFloatAioType) { _aoPowerRefSetPoint.Value = value; } else { _aoPowerRefSetPoint.Value = (short)value; } } } public float L1RatioSetPoint { get { return _aoL1RatioSetPoint == null ? 0 : (_isFloatAioType ? _aoL1RatioSetPoint.Value : _aoL1RatioSetPoint.Value); } set { if (_isFloatAioType) { _aoL1RatioSetPoint.Value = value; } else { _aoL1RatioSetPoint.Value = (short)value; } } } public float L2RatioSetPoint { get { return _aoL2RatioSetPoint == null ? 0 : (_isFloatAioType ? _aoL2RatioSetPoint.Value : _aoL2RatioSetPoint.Value); } set { if (_isFloatAioType) { _aoL2RatioSetPoint.Value = value; } else { _aoL2RatioSetPoint.Value = (short)value; } } } public float L3RatioSetPoint { get { return _aoL3RatioSetPoint == null ? 0 : (_isFloatAioType ? _aoL3RatioSetPoint.Value : _aoL3RatioSetPoint.Value); } set { if (_isFloatAioType) { _aoL3RatioSetPoint.Value = value; } else { _aoL3RatioSetPoint.Value = (short)value; } } } public float L1RatedSetPoint { get => _aoL1RatedSetPoint?.Value ?? 0; set { if (_isFloatAioType) { _aoL1RatedSetPoint.Value = value; } else { _aoL1RatedSetPoint.Value = (short)value; } } } public float L2RatedSetPoint { get { return _aoL2RatedSetPoint?.Value ?? 0; } set { if (_isFloatAioType) { _aoL2RatedSetPoint.Value = value; } else { _aoL2RatedSetPoint.Value = (short)value; } } } public float L3RatedSetPoint { get { return _aoL3RatedSetPoint?.Value ?? 0; } set { if (_isFloatAioType) { _aoL3RatedSetPoint.Value = value; } else { _aoL3RatedSetPoint.Value = (short)value; } } } public float L1VoltageLimited { get { return _aoL1VoltageLimited?.Value ?? 0; } set { if (_isFloatAioType) { _aoL1VoltageLimited.Value = value; } else { _aoL1VoltageLimited.Value = (short)value; } } } public float L2VoltageLimited { get { return _aoL2VoltageLimited?.Value ?? 0; } set { if (_isFloatAioType) { _aoL2VoltageLimited.Value = value; } else { _aoL2VoltageLimited.Value = (short)value; } } } public float L3VoltageLimited { get { return _aoL3VoltageLimited?.Value ?? 0; } set { if (_isFloatAioType) { _aoL3VoltageLimited.Value = value; } else { _aoL3VoltageLimited.Value = (short)value; } } } public float TtempCtrlTCIN { get { if (_aiTtempCtrlTCIN != null) { return _aiTtempCtrlTCIN.Value; } return 0; } } public int PSU1Y { get { return _aoPSU1Y == null ? 0 : (int)_aoPSU1Y.Value; } set { if (_isFloatAioType) { _aoPSU1Y.Value = value; } else { _aoPSU1Y.Value = (short)value; } } } public int PSU2Y { get { return _aoPSU2Y == null ? 0 : (int)_aoPSU2Y.Value; } set { if (_isFloatAioType) { _aoPSU2Y.Value = value; } else { _aoPSU2Y.Value = (short)value; } } } public int PSU3Y { get { return _aoPSU3Y == null ? 0 : (int)_aoPSU3Y.Value; } set { if (_isFloatAioType) { _aoPSU3Y.Value = value; } else { _aoPSU3Y.Value = (short)value; } } } // public double PSU1Power { get; set; } public double PSU2Power { get; set; } public double PSU3Power { get; set; } // public double PCPSU1Y { get { return SC.GetValue($"PM.{Module}.Heater.PSU1Y"); } } public double PCPSU2Y { get { return SC.GetValue($"PM.{Module}.Heater.PSU2Y"); } } public double PCPSU3Y { get { return SC.GetValue($"PM.{Module}.Heater.PSU3Y"); } } #endregion Property #region 人为给SCR增加扰动的逻辑 private int _disturbanceScr; private string _disturbanceType; private double _disturbancePeriodSec; private double _disturbanceDurationSec; private double _disturbanceAmplitude; private double _disturbance = 0; private readonly Stopwatch _swDisturbancePeriod = new(); private readonly Stopwatch _swDisturbanceDuration = new(); public void DisturbanceSimInitialize() { DATA.Subscribe($"{Module}.{Name}.Disturbance.PeriodTimerRunning", () => _swDisturbancePeriod.IsRunning); DATA.Subscribe($"{Module}.{Name}.Disturbance.DurationTimerRunning", () => _swDisturbanceDuration.IsRunning); DATA.Subscribe($"{Module}.{Name}.Disturbance.SCR", () => _disturbanceScr); DATA.Subscribe($"{Module}.{Name}.Disturbance.Value", () => _disturbance); DATA.Subscribe($"{Module}.{Name}.Disturbance.DurationSec", () => _disturbanceDurationSec); DATA.Subscribe($"{Module}.{Name}.Disturbance.ElapsedSec", () => _swDisturbanceDuration.IsRunning ? _swDisturbanceDuration.Elapsed.TotalSeconds : 0); OP.Subscribe($"{Module}.{Name}.StartDisturbanceSim", (arg1, args) => { // args[0]: SCR name // args[1]: type // args[2]: Period in seconds // args[3]: Duration in seconds // args[4]: Amplitude if (args is { Length: 5 } && args[0] is int scrNo && args[1] is string strType && args[2] is double periodSec && args[3] is double durationSec && args[4] is double amplitude) { _disturbanceScr = scrNo; _disturbanceType = strType.ToUpperInvariant(); _disturbancePeriodSec = periodSec; _disturbanceDurationSec = durationSec; _disturbanceAmplitude = amplitude; StartDisturbanceSimulation(); return true; } else { EV.PostAlarmLog(Module, "Invalid args of DisturbanceSCR operation"); return false; } }); OP.Subscribe($"{Module}.{Name}.StopDisturbanceSim", (arg1, args) => { StopDisturbanceSimulation(); return true; }); } /// /// 启动扰动模拟。 /// internal void StartDisturbanceSimulation() { if (_swDisturbanceDuration.IsRunning) { EV.PostWarningLog(Module, "Disturbance simulation process is running"); return; } if (_disturbanceScr != 1 && _disturbanceScr != 2 && _disturbanceScr != 3) { EV.PostAlarmLog(Module, "Disturbance SCR must be L1 or L2 or L3"); return; } if (_disturbanceType != "STEP" && _disturbanceType != "SINE") { EV.PostAlarmLog(Module, "Disturbance Type must be 'STEP' or 'SINE'"); return ; } if (_disturbancePeriodSec <= 0 || _disturbanceDurationSec <= 0 || _disturbanceAmplitude < 0) { EV.PostAlarmLog(Module, "Disturbance Period, Duration and Amplitude must be positive numbers"); return; } if(_disturbanceDurationSec < _disturbancePeriodSec) { EV.PostAlarmLog(Module, "Disturbance Duration must be greater than Period"); return; } if (!_swDisturbanceDuration.IsRunning) { _swDisturbanceDuration.Reset(); _swDisturbanceDuration.Start(); _swDisturbancePeriod.Reset(); _swDisturbancePeriod.Start(); LOG.Info( $"{Module} Disturbance Simulation starts, SCR={_disturbanceScr}, " + $"Type={_disturbanceType}, " + $"Amp={_disturbanceAmplitude}, " + $"Period={_disturbancePeriodSec:F3}s, " + $"Duration={_swDisturbanceDuration:F1}s"); } } /// /// 停止扰动模拟。 /// internal void StopDisturbanceSimulation() { _swDisturbancePeriod.Stop(); _swDisturbanceDuration.Stop(); _disturbancePeriodSec = 0; _disturbanceDurationSec = 0; _disturbance = 0; LOG.Info($"{Module} Disturbance Simulation stoped"); } internal void MonitorDisturbanceSimulation() { // 如果没有设置扰动计时器,则不进行扰动模拟 if (!_swDisturbanceDuration.IsRunning) { _swDisturbancePeriod.Stop(); _disturbance = 0; return; } // 到达扰动持续时间后,停止扰动模拟 if (_swDisturbanceDuration.Elapsed.TotalSeconds > _disturbanceDurationSec) { StopDisturbanceSimulation(); return; } // 计算扰动值 switch (_disturbanceType) { case "STEP": // 阶跃扰动 if (_swDisturbancePeriod.IsRunning) { // 如果正在运行,计算当前扰动值 var elapsedSec = _swDisturbancePeriod.Elapsed.TotalSeconds; if (elapsedSec < _disturbancePeriodSec / 2) _disturbance = _disturbanceAmplitude; else if (elapsedSec > _disturbancePeriodSec / 2 && elapsedSec < _disturbancePeriodSec) _disturbance = -_disturbanceAmplitude; else _swDisturbancePeriod.Restart(); } break; case "SINE": // 正弦扰动 if (_swDisturbancePeriod.IsRunning) { // 如果正在运行,计算当前扰动值 var elapsedSec = _swDisturbancePeriod.Elapsed.TotalSeconds; _disturbance = _disturbanceAmplitude * Math.Sin(2 * Math.PI * elapsedSec / _disturbancePeriodSec); } break; default: _disturbance = 0; break; } } #endregion } }