终端一体化运控平台
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.
 
 
 

669 lines
24 KiB

  1. using BPASmartClient.Helper;
  2. using BPASmartClient.Message;
  3. //using BPASmartClient.Message;
  4. using NModbus;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.Linq;
  9. using System.Net.Sockets;
  10. using System.Text;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. namespace BPASmartClient.Modbus
  14. {
  15. public class ModbusTcp
  16. {
  17. //private volatile static ModbusTcp _Instance;
  18. //public static ModbusTcp GetInstance => _Instance ?? (_Instance = new ModbusTcp());
  19. //private ModbusTcp() { }
  20. private ModbusFactory modbusFactory;
  21. private IModbusMaster master;
  22. private TcpClient tcpClient;
  23. private ManualResetEvent mre = new ManualResetEvent(false);
  24. public string IPAdress;
  25. public int Port;
  26. public Action<string> Show { get; set; }
  27. public Action<string> ShowEx { get; set; }
  28. /// <summary>
  29. /// 连接plc 原料设备成功
  30. /// </summary>
  31. public Action ConnectOk { get; set; }
  32. public Action ConnectFail { get; set; }
  33. public Action Disconnect { get; set; }
  34. public bool IsReconnect { get; set; } = true; //true=启用重连,false=禁用重连
  35. /// <summary>
  36. /// 判断是否连接成功
  37. /// </summary>
  38. public bool Connected => tcpClient == null ? false : tcpClient.Connected;
  39. /// <summary>
  40. /// ModbusTcp 连接设备
  41. /// </summary>
  42. /// <param name="ip">ip 地址</param>
  43. /// <param name="port">端口号,默认502</param>
  44. public void ModbusTcpConnect(string ip, int port = 502)
  45. {
  46. Show?.Invoke($"设备【{ip}:{port}】连接中。。。。");
  47. IPAdress = ip;
  48. Port = port;
  49. modbusFactory = new ModbusFactory();
  50. Connect();
  51. if (Connected)
  52. {
  53. master.Transport.ReadTimeout = 2000;//读取超时时间
  54. master.Transport.WriteTimeout = 2000;//写入超时时间
  55. master.Transport.Retries = 10;//重试次数
  56. ConnectOk?.Invoke();
  57. Show?.Invoke($"设备【{ip}:{port}】连接成功");
  58. }
  59. else
  60. {
  61. ConnectFail?.Invoke();
  62. }
  63. }
  64. public void Dispose()
  65. {
  66. master.Dispose();
  67. Disconnect?.Invoke();
  68. }
  69. private void Connect()
  70. {
  71. bool ErrorFlag = false;
  72. while (!Connected)
  73. {
  74. try
  75. {
  76. tcpClient = new TcpClient(IPAdress, Port);
  77. //mre.Reset();
  78. //tcpClient.BeginConnect(IPAdress, Port, (res) => { mre.Set(); }, null);
  79. //if (!mre.WaitOne(1500, false)) break;
  80. master = modbusFactory.CreateMaster(tcpClient);
  81. }
  82. catch (Exception ex)
  83. {
  84. if (!ErrorFlag)
  85. {
  86. ShowEx?.Invoke($"ModbusTcp 连接失败,IP = {IPAdress},Port = {Port}");
  87. ShowEx?.Invoke(ex.ToString());
  88. ErrorFlag = true;
  89. }
  90. if (!IsReconnect) break;
  91. Thread.Sleep(3000);
  92. }
  93. }
  94. if (ErrorFlag && IsReconnect) Show?.Invoke("ModbusTcp 重连成功!");
  95. }
  96. /// <summary>
  97. /// 获取 M 区 和 VW 区的Modbus的地址
  98. /// </summary>
  99. /// <param name="address"></param>
  100. /// <returns></returns>
  101. public int GetAddress(string address)
  102. {
  103. if (address == null) return -1;
  104. if (address.Length > 0)
  105. {
  106. address = address.Trim();
  107. if (address.ToUpper().Contains("M") && address.Length >= 4)
  108. {
  109. var res = address.Substring(1).Split('.');
  110. if (res != null && res.Length == 2)
  111. {
  112. if (int.TryParse(res[0], out int firstAddress) && int.TryParse(res[1], out int ExitAddress))
  113. {
  114. if (ExitAddress >= 0 && ExitAddress <= 7)
  115. {
  116. return (firstAddress * 8) + 320 + ExitAddress;
  117. }
  118. }
  119. }
  120. }
  121. if (address.ToUpper().Contains("LB") && address.Length >= 3)
  122. {
  123. var res = address.Substring(2);
  124. if (res != null && res.Length > 0)
  125. {
  126. if(int.TryParse(res, out int firstAddress))
  127. {
  128. return firstAddress;
  129. }
  130. }
  131. }
  132. else if (address.ToUpper().Contains("VW") && address.Length >= 3)
  133. {
  134. var res = address.Substring(2);
  135. if (res != null && int.TryParse(res, out int tempAddress))
  136. {
  137. return (tempAddress / 2) + 100;
  138. }
  139. }
  140. else if (address.ToUpper().Contains("LW") && address.Length >= 3)
  141. {
  142. var res = address.Substring(2);
  143. if (res != null && int.TryParse(res, out int LwAddress))
  144. {
  145. return LwAddress;
  146. }
  147. }
  148. else if (address.ToUpper().Contains("D") && address.Length == 5)
  149. {
  150. try
  151. {
  152. string head = (Convert.ToInt32(address.Substring(1, 1))).ToString();
  153. int num = Convert.ToInt32(address.Substring(2, 3));
  154. int len = num.ToString().Length;
  155. string tail = string.Empty;
  156. switch (len)
  157. {
  158. case 1:
  159. if ((Convert.ToInt32(address.Substring(4, 1))).ToString().Length > 1)
  160. {
  161. tail = "0" + (Convert.ToInt32(address.Substring(4, 1))).ToString();
  162. }
  163. else
  164. {
  165. tail = "00" + (Convert.ToInt32(address.Substring(4, 1))).ToString();
  166. }
  167. break;
  168. case 2:
  169. if ((Convert.ToInt32(address.Substring(3, 2))).ToString().Length > 2)
  170. {
  171. tail = (Convert.ToInt32(address.Substring(3, 2))).ToString();
  172. }
  173. else
  174. {
  175. tail = "0" + (Convert.ToInt32(address.Substring(3, 2))).ToString();
  176. }
  177. break;
  178. case 3:
  179. tail = (Convert.ToInt32(address.Substring(2, 3))).ToString();
  180. break;
  181. }
  182. address = head + tail;
  183. return Convert.ToInt32(address);
  184. }
  185. catch (Exception)
  186. {
  187. //打印日志
  188. }
  189. }
  190. }
  191. return -1;
  192. }
  193. private void ExceptionHandling(Exception ex)
  194. {
  195. if (ex.InnerException is SocketException)
  196. {
  197. if (IsReconnect)
  198. {
  199. tcpClient = null;
  200. Connect();
  201. }
  202. Disconnect?.Invoke();
  203. }
  204. }
  205. public short ReadShort(string address)
  206. {
  207. var res = Read(address);
  208. if (res != null && res is short[] shorts && shorts.Length == 1)
  209. {
  210. return shorts[0];
  211. }
  212. return 0;
  213. }
  214. public object Read(string address, ushort len = 1, byte slaveAddress = 1)
  215. {
  216. if (address == null || tcpClient == null) return default(object);
  217. ushort startAddress = (ushort)GetAddress(address);
  218. CommandType commandType = CommandType.Coils;
  219. try
  220. {
  221. if (address.ToUpper().Contains("M"))
  222. {
  223. commandType = CommandType.Coils;
  224. return master.ReadCoils(slaveAddress, startAddress, len);
  225. }
  226. else if (address.ToUpper().Contains("VW") || address.ToUpper().Contains("LW") || address.ToUpper().Contains("D"))
  227. {
  228. commandType = CommandType.HoldingRegisters;
  229. return master.ReadHoldingRegisters(slaveAddress, startAddress, len);
  230. }
  231. else if (address.ToUpper().Contains("I"))
  232. {
  233. commandType = CommandType.Inputs;
  234. return master.ReadInputs(slaveAddress, startAddress, len);
  235. }
  236. else if (address.ToUpper().Contains("LB"))
  237. {
  238. commandType = CommandType.Coils;
  239. return master.ReadCoils(slaveAddress, startAddress, len);
  240. }
  241. }
  242. catch (Exception ex)
  243. {
  244. ShowEx?.Invoke($"读取地址:【{address}:= {startAddress}】,读取类型:【{commandType.ToString()}】出错,{ex.ToString()}");
  245. ExceptionHandling(ex);
  246. }
  247. return default(object);
  248. }
  249. /// <summary>
  250. /// 写入数据
  251. /// </summary>
  252. /// <typeparam name="T"></typeparam>
  253. /// <param name="address">写入地址</param>
  254. /// <param name="value">写入值</param>
  255. /// <param name="Retries">重试次数</param>
  256. /// <param name="slaveAddress">从站地址</param>
  257. /// <returns></returns>
  258. public bool Write<T>(string address, T value, int Retries = 1, byte slaveAddress = 1)
  259. {
  260. int count = 0;
  261. if (Retries == 1 || Retries == 0)
  262. {
  263. return TempWrite(address, value, slaveAddress);
  264. }
  265. else if (Retries > 1)
  266. {
  267. bool isok = false;
  268. while (count < Retries)
  269. {
  270. count++;
  271. TempWrite(address, value, slaveAddress);
  272. var res = Read(address);
  273. if (res != null && res.ToString() == value.ToString())
  274. {
  275. isok = true;
  276. break;
  277. }
  278. }
  279. return isok;
  280. }
  281. return false;
  282. }
  283. private bool TempWrite<T>(string address, T value, byte slaveAddress = 1)
  284. {
  285. if (address == null || tcpClient == null)
  286. {
  287. Show?.Invoke("写入失败,地址为空或断开连接");
  288. return false;
  289. }
  290. ushort startAddress = (ushort)GetAddress(address);
  291. CommandType commandType = CommandType.Coils;
  292. try
  293. {
  294. if (address.ToUpper().Contains("M"))
  295. {
  296. commandType = CommandType.Coils;
  297. if (value is bool boolValue)
  298. master.WriteSingleCoil(slaveAddress, startAddress, boolValue);
  299. else if (value is bool[] boolsValue)
  300. master.WriteMultipleCoils(slaveAddress, startAddress, boolsValue);
  301. }
  302. else if(address.ToUpper().Contains("LB"))
  303. {
  304. commandType = CommandType.Coils;
  305. if (value is bool boolValue)
  306. master.WriteSingleCoil(slaveAddress, startAddress, boolValue);
  307. else if (value is bool[] boolsValue)
  308. master.WriteMultipleCoils(slaveAddress, startAddress, boolsValue);
  309. }
  310. else if (address.ToUpper().Contains("VW") || address.ToUpper().Contains("LW") || address.ToUpper().Contains("D"))
  311. {
  312. commandType = CommandType.HoldingRegisters;
  313. if (value is ushort ushortValue)
  314. master.WriteSingleRegister(slaveAddress, startAddress, ushortValue);
  315. else if (value is ushort[] ushortsValue)
  316. {
  317. int len = 100;
  318. if (ushortsValue.Length > len)
  319. {
  320. List<ushort[]> ushortLists = new List<ushort[]>();
  321. for (int i = 0; i < ushortsValue.Length / len; i++)
  322. {
  323. ushortLists.Add(ushortsValue.Skip(0).Take(len).ToArray());
  324. }
  325. int y = ushortsValue.Length % len;
  326. if (y > 0)
  327. {
  328. ushortLists.Add(ushortsValue.Skip(ushortsValue.Length - y).Take(y).ToArray());
  329. }
  330. foreach (var item in ushortLists)
  331. {
  332. master.WriteMultipleRegisters(slaveAddress, startAddress, item);
  333. startAddress += (ushort)item.Length;
  334. }
  335. }
  336. else
  337. {
  338. master.WriteMultipleRegisters(slaveAddress, startAddress, ushortsValue);
  339. }
  340. }
  341. }
  342. else if (address.ToUpper().Contains("I"))
  343. {
  344. commandType = CommandType.Inputs;
  345. }
  346. Show?.Invoke($"成功,地址:{address},值:{value}");
  347. return true;
  348. }
  349. catch (Exception ex)
  350. {
  351. ShowEx?.Invoke($"写入地址:【{address}:= {startAddress}】,写入类型:【{commandType.ToString()}】出错,{ex.ToString()}");
  352. ExceptionHandling(ex);
  353. return false;
  354. }
  355. }
  356. #region 字符串数据读写
  357. /// <summary>
  358. /// 赋值string
  359. /// </summary>
  360. /// <param name="StartAddress"></param>
  361. /// <param name="value"></param>
  362. /// <returns></returns>
  363. public void SetString(string StartAddress, string value)
  364. {
  365. //获取指定的编码不存在的时候需要安装 System.Text.Encoding.CodePages nuget包
  366. //然后使用下面的代码就可以获取了
  367. Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
  368. var bytes = Encoding.GetEncoding("gb2312")?.GetBytes(value);
  369. Write(StartAddress, bytes.BytesToUshorts());
  370. }
  371. /// <summary>
  372. /// 获取string
  373. /// </summary>
  374. /// <param name="StartAddress"></param>
  375. /// <param name="len"></param>
  376. /// <returns></returns>
  377. public string GetString(string StartAddress, ushort len)
  378. {
  379. var res = Read(StartAddress, len);
  380. if (res != null && res is ushort[] ushorts)
  381. {
  382. Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
  383. return Encoding.GetEncoding("gb2312")?.GetString(ushorts.UshortsToBytes()).Trim(new char[] { '\0' });
  384. }
  385. return String.Empty;
  386. }
  387. #endregion
  388. #region 浮点数数据读写
  389. /// <summary>
  390. /// 赋值Real类型数据
  391. /// </summary>
  392. /// <param name="StartAddress"></param>
  393. /// <param name="value"></param>
  394. public void SetReal(string StartAddress, float value)
  395. {
  396. var bytes = BitConverter.GetBytes(value);
  397. Write(StartAddress, bytes.BytesToUshorts());
  398. }
  399. /// <summary>
  400. /// 获取float类型数据
  401. /// </summary>
  402. /// <param name="StartAddress"></param>
  403. /// <returns></returns>
  404. public float GetReal(string StartAddress)
  405. {
  406. var res = Read(StartAddress, 2);
  407. if (res != null && res is ushort[] ushorts)
  408. {
  409. return BitConverter.ToSingle(ushorts.UshortsToBytes(), 0);
  410. }
  411. return 0;
  412. }
  413. #endregion
  414. #region 整数数据读写
  415. /// <summary>
  416. /// 赋值Real类型数据
  417. /// </summary>
  418. /// <param name="StartAddress"></param>
  419. /// <param name="value"></param>
  420. public void SetUint(string StartAddress, uint value)
  421. {
  422. var bytes = BitConverter.GetBytes(value);
  423. Write(StartAddress, bytes.BytesToUshorts());
  424. }
  425. /// <summary>
  426. /// 获取float类型数据
  427. /// </summary>
  428. /// <param name="StartAddress"></param>
  429. /// <returns></returns>
  430. public uint GetUint(string StartAddress)
  431. {
  432. var res = Read(StartAddress, 2);
  433. if (res != null && res is ushort[] ushorts)
  434. {
  435. return BitConverter.ToUInt32(ushorts.UshortsToBytes(), 0);
  436. }
  437. return 0;
  438. }
  439. #endregion
  440. #region 批量数据读取
  441. /// <summary>
  442. /// 读取多个线圈
  443. /// </summary>
  444. /// <param name="startAddress"></param>
  445. /// <param name="num"></param>
  446. /// <param name="slaveAddress"></param>
  447. /// <returns></returns>
  448. public bool[] ReadCoils(ushort startAddress, ushort num, byte slaveAddress = 1)
  449. {
  450. return master.ReadCoils(slaveAddress, startAddress, num);
  451. }
  452. /// <summary>
  453. /// 读取多个输入线圈
  454. /// </summary>
  455. /// <param name="startAddress"></param>
  456. /// <param name="num"></param>
  457. /// <param name="slaveAddress"></param>
  458. /// <returns></returns>
  459. public bool[] ReadInputs(ushort startAddress, ushort num, byte slaveAddress = 1)
  460. {
  461. return master.ReadInputs(slaveAddress, startAddress, num);
  462. }
  463. /// <summary>
  464. /// 读取多个保持寄存器
  465. /// </summary>
  466. /// <param name="startAddress"></param>
  467. /// <param name="num"></param>
  468. /// <param name="slaveAddress"></param>
  469. /// <returns></returns>
  470. public ushort[] ReadHoldingRegisters(ushort startAddress, ushort num, byte slaveAddress = 1)
  471. {
  472. return master.ReadHoldingRegisters(slaveAddress, startAddress, num);
  473. }
  474. /// <summary>
  475. /// 读取多个输入寄存器
  476. /// </summary>
  477. /// <param name="startAddress"></param>
  478. /// <param name="num"></param>
  479. /// <param name="slaveAddress"></param>
  480. /// <returns></returns>
  481. public ushort[] ReadInputRegisters(ushort startAddress, ushort num, byte slaveAddress = 1)
  482. {
  483. return master.ReadInputRegisters(slaveAddress, startAddress, num);
  484. }
  485. #endregion
  486. #region 批量数据写入
  487. /// <summary>
  488. /// 写入多个线圈
  489. /// </summary>
  490. /// <param name="startAddress"></param>
  491. /// <param name="value"></param>
  492. /// <param name="slaveAddress"></param>
  493. public void WriteMultipleCoils(ushort startAddress, bool[] value, byte slaveAddress = 1)
  494. {
  495. master.WriteMultipleCoils(slaveAddress, startAddress, value);
  496. }
  497. /// <summary>
  498. /// 写入多个寄存器
  499. /// </summary>
  500. /// <param name="startAddress"></param>
  501. /// <param name="value"></param>
  502. /// <param name="slaveAddress"></param>
  503. public void WriteMultipleRegisters(ushort startAddress, ushort[] value, byte slaveAddress = 1)
  504. {
  505. master.WriteMultipleRegisters(slaveAddress, startAddress, value);
  506. }
  507. #endregion
  508. #region 单个数据读取
  509. /// <summary>
  510. /// 读取单个线圈
  511. /// </summary>
  512. /// <param name="startAddress"></param>
  513. /// <param name="slaveAddress"></param>
  514. /// <returns></returns>
  515. public bool ReadCoils(ushort startAddress, byte slaveAddress = 1)
  516. {
  517. var result = master.ReadCoils(slaveAddress, startAddress, 1);
  518. if (result == null) return false;
  519. if (result.Length == 1) return result[0];
  520. return false;
  521. //return master.ReadCoils(slaveAddress, startAddress, 1)[0];
  522. }
  523. /// <summary>
  524. /// 读取单个输入线圈
  525. /// </summary>
  526. /// <param name="startAddress"></param>
  527. /// <param name="slaveAddress"></param>
  528. /// <returns></returns>
  529. public bool ReadInputs(ushort startAddress, byte slaveAddress = 1)
  530. {
  531. var result = master.ReadInputs(slaveAddress, startAddress, 1);
  532. if (result == null) return false;
  533. if (result.Length == 1) return result[0];
  534. return false;
  535. //return master.ReadInputs(slaveAddress, startAddress, 1)[0];
  536. }
  537. /// <summary>
  538. /// 读取单个保持寄存器
  539. /// </summary>
  540. /// <param name="startAddress"></param>
  541. /// <param name="slaveAddress"></param>
  542. /// <returns></returns>
  543. public ushort ReadHoldingRegisters(ushort startAddress, byte slaveAddress = 1)
  544. {
  545. if (tcpClient == null) return 0;
  546. ushort[] result = null;
  547. try
  548. {
  549. result = master.ReadHoldingRegisters(slaveAddress, startAddress, 1);
  550. }
  551. catch (Exception ex)
  552. {
  553. ShowEx?.Invoke(ex.ToString());
  554. tcpClient = null;
  555. Connect();
  556. }
  557. if (result == null) return 0;
  558. if (result.Length == 1) return result[0];
  559. return 0;
  560. //return master.ReadHoldingRegisters(slaveAddress, startAddress, 1)[0];
  561. }
  562. /// <summary>
  563. /// 读取单个输入寄存器
  564. /// </summary>
  565. /// <param name="startAddress"></param>
  566. /// <param name="slaveAddress"></param>
  567. /// <returns></returns>
  568. public ushort ReadInputRegisters(ushort startAddress, byte slaveAddress = 1)
  569. {
  570. var result = master.ReadInputRegisters(slaveAddress, startAddress, 1);
  571. if (result == null) return 0;
  572. if (result.Length == 1) return result[0];
  573. return 0;
  574. //return master.ReadInputRegisters(slaveAddress, startAddress, 1)[0];
  575. }
  576. #endregion
  577. #region 单个数据写入
  578. /// <summary>
  579. /// 写入单个线圈
  580. /// </summary>
  581. /// <param name="startAddress"></param>
  582. /// <param name="value"></param>
  583. /// <param name="slaveAddress"></param>
  584. public void WriteSingleCoil(ushort startAddress, bool value, byte slaveAddress = 1)
  585. {
  586. master.WriteSingleCoil(slaveAddress, startAddress, value);
  587. }
  588. /// <summary>
  589. /// 写入单个寄存器
  590. /// </summary>
  591. /// <param name="startAddress"></param>
  592. /// <param name="value"></param>
  593. /// <param name="slaveAddress"></param>
  594. public void WriteSingleRegister(ushort startAddress, ushort value, byte slaveAddress = 1)
  595. {
  596. master.WriteSingleRegister(slaveAddress, startAddress, value);
  597. }
  598. #endregion
  599. }
  600. public enum CommandType
  601. {
  602. /// <summary>
  603. /// 线圈操作
  604. /// </summary>
  605. Coils,
  606. /// <summary>
  607. /// 输入线圈操作
  608. /// </summary>
  609. Inputs,
  610. /// <summary>
  611. /// 保持寄存器操作
  612. /// </summary>
  613. HoldingRegisters,
  614. /// <summary>
  615. /// 输入寄存器操作
  616. /// </summary>
  617. InputRegisters,
  618. }
  619. }