IoHeater.cs 71 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625
  1. using Aitex.Common.Util;
  2. using Aitex.Core.Common.DeviceData;
  3. using Aitex.Core.RT.DataCenter;
  4. using Aitex.Core.RT.Event;
  5. using Aitex.Core.RT.IOCore;
  6. using Aitex.Core.RT.Log;
  7. using Aitex.Core.RT.OperationCenter;
  8. using Aitex.Core.RT.ParameterCenter;
  9. using Aitex.Core.RT.SCCore;
  10. using Aitex.Core.RT.Tolerance;
  11. using Aitex.Core.Util;
  12. using MECF.Framework.Common.CommonData;
  13. using MECF.Framework.Common.Device.Bases;
  14. using MECF.Framework.Common.Equipment;
  15. using MECF.Framework.Common.Event;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Diagnostics;
  19. using System.IO;
  20. using System.Linq;
  21. using System.Text;
  22. using System.Threading.Tasks;
  23. using System.Xml;
  24. namespace FurnaceRT.Devices
  25. {
  26. public class IoHeater : HeaterBase
  27. {
  28. public IoHeater(string module, XmlElement node, string ioModule = "")
  29. {
  30. base.Module = string.IsNullOrEmpty(node.GetAttribute("module")) ? module : node.GetAttribute("module");
  31. base.Name = node.GetAttribute("id");
  32. base.Display = node.GetAttribute("display");
  33. base.DeviceID = node.GetAttribute("schematicId");
  34. formatString = node.GetAttribute("formatString");
  35. InstallZone = node.GetAttribute("installzone");
  36. _uniqueName = $"{Module}{Name}";
  37. _isFloatAioType = !string.IsNullOrEmpty(node.GetAttribute("aioType")) && (node.GetAttribute("aioType") == "float");
  38. _aiCascadePV = ParseAiNode("aiCascadePV", node, ioModule);
  39. _aiHeaterPV = ParseAiNode("aiHeaterPV", node, ioModule);
  40. _aiWorkingOutput = ParseAiNode("aiWorkingOutput", node, ioModule);
  41. _aiOverTemp = ParseAiNode("aiOverTemp", node, ioModule);
  42. _aoCascadeControlModeSetPoint = ParseAoNode("aoCascadeControlModeSetPoint", node, ioModule);
  43. _aoHeaterControlModeSetPoint = ParseAoNode("aoHeaterControlModeSetPoint", node, ioModule);
  44. _aoCascadePID_P = ParseAoNode("aoCascadePID_P", node, ioModule);
  45. _aoCascadePID_I = ParseAoNode("aoCascadePID_I", node, ioModule);
  46. _aoCascadePID_D = ParseAoNode("aoCascadePID_D", node, ioModule);
  47. _aoHeaterPID_P = ParseAoNode("aoHeaterPID_P", node, ioModule);
  48. _aoHeaterPID_I = ParseAoNode("aoHeaterPID_I", node, ioModule);
  49. _aoHeaterPID_D = ParseAoNode("aoHeaterPID_D", node, ioModule);
  50. _aoUpRate = ParseAoNode("aoUpRate", node, ioModule);
  51. _aoDownRate = ParseAoNode("aoDownRate", node, ioModule);
  52. _aoTCOpenOffsetOffset = ParseAoNode("aoTCOpenOffsetOffset", node, ioModule);
  53. _diCascadePVSBrk = ParseDiNode("diCascadePVSBrk", node, ioModule);
  54. _diHeaterPVSBrk = ParseDiNode("diHeaterPVSBrk", node, ioModule);
  55. _diEnableOutput = ParseDiNode("diEnableOutput", node, ioModule);
  56. _doEnableIn = ParseDoNode("doEnableIn", node, ioModule);
  57. _doAutoManual = ParseDoNode("doAutoManual", node, ioModule);
  58. _doSelect = ParseDoNode("doMainPVSelect", node, ioModule);
  59. _doCascadeMode = ParseDoNode("doCascadeMode", node, ioModule);
  60. _scRoot = node.GetAttribute("scRoot");
  61. }
  62. #region fields
  63. private AIAccessor _aiCascadePV;
  64. private AIAccessor _aiHeaterPV;
  65. private AIAccessor _aiWorkingOutput;
  66. private AIAccessor _aiOverTemp;
  67. private AOAccessor _aoCascadeControlModeSetPoint;
  68. private AOAccessor _aoHeaterControlModeSetPoint;
  69. private AOAccessor _aoCascadePID_P;
  70. private AOAccessor _aoCascadePID_I;
  71. private AOAccessor _aoCascadePID_D;
  72. private AOAccessor _aoHeaterPID_P;
  73. private AOAccessor _aoHeaterPID_I;
  74. private AOAccessor _aoHeaterPID_D;
  75. private AOAccessor _aoUpRate;
  76. private AOAccessor _aoDownRate;
  77. private AOAccessor _aoTCOpenOffsetOffset;
  78. private DIAccessor _diCascadePVSBrk;
  79. private DIAccessor _diHeaterPVSBrk;
  80. private DIAccessor _diEnableOutput;
  81. private DOAccessor _doEnableIn;
  82. private DOAccessor _doAutoManual;
  83. private DOAccessor _doSelect;
  84. private DOAccessor _doCascadeMode;
  85. private string formatString;
  86. private const int physicalMax = 16000;
  87. private ToleranceChecker _toleranceCheckerWarning = new ToleranceChecker();
  88. private ToleranceChecker _toleranceCheckerAlarm = new ToleranceChecker();
  89. //tolerance check
  90. private float _alarmJudgmentRange;
  91. private float _warningJudgmentRange;
  92. private float _alarmJudgmentTime;
  93. private float _warningJudgmentTime;
  94. private float _toleranceJudgmentDelayTime;
  95. private DeviceTimer _toleranceJudgmentDelayTimer = new DeviceTimer();
  96. //stable check
  97. private Stopwatch _stableJudgmentTimer = new Stopwatch();
  98. private float _stableJudgmentTime = 1;
  99. private float _stableMinValue;
  100. private float _stableMaxValue;
  101. private SCConfigItem _scEnableCalibration;
  102. private SCConfigItem _scCalibrationTable;
  103. private SCConfigItem _scRange;
  104. private SCConfigItem _scRampRate;//°C/min
  105. private List<CalibrationItem> _calibrationTable = new List<CalibrationItem>();
  106. private string _previousSetting;
  107. private DeviceTimer _rampTimer = new DeviceTimer();
  108. private double _rampTarget;
  109. private double _rampInitValue;
  110. private int _rampTime;
  111. private DeviceTimer _stableTimer = new DeviceTimer();
  112. private bool _isWarned;
  113. private string _uniqueName;
  114. private float _tempSetpoint = 0.0f;
  115. private string _scRoot;
  116. private bool _isStartRamp = false;
  117. private bool _isFloatAioType = false;
  118. private Dictionary<string, int> _pidTableAssociate;
  119. #region temp correct
  120. private float _profileTemp = 0;
  121. private float _profileCorrect = 0;
  122. private float _profileTCCalib = 0;
  123. private float _cascadeTCCorrect = 0;
  124. public string CurrentCorrectFileName { get; private set; }
  125. public int CurrentCorrectIndex { get; private set; }
  126. private string _writeLog = "";
  127. #endregion
  128. #region temp profile
  129. private float _preheatTime = 0;
  130. private float _checkTime = 0;
  131. private float _checkLimit = 0;
  132. private float _alarmLimit = 0;
  133. private float _totalTime = 0;
  134. private Stopwatch _profileTimer = new Stopwatch();
  135. private Stopwatch _profileStableTimer = new Stopwatch();
  136. public bool IsProfileMode => _profileTimer != null && _profileTimer.IsRunning;
  137. public bool IsProfileSuccess { get; set; }
  138. private R_TRIG _profileTotalTimeoutTrig = new R_TRIG();
  139. private R_TRIG _profileAlarmLimitTrig = new R_TRIG();
  140. private R_TRIG _profileSuccessTrig = new R_TRIG();
  141. private bool _isWait;
  142. private float _waitHigh;
  143. private float _waitLow;
  144. private bool _isInit = false;
  145. private Stopwatch _initTimer = new Stopwatch();
  146. #endregion
  147. #endregion
  148. #region properties
  149. public string InstallZone { get; set; }
  150. public double Range => _scRange.DoubleValue;
  151. public AlarmEventItem AlarmToleranceWarning { get; set; }
  152. public AlarmEventItem AlarmToleranceAlarm { get; set; }
  153. public AlarmEventItem InterlockAlarm { get; set; }
  154. public AlarmEventItem HeaterErrorAlarm { get; set; }
  155. public AlarmEventItem HeaterErrorRecoveryWarning { get; set; }
  156. public AlarmEventItem HeaterStripBreakAlarm { get; set; }
  157. public AlarmEventItem HeaterStripBreakWarning { get; set; }
  158. public bool IsHeaterStripBreak { get; set; }
  159. private RD_TRIG _trigHeaterErrorSignalOn = new RD_TRIG();
  160. public R_TRIG TrigHeaterStripBreakSignalOn = new R_TRIG();
  161. public DeviceTimer HeaterStripBreakTimer = new DeviceTimer();
  162. public string InstallPosition => SC.GetStringValue($"{_scRoot}.Heater.{InstallZone}.{Name}.InstallPosition");
  163. public bool IsError => _diCascadePVSBrk == null || _diHeaterPVSBrk == null ? false : _diCascadePVSBrk.Value || _diHeaterPVSBrk.Value;
  164. private R_TRIG _tcBreakTrig = new R_TRIG();
  165. public bool IsStable
  166. {
  167. get
  168. {
  169. if (_stableJudgmentTimer.IsRunning)
  170. {
  171. if (DeviceData.FeedBack < (DeviceData.SetPoint - _stableMinValue) ||
  172. DeviceData.FeedBack > (DeviceData.SetPoint + _stableMaxValue))
  173. {
  174. _stableJudgmentTimer.Restart();
  175. }
  176. if (_stableJudgmentTimer.ElapsedMilliseconds >= _stableJudgmentTime * 1000)
  177. {
  178. return true;
  179. }
  180. }
  181. else
  182. {
  183. _stableJudgmentTimer.Restart();
  184. }
  185. return false;
  186. }
  187. }
  188. public override float TempSetPoint
  189. {
  190. get
  191. {
  192. return _tempSetpoint;
  193. }
  194. set
  195. {
  196. if (_aoCascadeControlModeSetPoint != null && _aoHeaterControlModeSetPoint != null)
  197. {
  198. if (_isFloatAioType)
  199. {
  200. if(ControlMode == 0)
  201. {
  202. if (Math.Abs(_aoCascadeControlModeSetPoint.FloatValue - value) > 0.0001)
  203. {
  204. _aoCascadeControlModeSetPoint.SetPulseValue(value, 1500);
  205. }
  206. }
  207. else
  208. {
  209. if (Math.Abs(_aoHeaterControlModeSetPoint.FloatValue - value) > 0.0001)
  210. {
  211. _aoHeaterControlModeSetPoint.SetPulseValue(value, 1500);
  212. }
  213. }
  214. }
  215. }
  216. }
  217. }
  218. public float HeaterPID_P
  219. {
  220. get
  221. {
  222. return _aoHeaterPID_P.FloatValue;
  223. }
  224. set
  225. {
  226. if (Math.Abs(_aoHeaterPID_P.FloatValue - value) > 0.0001)
  227. _aoHeaterPID_P.FloatValue = value;
  228. }
  229. }
  230. public float HeaterPID_I
  231. {
  232. get
  233. {
  234. return _aoHeaterPID_I.FloatValue;
  235. }
  236. set
  237. {
  238. if (Math.Abs(_aoHeaterPID_I.FloatValue - value) > 0.0001)
  239. _aoHeaterPID_I.FloatValue = value;
  240. }
  241. }
  242. public float HeaterPID_D
  243. {
  244. get
  245. {
  246. return _aoHeaterPID_D.FloatValue;
  247. }
  248. set
  249. {
  250. if (Math.Abs(_aoHeaterPID_D.FloatValue - value) > 0.0001)
  251. _aoHeaterPID_D.FloatValue = value;
  252. }
  253. }
  254. public float CascadePID_P
  255. {
  256. get
  257. {
  258. return _aoCascadePID_P.FloatValue;
  259. }
  260. set
  261. {
  262. if (Math.Abs(_aoCascadePID_P.FloatValue - value) > 0.0001)
  263. _aoCascadePID_P.FloatValue = value;
  264. }
  265. }
  266. public float CascadePID_I
  267. {
  268. get
  269. {
  270. return _aoCascadePID_I.FloatValue;
  271. }
  272. set
  273. {
  274. if (Math.Abs(_aoCascadePID_I.FloatValue - value) > 0.0001)
  275. _aoCascadePID_I.FloatValue = value;
  276. }
  277. }
  278. public float CascadePID_D
  279. {
  280. get
  281. {
  282. return _aoCascadePID_D.FloatValue;
  283. }
  284. set
  285. {
  286. if (Math.Abs(_aoCascadePID_D.FloatValue - value) > 0.0001)
  287. _aoCascadePID_D.FloatValue = value;
  288. }
  289. }
  290. public float UpRate
  291. {
  292. get
  293. {
  294. return _aoUpRate.FloatValue;
  295. }
  296. set
  297. {
  298. if (Math.Abs(_aoUpRate.FloatValue - value) > 0.0001)
  299. _aoUpRate.FloatValue = value;
  300. }
  301. }
  302. public float DownRate
  303. {
  304. get
  305. {
  306. return _aoDownRate.FloatValue;
  307. }
  308. set
  309. {
  310. if (Math.Abs(_aoDownRate.FloatValue - value) > 0.0001)
  311. _aoDownRate.FloatValue = value;
  312. }
  313. }
  314. public int ControlMode
  315. {
  316. get
  317. {
  318. if (_doSelect != null && _doCascadeMode != null)
  319. {
  320. if (!_doSelect.Value && !_doCascadeMode.Value)
  321. {
  322. // Furnace control
  323. return 0;
  324. }
  325. else if (!_doSelect.Value && _doCascadeMode.Value)
  326. {
  327. // Heater control
  328. return 1;
  329. }
  330. else if (_doSelect.Value && _doCascadeMode.Value)
  331. {
  332. //Furnace Diect control
  333. return 2;
  334. }
  335. }
  336. return 0;
  337. }
  338. }
  339. public float TCOpenOffsetOffset
  340. {
  341. get
  342. {
  343. return _aoTCOpenOffsetOffset != null ? _aoTCOpenOffsetOffset.FloatValue : 0;
  344. }
  345. set
  346. {
  347. if (_aoTCOpenOffsetOffset != null && Math.Abs(_aoTCOpenOffsetOffset.FloatValue - value) > 0.0001)
  348. _aoTCOpenOffsetOffset.FloatValue = value;
  349. }
  350. }
  351. public override float TempFeedback => (float)(ControlMode == 1 ? (_aiHeaterPV == null ? 0 : _aiHeaterPV.FloatValue - _profileTCCalib) : (_aiCascadePV == null ? 0 : _aiCascadePV.FloatValue - _profileTCCalib));
  352. #endregion
  353. public override bool Initialize()
  354. {
  355. DeviceData = new AITHeaterData()
  356. {
  357. DeviceName = Name,
  358. DeviceSchematicId = DeviceID,
  359. DisplayName = Display,
  360. Module = Module,
  361. Scale = SC.GetValue<double>($"{_scRoot}.{Name}.Range"),
  362. OverTempScale= SC.GetValue<double>($"{_scRoot}.{Name}.OverTempRange"),
  363. //Scale = 1200,
  364. Unit = "°C",
  365. //SetPoint = TempSetPoint,
  366. FeedBack = TempFeedback,
  367. };
  368. _pidTableAssociate = new Dictionary<string, int>()
  369. {
  370. { "HeaterU",0 },
  371. { "HeaterCU",1 },
  372. { "HeaterC",2 },
  373. { "HeaterCL",3 },
  374. { "HeaterL",4 },
  375. };
  376. _scEnableCalibration = SC.GetConfigItem($"{_scRoot}.{Name}.EnableCalibration");
  377. _scCalibrationTable = SC.GetConfigItem($"{_scRoot}.{Name}.CalibrationTable");
  378. //_scRange = SC.GetConfigItem($"{_scRoot}.{Name}.Range");
  379. _scRampRate = SC.GetConfigItem($"{_scRoot}.{Name}.RampRate");
  380. //AlarmToleranceWarning = SubscribeAlarm($"{_scRoot}.{Name}.ToleranceWarning", "", ResetWarningChecker, EventLevel.Warning);
  381. //AlarmToleranceAlarm = SubscribeAlarm($"{_scRoot}.{Name}.ToleranceAlarm", "", ResetAlarmChecker);
  382. //AlarmToleranceWarning.Id = SC.ContainsItem($"{_scRoot}.{Name}.ToleranceWarningID") ? SC.GetValue<int>($"{_scRoot}.{Name}.ToleranceWarningID") : 0;
  383. //AlarmToleranceAlarm.Id = SC.ContainsItem($"{_scRoot}.{Name}.ToleranceAlarmID") ? SC.GetValue<int>($"{_scRoot}.{Name}.ToleranceAlarmID") : 0;
  384. //_stableJudgmentTime = (float)SC.GetValue<double>($"{_scRoot}.{Name}.Stabilize.JudgmentTime");
  385. //_stableMinValue = (float)SC.GetValue<double>($"{_scRoot}.{Name}.Stabilize.MinusValue");
  386. //_stableMaxValue = (float)SC.GetValue<double>($"{_scRoot}.{Name}.Stabilize.PlusValue");
  387. DATA.Subscribe($"{Module}.{Name}.CascadePV", () => (float)DeviceData.CascadePV);
  388. DATA.Subscribe($"{Module}.{Name}.HeaterPV", () => (float)DeviceData.HeaterPV);
  389. DATA.Subscribe($"{Module}.{Name}.CascadePID_P", () => (float)DeviceData.CascadePID_P);
  390. DATA.Subscribe($"{Module}.{Name}.CascadePID_I", () => (float)DeviceData.CascadePID_I);
  391. DATA.Subscribe($"{Module}.{Name}.CascadePID_D", () => (float)DeviceData.CascadePID_D);
  392. DATA.Subscribe($"{Module}.{Name}.HeaterPID_P", () => (float)DeviceData.HeaterPID_P);
  393. DATA.Subscribe($"{Module}.{Name}.HeaterPID_I", () => (float)DeviceData.HeaterPID_I);
  394. DATA.Subscribe($"{Module}.{Name}.HeaterPID_D", () => (float)DeviceData.HeaterPID_D);
  395. DATA.Subscribe($"{Module}.{Name}.CascadeControlModeSV", () => _aoCascadeControlModeSetPoint != null ? _aoCascadeControlModeSetPoint.FloatValue : 0.0f);
  396. DATA.Subscribe($"{Module}.{Name}.HeaterControlModeSV", () => _aoHeaterControlModeSetPoint != null ? _aoHeaterControlModeSetPoint.FloatValue : 0.0f);
  397. DATA.Subscribe($"{Module}.{Name}.UpRate", () => UpRate);
  398. DATA.Subscribe($"{Module}.{Name}.DownRate", () => DownRate);
  399. DATA.Subscribe($"{Module}.{Name}.WorkingOutput", () => _aiWorkingOutput.FloatValue);
  400. DATA.Subscribe($"{Module}.{Name}.OverTemp", () => _aiOverTemp.FloatValue);
  401. DATA.Subscribe($"{Module}.{Name}.IsCascadePVBreak", () => _diCascadePVSBrk.Value);
  402. DATA.Subscribe($"{Module}.{Name}.IsHeaterPVBreak", () => _diHeaterPVSBrk.Value);
  403. DATA.Subscribe($"{Module}.{Name}.IsEnableOutput", () => _diEnableOutput.Value);
  404. DATA.Subscribe($"{Module}.{Name}.IsEnableIn", () => _doEnableIn.Value);
  405. DATA.Subscribe($"{Module}.{Name}.IsAutoManual", () => _doAutoManual.Value);
  406. DATA.Subscribe($"{Module}.{Name}.IsSelect", () => _doSelect.Value);
  407. DATA.Subscribe($"{Module}.{Name}.IsCascadeMode", () => _doCascadeMode.Value);
  408. DATA.Subscribe($"{Module}.{Name}.ControlMode", () => ControlMode);
  409. DATA.Subscribe($"{Module}.{Name}.TCOpenOffsetOffset", () => TCOpenOffsetOffset);
  410. OP.Subscribe($"{Module}.{Name}.SetRemoteMode", SetRemoteMode);
  411. OP.Subscribe($"{Module}.{Name}.SetAutoTuning", SetAutoTuning);
  412. OP.Subscribe($"{Module}.{Name}.SetOnOff", SetOnOff);
  413. OP.Subscribe($"{Module}.{Name}.SetUpDownRate", (out string reason, int time, object[] param) =>
  414. {
  415. reason = string.Empty;
  416. SetUpDownRate(param);
  417. return true;
  418. });
  419. OP.Subscribe($"{Module}.{Name}.SetManualParameters", (out string reason, int time, object[] param) =>
  420. {
  421. reason = string.Empty;
  422. SetManualParameters(param);
  423. return true;
  424. });
  425. //recipe
  426. OP.Subscribe($"PM1.{Name}.SetParameters", (out string reason, int time, object[] param) =>
  427. {
  428. reason = string.Empty;
  429. SetParameters(param);
  430. return true;
  431. });
  432. _doEnableIn.SetValue(true, out _);
  433. SetCorrectParameters(SC.GetStringValue("PM1.TempCorrection"));
  434. return base.Initialize();
  435. }
  436. public override void Monitor()
  437. {
  438. if(!string.IsNullOrEmpty(_writeLog))
  439. {
  440. LOG.Write(_writeLog);
  441. _writeLog = "";
  442. }
  443. if (!_isInit)
  444. {
  445. if (!_initTimer.IsRunning)
  446. _initTimer.Start();
  447. if (_initTimer.ElapsedMilliseconds > 5 * 1000)
  448. {
  449. _initTimer.Stop();
  450. _isInit = true;
  451. if (_tempSetpoint == 0)
  452. {
  453. if(ControlMode == 0)
  454. {
  455. if(_aoCascadeControlModeSetPoint != null && _aoCascadeControlModeSetPoint.FloatValue > 0)
  456. {
  457. _tempSetpoint = _aoCascadeControlModeSetPoint.FloatValue;
  458. }
  459. }
  460. else
  461. {
  462. if (_aoCascadeControlModeSetPoint != null && _aoHeaterControlModeSetPoint.FloatValue > 0)
  463. {
  464. _tempSetpoint = _aoHeaterControlModeSetPoint.FloatValue;
  465. }
  466. }
  467. }
  468. SetPIDParameters(SC.GetStringValue("PM1.Heater.PID"));
  469. }
  470. }
  471. if(DeviceData != null)
  472. {
  473. DeviceData.CascadePID_P = _aoCascadePID_P == null ? 0 : _aoCascadePID_P.FloatValue;
  474. DeviceData.CascadePID_I = _aoCascadePID_I == null ? 0 : _aoCascadePID_I.FloatValue;
  475. DeviceData.CascadePID_D = _aoCascadePID_D == null ? 0 : _aoCascadePID_D.FloatValue;
  476. DeviceData.HeaterPID_P = _aoHeaterPID_P == null ? 0 : _aoHeaterPID_P.FloatValue;
  477. DeviceData.HeaterPID_I = _aoHeaterPID_I == null ? 0 : _aoHeaterPID_I.FloatValue;
  478. DeviceData.HeaterPID_D = _aoHeaterPID_D == null ? 0 : _aoHeaterPID_D.FloatValue;
  479. DeviceData.OverTemp = _aiOverTemp == null ? 0 : _aiOverTemp.FloatValue;
  480. DeviceData.CascadePV = _aiCascadePV == null ? 0 : _aiCascadePV.FloatValue;
  481. DeviceData.HeaterPV = _aiHeaterPV == null ? 0 : _aiHeaterPV.FloatValue;
  482. DeviceData.FeedBack = TempFeedback;
  483. DeviceData.SetPoint = _tempSetpoint;
  484. DeviceData.ManipulatedVariable = _aiWorkingOutput == null ? 0 : _aiWorkingOutput.FloatValue;
  485. DeviceData.RampSetPoint = TempSetPoint;//Ramp的过程值
  486. DeviceData.Ramping = _aoUpRate == null ? 0 : _aoUpRate.FloatValue;
  487. DeviceData.ControlMode = ControlMode;// 控制模式
  488. DeviceData.IsAlarm = IsError;
  489. DeviceData.UpRateSetPoint = UpRate;
  490. DeviceData.DownRateSetPoint = DownRate;
  491. DeviceData.EnableOutput = _diEnableOutput.Value;
  492. DeviceData.IsCascadePVBreak = _diCascadePVSBrk.Value;
  493. DeviceData.IsHeaterPVBreak = _diHeaterPVSBrk.Value;
  494. DeviceData.IsProfiling = IsProfileMode;
  495. DeviceData.IsOverTempError = DeviceData.OverTemp > DeviceData.OverTempScale;
  496. DeviceData.IsTempLmtError = DeviceData.FeedBack > DeviceData.Scale;
  497. if (_profileTimer != null && _profileTimer.IsRunning)
  498. {
  499. DeviceData.ProfileTotalTime = _totalTime - _profileTimer.ElapsedMilliseconds / 1000 > 0 ? _totalTime - _profileTimer.ElapsedMilliseconds / 1000 : 0;
  500. DeviceData.ProfilePreheatTime = _preheatTime - _profileTimer.ElapsedMilliseconds / 1000 > 0 ? _preheatTime - _profileTimer.ElapsedMilliseconds / 1000 : 0;
  501. }
  502. if(_profileStableTimer != null && _profileStableTimer.IsRunning)
  503. {
  504. DeviceData.ProfileCheckTime = _checkTime - _profileStableTimer.ElapsedMilliseconds / 1000 > 0 ? _checkTime - _profileStableTimer.ElapsedMilliseconds / 1000 : 0;
  505. }
  506. }
  507. MonitorTolerance();
  508. Ramping();
  509. _tcBreakTrig.CLK = IsError;
  510. if (_tcBreakTrig.Q)
  511. TCOpenOffsetOffset = _profileCorrect;
  512. base.Monitor();
  513. }
  514. public override void SetTemperature(float temperature)
  515. {
  516. //if (temperature != SetpointFeedback)
  517. {
  518. var _rampTime = _scRampRate.DoubleValue != 0 ? (Math.Abs(temperature - TempFeedback) / _scRampRate.DoubleValue) * 60 * 1000 : 0;
  519. Ramp(temperature, (int)_rampTime);
  520. }
  521. }
  522. public void SetRamping(float ramping)
  523. {
  524. SaveRampRate(ramping.ToString("F1"));
  525. }
  526. private void SetManualParameters(object[] param)
  527. {
  528. //value:ramp
  529. if (param == null || param.Length < 1)
  530. {
  531. EV.PostWarningLog(ModuleName.PM1.ToString(), $"Invalid heater temperature set parameter");
  532. return;
  533. }
  534. var array = param[0].ToString().Split(';');
  535. if (array == null || array.Length < 2)
  536. {
  537. EV.PostWarningLog(ModuleName.PM1.ToString(), $"Invalid heater temperature set parameter");
  538. return;
  539. }
  540. float.TryParse(array[0], out float temperature);
  541. float.TryParse(array[1], out float rampTime);//°C/min
  542. DeviceData.SetPoint = temperature;
  543. DeviceData.RampSetPoint = rampTime;
  544. var profileCorrect = 0.0f;
  545. var profileTCCalib = 0.0f;
  546. //var actualSet = temperature - profileCorrect < 0 ? 0 : temperature - profileCorrect;
  547. var actualSet = temperature + _profileTCCalib;//加上flat zone的值
  548. UpRate = rampTime;
  549. DownRate = rampTime;
  550. var controlMode = "";
  551. if (array.Length > 2)
  552. {
  553. controlMode = array.Length > 2 ? array[2].ToString() : "";
  554. SetControlMode(controlMode);
  555. }
  556. _tempSetpoint = temperature;
  557. TempSetPoint = actualSet;
  558. LOG.Write($"{Name} setpoint={temperature} control mode={controlMode}");
  559. //Ramp(actualSet, (int)rampTime);
  560. }
  561. public void SetControlMode(string mode, string recipeProfileFileName = "")
  562. {
  563. var reason = "";
  564. DeviceData.ControlModeSetpoint = mode;
  565. switch(mode.ToLower())
  566. {
  567. case "heater":
  568. case "heater control":
  569. if(_doSelect.Value)
  570. _doSelect.SetValue(false, out reason);
  571. if (!_doCascadeMode.Value)
  572. _doCascadeMode.SetValue(true, out reason);
  573. break;
  574. case "furnace":
  575. case "furnace control":
  576. if (_doSelect.Value)
  577. _doSelect.SetValue(false, out reason);
  578. if (_doCascadeMode.Value)
  579. _doCascadeMode.SetValue(false, out reason);
  580. break;
  581. case "furnace direct":
  582. case "furnace direct control":
  583. if (!_doSelect.Value)
  584. _doSelect.SetValue(true, out reason);
  585. if (!_doCascadeMode.Value)
  586. _doCascadeMode.SetValue(true, out reason);
  587. break;
  588. default:
  589. if(mode.ToLower().StartsWith("profile"))
  590. {
  591. var arry = mode.Replace(")","").Split('(');
  592. if(arry != null && arry.Length > 1)
  593. {
  594. var profileArray = arry[1].Split(',');
  595. if(profileArray != null && profileArray.Length > 2)
  596. {
  597. var profileFileName = profileArray[0];
  598. int.TryParse(profileArray[1], out int profileTableIndex);
  599. GetProfileParameters(profileFileName, profileTableIndex, recipeProfileFileName);
  600. _profileTimer.Restart();
  601. _profileStableTimer.Stop();
  602. _profileTotalTimeoutTrig.RST = true;
  603. _profileAlarmLimitTrig.RST = true;
  604. _profileSuccessTrig.RST = true;
  605. IsProfileSuccess = false;
  606. DeviceData.ProfileResult = 0;
  607. LOG.Write($"{Name} profile start preheatTime={_preheatTime} checkTime={_checkTime} totalTime={_totalTime} checkLimit={_checkLimit} alarmLimit={_alarmLimit}");
  608. }
  609. }
  610. }
  611. break;
  612. }
  613. }
  614. private void SetUpDownRate(object[] param)
  615. {
  616. }
  617. public void SetParameters(object[] param)
  618. {
  619. if (param != null && param.Length > 0)
  620. {
  621. string zoneName = "";
  622. float temperature = 0.0f;
  623. string tempUnit = "";
  624. float ramp = 0.0f;
  625. string rampUnit = "";
  626. string controlMode = "";
  627. string correct = "";
  628. string PID = "";
  629. var array = param[0].ToString().Split(';');//zoneName;temp;tempUnit;ramp;rampUnit;checkWait;tempHigh;tempLow;unitStepCompletionCondition;controlMode;correct;PID
  630. if (System.Text.RegularExpressions.Regex.Match(array[1].ToString(), @"[a-zA-Z]").Success)
  631. {
  632. var table = array[1].ToString().Split(':');//AssociateParameterTable
  633. if (SC.ContainsItem($"PM1.RecipeEditParameter.TempSetting.{table[0]}.{Name}"))
  634. {
  635. temperature = (float)SC.GetValue<double>($"PM1.RecipeEditParameter.TempSetting.{table[0]}.{Name}");
  636. }
  637. }
  638. else
  639. {
  640. float.TryParse(array[1].ToString(), out temperature);
  641. }
  642. tempUnit = array.Length > 2 ? array[2].ToString() : "";
  643. if (array.Length > 3)
  644. {
  645. float.TryParse(array[3], out ramp);
  646. }
  647. rampUnit = array.Length > 4 ? array[4].ToString() : "";
  648. if (array.Length > 5)
  649. {
  650. bool.TryParse(array[5], out _isWait);
  651. }
  652. if (array.Length > 6)
  653. {
  654. float.TryParse(array[6], out _waitHigh);
  655. }
  656. if (array.Length > 7)
  657. {
  658. float.TryParse(array[7], out _waitLow);
  659. }
  660. var unitStepCompletionCondition = array.Length > 8 ? array[8].ToString() : "";
  661. _stableJudgmentTimer.Stop();
  662. controlMode = array.Length > 9 ? array[9].ToString() : "";
  663. correct = array.Length > 10 ? array[10].ToString() : "";
  664. PID = array.Length > 11 ? array[11].ToString() : "";
  665. var correctileName = array.Length > 12 ? array[12].ToString() : "";
  666. var PIDFileName = array.Length > 13 ? array[13].ToString() : "";
  667. var profileFileName = array.Length > 14 ? array[14].ToString() : "";
  668. var DPR = array.Length > 16 ? (array[15].ToString().ToLower() == "open" ? true : false) : false;
  669. var BWR = array.Length > 16 ? (array[15].ToString().ToLower() == "open" ? true : false) : false;
  670. SetControlMode(controlMode, profileFileName);
  671. SetPIDParameters(PID, PIDFileName);
  672. SetCorrectParameters(correct, correctileName, profileFileName);
  673. var profileCorrect = 0.0f;
  674. var profileTCCalib = 0.0f;
  675. //var actualSet = temperature - profileCorrect < 0 ? 0 : temperature - profileCorrect;
  676. var actualSet = temperature + _profileTCCalib;//加上flat zone的值
  677. float _rampTime = 0;
  678. if(DPR && BWR)
  679. {
  680. DownRate = 0;
  681. UpRate = 0;
  682. }
  683. else
  684. {
  685. if (ramp == 0)
  686. {
  687. DownRate = Math.Abs(TempFeedback - actualSet);//单位是°C/min
  688. UpRate = Math.Abs(TempFeedback - actualSet);//单位是°C/min
  689. }
  690. else if (rampUnit.ToLower() == "time")
  691. {
  692. DownRate = Math.Abs(TempFeedback - actualSet) / ramp;//单位是°C/min
  693. UpRate = Math.Abs(TempFeedback - actualSet) / ramp;//单位是°C/min
  694. }
  695. else
  696. {
  697. DownRate = ramp;//单位是°C/min
  698. UpRate = ramp;//单位是°C/min
  699. }
  700. }
  701. DeviceData.SetPoint = temperature;
  702. DeviceData.RampSetPoint = _rampTime;
  703. _tempSetpoint = temperature;
  704. TempSetPoint = actualSet;
  705. //LOG.Write($"{Name} setpoint={temperature} control mode={controlMode}, PID={PID}, correct={correct}");
  706. _writeLog = $"{Name} setpoint={temperature} control mode={controlMode}, PID={PID}, correct={correct}";
  707. //Ramp(actualSet, (int)_rampTime);
  708. }
  709. return;
  710. }
  711. private bool SetRemoteMode(out string reason, int time, params object[] param)
  712. {
  713. reason = string.Empty;
  714. if (param == null || param.Length == 0)
  715. {
  716. reason = $"invalid parameter";
  717. return false;
  718. }
  719. bool.TryParse(param[0].ToString(), out bool isRemoteMode);
  720. //_doRemoteControl.SetValue(isRemoteMode, out _);
  721. return true;
  722. }
  723. private bool SetOnOff(out string reason, int time, params object[] param)
  724. {
  725. reason = string.Empty;
  726. if (param == null || param.Length == 0)
  727. {
  728. reason = $"invalid parameter";
  729. return false;
  730. }
  731. bool.TryParse(param[0].ToString(), out bool isOn);
  732. //if (!_doStartHeating.Check(isOn, out reason))
  733. //{
  734. // return false;
  735. //}
  736. //return _doStartHeating.SetValue(isOn, out reason);
  737. return true;
  738. }
  739. public void SetRun(bool isOn)
  740. {
  741. //_doStartHeating.SetValue(isOn, out _);
  742. EV.PostInfoLog(Name, $"{Name}.SetRun({isOn})");
  743. }
  744. public void SetRemote(bool isRemote)
  745. {
  746. //_doRemoteControl.SetValue(isRemote, out _);
  747. }
  748. private bool SetAutoTuning(out string reason, int time, params object[] param)
  749. {
  750. reason = string.Empty;
  751. if (param == null || param.Length == 0)
  752. {
  753. reason = $"invalid parameter";
  754. return false;
  755. }
  756. bool.TryParse(param[0].ToString(), out bool isAutoSetAutoTuning);
  757. if (isAutoSetAutoTuning)
  758. {
  759. //_doCanWritePara.SetPulseValue(true, 2000);
  760. //_doStopAutoTunningMode.SetValue(false, out _);
  761. //_doStartAutoTunningMode.SetPulseValue(true, 2000);
  762. }
  763. else
  764. {
  765. //_doCanWritePara.SetPulseValue(true, 2000);
  766. //_doStartAutoTunningMode.SetValue(false, out _);
  767. //_doStopAutoTunningMode.SetPulseValue(true, 2000);
  768. }
  769. return true;
  770. }
  771. public override void Terminate()
  772. {
  773. ProfileFinish();
  774. base.Terminate();
  775. }
  776. public void ProfileFinish()
  777. {
  778. _profileTimer.Stop();
  779. _profileStableTimer.Stop();
  780. _profileTotalTimeoutTrig.RST = true;
  781. _profileAlarmLimitTrig.RST = true;
  782. _profileSuccessTrig.RST = true;
  783. IsProfileSuccess = false;
  784. DeviceData.ProfileTable = "";
  785. DeviceData.ProfileTotalTime = 0;
  786. DeviceData.ProfilePreheatTime = 0;
  787. DeviceData.ProfileCheckTime = 0;
  788. DeviceData.ProfileAlarmLimit = 0;
  789. DeviceData.ProfileCheckLimit = 0;
  790. }
  791. public bool SetEnable(bool isEnable)
  792. {
  793. return _doEnableIn.SetValue(isEnable, out _);
  794. }
  795. public bool ResetWarningChecker()
  796. {
  797. _toleranceCheckerWarning.Reset(_warningJudgmentTime);
  798. return true;
  799. }
  800. public bool ResetAlarmChecker()
  801. {
  802. _toleranceCheckerAlarm.Reset(_alarmJudgmentTime);
  803. return true;
  804. }
  805. public override void Reset()
  806. {
  807. AlarmToleranceWarning?.Reset();
  808. AlarmToleranceAlarm?.Reset();
  809. _toleranceJudgmentDelayTimer?.Stop();
  810. }
  811. public void ResetHeaterError()
  812. {
  813. _trigHeaterErrorSignalOn.RST = true;
  814. if (HeaterErrorAlarm != null)
  815. HeaterErrorAlarm.IsAcknowledged = true;
  816. }
  817. public void ResetHeaterStripBreak()
  818. {
  819. TrigHeaterStripBreakSignalOn.RST = true;
  820. if (HeaterStripBreakAlarm != null)
  821. HeaterStripBreakAlarm.IsAcknowledged = true;
  822. }
  823. private void MonitorTolerance()
  824. {
  825. if (IsHeaterStripBreak || TempSetPoint < 0.001 || _toleranceJudgmentDelayTimer.IsIdle() || _toleranceJudgmentDelayTimer.GetElapseTime() < _toleranceJudgmentDelayTime * 1000)
  826. {
  827. _toleranceCheckerWarning.RST = true;
  828. _toleranceCheckerAlarm.RST = true;
  829. return;
  830. }
  831. if (_alarmJudgmentRange != 0 && _alarmJudgmentTime > 0)
  832. {
  833. _toleranceCheckerAlarm.Monitor(DeviceData.FeedBack, DeviceData.RampSetPoint - Math.Abs(_alarmJudgmentRange), DeviceData.RampSetPoint + Math.Abs(_alarmJudgmentRange), _alarmJudgmentTime);
  834. }
  835. if (_warningJudgmentRange != 0 && _warningJudgmentTime > 0)
  836. {
  837. _toleranceCheckerWarning.Monitor(DeviceData.FeedBack, DeviceData.RampSetPoint - Math.Abs(_warningJudgmentRange), DeviceData.RampSetPoint + Math.Abs(_warningJudgmentRange), _warningJudgmentTime);
  838. }
  839. }
  840. public override bool CheckToleranceAlarm()
  841. {
  842. return _toleranceCheckerAlarm.Result;
  843. }
  844. public override bool CheckToleranceWarning()
  845. {
  846. return _toleranceCheckerWarning.Result;
  847. }
  848. public void SetToleranceAlarm()
  849. {
  850. AlarmToleranceAlarm.Description = $"{Display} temperature out of range {_alarmJudgmentRange} °C in {_alarmJudgmentTime:F0} seconds";
  851. AlarmToleranceAlarm.Set();
  852. }
  853. public void SetToleranceWarning()
  854. {
  855. AlarmToleranceWarning.Description = $"{Display} temperature out of range {_warningJudgmentRange} °C in {_warningJudgmentTime:F0} seconds";
  856. AlarmToleranceWarning.Set();
  857. }
  858. public void Ramp(double target, int time)
  859. {
  860. target = Math.Max(0, target);
  861. //target = Math.Min(Range, target);
  862. _rampInitValue = TempFeedback; //ramp 初始值取当前设定值,而非实际读取值.零漂问题
  863. _rampTime = time;
  864. _rampTarget = target;
  865. _rampTimer.Start(_rampTime);
  866. _isStartRamp = true;
  867. }
  868. private void Ramping()
  869. {
  870. //只有修改了温度,才开始ramp,避免一开机温度设定值大于0的问题。
  871. if (!_isStartRamp)
  872. return;
  873. if (_rampTimer.IsTimeout() || _rampTime == 0)
  874. {
  875. TempSetPoint = (float)_rampTarget;
  876. }
  877. else
  878. {
  879. TempSetPoint = (float)(_rampInitValue + (_rampTarget - _rampInitValue) * _rampTimer.GetElapseTime() / _rampTime);
  880. }
  881. }
  882. private void SaveRampRate(string ramping)
  883. {
  884. SC.SetItemValueFromString($"{_scRoot}.{Name}.RampRate", ramping);
  885. }
  886. public void SetCorrectParameters(string name, string recipeCorrectFileName = null, string recipeProfileFileName = null)
  887. {
  888. //"Heater;Parameter\\TempCorrection\\tempCorrect,2,Name2;Parameter\\TempPID\\tempPID,3,Name3"
  889. //var defaultPID = SC.GetStringValue("PM1.APCPID");
  890. if (string.IsNullOrEmpty(name))
  891. {
  892. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature correct file is empty");
  893. name = SC.GetStringValue("PM1.TempCorrection");
  894. }
  895. var array = name.Split(',');
  896. if (array == null || array.Length < 2)
  897. {
  898. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"Invalid heater temperature correct parameter");
  899. name = SC.GetStringValue("PM1.TempCorrection");
  900. array = name.Split(',');
  901. }
  902. if (!File.Exists($"{PathManager.GetParameterDir()}\\{array[0]}.rcp"))
  903. {
  904. name = SC.GetStringValue("PM1.TempCorrection");
  905. var temp = name.Split(',');
  906. if (temp != null && array.Length > 0)
  907. {
  908. array[0] = temp[0];
  909. name = string.Join(",", array);
  910. }
  911. }
  912. if (array == null || array.Length < 2)
  913. {
  914. return;
  915. }
  916. var fileNameAndPath = array[0];
  917. int.TryParse(array[1], out int index);
  918. var para = fileNameAndPath.Split('\\').ToList().Skip(2);//"Parameter\\TempCorrection"
  919. if (para == null)
  920. {
  921. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature correct file is empty");
  922. return;
  923. }
  924. var fileName = string.Join("\\", para.ToArray());
  925. if (!string.IsNullOrEmpty(recipeCorrectFileName))
  926. fileName = recipeCorrectFileName;
  927. CurrentCorrectFileName = fileName;
  928. CurrentCorrectIndex = index;
  929. var content = ParameterFileManager.Instance.LoadParameter("Parameter\\TempCorrection", fileName, false);
  930. if (string.IsNullOrEmpty(content))
  931. {
  932. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"{fileNameAndPath} heater temperature correct file is empty");
  933. return;
  934. }
  935. var doc = new XmlDocument();
  936. doc.LoadXml(content);
  937. XmlNodeList nodeSteps = doc.SelectNodes($"Aitex/TableParameterData/Module[@Name='']/Step");
  938. if (nodeSteps == null)
  939. nodeSteps = doc.SelectNodes($"Aitex/TableParameterData/Step");
  940. if (nodeSteps == null)
  941. {
  942. EV.PostWarningLog(ModuleName.PM1.ToString(), $"Invalid heater temperature correct file {fileNameAndPath}");
  943. return;
  944. }
  945. Dictionary<int, CorrectTableParameter> dic = new Dictionary<int, CorrectTableParameter>();
  946. for(int i= 0;i < nodeSteps.Count;i++)
  947. {
  948. var step = nodeSteps[i];
  949. XmlElement stepNode = step as XmlElement;
  950. var correctTableParameter = new CorrectTableParameter();
  951. correctTableParameter.CorrectParameterLst = new List<CorrectParameter>();
  952. int tableIndex = i + 1;
  953. foreach (XmlAttribute att in stepNode.Attributes)
  954. {
  955. switch (att.Name.ToLower())
  956. {
  957. case "index":
  958. int.TryParse(att.Value, out int no);
  959. correctTableParameter.No = no;
  960. break;
  961. case "name":
  962. correctTableParameter.Name = att.Value;
  963. break;
  964. case "tableuserangemax":
  965. float.TryParse(att.Value, out float tableuserangemax);
  966. correctTableParameter.TableUseRangeMax = tableuserangemax;
  967. break;
  968. case "tableuserangemin":
  969. float.TryParse(att.Value, out float tableuserangemin);
  970. correctTableParameter.TableUseRangeMin = tableuserangemin;
  971. break;
  972. case "profileconditiontableno":
  973. int.TryParse(att.Value, out int profileconditiontableno);
  974. correctTableParameter.ProfileConditionTableNo = profileconditiontableno;
  975. break;
  976. case "pemppidtableno":
  977. int.TryParse(att.Value, out int pemppidtableno);
  978. correctTableParameter.TempPIDTableNo = pemppidtableno;
  979. break;
  980. case "profiletccalibtemp":
  981. float.TryParse(att.Value, out float profiletccalibtemp);
  982. correctTableParameter.ProfileTCCalibTemp = profiletccalibtemp;
  983. break;
  984. }
  985. if (att.Name.ToLower() == "correctiondata" && !dic.ContainsKey(tableIndex))
  986. {
  987. //"Index:1;Name:U;ProfileTemp:0;ProfileCorrect:0;CascadeTCCorrect:0;ProfileTCCalib:0|Index:2;Name:CU;ProfileTemp:0;ProfileCorrect:0;CascadeTCCorrect:0;ProfileTCCalib:0|Index:3;Name:C;ProfileTemp:0;ProfileCorrect:0;CascadeTCCorrect:0;ProfileTCCalib:0|Index:4;Name:CL;ProfileTemp:0;ProfileCorrect:0;CascadeTCCorrect:0;ProfileTCCalib:0|Index:5;Name:L;ProfileTemp:0;ProfileCorrect:0;CascadeTCCorrect:0;ProfileTCCalib:0"
  988. var correctionDataString = att.Value;
  989. if (string.IsNullOrEmpty(correctionDataString))
  990. {
  991. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature correct file is empty");
  992. return;
  993. }
  994. var correctionDatas = correctionDataString.Split('|');
  995. if (correctionDatas.Length < 5)
  996. {
  997. EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature correct file data length is invalid");
  998. return;
  999. }
  1000. foreach (var datas in correctionDatas)
  1001. {
  1002. var dataArry = datas.Split(';');
  1003. if (dataArry.Length < 6)
  1004. {
  1005. EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature correct file data length is invalid");
  1006. return;
  1007. }
  1008. var correctParameter = new CorrectParameter();
  1009. foreach (var item in dataArry)
  1010. {
  1011. var itemArry = item.Split(':');
  1012. if (itemArry.Length < 2)
  1013. {
  1014. EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature correct file data length is invalid");
  1015. return;
  1016. }
  1017. switch (itemArry[0].ToLower())
  1018. {
  1019. case "index":
  1020. int.TryParse(itemArry[1], out int no);
  1021. correctParameter.No = no;
  1022. break;
  1023. case "name":
  1024. correctParameter.Name = itemArry[1];
  1025. break;
  1026. case "profiletemp":
  1027. float.TryParse(itemArry[1], out float profiletemp);
  1028. correctParameter.ProfileTemp = profiletemp;
  1029. break;
  1030. case "profilecorrect":
  1031. float.TryParse(itemArry[1], out float profilecorrect);
  1032. correctParameter.ProfileCorrect = profilecorrect;
  1033. break;
  1034. case "cascadetccorrect":
  1035. float.TryParse(itemArry[1], out float cascadetccorrect);
  1036. correctParameter.CascadeTCCorrect = cascadetccorrect;
  1037. break;
  1038. case "profiletccalib":
  1039. float.TryParse(itemArry[1], out float profiletccalib);
  1040. correctParameter.ProfileTCCalib = profiletccalib;
  1041. break;
  1042. }
  1043. }
  1044. correctTableParameter.CorrectParameterLst.Add(correctParameter);
  1045. }
  1046. break;
  1047. }
  1048. }
  1049. dic.Add(tableIndex, correctTableParameter);
  1050. }
  1051. if(dic.ContainsKey(index))
  1052. {
  1053. var item = dic[index];
  1054. var heaterIndex = GetHeaterIndex() - 1;//U的index是0
  1055. if(item.CorrectParameterLst.Count > heaterIndex)
  1056. {
  1057. _profileTemp = item.CorrectParameterLst[heaterIndex].ProfileTemp;
  1058. _profileCorrect = item.CorrectParameterLst[heaterIndex].ProfileCorrect;
  1059. _profileTCCalib = item.CorrectParameterLst[heaterIndex].ProfileTCCalib;
  1060. _cascadeTCCorrect = item.CorrectParameterLst[heaterIndex].CascadeTCCorrect;
  1061. }
  1062. else
  1063. {
  1064. _profileTemp = 0;
  1065. _profileCorrect = 0;
  1066. _profileTCCalib = 0;
  1067. _cascadeTCCorrect = 0;
  1068. }
  1069. GetProfileParameters(SC.GetStringValue("PM1.TempProfile"), item.ProfileConditionTableNo, recipeProfileFileName);
  1070. }
  1071. else
  1072. {
  1073. _profileTemp = 0;
  1074. _profileCorrect = 0;
  1075. _profileTCCalib = 0;
  1076. _cascadeTCCorrect = 0;
  1077. //auto select
  1078. }
  1079. var temperature = DeviceData.SetPoint;
  1080. var actualSet = temperature + _profileTCCalib;
  1081. if(temperature > 0)
  1082. TempSetPoint = (float)actualSet;
  1083. DeviceData.CorrectTable = name;
  1084. if (SC.GetStringValue("PM1.TempCorrection") != name)
  1085. SC.SetItemValueFromString("PM1.TempCorrection", name);
  1086. }
  1087. public void SetPIDParameters(string name, string recipePIDFileName = null)
  1088. {
  1089. //"Heater;Parameter\\TempCorrection\\tempCorrect,2,Name2;Parameter\\TempPID\\tempPID,3,Name3"
  1090. //var defaultPID = SC.GetStringValue("PM1.APCPID");
  1091. if (string.IsNullOrEmpty(name))
  1092. {
  1093. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature PID file is empty");
  1094. return;
  1095. }
  1096. var array = name.Split(',');
  1097. if (array == null || array.Length < 2)
  1098. {
  1099. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"Invalid heater temperature PID parameter");
  1100. return;
  1101. }
  1102. if (!File.Exists($"{PathManager.GetParameterDir()}\\{array[0]}.rcp"))
  1103. return;
  1104. var fileNameAndPath = array[0];
  1105. int.TryParse(array[1], out int index);
  1106. var para = fileNameAndPath.Split('\\').ToList().Skip(2);//"Parameter\\TempPID"
  1107. if (para == null)
  1108. {
  1109. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature PID file is empty");
  1110. return;
  1111. }
  1112. var fileName = string.Join("\\", para.ToArray());
  1113. if (!string.IsNullOrEmpty(recipePIDFileName))
  1114. fileName = recipePIDFileName;
  1115. var content = ParameterFileManager.Instance.LoadParameter("Parameter\\TempPID", fileName, false);
  1116. if (string.IsNullOrEmpty(content))
  1117. {
  1118. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"{fileNameAndPath} heater temperature PID file is empty");
  1119. return;
  1120. }
  1121. var doc = new XmlDocument();
  1122. doc.LoadXml(content);
  1123. XmlNodeList nodeSteps = doc.SelectNodes($"Aitex/TableParameterData/Module[@Name='']/Step");
  1124. if (nodeSteps == null)
  1125. nodeSteps = doc.SelectNodes($"Aitex/TableParameterData/Step");
  1126. if (nodeSteps == null)
  1127. {
  1128. EV.PostWarningLog(ModuleName.PM1.ToString(), $"Invalid heater temperature PID file {fileNameAndPath}");
  1129. return;
  1130. }
  1131. Dictionary<int, List<PIDParameter>> dic = new Dictionary<int, List<PIDParameter>>();
  1132. for (int i = 0; i < nodeSteps.Count; i++)
  1133. {
  1134. var step = nodeSteps[i];
  1135. XmlElement stepNode = step as XmlElement;
  1136. var pidParameters = new List<PIDParameter>();
  1137. int tableIndex = i + 1;
  1138. foreach (XmlAttribute att in stepNode.Attributes)
  1139. {
  1140. if (att.Name == "PIDData" && !dic.ContainsKey(tableIndex))
  1141. {
  1142. //"Index:1;Name:HT.U;P:11.5;I:12;D:13|Index:2;Name:HTCU;P:21.5;I:22;D:23|Index:3;Name:HT.C;P:31.5;I:32;D:33|Index:4;Name:HTCL;P:41.5;I:42;D:43|Index:5;Name:HT.L;P:51.5;I:52;D:53|Index:6;Name:PR.U;P:0.5;I:10;D:0|Index:7;Name:PRCU;P:0.5;I:10;D:0|Index:8;Name:PR.C;P:0.5;I:10;D:0|Index:9;Name:PRCL;P:0.5;I:10;D:0|Index:10;Name:PR.L;P:0.5;I:10;D:0"
  1143. var PIDDataString = att.Value;
  1144. if (string.IsNullOrEmpty(PIDDataString))
  1145. {
  1146. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature PID file is empty");
  1147. return;
  1148. }
  1149. var pidDatas = PIDDataString.Split('|');
  1150. if (pidDatas.Length < 5)
  1151. {
  1152. EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature PID file data length is invalid");
  1153. return;
  1154. }
  1155. foreach (var datas in pidDatas)
  1156. {
  1157. var dataArry = datas.Split(';');
  1158. if (dataArry.Length < 5)
  1159. {
  1160. EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature PID file data length is invalid");
  1161. return;
  1162. }
  1163. var pidParameter = new PIDParameter();
  1164. foreach (var item in dataArry)
  1165. {
  1166. var itemArry = item.Split(':');
  1167. if (itemArry.Length < 2)
  1168. {
  1169. EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature PID file data length is invalid");
  1170. return;
  1171. }
  1172. switch (itemArry[0].ToLower())
  1173. {
  1174. case "index":
  1175. int.TryParse(itemArry[1], out int no);
  1176. pidParameter.No = no;
  1177. break;
  1178. case "name":
  1179. pidParameter.Name = itemArry[1];
  1180. break;
  1181. case "p":
  1182. float.TryParse(itemArry[1], out float p);
  1183. pidParameter.P = p;
  1184. break;
  1185. case "i":
  1186. float.TryParse(itemArry[1], out float ii);
  1187. pidParameter.I = ii;
  1188. break;
  1189. case "d":
  1190. float.TryParse(itemArry[1], out float d);
  1191. pidParameter.D = d;
  1192. break;
  1193. }
  1194. }
  1195. pidParameters.Add(pidParameter);
  1196. }
  1197. break;
  1198. }
  1199. }
  1200. dic.Add(tableIndex, pidParameters);
  1201. }
  1202. if (dic.ContainsKey(index))
  1203. {
  1204. var item = dic[index];
  1205. if (_pidTableAssociate.ContainsKey(Name) && item.Count > _pidTableAssociate[Name] + 5)
  1206. {
  1207. HeaterPID_P = item[_pidTableAssociate[Name]].P;
  1208. HeaterPID_I = item[_pidTableAssociate[Name]].I;
  1209. HeaterPID_D = item[_pidTableAssociate[Name]].D;
  1210. CascadePID_P = item[_pidTableAssociate[Name] + 5].P;
  1211. CascadePID_I = item[_pidTableAssociate[Name] + 5].I;
  1212. CascadePID_D = item[_pidTableAssociate[Name] + 5].D;
  1213. }
  1214. }
  1215. else
  1216. {
  1217. //auto select
  1218. }
  1219. if (SC.GetStringValue("PM1.Heater.PID") != name)
  1220. SC.SetItemValueFromString("PM1.Heater.PID", name);
  1221. DeviceData.PIDTable = name;
  1222. }
  1223. public void GetProfileParameters(string fileNameAndPath, int index, string recipeProfileFileName = null)
  1224. {
  1225. //"Heater;Parameter\\TempCorrection\\tempCorrect,2,Name2;Parameter\\TempPID\\tempPID,3,Name3"
  1226. if (string.IsNullOrEmpty(fileNameAndPath))
  1227. {
  1228. DeviceData.ProfileTable = "";
  1229. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature Profile file is empty");
  1230. return;
  1231. }
  1232. if (!File.Exists($"{PathManager.GetParameterDir()}\\{fileNameAndPath}.rcp"))
  1233. return;
  1234. var para = fileNameAndPath.Split('\\').ToList().Skip(2);//"Parameter\\TempPID"
  1235. if (para == null)
  1236. {
  1237. DeviceData.ProfileTable = "";
  1238. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"Heater temperature Profile file is empty");
  1239. return;
  1240. }
  1241. var fileName = string.Join("\\", para.ToArray());
  1242. if (!string.IsNullOrEmpty(recipeProfileFileName))
  1243. fileName = recipeProfileFileName;
  1244. var content = ParameterFileManager.Instance.LoadParameter("Parameter\\TempProfile", fileName, false);
  1245. if (string.IsNullOrEmpty(content))
  1246. {
  1247. DeviceData.ProfileTable = "";
  1248. //EV.PostWarningLog(ModuleName.PM1.ToString(), $"{fileNameAndPath} heater temperature Profile file is empty");
  1249. return;
  1250. }
  1251. var doc = new XmlDocument();
  1252. doc.LoadXml(content);
  1253. XmlNodeList nodeSteps = doc.SelectNodes($"Aitex/TableParameterData/Module[@Name='']/Step");
  1254. if (nodeSteps == null)
  1255. nodeSteps = doc.SelectNodes($"Aitex/TableParameterData/Step");
  1256. if (nodeSteps == null)
  1257. {
  1258. DeviceData.ProfileTable = "";
  1259. EV.PostWarningLog(ModuleName.PM1.ToString(), $"Invalid heater temperature Profile file {fileNameAndPath}");
  1260. return;
  1261. }
  1262. Dictionary<int, ProfileParameter> dic = new Dictionary<int, ProfileParameter>();
  1263. for (int i = 0; i < nodeSteps.Count; i++)
  1264. {
  1265. var step = nodeSteps[i];
  1266. XmlElement stepNode = step as XmlElement;
  1267. var profileParameter = new ProfileParameter();
  1268. int tableIndex = i + 1;
  1269. foreach (XmlAttribute att in stepNode.Attributes)
  1270. {
  1271. switch (att.Name.ToLower())
  1272. {
  1273. case "index":
  1274. int.TryParse(att.Value, out int no);
  1275. profileParameter.No = no;
  1276. break;
  1277. case "name":
  1278. profileParameter.Name = att.Value;
  1279. break;
  1280. case "preheattime":
  1281. float.TryParse(att.Value, out float preheattime);
  1282. profileParameter.PreheatTime = preheattime;
  1283. break;
  1284. case "checktime":
  1285. float.TryParse(att.Value, out float checktime);
  1286. profileParameter.CheckTime = checktime;
  1287. break;
  1288. case "totaltime":
  1289. int.TryParse(att.Value, out int totaltime);
  1290. profileParameter.TotalTime = totaltime;
  1291. break;
  1292. case "alarmlimit":
  1293. int.TryParse(att.Value, out int alarmlimit);
  1294. profileParameter.AlarmLimit = alarmlimit;
  1295. break;
  1296. case "u":
  1297. float.TryParse(att.Value, out float u);
  1298. profileParameter.U = u;
  1299. break;
  1300. case "cu":
  1301. float.TryParse(att.Value, out float cu);
  1302. profileParameter.CU = cu;
  1303. break;
  1304. case "c":
  1305. float.TryParse(att.Value, out float c);
  1306. profileParameter.C = c;
  1307. break;
  1308. case "cl":
  1309. float.TryParse(att.Value, out float cl);
  1310. profileParameter.CL = cl;
  1311. break;
  1312. case "l":
  1313. float.TryParse(att.Value, out float l);
  1314. profileParameter.L = l;
  1315. break;
  1316. }
  1317. }
  1318. dic.Add(tableIndex, profileParameter);
  1319. }
  1320. if (dic.ContainsKey(index))
  1321. {
  1322. var item = dic[index];
  1323. _preheatTime = item.PreheatTime;
  1324. _checkTime = item.CheckTime;
  1325. _alarmLimit = item.AlarmLimit;
  1326. _totalTime = item.TotalTime;
  1327. DeviceData.ProfileTable = $"{fileNameAndPath},{index},{item.Name}";
  1328. switch (Name)
  1329. {
  1330. case "HeaterU":
  1331. _checkLimit = item.U;
  1332. break;
  1333. case "HeaterCU":
  1334. _checkLimit = item.CU;
  1335. break;
  1336. case "HeaterC":
  1337. _checkLimit = item.C;
  1338. break;
  1339. case "HeaterCL":
  1340. _checkLimit = item.CL;
  1341. break;
  1342. case "HeaterL":
  1343. _checkLimit = item.L;
  1344. break;
  1345. }
  1346. DeviceData.ProfileTotalTime = _totalTime;
  1347. DeviceData.ProfilePreheatTime = _preheatTime;
  1348. DeviceData.ProfileCheckTime = _checkTime;
  1349. DeviceData.ProfileAlarmLimit = _alarmLimit;
  1350. DeviceData.ProfileCheckLimit = _checkLimit;
  1351. DeviceData.ProfileStatus = "PreHeat";
  1352. }
  1353. else
  1354. {
  1355. _checkLimit = 0;
  1356. _preheatTime = 0;
  1357. _checkTime = 0;
  1358. _alarmLimit = 0;
  1359. _totalTime = 0;
  1360. DeviceData.ProfileTable = "";
  1361. DeviceData.ProfileTotalTime = 0;
  1362. DeviceData.ProfilePreheatTime = 0;
  1363. DeviceData.ProfileCheckTime = 0;
  1364. DeviceData.ProfileAlarmLimit = 0;
  1365. DeviceData.ProfileCheckLimit = 0;
  1366. }
  1367. }
  1368. public bool CheckProfileFinish(out string reason)
  1369. {
  1370. reason = "";
  1371. if (_profileTimer != null && _profileTimer.IsRunning)
  1372. {
  1373. _profileTotalTimeoutTrig.CLK = _profileTimer.ElapsedMilliseconds >= _totalTime * 1000;
  1374. if (_profileTotalTimeoutTrig.Q)
  1375. {
  1376. LOG.Write($"{Name} profile timeout={_totalTime}");
  1377. EV.PostWarningLog(ModuleName.PM1.ToString(), $"{Name} profile timeout={_totalTime}");//超过total time之后,只是报warning提示
  1378. }
  1379. if (_profileTimer.ElapsedMilliseconds >= _preheatTime * 1000)//preheat time之后,才开始判断
  1380. {
  1381. DeviceData.ProfileStatus = "Profile Check";
  1382. if (_profileStableTimer != null)
  1383. {
  1384. if (!_profileStableTimer.IsRunning)
  1385. _profileStableTimer.Restart();
  1386. if(Math.Abs(TempFeedback - TempSetPoint) > _checkLimit)
  1387. _profileStableTimer.Restart();
  1388. _profileAlarmLimitTrig.CLK = Math.Abs(TempFeedback - TempSetPoint) > _alarmLimit && _alarmLimit > 0;
  1389. if (_profileAlarmLimitTrig.Q)
  1390. EV.PostWarningLog(ModuleName.PM1.ToString(), $"{Name} profile success setpoint={TempSetPoint} feedback={TempFeedback}, difference={Math.Abs(TempFeedback - TempSetPoint)} is more than alarm limit={_alarmLimit}");
  1391. _profileSuccessTrig.CLK = _profileStableTimer.ElapsedMilliseconds > _checkTime * 1000;
  1392. if (_profileStableTimer.ElapsedMilliseconds > _checkTime * 1000)
  1393. {
  1394. IsProfileSuccess = true;
  1395. if(_profileSuccessTrig.Q)
  1396. LOG.Write($"{Name} profile success setpoint={TempSetPoint} feedback={TempFeedback}");
  1397. return true;
  1398. }
  1399. }
  1400. }
  1401. reason = $"{Name} profile not finish";
  1402. return false;
  1403. }
  1404. else
  1405. {
  1406. return true;
  1407. }
  1408. }
  1409. private int GetHeaterIndex()
  1410. {
  1411. int.TryParse(Name.Replace("Heater", ""), out int heaterIndex);//改了heater数量这边需要改
  1412. switch (Name)
  1413. {
  1414. case "HeaterU":
  1415. heaterIndex = 1;
  1416. break;
  1417. case "HeaterCU":
  1418. heaterIndex = 2;
  1419. break;
  1420. case "HeaterC":
  1421. heaterIndex = 3;
  1422. break;
  1423. case "HeaterCL":
  1424. heaterIndex = 4;
  1425. break;
  1426. case "HeaterL":
  1427. heaterIndex = 5;
  1428. break;
  1429. }
  1430. return heaterIndex;
  1431. }
  1432. public bool CheckWaitCondition(out string reason)
  1433. {
  1434. reason = "";
  1435. if (!_isWait || _waitHigh == 0 || _waitLow == 0)
  1436. return true;
  1437. if (_stableJudgmentTimer.IsRunning)
  1438. {
  1439. if (TempFeedback < _tempSetpoint - _waitLow ||
  1440. TempFeedback > _tempSetpoint + _waitHigh)
  1441. {
  1442. _stableJudgmentTimer.Restart();
  1443. }
  1444. if (_stableJudgmentTimer.ElapsedMilliseconds >= _stableJudgmentTime * 1000)
  1445. {
  1446. return true;
  1447. }
  1448. }
  1449. else
  1450. {
  1451. _stableJudgmentTimer.Restart();
  1452. }
  1453. reason = $"{Name} feedback={TempFeedback}, wait limit is ({_tempSetpoint - _waitLow}, {_tempSetpoint + _waitHigh})";
  1454. return false;
  1455. }
  1456. struct PIDParameter
  1457. {
  1458. public int No { get; set; }
  1459. public string Name { get; set; }
  1460. public float P { get; set; }
  1461. public float I { get; set; }
  1462. public float D { get; set; }
  1463. }
  1464. struct CorrectTableParameter
  1465. {
  1466. public int No { get; set; }
  1467. public string Name { get; set; }
  1468. public float ProfileTCCalibTemp { get; set; }
  1469. public float TableUseRangeMin { get; set; }
  1470. public float TableUseRangeMax { get; set; }
  1471. public int ProfileConditionTableNo { get; set; }
  1472. public int TempPIDTableNo { get; set; }
  1473. public List<CorrectParameter> CorrectParameterLst{ get; set; }
  1474. }
  1475. struct ProfileParameter
  1476. {
  1477. public int No { get; set; }
  1478. public string Name { get; set; }
  1479. public float PreheatTime { get; set; }
  1480. public float CheckTime { get; set; }
  1481. public float TotalTime { get; set; }
  1482. public float AlarmLimit { get; set; }
  1483. public float U { get; set; }
  1484. public float CU { get; set; }
  1485. public float C { get; set; }
  1486. public float CL { get; set; }
  1487. public float L { get; set; }
  1488. }
  1489. }
  1490. public struct CorrectParameter
  1491. {
  1492. public int No { get; set; }
  1493. public string Name { get; set; }
  1494. public float ProfileTemp { get; set; }
  1495. public float ProfileTCCalib { get; set; }
  1496. public float ProfileCorrect { get; set; }
  1497. public float CascadeTCCorrect { get; set; }
  1498. }
  1499. }