You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

471 lines
15 KiB

8 months ago
  1. using Mirle.Component.MPLC.DataBlocks;
  2. using Mirle.Component.MPLC.DataType;
  3. using Mirle.Component.MPLC.Interfaces;
  4. using System;
  5. using System.Collections.Concurrent;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Text;
  11. using System.Threading.Tasks;
  12. namespace Mirle.Component.MPLC.MCProtocol
  13. {
  14. /// <summary>
  15. ///
  16. /// </summary>
  17. public class PLCHost : IDisposable, IMPLCProvider, IPLCHost
  18. {
  19. /// <summary>
  20. /// 建構式
  21. /// </summary>
  22. /// <param name="plcHostInfo"></param>
  23. public PLCHost(PLCHostInfo plcHostInfo)
  24. {
  25. HostInfo = plcHostInfo;
  26. foreach (var block in HostInfo.BlockInfos)
  27. {
  28. _cachedBlocks.Add(new DataBlock(block.DeviceRange));
  29. }
  30. _mplc = new ReadWriteAdapter(HostInfo.HostID, HostInfo.IPAddress, HostInfo.TcpPort);
  31. _mplc.Connect();
  32. _heartbeat = new ThreadWorker(RunProcess, 200, false);
  33. _writeRawdataWorker = new ThreadWorker(WriteRawdataProcess, 1000, false);
  34. }
  35. /// <summary>
  36. ///
  37. /// </summary>
  38. private class RawdataInfo
  39. {
  40. public DateTime Datatime { get; set; }
  41. public string Rawdata { get; set; }
  42. }
  43. /// <summary>
  44. ///
  45. /// </summary>
  46. public bool IsConnected => _mplc != null && _mplc.IsConnected;
  47. /// <summary>
  48. ///
  49. /// </summary>
  50. public PLCHostInfo HostInfo { get; }
  51. /// <summary>
  52. ///
  53. /// </summary>
  54. public bool EnableWriteShareMemory
  55. {
  56. get
  57. {
  58. return _enableWriteShareMemory;
  59. }
  60. set
  61. {
  62. _enableWriteShareMemory = value;
  63. if (_enableWriteShareMemory)
  64. {
  65. _smBlocks.Clear();
  66. foreach (var block in HostInfo.BlockInfos)
  67. {
  68. _smBlocks.Add(new SMDataBlockInt32(block.DeviceRange, block.SharedMemoryName));
  69. }
  70. }
  71. else
  72. {
  73. _smBlocks.Clear();
  74. }
  75. }
  76. }
  77. /// <summary>
  78. ///
  79. /// </summary>
  80. public bool EnableWriteRawData { get; set; } = false;
  81. /// <summary>
  82. ///
  83. /// </summary>
  84. public bool RawDataUse16BitInteger { get; set; } = false;
  85. /// <summary>
  86. ///
  87. /// </summary>
  88. public bool RawDataUseCommaSeparated { get; set; } = false;
  89. /// <summary>
  90. ///
  91. /// </summary>
  92. public bool EnableAutoReconnect { get; set; } = true;
  93. /// <summary>
  94. ///
  95. /// </summary>
  96. public string LogBaseDirectory { get; set; } = $@"{AppDomain.CurrentDomain.BaseDirectory}LOG";
  97. /// <summary>
  98. ///
  99. /// </summary>
  100. public int Interval
  101. {
  102. get => _heartbeat.Interval;
  103. set
  104. {
  105. if (value < 200)
  106. {
  107. _heartbeat.Interval = 200;
  108. }
  109. else if (value > 10000)
  110. {
  111. _heartbeat.Interval = 10000;
  112. }
  113. else
  114. {
  115. _heartbeat.Interval = value;
  116. }
  117. }
  118. }
  119. /// <summary>
  120. ///
  121. /// </summary>
  122. public int MPLCTimeout
  123. {
  124. get => _mplc?.Timeout ?? 600;
  125. set
  126. {
  127. if (value < 600)
  128. {
  129. _mplc.Timeout = 600;
  130. }
  131. else if (value > 60_000)
  132. {
  133. _mplc.Timeout = 60_000;
  134. }
  135. else
  136. {
  137. _mplc.Timeout = value;
  138. }
  139. }
  140. }
  141. /// <summary>
  142. ///
  143. /// </summary>
  144. private readonly ConcurrentQueue<RawdataInfo> _rawdataQueue = new ConcurrentQueue<RawdataInfo>();
  145. /// <summary>
  146. ///
  147. /// </summary>
  148. private readonly List<DataBlock> _cachedBlocks = new List<DataBlock>();
  149. /// <summary>
  150. ///
  151. /// </summary>
  152. private readonly List<SMDataBlockInt32> _smBlocks = new List<SMDataBlockInt32>();
  153. /// <summary>
  154. /// 讀寫控制器
  155. /// </summary>
  156. private readonly ReadWriteAdapter _mplc;
  157. /// <summary>
  158. /// 心跳包執行續
  159. /// </summary>
  160. private readonly ThreadWorker _heartbeat;
  161. /// <summary>
  162. /// 讀寫執行續
  163. /// </summary>
  164. private readonly ThreadWorker _writeRawdataWorker;
  165. /// <summary>
  166. /// 最後一個控制器字元
  167. /// </summary>
  168. private string _lastMPLCWordRecord = string.Empty;
  169. /// <summary>
  170. /// 啟用寫入共享記憶體
  171. /// </summary>
  172. private bool _enableWriteShareMemory = false;
  173. /// <summary>
  174. /// 開始處理流程
  175. /// </summary>
  176. private void RunProcess()
  177. {
  178. try
  179. {
  180. ReadPLCDataFormPLC();
  181. if (EnableWriteShareMemory)
  182. {
  183. WritePLCDataToSharedMemory();
  184. }
  185. if (EnableWriteRawData)
  186. {
  187. ExportPLCData();
  188. }
  189. if (_mplc.IsConnected == false)
  190. {
  191. _mplc.TestConnectionByPing();
  192. }
  193. }
  194. catch (Exception ex)
  195. {
  196. Debug.WriteLine($"{ex}");
  197. Task.Delay(1000).Wait();
  198. }
  199. }
  200. /// <summary>
  201. /// 讀取控制器資料
  202. /// </summary>
  203. private void ReadPLCDataFormPLC()
  204. {
  205. if (_mplc.IsConnected)
  206. {
  207. foreach (var block in _cachedBlocks)
  208. {
  209. block.SetRawData(_mplc.ReadWords(block.DeviceRange.StartAddress, block.DeviceRange.WordLength).ToBytes());
  210. }
  211. }
  212. else if (EnableAutoReconnect)
  213. {
  214. _mplc.ReConnect();
  215. }
  216. }
  217. /// <summary>
  218. /// 控制器資料寫入共享記憶體
  219. /// </summary>
  220. private void WritePLCDataToSharedMemory()
  221. {
  222. for (int i = 0; i < _cachedBlocks.Count; i++)
  223. {
  224. _smBlocks[i].SetRawData(_cachedBlocks[i].GetRawData());
  225. }
  226. }
  227. /// <summary>
  228. /// 匯出控制器資料
  229. /// </summary>
  230. private void ExportPLCData()
  231. {
  232. StringBuilder sb = new StringBuilder();
  233. if (RawDataUse16BitInteger)
  234. {
  235. foreach (DataBlock block in _cachedBlocks)
  236. {
  237. if (RawDataUseCommaSeparated)
  238. sb.Append(string.Join(",", block.GetRawData().To16BitInteger().Select(x => x.ToString("D5"))));
  239. else
  240. sb.Append(string.Concat(block.GetRawData().To16BitInteger().Select(x => x.ToString("D5"))));
  241. sb.Append('|');
  242. }
  243. }
  244. else
  245. {
  246. foreach (DataBlock block in _cachedBlocks)
  247. {
  248. if (BitConverter.IsLittleEndian)
  249. {
  250. byte[] rawData = block.GetRawData();
  251. byte[] values = new byte[rawData.Length];
  252. for (int i1 = 0, i2 = i1 + 1; i2 < values.Length; i1 += 2, i2 = i1 + 1)
  253. {
  254. byte tmp = rawData[i1];
  255. values[i1] = rawData[i2];
  256. values[i2] = tmp;
  257. }
  258. if (RawDataUseCommaSeparated)
  259. sb.Append(BitConverter.ToString(values).Replace("-", ","));
  260. else
  261. sb.Append(BitConverter.ToString(values).Replace("-", ""));
  262. }
  263. else
  264. {
  265. if (RawDataUseCommaSeparated)
  266. sb.Append(BitConverter.ToString(block.GetRawData()).Replace("-", ","));
  267. else
  268. sb.Append(BitConverter.ToString(block.GetRawData()).Replace("-", ""));
  269. }
  270. sb.Append('|');
  271. }
  272. }
  273. string strTemp = sb.ToString();
  274. if (_lastMPLCWordRecord != strTemp)
  275. {
  276. sb.Append(strTemp);
  277. _rawdataQueue.Enqueue(new RawdataInfo() { Datatime = DateTime.Now, Rawdata = strTemp });
  278. _lastMPLCWordRecord = strTemp;
  279. }
  280. }
  281. /// <summary>
  282. /// 寫入原始資料
  283. /// </summary>
  284. private void WriteRawdataProcess()
  285. {
  286. try
  287. {
  288. if (_rawdataQueue.TryPeek(out var rawdataInfo))
  289. {
  290. string logPath = LogBaseDirectory;
  291. if (logPath.EndsWith(@"\"))
  292. {
  293. if (logPath.EndsWith($@"{rawdataInfo.Datatime:yyyy-MM-dd}\") == false)
  294. logPath = $@"{logPath}{rawdataInfo.Datatime:yyyy-MM-dd}\{HostInfo.HostID}\";
  295. else
  296. logPath = $@"{logPath}{HostInfo.HostID}\";
  297. }
  298. else
  299. {
  300. if (logPath.EndsWith($@"{rawdataInfo.Datatime:yyyy-MM-dd}") == false)
  301. logPath = $@"{logPath}\{rawdataInfo.Datatime:yyyy-MM-dd}\{HostInfo.HostID}\";
  302. else
  303. logPath = $@"{logPath}\{HostInfo.HostID}\";
  304. }
  305. if (!Directory.Exists(logPath))
  306. Directory.CreateDirectory(logPath);
  307. string lastLogFilename = $@"{logPath}{HostInfo.HostID}_PLCR_RawData_{rawdataInfo.Datatime:yyyyMMddHH}.log";
  308. using StreamWriter file = new StreamWriter(File.Open(lastLogFilename, FileMode.Append));
  309. while (_rawdataQueue.TryPeek(out rawdataInfo))
  310. {
  311. string logFilename = $@"{logPath}{HostInfo.HostID}_PLCR_RawData_{rawdataInfo.Datatime:yyyyMMddHH}.log";
  312. if (lastLogFilename != logFilename)
  313. break;
  314. file.WriteLine($"[{rawdataInfo.Datatime:HH:mm:ss.fffff}] {rawdataInfo.Rawdata}");
  315. _rawdataQueue.TryDequeue(out var result);
  316. }
  317. }
  318. }
  319. catch (Exception ex)
  320. {
  321. Debug.WriteLine($"{ex}");
  322. if (_rawdataQueue.Count > 1000)
  323. {
  324. _rawdataQueue.TryDequeue(out var result);
  325. }
  326. }
  327. }
  328. /// <summary>
  329. /// 取得位元
  330. /// </summary>
  331. /// <param name="address">位置</param>
  332. /// <returns>True/False</returns>
  333. public bool GetBit(string address)
  334. {
  335. foreach (DataBlock block in _cachedBlocks)
  336. {
  337. if (block.TryGetBit(address, out bool value))
  338. return value;
  339. }
  340. return false;
  341. }
  342. /// <summary>
  343. /// 設置位元開啟
  344. /// </summary>
  345. /// <param name="address">位置</param>
  346. public void SetBitOn(string address)
  347. {
  348. _mplc.SetBitOn(address);
  349. }
  350. /// <summary>
  351. /// 設置位元關閉
  352. /// </summary>
  353. /// <param name="address">位置</param>
  354. public void SetBitOff(string address)
  355. {
  356. _mplc.SetBitOff(address);
  357. }
  358. /// <summary>
  359. /// 讀取字元
  360. /// </summary>
  361. /// <param name="address">位置</param>
  362. /// <returns>字元</returns>
  363. public int ReadWord(string address)
  364. {
  365. foreach (DataBlock block in _cachedBlocks)
  366. {
  367. if (block.TryGetWord(address, out int value))
  368. return value;
  369. }
  370. return 0;
  371. }
  372. /// <summary>
  373. /// 寫入字元
  374. /// </summary>
  375. /// <param name="address">位置</param>
  376. /// <param name="data">字元</param>
  377. public void WriteWord(string address, int data)
  378. {
  379. _mplc.WriteWord(address, data);
  380. }
  381. /// <summary>
  382. /// 讀取多個字元
  383. /// </summary>
  384. /// <param name="startAddress">起始位置</param>
  385. /// <param name="length">長度</param>
  386. /// <returns>字元陣列</returns>
  387. public int[] ReadWords(string startAddress, int length)
  388. {
  389. foreach (DataBlock block in _cachedBlocks)
  390. {
  391. if (block.TryGetWords(startAddress, out int[] data, length))
  392. return data;
  393. }
  394. return new int[length];
  395. }
  396. /// <summary>
  397. /// 寫入多個字元
  398. /// </summary>
  399. /// <param name="startAddress">起始位置</param>
  400. /// <param name="data">字元陣列</param>
  401. public void WriteWords(string startAddress, int[] data)
  402. {
  403. _mplc.WriteWords(startAddress, data);
  404. }
  405. /// <summary>
  406. /// 取得控制器提供介面
  407. /// </summary>
  408. /// <returns>控制器提供介面</returns>
  409. public IMPLCProvider GetMPLCProvider()
  410. {
  411. return _mplc;
  412. }
  413. /// <summary>
  414. /// 暫停
  415. /// </summary>
  416. public void Stop()
  417. {
  418. _heartbeat.Pause();
  419. _writeRawdataWorker.Pause();
  420. }
  421. /// <summary>
  422. /// 開始
  423. /// </summary>
  424. public void Start()
  425. {
  426. _heartbeat.Start();
  427. _writeRawdataWorker.Start();
  428. }
  429. #region IDisposable Support
  430. private bool _disposedValue = false;
  431. /// <summary>
  432. /// 釋放資源
  433. /// </summary>
  434. /// <param name="disposing"></param>
  435. protected virtual void Dispose(bool disposing)
  436. {
  437. if (!_disposedValue)
  438. {
  439. if (disposing)
  440. {
  441. _heartbeat.Dispose();
  442. _mplc.Dispose();
  443. _writeRawdataWorker.Dispose();
  444. }
  445. _disposedValue = true;
  446. }
  447. }
  448. /// <summary>
  449. /// 解構式
  450. /// </summary>
  451. ~PLCHost()
  452. {
  453. Dispose(false);
  454. }
  455. /// <summary>
  456. /// 釋放資源
  457. /// </summary>
  458. public void Dispose()
  459. {
  460. Dispose(true);
  461. GC.SuppressFinalize(this);
  462. }
  463. #endregion
  464. }
  465. }