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

643 lines
23 KiB

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