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.

573 lines
18 KiB

8 months ago
  1. using Mirle.Component.MPLC.Interfaces;
  2. using NetMQ;
  3. using NetMQ.Sockets;
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Net;
  10. using System.Net.NetworkInformation;
  11. using System.Net.Sockets;
  12. using System.Reflection;
  13. using System.Threading;
  14. namespace Mirle.Component.MPLC.MCProtocol
  15. {
  16. /// <summary>
  17. ///
  18. /// </summary>
  19. public class ReadWriteAdapter : IDisposable, IMPLCProvider, IConnectable
  20. {
  21. /// <summary>
  22. ///
  23. /// </summary>
  24. /// <param name="hostId"></param>
  25. /// <param name="ipAddress"></param>
  26. /// <param name="port"></param>
  27. public ReadWriteAdapter(string hostId, string ipAddress, int port)
  28. {
  29. _hostId = hostId;
  30. _plcPort = port;
  31. _plcIPAddress = IPAddress.Parse(ipAddress);
  32. _frame3E = new Frame3E();
  33. }
  34. /// <summary>
  35. ///
  36. /// </summary>
  37. private int _timeout = 600;
  38. /// <summary>
  39. ///
  40. /// </summary>
  41. private StreamSocket _socket;
  42. /// <summary>
  43. ///
  44. /// </summary>
  45. private readonly string _hostId;
  46. /// <summary>
  47. ///
  48. /// </summary>
  49. private readonly int _plcPort;
  50. /// <summary>
  51. ///
  52. /// </summary>
  53. private readonly IPAddress _plcIPAddress = IPAddress.Parse("127.0.0.1");
  54. /// <summary>
  55. ///
  56. /// </summary>
  57. private readonly Frame3E _frame3E;
  58. /// <summary>
  59. ///
  60. /// </summary>
  61. private readonly object _plcLock = new object();
  62. /// <summary>
  63. ///
  64. /// </summary>
  65. private readonly object _socketLock = new object();
  66. /// <summary>
  67. ///
  68. /// </summary>
  69. public bool IsConnected { get; private set; } = false;
  70. /// <summary>
  71. ///
  72. /// </summary>
  73. public int Timeout
  74. {
  75. get => _timeout;
  76. set
  77. {
  78. if (value < 600)
  79. {
  80. _timeout = 600;
  81. }
  82. else if (value > 60_000)
  83. {
  84. _timeout = 60_000;
  85. }
  86. else
  87. {
  88. _timeout = value;
  89. }
  90. }
  91. }
  92. /// <summary>
  93. ///
  94. /// </summary>
  95. /// <returns></returns>
  96. public bool TestConnection()
  97. {
  98. return TestConnection(_plcIPAddress.ToString(), _plcPort);
  99. }
  100. /// <summary>
  101. ///
  102. /// </summary>
  103. /// <param name="ipAddress"></param>
  104. /// <param name="port"></param>
  105. /// <returns></returns>
  106. public bool TestConnection(string ipAddress, int port)
  107. {
  108. try
  109. {
  110. var client = new TcpClient();
  111. var ip = IPAddress.Parse(ipAddress);
  112. var asyncResult = client.BeginConnect(ip, port, null, null);
  113. bool workIsCompleted = asyncResult.AsyncWaitHandle.WaitOne(_timeout, true);
  114. if (workIsCompleted && client.Connected)
  115. {
  116. client.Close();
  117. return true;
  118. }
  119. else
  120. {
  121. return false;
  122. }
  123. }
  124. catch (Exception ex)
  125. {
  126. TraceLog($"TestConnection: ({ipAddress}:{port}) {ex}");
  127. return false;
  128. }
  129. }
  130. /// <summary>
  131. ///
  132. /// </summary>
  133. internal void TestConnectionByPing()
  134. {
  135. using Ping ping = new Ping();
  136. var pingReply = ping.Send(_plcIPAddress);
  137. TraceLog($"TestConnectionByPing: ({_plcIPAddress}) {pingReply.Status}");
  138. }
  139. /// <summary>
  140. ///
  141. /// </summary>
  142. public void Close()
  143. {
  144. if (_socket != null)
  145. {
  146. _socket.Close();
  147. _socket.Dispose();
  148. _socket = null;
  149. }
  150. IsConnected = false;
  151. }
  152. /// <summary>
  153. ///
  154. /// </summary>
  155. /// <returns></returns>
  156. public bool ReConnect()
  157. {
  158. if (IsConnected)
  159. {
  160. TraceLog($"ReConnect : Connect Is Alive");
  161. return true;
  162. }
  163. else
  164. {
  165. TraceLog($"ReConnect : Wait NetMQ Reconnect...");
  166. Thread.Sleep(1000);
  167. if (Connect())
  168. {
  169. TraceLog($"ReConnect : Success!");
  170. return true;
  171. }
  172. else
  173. {
  174. TraceLog($"ReConnect : Fail!");
  175. return false;
  176. }
  177. }
  178. }
  179. /// <summary>
  180. ///
  181. /// </summary>
  182. /// <returns></returns>
  183. public bool Connect()
  184. {
  185. try
  186. {
  187. using Ping ping = new Ping();
  188. var pingReply = ping.Send(_plcIPAddress);
  189. if (pingReply.Status == IPStatus.Success)
  190. {
  191. if (_socket != null)
  192. Close();
  193. _socket = new StreamSocket();
  194. _socket.Connect($"tcp://{_plcIPAddress}:{_plcPort}");
  195. IsConnected = true;
  196. return true;
  197. }
  198. }
  199. catch (Exception ex)
  200. {
  201. TraceLog($"IP: {_plcIPAddress}:{_plcPort}: {ex}");
  202. }
  203. return false;
  204. }
  205. /// <summary>
  206. ///
  207. /// </summary>
  208. /// <param name="address"></param>
  209. /// <returns></returns>
  210. public bool GetBit(string address)
  211. {
  212. lock (_plcLock)
  213. {
  214. if (IsConnected == false)
  215. {
  216. return false;
  217. }
  218. var device = MCDevice.Parse(address);
  219. if (device == null)
  220. {
  221. return false;
  222. }
  223. if (device.IsWord)
  224. {
  225. int[] data = RequestReadWords(device, 1);
  226. if (data.Length > 0)
  227. {
  228. var bitArray = new BitArray(BitConverter.GetBytes(data[0]));
  229. return bitArray.Get(device.BitIndex);
  230. }
  231. }
  232. else if (device.IsBit)
  233. {
  234. return RequestReadBit(device);
  235. }
  236. return false;
  237. }
  238. }
  239. /// <summary>
  240. ///
  241. /// </summary>
  242. /// <param name="address"></param>
  243. public void SetBitOn(string address)
  244. {
  245. lock (_plcLock)
  246. {
  247. SetBit(address, true);
  248. }
  249. }
  250. /// <summary>
  251. ///
  252. /// </summary>
  253. /// <param name="address"></param>
  254. public void SetBitOff(string address)
  255. {
  256. lock (_plcLock)
  257. {
  258. SetBit(address, false);
  259. }
  260. }
  261. /// <summary>
  262. ///
  263. /// </summary>
  264. /// <param name="address"></param>
  265. /// <returns></returns>
  266. public int ReadWord(string address)
  267. {
  268. lock (_plcLock)
  269. {
  270. if (IsConnected == false)
  271. return 0;
  272. MCDevice device = MCDevice.Parse(address);
  273. if (device == null)
  274. return 0;
  275. int[] data = RequestReadWords(device, 1);
  276. return data?.Length > 0 ? data[0] : 0;
  277. }
  278. }
  279. /// <summary>
  280. ///
  281. /// </summary>
  282. /// <param name="startAddress"></param>
  283. /// <param name="length"></param>
  284. /// <returns></returns>
  285. public int[] ReadWords(string startAddress, int length)
  286. {
  287. lock (_plcLock)
  288. {
  289. if (IsConnected == false)
  290. return Array.Empty<int>();
  291. List<int> data = new List<int>();
  292. int offset = 0;
  293. do
  294. {
  295. MCDevice device = MCDevice.Parse(startAddress, offset);
  296. if (device == null)
  297. return null;
  298. int numberOfWords = Math.Min(_frame3E.MaximumWords, length - offset);
  299. data.AddRange(RequestReadWords(device, numberOfWords));
  300. offset += _frame3E.MaximumWords;
  301. }
  302. while (offset < length);
  303. return data.ToArray();
  304. }
  305. }
  306. /// <summary>
  307. ///
  308. /// </summary>
  309. /// <param name="address"></param>
  310. /// <param name="data"></param>
  311. public void WriteWord(string address, int data)
  312. {
  313. lock (_plcLock)
  314. {
  315. if (IsConnected == false)
  316. return;
  317. MCDevice device = MCDevice.Parse(address);
  318. if (device == null)
  319. return;
  320. RequestWriteWords(device, new[] { data });
  321. }
  322. }
  323. /// <summary>
  324. ///
  325. /// </summary>
  326. /// <param name="startAddress"></param>
  327. /// <param name="data"></param>
  328. public void WriteWords(string startAddress, int[] data)
  329. {
  330. lock (_plcLock)
  331. {
  332. if (IsConnected == false)
  333. return;
  334. int offset = 0;
  335. do
  336. {
  337. MCDevice device = MCDevice.Parse(startAddress, offset);
  338. if (device == null)
  339. return;
  340. int numberOfWords = Math.Min(_frame3E.MaximumWords, data.Length - offset);
  341. int[] readyToWriteData = new int[numberOfWords];
  342. Array.Copy(data, offset, readyToWriteData, 0, numberOfWords);
  343. RequestWriteWords(device, readyToWriteData);
  344. offset += _frame3E.MaximumWords;
  345. }
  346. while (offset < data.Length);
  347. }
  348. }
  349. /// <summary>
  350. ///
  351. /// </summary>
  352. /// <param name="device"></param>
  353. /// <param name="length"></param>
  354. /// <returns></returns>
  355. private int[] RequestReadWords(MCDevice device, int length)
  356. {
  357. try
  358. {
  359. byte[] receiveData = SendData(_frame3E.CreateReadWordsFrame(device, length));
  360. int completeCode = _frame3E.ResolveReadWordsReturnFrame(receiveData, out int[] data);
  361. if (completeCode != 0)
  362. throw new InvalidOperationException($"{MethodBase.GetCurrentMethod().Name}: MC ErrorCode: 0x{completeCode:X8}");
  363. else
  364. return data;
  365. }
  366. catch (Exception ex)
  367. {
  368. TraceLog($"MCDevice: {device.AsciiDeviceCode + device.AsciiAddress}.{device.BitIndex}: {ex}");
  369. return new int[length];
  370. }
  371. }
  372. /// <summary>
  373. ///
  374. /// </summary>
  375. /// <param name="device"></param>
  376. /// <returns></returns>
  377. private bool RequestReadBit(MCDevice device)
  378. {
  379. try
  380. {
  381. byte[] receiveData = SendData(_frame3E.CreateReadBitFrame(device));
  382. int completeCode = _frame3E.ResolveReadBitReturnFrame(receiveData, out bool data);
  383. if (completeCode != 0)
  384. throw new InvalidOperationException($"{MethodBase.GetCurrentMethod().Name}: MC ErrorCode: 0x{completeCode:X8}");
  385. else
  386. return data;
  387. }
  388. catch (Exception ex)
  389. {
  390. TraceLog($"MCDevice: {device.AsciiDeviceCode + device.AsciiAddress}.{device.BitIndex}: {ex}");
  391. return false;
  392. }
  393. }
  394. /// <summary>
  395. ///
  396. /// </summary>
  397. /// <param name="address"></param>
  398. /// <param name="isOn"></param>
  399. private void SetBit(string address, bool isOn)
  400. {
  401. if (IsConnected == false)
  402. return;
  403. MCDevice device = MCDevice.Parse(address);
  404. if (device == null)
  405. return;
  406. if (device.IsWord)
  407. {
  408. int[] data = RequestReadWords(device, 1);
  409. if (data.Length > 0)
  410. {
  411. BitArray bitArray = new BitArray(BitConverter.GetBytes(data[0]));
  412. bitArray.Set(device.BitIndex, isOn);
  413. int[] newData = new int[1];
  414. bitArray.CopyTo(newData, 0);
  415. RequestWriteWords(device, newData);
  416. }
  417. }
  418. else if (device.IsBit)
  419. {
  420. RequestWriteBit(device, isOn);
  421. }
  422. }
  423. /// <summary>
  424. ///
  425. /// </summary>
  426. /// <param name="device"></param>
  427. /// <param name="isOn"></param>
  428. /// <returns></returns>
  429. private bool RequestWriteBit(MCDevice device, bool isOn)
  430. {
  431. try
  432. {
  433. byte[] receiveData = SendData(_frame3E.CreateWriteBitFrame(device, isOn));
  434. int completeCode = _frame3E.ResolveWriteBitReturnFrame(receiveData);
  435. if (completeCode != 0)
  436. throw new InvalidOperationException($"{MethodBase.GetCurrentMethod().Name}: MC ErrorCode: 0x{completeCode:X8}");
  437. else
  438. return true;
  439. }
  440. catch (Exception ex)
  441. {
  442. TraceLog($"MCDevice: {device.AsciiDeviceCode + device.AsciiAddress}.{device.BitIndex}: {ex}");
  443. return false;
  444. }
  445. }
  446. /// <summary>
  447. ///
  448. /// </summary>
  449. /// <param name="device"></param>
  450. /// <param name="data"></param>
  451. /// <returns></returns>
  452. private bool RequestWriteWords(MCDevice device, int[] data)
  453. {
  454. try
  455. {
  456. byte[] receiveData = SendData(_frame3E.CreateWriteWordsFrame(device, data));
  457. int completeCode = _frame3E.ResolveWriteWordsReturnFrame(receiveData);
  458. if (completeCode != 0)
  459. throw new InvalidOperationException($"{MethodBase.GetCurrentMethod().Name}: MC ErrorCode: 0x{completeCode:X4}");
  460. else
  461. return true;
  462. }
  463. catch (Exception ex)
  464. {
  465. TraceLog($"MCDevice: {device.AsciiDeviceCode + device.AsciiAddress}.{device.BitIndex}: {ex}");
  466. return false;
  467. }
  468. }
  469. /// <summary>
  470. ///
  471. /// </summary>
  472. /// <param name="message"></param>
  473. private void TraceLog(string message)
  474. {
  475. string logPath = $@"{AppDomain.CurrentDomain.BaseDirectory}LOG\{DateTime.Now:yyyy-MM-dd}\{_hostId}\";
  476. if (!Directory.Exists(logPath))
  477. Directory.CreateDirectory(logPath);
  478. string logFilename = $@"{logPath}MC_{_plcIPAddress.ToString().Replace('.', '_')}_{_plcPort}_Trace.log";
  479. using var file = new StreamWriter(File.Open(logFilename, FileMode.Append));
  480. file.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] {message}");
  481. }
  482. /// <summary>
  483. /// 傳送資料
  484. /// </summary>
  485. /// <param name="frame"></param>
  486. /// <returns></returns>
  487. private byte[] SendData(byte[] frame)
  488. {
  489. lock (_socketLock)
  490. {
  491. if (_socket == null || _socket.IsDisposed)
  492. {
  493. IsConnected = false;
  494. TraceLog($"SendData: Fail, NoConnect!");
  495. }
  496. try
  497. {
  498. for (int i = 0; i < 5; i++)
  499. {
  500. if (_socket == null)
  501. continue;
  502. TimeSpan timeout = TimeSpan.FromMilliseconds(_timeout);
  503. byte[] id = _socket.Options.Identity;
  504. if (_socket.TrySendFrame(timeout, id, true) && _socket.TrySendFrame(timeout, frame))
  505. {
  506. if (_socket.TryReceiveFrameBytes(timeout, out byte[] receiveId) && _socket.TryReceiveFrameBytes(timeout, out byte[] dataFrame))
  507. {
  508. if (receiveId.SequenceEqual(id))
  509. return dataFrame;
  510. else
  511. TraceLog($"SendData: SequenceEqual Id Fail, Retrt Count{i}");
  512. }
  513. else
  514. TraceLog($"SendData: TryReceiveFrameBytes Fail, Retrt Count{i}");
  515. }
  516. }
  517. }
  518. catch (Exception ex)
  519. {
  520. TraceLog($"SendData: {ex}");
  521. }
  522. IsConnected = false;
  523. return null;
  524. }
  525. }
  526. #region IDisposable Support
  527. private bool _disposedValue = false;
  528. /// <summary>
  529. /// 釋放資源
  530. /// </summary>
  531. /// <param name="disposing"></param>
  532. protected virtual void Dispose(bool disposing)
  533. {
  534. if (!_disposedValue)
  535. {
  536. if (disposing)
  537. {
  538. _socket?.Close();
  539. _socket?.Dispose();
  540. }
  541. _disposedValue = true;
  542. }
  543. }
  544. /// <summary>
  545. /// 解構是
  546. /// </summary>
  547. ~ReadWriteAdapter()
  548. {
  549. Dispose(false);
  550. }
  551. /// <summary>
  552. /// 釋放資源
  553. /// </summary>
  554. public void Dispose()
  555. {
  556. Dispose(true);
  557. GC.SuppressFinalize(this);
  558. }
  559. #endregion
  560. }
  561. }