Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

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