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.

628 lines
22 KiB

  1. using DryIoc;
  2. using ModbusDemo.Model;
  3. using ModbusDemo.Uitls;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO.Ports;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using System.Windows;
  12. namespace ModbusDemo.Device
  13. {
  14. public class ModbusRTU : IModbusRTU
  15. {
  16. //串口
  17. private SerialPort _serialPort;
  18. //用于操作数据库
  19. private ModbusDbContext _modbusDbContext;
  20. //TODO,修改
  21. //private SerialPortAdapter _portAdapter;
  22. public ModbusRTU(SerialPort serialPort, ModbusDbContext modbusDbContext)
  23. {
  24. _serialPort = serialPort;
  25. _modbusDbContext = modbusDbContext;
  26. //_portAdapter = new SerialPortAdapter(serialPort);
  27. }
  28. /// <summary>
  29. /// 用来发送读写线圈的指令
  30. /// </summary>
  31. /// <param name="slaveAddress"></param>
  32. /// <param name="startAddress"></param>
  33. /// <param name="numberOfPoints"></param>
  34. /// <returns></returns>
  35. public bool[] ReadCoil(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  36. {
  37. bool[] resultValue = null;
  38. if (!_serialPort.IsOpen)
  39. {
  40. MessageBox.Show("串口没有链接,请先链接");
  41. return resultValue;
  42. }
  43. List<byte> sendByteList = new List<byte>
  44. {
  45. slaveAddress, // 从站地址
  46. 0x01, // 功能码:读线圈
  47. };
  48. //设置起始地址的高位和低位
  49. sendByteList.AddRange(BitConverter.GetBytes(startAddress).Reverse());
  50. //设置读取几个线圈的高位和低位
  51. sendByteList.AddRange(BitConverter.GetBytes(numberOfPoints).Reverse());
  52. //获取CRC校验码
  53. byte[] getCRC = sendByteList.ToArray();
  54. byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length);
  55. sendByteList.Add(resultCRC[0]);
  56. sendByteList.Add(resultCRC[1]);
  57. byte[] sendByte = sendByteList.ToArray();
  58. int maxRetries = 3; // 最大重试次数
  59. int attempt = 0;
  60. byte[] response = null;
  61. List<byte> responseData = new List<byte>();
  62. while (attempt < maxRetries)
  63. {
  64. if (_serialPort.IsOpen)
  65. {
  66. // 每次重试前清空缓冲区
  67. responseData.Clear();
  68. // 清除输入缓冲区残留数据
  69. _serialPort.DiscardInBuffer();
  70. }
  71. try
  72. {
  73. _serialPort.Write(sendByte, 0, sendByte.Length);
  74. //使用时间去轮询查询数据
  75. DateTime startTime = DateTime.Now;
  76. while ((DateTime.Now - startTime).TotalMilliseconds < _serialPort.ReadTimeout)
  77. {
  78. int bytesToRead = _serialPort.BytesToRead;
  79. if (bytesToRead > 0)
  80. {
  81. byte[] buffer = new byte[bytesToRead];
  82. int bytesRead = _serialPort.Read(buffer, 0, bytesToRead);
  83. responseData.AddRange(buffer.Take(bytesRead));
  84. }
  85. //延迟20毫秒,节约cpu资源
  86. Thread.Sleep(20);
  87. }
  88. response = responseData.ToArray();
  89. if (response.Length == 0)
  90. {
  91. attempt++;
  92. continue;
  93. }
  94. resultValue = ParseCoilresponse(sendByte, response, numberOfPoints);
  95. return resultValue;
  96. }
  97. catch (Exception)
  98. {
  99. }
  100. finally
  101. {
  102. attempt++;
  103. }
  104. }
  105. try
  106. {
  107. //将操作失败的指令,插入数据库
  108. string RequestStr = ByteArrayToString(sendByte);
  109. ModbusLog m = new ModbusLog();
  110. m.OperationType = "读线圈";
  111. m.ResponseData = "";
  112. m.RequestData = RequestStr;
  113. _modbusDbContext.Add(m);
  114. _modbusDbContext.SaveChanges();
  115. }
  116. catch (Exception)
  117. {
  118. MessageBox.Show(ErrorCode.ErrorCode.DatabaseException.ToString());
  119. }
  120. return resultValue;
  121. }
  122. /// <summary>
  123. /// 用来解析返回的数据
  124. /// </summary>
  125. /// <param name="sendByte"></param>
  126. /// <param name="response"></param>
  127. /// <param name="numberOfPoints"></param>
  128. /// <returns></returns>
  129. public bool[] ParseCoilresponse(byte[] sendByte, byte[] response, ushort numberOfPoints)
  130. {
  131. try
  132. {
  133. //将操作的指令,插入数据库
  134. string RequestStr = ByteArrayToString(sendByte);
  135. string responseStr = ByteArrayToString(response);
  136. ModbusLog modbusLog = new ModbusLog();
  137. modbusLog.OperationType = "读线圈";
  138. modbusLog.ResponseData = responseStr;
  139. modbusLog.RequestData = RequestStr;
  140. _modbusDbContext.Add(modbusLog);
  141. _modbusDbContext.SaveChanges();
  142. }
  143. catch (Exception)
  144. {
  145. MessageBox.Show(ErrorCode.ErrorCode.DatabaseException.ToString());
  146. }
  147. //判断发送回来的数据是否正确
  148. if (CheckData.CheckResponse(response))
  149. {
  150. // 移除前3个字节和后2个校验码字节,并反转数组
  151. var processedBytes = response
  152. .Skip(3) // 移除前3个字节
  153. .Take(response.Count() - 5) // 保留中间部分 (总长度-5)
  154. .Reverse() // 反转字节顺序
  155. .ToList();
  156. // 将每个字节转换为8位二进制字符串,不足8位时左边补0
  157. string binaryString = string.Concat(
  158. processedBytes.Select(b => Convert.ToString(b, 2).PadLeft(8, '0'))
  159. );
  160. // 反转二进制字符串中的所有位,并转换为布尔数组
  161. return binaryString
  162. .Reverse() // 反转所有位的顺序
  163. .Take(numberOfPoints) // 取指定数量的位
  164. .Select(c => c == '1') // 将字符'1'转换为true,其他转换为false
  165. .ToArray();
  166. }
  167. else
  168. {
  169. return null;
  170. }
  171. }
  172. /// <summary>
  173. /// 写入多个线圈操作
  174. /// </summary>
  175. /// <param name="slaveAddress">从站地址</param>
  176. /// <param name="startAddress">起始地址</param>
  177. /// <param name="data">写入的数据</param>
  178. public void WriteCoil(byte slaveAddress, ushort startAddress, bool[] data)
  179. {
  180. if (!_serialPort.IsOpen)
  181. {
  182. MessageBox.Show("串口没有链接,请先链接");
  183. return;
  184. }
  185. List<byte> sendByteList = new List<byte>
  186. {
  187. slaveAddress, // 从站地址
  188. 0x0F, // 功能码:写多个线圈
  189. };
  190. // 添加起始地址(高字节在前,低字节在后)
  191. sendByteList.AddRange(BitConverter.GetBytes(startAddress).Reverse());
  192. // 添加线圈数量(高字节在前,低字节在后)
  193. ushort coilCount = (ushort)data.Length;
  194. sendByteList.AddRange(BitConverter.GetBytes(coilCount).Reverse());
  195. // 计算所需字节数(每8个线圈占1字节)
  196. int byteCount = (coilCount + 7) / 8;
  197. sendByteList.Add((byte)byteCount);
  198. // 将布尔数组转换为字节数组(每个位表示一个线圈状态)
  199. byte[] coilBytes = ConvertBoolsToBytes(data);
  200. sendByteList.AddRange(coilBytes);
  201. //获取CRC校验码
  202. byte[] getCRC = sendByteList.ToArray();
  203. byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length);
  204. sendByteList.Add(resultCRC[0]);
  205. sendByteList.Add(resultCRC[1]);
  206. byte[] sendByte = sendByteList.ToArray();
  207. int maxRetries = 3; // 最大重试次数
  208. int attempt = 0;
  209. byte[] response = null;
  210. List<byte> responseData = new List<byte>();
  211. while (attempt < maxRetries)
  212. {
  213. if (_serialPort.IsOpen)
  214. {
  215. // 每次重试前清空缓冲区
  216. responseData.Clear();
  217. // 清除输入缓冲区残留数据
  218. _serialPort.DiscardInBuffer();
  219. }
  220. try
  221. {
  222. _serialPort.Write(sendByte, 0, sendByte.Length);
  223. //使用时间去轮询查询数据
  224. DateTime startTime = DateTime.Now;
  225. while ((DateTime.Now - startTime).TotalMilliseconds < _serialPort.ReadTimeout)
  226. {
  227. int bytesToRead = _serialPort.BytesToRead;
  228. if (bytesToRead > 0)
  229. {
  230. byte[] buffer = new byte[bytesToRead];
  231. int bytesRead = _serialPort.Read(buffer, 0, bytesToRead);
  232. responseData.AddRange(buffer.Take(bytesRead));
  233. }
  234. //延迟20毫秒,节约cpu资源
  235. Thread.Sleep(20);
  236. }
  237. response = responseData.ToArray();
  238. if (response.Length != 0)
  239. {
  240. break;
  241. }
  242. }
  243. catch (Exception)
  244. {
  245. }
  246. finally
  247. {
  248. attempt++;
  249. }
  250. }
  251. try
  252. {
  253. //将操作的指令,插入数据库
  254. string RequestStr = ByteArrayToString(sendByte);
  255. string responseStr = ByteArrayToString(response);
  256. ModbusLog modbusLog = new ModbusLog();
  257. modbusLog.OperationType = "写线圈";
  258. modbusLog.ResponseData = responseStr;
  259. modbusLog.RequestData = RequestStr;
  260. _modbusDbContext.Add(modbusLog);
  261. _modbusDbContext.SaveChanges();
  262. }
  263. catch (Exception)
  264. {
  265. MessageBox.Show(ErrorCode.ErrorCode.DatabaseException.ToString());
  266. }
  267. //判断发送回来的数据是否正确
  268. CheckData.CheckResponse(response);
  269. }
  270. /// <summary>
  271. /// 将布尔数组转换为字节数组(每个位表示一个线圈状态)
  272. /// </summary>
  273. /// <param name="data"></param>
  274. /// <returns></returns>
  275. private static byte[] ConvertBoolsToBytes(bool[] data)
  276. {
  277. int byteCount = (data.Length + 7) / 8;
  278. byte[] result = new byte[byteCount];
  279. for (int i = 0; i < data.Length; i++)
  280. {
  281. if (data[i])
  282. {
  283. int byteIndex = i / 8;
  284. int bitIndex = i % 8;
  285. result[byteIndex] |= (byte)(1 << bitIndex);
  286. }
  287. }
  288. return result;
  289. }
  290. /// <summary>
  291. /// 读寄存器操作
  292. /// </summary>
  293. /// <param name="slaveAddress"></param>
  294. /// <param name="startAddress"></param>
  295. /// <param name="numberOfPoints"></param>
  296. /// <returns></returns>
  297. public ushort[] ReadRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  298. {
  299. ushort[] resultValue = null;
  300. if (!_serialPort.IsOpen)
  301. {
  302. MessageBox.Show("串口没有链接,请先链接");
  303. return resultValue;
  304. }
  305. var sendByteList = new List<byte>(8)
  306. {
  307. slaveAddress, // 从站地址
  308. 0x03, // 功能码:读保持寄存器
  309. };
  310. // 添加起始地址(高字节在前)
  311. sendByteList.AddRange(BitConverter.GetBytes(startAddress).Reverse());
  312. // 添加读取点数(高字节在前)
  313. sendByteList.AddRange(BitConverter.GetBytes(numberOfPoints).Reverse());
  314. //获取CRC校验码
  315. byte[] getCRC = sendByteList.ToArray();
  316. byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length);
  317. sendByteList.Add(resultCRC[0]);
  318. sendByteList.Add(resultCRC[1]);
  319. byte[] sendByte = sendByteList.ToArray();
  320. int maxRetries = 3; // 最大重试次数
  321. int attempt = 0;
  322. List<byte> responseData = new List<byte>();
  323. byte[] response = null;
  324. while (attempt < maxRetries)
  325. {
  326. if (_serialPort.IsOpen)
  327. {
  328. // 每次重试前清空缓冲区
  329. responseData.Clear();
  330. // 清除输入缓冲区残留数据
  331. _serialPort.DiscardInBuffer();
  332. }
  333. try
  334. {
  335. _serialPort.Write(sendByte, 0, sendByte.Length);
  336. //使用时间去轮询查询数据
  337. DateTime startTime = DateTime.Now;
  338. while ((DateTime.Now - startTime).TotalMilliseconds < _serialPort.ReadTimeout)
  339. {
  340. int bytesToRead = _serialPort.BytesToRead;
  341. if (bytesToRead > 0)
  342. {
  343. byte[] buffer = new byte[bytesToRead];
  344. int bytesRead = _serialPort.Read(buffer, 0, bytesToRead);
  345. responseData.AddRange(buffer.Take(bytesRead));
  346. }
  347. //延迟20毫秒,节约cpu资源
  348. Thread.Sleep(20);
  349. }
  350. response = responseData.ToArray();
  351. if (response.Length == 0)
  352. {
  353. continue;
  354. }
  355. resultValue = ParseRegistersresponse(sendByte, response, numberOfPoints);
  356. return resultValue;
  357. }
  358. catch (Exception)
  359. {
  360. }
  361. finally
  362. {
  363. attempt++;
  364. }
  365. }
  366. try
  367. {
  368. //将操作失败的指令,插入数据库
  369. string RequestStr = ByteArrayToString(sendByte);
  370. ModbusLog modbusLog = new ModbusLog();
  371. modbusLog.OperationType = "读寄存器";
  372. modbusLog.ResponseData = "";
  373. modbusLog.RequestData = RequestStr;
  374. _modbusDbContext.Add(modbusLog);
  375. _modbusDbContext.SaveChanges();
  376. }
  377. catch (Exception)
  378. {
  379. MessageBox.Show(ErrorCode.ErrorCode.DatabaseException.ToString());
  380. }
  381. return resultValue;
  382. }
  383. /// <summary>
  384. /// 解析读取到的操作
  385. /// </summary>
  386. /// <param name="sendByte"></param>
  387. /// <param name="response"></param>
  388. /// <param name="numberOfPoints"></param>
  389. /// <returns></returns>
  390. public ushort[] ParseRegistersresponse(byte[] sendByte, byte[] response, ushort numberOfPoints)
  391. {
  392. try
  393. {
  394. //将操作的指令,插入数据库
  395. string RequestStr = ByteArrayToString(sendByte);
  396. string responseStr = ByteArrayToString(response);
  397. ModbusLog modbusLog = new ModbusLog();
  398. modbusLog.OperationType = "读寄存器";
  399. modbusLog.ResponseData = responseStr;
  400. modbusLog.RequestData = RequestStr;
  401. _modbusDbContext.Add(modbusLog);
  402. _modbusDbContext.SaveChanges();
  403. }
  404. catch (Exception)
  405. {
  406. MessageBox.Show(ErrorCode.ErrorCode.DatabaseException.ToString());
  407. }
  408. //判断发送回来的数据是否正确
  409. if (CheckData.CheckResponse(response))
  410. {
  411. ushort[] registers = new ushort[numberOfPoints];
  412. for (int i = 0; i < numberOfPoints; i++)
  413. {
  414. int pos = 3 + i * 2;
  415. // 大端序转换 (高位在前)
  416. registers[i] = (ushort)((response[pos] << 8) | response[pos + 1]);
  417. }
  418. return registers;
  419. }
  420. else
  421. {
  422. return null;
  423. }
  424. }
  425. /// <summary>
  426. /// 写寄存器
  427. /// </summary>
  428. /// <param name="slaveAddress"></param>
  429. /// <param name="startAddress"></param>
  430. /// <param name="data"></param>
  431. public void WriteRegisters(byte slaveAddress, ushort startAddress, ushort[] data)
  432. {
  433. if (!_serialPort.IsOpen)
  434. {
  435. MessageBox.Show("串口没有链接,请先链接");
  436. return;
  437. }
  438. // 计算字节数 (每个寄存器占2字节)
  439. int byteCount = (data.Length) * 2;
  440. // 预分配列表容量,避免多次扩容
  441. var sendByteList = new List<byte>(9 + byteCount)
  442. {
  443. slaveAddress, // 从站地址
  444. 0x10, // 功能码:写多个保持寄存器
  445. };
  446. // 添加起始地址(高字节在前)
  447. sendByteList.AddRange(BitConverter.GetBytes(startAddress).Reverse());
  448. // 添加寄存器数量(高字节在前)
  449. sendByteList.AddRange(BitConverter.GetBytes((ushort)data.Length).Reverse());
  450. // 添加字节数
  451. sendByteList.Add((byte)byteCount);
  452. // 添加寄存器数据(每个寄存器2字节,高字节在前)
  453. foreach (ushort value in data)
  454. {
  455. sendByteList.AddRange(BitConverter.GetBytes(value).Reverse());
  456. }
  457. //获取CRC校验码
  458. byte[] getCRC = sendByteList.ToArray();
  459. byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length);
  460. sendByteList.Add(resultCRC[0]);
  461. sendByteList.Add(resultCRC[1]);
  462. byte[] sendByte = sendByteList.ToArray();
  463. int maxRetries = 3; // 最大重试次数
  464. int attempt = 0;
  465. byte[] response = null;
  466. List<byte> responseData = new List<byte>();
  467. while (attempt < maxRetries)
  468. {
  469. if (_serialPort.IsOpen)
  470. {
  471. // 每次重试前清空缓冲区
  472. responseData.Clear();
  473. // 清除输入缓冲区残留数据
  474. _serialPort.DiscardInBuffer();
  475. }
  476. try
  477. {
  478. _serialPort.Write(sendByte, 0, sendByte.Length);
  479. //使用时间去轮询查询数据
  480. DateTime startTime = DateTime.Now;
  481. while ((DateTime.Now - startTime).TotalMilliseconds < _serialPort.ReadTimeout)
  482. {
  483. int bytesToRead = _serialPort.BytesToRead;
  484. if (bytesToRead > 0)
  485. {
  486. byte[] buffer = new byte[bytesToRead];
  487. int bytesRead = _serialPort.Read(buffer, 0, bytesToRead);
  488. responseData.AddRange(buffer.Take(bytesRead));
  489. }
  490. //延迟20毫秒,节约cpu资源
  491. Thread.Sleep(20);
  492. }
  493. response = responseData.ToArray();
  494. if (response.Length != 0)
  495. {
  496. break;
  497. }
  498. }
  499. catch (Exception)
  500. {
  501. }
  502. finally
  503. {
  504. attempt++;
  505. }
  506. }
  507. try
  508. {
  509. //将操作的指令,插入数据库
  510. string RequestStr = ByteArrayToString(sendByte);
  511. string responseStr = ByteArrayToString(response);
  512. ModbusLog modbusLog = new ModbusLog();
  513. modbusLog.OperationType = "写寄存器";
  514. modbusLog.ResponseData = responseStr;
  515. modbusLog.RequestData = RequestStr;
  516. _modbusDbContext.Add(modbusLog);
  517. _modbusDbContext.SaveChanges();
  518. }
  519. catch (Exception)
  520. {
  521. MessageBox.Show("0x15:数据库插入错误");
  522. }
  523. //判断发送回来的数据是否正确
  524. CheckData.CheckResponse(response);
  525. }
  526. /// <summary>
  527. /// 将数组转换为字符串
  528. /// </summary>
  529. /// <param name="byteArray"></param>
  530. /// <returns></returns>
  531. static string ByteArrayToString(byte[] byteArray)
  532. {
  533. if (byteArray == null || byteArray.Length == 0)
  534. return string.Empty;
  535. StringBuilder sb = new StringBuilder();
  536. // 处理第一个元素(无前导空格)
  537. sb.Append(byteArray[0].ToString("X2"));
  538. // 处理后续元素(每个前面加空格)
  539. for (int i = 1; i < byteArray.Length; i++)
  540. {
  541. sb.Append(' ');
  542. sb.Append(byteArray[i].ToString("X2"));
  543. }
  544. return sb.ToString();
  545. }
  546. }
  547. }