TemperatureController.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. using Aitex.Core.RT.DataCenter;
  2. using Aitex.Core.RT.Device;
  3. using Aitex.Core.RT.Fsm;
  4. using Aitex.Core.RT.Log;
  5. using Aitex.Core.RT.OperationCenter;
  6. using Aitex.Core.RT.SCCore;
  7. using Aitex.Core.Util;
  8. using PunkHPX8_RT.Devices.Facilities;
  9. using PunkHPX8_RT.Devices.Reservoir;
  10. using PunkHPX8_RT.Modules;
  11. using PunkHPX8_RT.Modules.Reservoir;
  12. using MECF.Framework.Common.CommonData.TemperatureControl;
  13. using MECF.Framework.Common.Device.TemperatureController;
  14. using MECF.Framework.Common.Persistent.Temperature;
  15. using MECF.Framework.Common.ToolLayout;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Linq;
  19. using System.Reflection;
  20. using System.Text;
  21. using System.Threading.Tasks;
  22. namespace PunkHPX8_RT.Devices.Temperature
  23. {
  24. public class TemperatureController : BaseDevice, IDevice
  25. {
  26. #region 常量
  27. private const string STRATUS = "Stratus";
  28. private const string TARGET_TEMPERATURE = "TargetTemperature";
  29. private const string RESERVIOR_TEMPERATURE = "ReserviorTemperature";
  30. private const string HEAT_EXCHANGER_TEMPERATURE = "HeatExchangerTemperature";
  31. private const string ALARM = "Alarm";
  32. private const string OFFSET = "Offset";
  33. private const string CONTROL_OPERATION_MODEL="ControlOperationModel";
  34. private const string PB_RANGE = "PBRange";
  35. private const string ARW_RANGE = "ARWRange";
  36. private const string I_CONSTANT = "IConstant";
  37. private const string D_CONSTANT = "DConstant";
  38. private const string HEATING_POWER_UPPER_LIMIT = "HeatingPowerUpperLimit";
  39. private const string COOLING_POWER_UPPER_LIMIT="CoolingPowerUpperLimit";
  40. private const string OUT_PUT_RATIO="OutputRatio";
  41. private const string TEMPERATURE_DATA = "TemperatureData";
  42. private const string IS_CONNECTED = "IsConnected";
  43. private const int ENABLE = 5;
  44. private const int DISABLE = 0;
  45. private const string PERSISTENT_VALUE = "PersistentValue";
  46. #endregion
  47. #region 内部变量
  48. private byte _address;
  49. private TemperatureControllerData _temperatureData = new TemperatureControllerData();
  50. private PeriodicJob _periodicJob = null;
  51. private bool _startMonitorData = false;
  52. private bool _readAlarm = false;
  53. private double _temeratureDeltaLimit = SC.GetValue<double>("System.TemeratureDelatLimit");
  54. private bool _isApplying = false; //用于判断是否在apply中
  55. private bool _isAlarmErrorLoged = false; //用于判断是否打印过alarm触发
  56. private bool _isAlarmWarningLoged = false; //用于判断是否打印过alarm warning触发
  57. private bool _isTCConnect = false;
  58. /// <summary>
  59. /// TC 持久性数值对象
  60. /// </summary>
  61. private TCPersistentValue _tCPersistentValue;
  62. private Dictionary<int,string> _errorMessage = new Dictionary<int, string>
  63. { { 3, " HighTempCutoff Property" },
  64. { 2, " LowTempCutoff Property" },
  65. { 1, " Fan Property" },
  66. { 0, " OutputFailure Property" },
  67. { 7, " TempLimitWarn Property" },
  68. { 6, " RemoteOff Property" },
  69. { 5, " Thermostat Property" },
  70. { 4, " PowerFailure Property" },
  71. { 11, " ExtSensorFailure Property" },
  72. { 10, " IntSensorFailure Property" },
  73. { 9, " AutoTuning Property" },
  74. { 8, " Leak Property" }};
  75. #endregion
  76. #region 属性
  77. /// <summary>
  78. /// 连接状态
  79. /// </summary>
  80. public bool IsConnected { get { return TemperatureConfigManager.Instance.GetDeviceConnect(Module); } }
  81. /// <summary>
  82. /// 数据
  83. /// </summary>
  84. public TemperatureControllerData TemperatureData
  85. {
  86. get { return _temperatureData; }
  87. }
  88. #endregion
  89. /// <summary>
  90. /// 构造函数
  91. /// </summary>;
  92. /// <param name="moduleName"></param>
  93. public TemperatureController(string moduleName) : base(moduleName, moduleName, moduleName, moduleName)
  94. {
  95. SubscribeValueAction();
  96. InitializeData();
  97. InitializeOperation();
  98. _periodicJob = new PeriodicJob(5000, OnTimer, $"{moduleName}_reader");
  99. _temperatureData.Name = $"{moduleName}";
  100. _isAlarmErrorLoged = false;
  101. _isAlarmWarningLoged = false;
  102. }
  103. /// <summary>
  104. /// 初始化
  105. /// </summary>
  106. /// <returns></returns>
  107. public bool Initialize()
  108. {
  109. TemperatureConfigManager.Instance.InitialDevice(Module);
  110. _periodicJob.Start();
  111. return true;
  112. }
  113. /// <summary>
  114. /// 初始化操作
  115. /// </summary>
  116. private void InitializeOperation()
  117. {
  118. OP.Subscribe($"{Module}.Apply", SetTargetTemperatureOperation);
  119. OP.Subscribe($"{Module}.Enable", EnableOperation);
  120. OP.Subscribe($"{Module}.Disable", DisableOperation);
  121. }
  122. /// <summary>
  123. /// 监控TC电源
  124. /// </summary>
  125. /// <returns></returns>
  126. private bool CheckTCIsConnect()
  127. {
  128. return TemperatureConfigManager.Instance.GetDevicePowerConnect(Module);
  129. }
  130. /// <summary>
  131. /// 应用
  132. /// </summary>
  133. /// <param name="cmd"></param>
  134. /// <param name="param"></param>
  135. /// <returns></returns>
  136. public bool SetTargetTemperatureOperation(string cmd,object[] param)
  137. {
  138. _isApplying = true; //表示正在调温
  139. _temperatureData.HeatExchangerSeries = new List<double>();
  140. _temperatureData.ReserviorSeries = new List<double>();
  141. if (param.Length == 3 && double.TryParse(param[0].ToString(), out double targetTemperature) && double.TryParse(param[1].ToString(), out double targetTemperatureLowLimit) && double.TryParse(param[2].ToString(), out double targetTemperatureHighLimit))
  142. {
  143. TemperatureConfigManager.Instance.SetTargetTemperature(Module, _address, targetTemperature);
  144. if (TemperatureData.ControlOperationModel == ENABLE)
  145. {
  146. _startMonitorData = true;
  147. }
  148. //将前端输入的数据存入持久化文件
  149. TCPersistentManager.Instance.UpdateTemperatureValue(Module, targetTemperature, targetTemperatureLowLimit, targetTemperatureHighLimit);
  150. return true;
  151. }
  152. if (param.Length == 1 && double.TryParse(param[0].ToString(), out double targetTemperature1))
  153. {
  154. TemperatureConfigManager.Instance.SetTargetTemperature(Module, _address, targetTemperature1);
  155. if (TemperatureData.ControlOperationModel == ENABLE)
  156. {
  157. _startMonitorData = true;
  158. }
  159. //将前端输入的数据存入持久化文件
  160. TCPersistentManager.Instance.UpdateTemperatureValue(Module, targetTemperature1, 0, 0); // 0 0是上下限
  161. return true;
  162. }
  163. else
  164. {
  165. LOG.WriteLog(eEvent.INFO_TEMPERATURE, Module, $"{param[0]} is invalid");
  166. return false;
  167. }
  168. }
  169. /// <summary>
  170. /// 启用
  171. /// </summary>
  172. /// <param name="cmd"></param>
  173. /// <param name="param"></param>
  174. /// <returns></returns>
  175. public bool EnableOperation(string cmd, object[] param)
  176. {
  177. if (!JudgeReservoirCondition())
  178. {
  179. return false;
  180. }
  181. if (!CheckTCIsConnect())
  182. {
  183. LOG.WriteLog(eEvent.ERR_TEMPERATURE, Module, $"TC is not connect");
  184. return false ;
  185. }
  186. //校验TC状态
  187. if (TemperatureData.Alarm != null && TemperatureData.Alarm.Contains("1"))
  188. {
  189. string errorItemString = TemperatureData.Alarm.Substring(0, 11) + TemperatureData.Alarm.Substring(13, 1) + TemperatureData.Alarm.Substring(15);
  190. if (errorItemString.Contains("1"))
  191. {
  192. LOG.WriteLog(eEvent.ERR_TEMPERATURE, Module, $"TC is in error state");
  193. return false;
  194. }
  195. }
  196. TemperatureData.ControlOperationModel = ENABLE;
  197. bool result= TemperatureConfigManager.Instance.EnableControl(Module, _address,ENABLE);
  198. if (result)
  199. {
  200. LOG.WriteLog(eEvent.INFO_TEMPERATURE, Module, "control operation set enable");
  201. _startMonitorData = true;
  202. }
  203. return result;
  204. }
  205. /// <summary>
  206. /// 禁用
  207. /// </summary>
  208. /// <param name="cmd"></param>
  209. /// <param name="param"></param>
  210. /// <returns></returns>
  211. public bool DisableOperation(string cmd, object[] param)
  212. {
  213. _isApplying = false;
  214. TemperatureData.ControlOperationModel = DISABLE;
  215. bool result= TemperatureConfigManager.Instance.DisableController(Module, _address,DISABLE);
  216. if(result)
  217. {
  218. if (result)
  219. {
  220. ReservoirPostError();
  221. LOG.WriteLog(eEvent.INFO_TEMPERATURE, Module, "control operation set disable");
  222. }
  223. _startMonitorData = false;
  224. if(TemperatureData.ReserviorSeries != null && TemperatureData.HeatExchangerSeries != null)
  225. {
  226. _temperatureData.HeatExchangerSeries.Clear();
  227. _temperatureData.ReserviorSeries.Clear();
  228. }
  229. }
  230. return result;
  231. }
  232. /// <summary>
  233. /// Reservoir通知进入错误状态
  234. /// </summary>
  235. private void ReservoirPostError()
  236. {
  237. }
  238. /// <summary>
  239. /// 访问数据变更
  240. /// </summary>
  241. private void SubscribeValueAction()
  242. {
  243. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, TARGET_TEMPERATURE, UpdateVariableValue);
  244. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, RESERVIOR_TEMPERATURE, UpdateVariableValue);
  245. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, HEAT_EXCHANGER_TEMPERATURE, UpdateVariableValue);
  246. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, ALARM, UpdateVariableValue);
  247. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, OFFSET, UpdateVariableValue);
  248. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, CONTROL_OPERATION_MODEL, UpdateVariableValue);
  249. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, PB_RANGE, UpdateVariableValue);
  250. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, ARW_RANGE, UpdateVariableValue);
  251. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, I_CONSTANT, UpdateVariableValue);
  252. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, D_CONSTANT, UpdateVariableValue);
  253. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, OUT_PUT_RATIO, UpdateVariableValue);
  254. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, HEATING_POWER_UPPER_LIMIT, UpdateVariableValue);
  255. TemperatureConfigManager.Instance.SubscribeModuleVariable(Module, COOLING_POWER_UPPER_LIMIT, UpdateVariableValue);
  256. }
  257. /// <summary>
  258. /// 初始化数据
  259. /// </summary>
  260. private void InitializeData()
  261. {
  262. _address= TemperatureConfigManager.Instance.GetAddress(Module);
  263. _tCPersistentValue = TCPersistentManager.Instance.GetTCPersistentValue(Module);
  264. DATA.Subscribe($"{Module}.{TEMPERATURE_DATA}", () => _temperatureData, SubscriptionAttribute.FLAG.IgnoreSaveDB);
  265. DATA.Subscribe($"{Module}.{IS_CONNECTED}", () => _isTCConnect, SubscriptionAttribute.FLAG.IgnoreSaveDB);
  266. DATA.Subscribe($"{Module}.{PERSISTENT_VALUE}", () => _tCPersistentValue, SubscriptionAttribute.FLAG.IgnoreSaveDB);
  267. DATA.Subscribe($"{Module}.TargetTemperature", () => _temperatureData.TargetTemperature, SubscriptionAttribute.FLAG.IgnoreSaveDB);
  268. DATA.Subscribe($"{Module}.Status", () => _temperatureData.Status, SubscriptionAttribute.FLAG.IgnoreSaveDB);
  269. DATA.Subscribe($"{Module}.Alarm", () => _temperatureData.Alarm, SubscriptionAttribute.FLAG.IgnoreSaveDB);
  270. DATA.Subscribe($"{Module}.ReserviorTemperature", () => _temperatureData.ReserviorTemperature, SubscriptionAttribute.FLAG.IgnoreSaveDB);
  271. DATA.Subscribe($"{Module}.HeatExchangerTemperature", () => _temperatureData.HeatExchangerTemperature, SubscriptionAttribute.FLAG.IgnoreSaveDB);
  272. }
  273. /// <summary>
  274. /// 更新变量数值
  275. /// </summary>
  276. /// <param name="variable"></param>
  277. /// <param name="value"></param>
  278. private void UpdateVariableValue(string variable, object value)
  279. {
  280. if(!TemperatureData.IsInitialized)
  281. {
  282. TemperatureData.IsInitialized = true;
  283. TemperatureConfigManager.Instance.ReadControlOperationModel(Module, _address);
  284. TemperatureConfigManager.Instance.ReadTargetTemperature(Module, _address);
  285. }
  286. PropertyInfo property = TemperatureData.GetType().GetProperty(variable);
  287. if (property != null)
  288. {
  289. property.SetValue(TemperatureData, value);
  290. }
  291. //判断temperatureReached AtTemperatureRange
  292. double atTemperatureRange = SC.GetValue<double>("System.AtTemperatureRange");
  293. atTemperatureRange = atTemperatureRange == 0 ? 0.1 : atTemperatureRange;
  294. if (Math.Abs(TemperatureData.ReserviorTemperature - TemperatureData.TargetTemperature) < atTemperatureRange)
  295. {
  296. TemperatureData.TemperatureReached = true;
  297. //_startMonitorData = false;
  298. }
  299. else
  300. {
  301. TemperatureData.TemperatureReached = false;
  302. }
  303. //判断deltaexceed
  304. if (Math.Abs(TemperatureData.ReserviorTemperature - TemperatureData.ReserviorTemperature) > _temeratureDeltaLimit)
  305. {
  306. TemperatureData.DeltaExceed = true;
  307. }
  308. else
  309. {
  310. TemperatureData.DeltaExceed = false;
  311. }
  312. if (_startMonitorData && TemperatureData.ReserviorSeries!=null && TemperatureData.HeatExchangerSeries!=null)
  313. {
  314. if (TemperatureData.ReserviorSeries.Count == 20)
  315. {
  316. TemperatureData.ReserviorSeries.RemoveAt(0);
  317. for (int i = 1; i < TemperatureData.ReserviorSeries.Count; i++)
  318. {
  319. TemperatureData.ReserviorSeries[i - 1] = TemperatureData.ReserviorSeries[i];
  320. }
  321. TemperatureData.ReserviorSeries.Add(TemperatureData.ReserviorTemperature);
  322. }
  323. else
  324. {
  325. TemperatureData.ReserviorSeries.Add(TemperatureData.ReserviorTemperature);
  326. }
  327. if (TemperatureData.HeatExchangerSeries.Count == 20)
  328. {
  329. TemperatureData.HeatExchangerSeries.RemoveAt(0);
  330. for (int i = 1; i < TemperatureData.HeatExchangerSeries.Count; i++)
  331. {
  332. TemperatureData.HeatExchangerSeries[i - 1] = TemperatureData.HeatExchangerSeries[i];
  333. }
  334. TemperatureData.HeatExchangerSeries.Add(TemperatureData.HeatExchangerTemperature);
  335. }
  336. else
  337. {
  338. TemperatureData.HeatExchangerSeries.Add(TemperatureData.HeatExchangerTemperature);
  339. }
  340. }
  341. }
  342. /// <summary>
  343. /// 定时器
  344. /// </summary>
  345. /// <returns></returns>
  346. private bool OnTimer()
  347. {
  348. _isTCConnect = CheckTCIsConnect();
  349. if (!_isTCConnect)
  350. {
  351. }
  352. TemperatureConfigManager.Instance.ReadReserviorExtendSensorTemperature(Module, _address);
  353. TemperatureConfigManager.Instance.ReadHeatExchangerInternelSensorTemperature(Module, _address);
  354. if (_readAlarm)
  355. {
  356. TemperatureConfigManager.Instance.ReadAlarmStatus(Module, _address);
  357. _readAlarm= false;
  358. }
  359. else
  360. {
  361. _readAlarm = true;
  362. }
  363. double rampStepSize = SC.GetValue<double>("System.RampStepSize");
  364. rampStepSize = rampStepSize == 0 ? 0.11 : rampStepSize;
  365. if (TemperatureData.TargetTemperature - TemperatureData.ReserviorTemperature > rampStepSize + 0.1 && _isApplying == true)
  366. {
  367. TemperatureData.Status = "RampingUp";
  368. }
  369. else if(TemperatureData.TargetTemperature - TemperatureData.ReserviorTemperature < -rampStepSize - 0.1 && _isApplying == true)
  370. {
  371. TemperatureData.Status = "RampingDown";
  372. }
  373. else if (TemperatureData.TargetTemperature - TemperatureData.ReserviorTemperature <= rampStepSize + 0.1 && TemperatureData.TargetTemperature - TemperatureData.ReserviorTemperature >= - rampStepSize - 0.1 && _isApplying == true)
  374. {
  375. TemperatureData.Status = "Maintaining";
  376. }
  377. else if(TemperatureData.Alarm!=null&&TemperatureData.Alarm.Contains("1"))
  378. {
  379. string errorItemString = TemperatureData.Alarm.Substring(0, 11) + TemperatureData.Alarm.Substring(13, 1) + TemperatureData.Alarm.Substring(15);
  380. if (TemperatureData.Alarm.Substring(12,1) == "1" || TemperatureData.Alarm.Substring(14, 1)== "1")
  381. {
  382. TemperatureData.Status = "Warning";
  383. if (!_isAlarmWarningLoged)
  384. {
  385. LOG.WriteLog(eEvent.WARN_TEMPERATURE, Module, $"{Module} Warning is activate");
  386. _isAlarmWarningLoged = true;
  387. }
  388. }
  389. if(errorItemString.Contains("1"))
  390. {
  391. if (!_isAlarmErrorLoged)
  392. {
  393. string errormessage = "";
  394. string[] strAry = TemperatureData.Alarm.ToString().Split('-');
  395. if (strAry.Length > 0)
  396. {
  397. for (int i = 0; i < strAry.Length; i++)
  398. {
  399. if (strAry[i] == "1")
  400. {
  401. errormessage += _errorMessage[i];
  402. }
  403. }
  404. }
  405. LOG.WriteLog(eEvent.ERR_TEMPERATURE, Module, $"{Module} {errormessage} is activate");
  406. ReservoirPostError();//将对应的reservoir切成error
  407. _isAlarmErrorLoged = true;
  408. }
  409. }
  410. }
  411. else
  412. {
  413. TemperatureData.Status = "Normal";
  414. _isAlarmErrorLoged = false; //用于控制触发alarm要不要打印error日志
  415. _isAlarmWarningLoged = false; //用于控制触发alarm要不要打印error日志
  416. }
  417. if (TemperatureData.ControlOperationModel != 0)
  418. {
  419. if (!JudgeReservoirCondition())
  420. {
  421. DisableOperation("", null);
  422. }
  423. }
  424. if (TemperatureData.Alarm!=null && TemperatureData.Alarm.Contains("1") && _isApplying == true)
  425. {
  426. string errorItemString = TemperatureData.Alarm.Substring(0, 11) + TemperatureData.Alarm.Substring(13, 1) + TemperatureData.Alarm.Substring(15);
  427. if (errorItemString.Contains("1"))
  428. {
  429. DisableOperation("", null);
  430. string errormessage = "";
  431. string[] strAry = errorItemString.ToString().Split('-');
  432. if (strAry.Length > 0)
  433. {
  434. for (int i = 0; i < strAry.Length; i++)
  435. {
  436. if (strAry[i] == "1")
  437. {
  438. errormessage += _errorMessage[i];
  439. }
  440. }
  441. }
  442. ReservoirPostError();//将对应的reservoir切成error
  443. LOG.WriteLog(eEvent.ERR_TEMPERATURE, Module, $"{Module} {errormessage} is activate");
  444. _isApplying = false;
  445. }
  446. }
  447. return true;
  448. }
  449. /// <summary>
  450. /// 检验Reservoir条件
  451. /// </summary>
  452. private bool JudgeReservoirCondition()
  453. {
  454. SystemFacilities systemFacilities = DEVICE.GetDevice<SystemFacilities>("System.Facilities");
  455. if (systemFacilities == null)
  456. {
  457. return false;
  458. }
  459. //冷却水没开
  460. if (!systemFacilities.HouseChilledWaterEnable)
  461. {
  462. LOG.WriteLog(eEvent.ERR_TEMPERATURE, Module, "Facilities HouseChilledWaterEnable is off");
  463. return false;
  464. }
  465. var houseChilledResult = systemFacilities.CheckHouseChilledWaterResult();
  466. if (!houseChilledResult.result)
  467. {
  468. LOG.WriteLog(eEvent.ERR_TEMPERATURE, Module, houseChilledResult.reason);
  469. return false;
  470. }
  471. string reservoir = ReservoirItemManager.Instance.GetReservoirByTC(Module);
  472. if (string.IsNullOrEmpty(reservoir))
  473. {
  474. LOG.WriteLog(eEvent.ERR_TEMPERATURE, Module, $"{Module} reservoir is empty");
  475. return false;
  476. }
  477. ReservoirItem reservoirItem = ReservoirItemManager.Instance.GetReservoirItem(reservoir);
  478. if (reservoirItem.SubType == STRATUS)
  479. {
  480. }
  481. return true;
  482. }
  483. /// <summary>
  484. /// 设置Enable并设置温度
  485. /// </summary>
  486. /// <param name="targetTemperature"></param>
  487. /// <returns></returns>
  488. public bool SetEnableTargetTemperature(double targetTemperature)
  489. {
  490. TemperatureConfigManager.Instance.SetTargetTemperature(Module, _address, targetTemperature);
  491. if (_temperatureData.ControlOperationModel == DISABLE)
  492. {
  493. EnableOperation("", null);
  494. }
  495. return true;
  496. }
  497. /// <summary>
  498. /// 监控
  499. /// </summary>
  500. public void Monitor()
  501. {
  502. }
  503. public void Reset()
  504. {
  505. }
  506. public void Terminate()
  507. {
  508. }
  509. }
  510. }