Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

525 řádky
19 KiB

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