IoHeater.cs 71 KB

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