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.

410 lines
15 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.Tasks;
  9. using System.Windows;
  10. namespace ModbusDemo.Device
  11. {
  12. public class ModbusRTU : IModbusRTU
  13. {
  14. private SerialPort _serialPort;
  15. //用于操作数据库
  16. private ModbusDbContext _modbusDbContext;
  17. public ModbusRTU(SerialPort serialPort, ModbusDbContext modbusDbContext)
  18. {
  19. _serialPort = serialPort;
  20. _modbusDbContext = modbusDbContext;
  21. }
  22. /// <summary>
  23. /// 用来发送读写线圈的指令
  24. /// </summary>
  25. /// <param name="slaveAddress"></param>
  26. /// <param name="startAddress"></param>
  27. /// <param name="numberOfPoints"></param>
  28. /// <returns></returns>
  29. public bool[] ReadCoil(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  30. {
  31. bool[] resultValue = null;
  32. if (!_serialPort.IsOpen)
  33. {
  34. MessageBox.Show("串口没有链接,请先链接");
  35. return resultValue;
  36. }
  37. List<byte> sendByteList = new List<byte>();
  38. //设置从站地址
  39. sendByteList.Add(slaveAddress);
  40. //设置功能码
  41. sendByteList.Add(0x01);
  42. //设置起始地址的高位和低位
  43. sendByteList.Add(BitConverter.GetBytes(startAddress)[1]);
  44. sendByteList.Add(BitConverter.GetBytes(startAddress)[0]);
  45. //设置读取几个线圈的高位和低位
  46. sendByteList.Add(BitConverter.GetBytes(numberOfPoints)[1]);
  47. sendByteList.Add(BitConverter.GetBytes(numberOfPoints)[0]);
  48. //获取CRC校验码
  49. byte[] getCRC = sendByteList.ToArray();
  50. byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length);
  51. sendByteList.Add(resultCRC[0]);
  52. sendByteList.Add(resultCRC[1]);
  53. byte[] sendByte = sendByteList.ToArray();
  54. int maxRetries = 3; // 最大重试次数
  55. int attempt = 0;
  56. while (attempt < maxRetries)
  57. {
  58. try
  59. {
  60. // 清空接收缓冲区(避免残留数据干扰)
  61. _serialPort.DiscardInBuffer();
  62. _serialPort.Write(sendByte, 0, sendByte.Length);
  63. Thread.Sleep(300);
  64. byte[] response = new byte[_serialPort.BytesToRead];
  65. _serialPort.Read(response, 0, _serialPort.BytesToRead);
  66. if (response[0] == 0x00)
  67. {
  68. continue;
  69. }
  70. resultValue = ParseCoilresponse(sendByte, response, numberOfPoints);
  71. return resultValue;
  72. }
  73. catch (Exception)
  74. {
  75. attempt++;
  76. }
  77. }
  78. //将操作失败的指令,插入数据库
  79. string RequestStr = ByteArrayToString(sendByte);
  80. ModbusLog m = new ModbusLog();
  81. m.OperationType = "读线圈";
  82. m.ResponseData = "";
  83. m.RequestData = RequestStr;
  84. _modbusDbContext.Add(m);
  85. _modbusDbContext.SaveChanges();
  86. return resultValue;
  87. }
  88. public bool[] ParseCoilresponse(byte[] sendByte, byte[] response, ushort numberOfPoints)
  89. {
  90. //将操作的指令,插入数据库
  91. string RequestStr = ByteArrayToString(sendByte);
  92. string responseStr = ByteArrayToString(response);
  93. ModbusLog m = new ModbusLog();
  94. m.OperationType = "读线圈";
  95. m.ResponseData = RequestStr;
  96. m.RequestData = responseStr;
  97. _modbusDbContext.Add(m);
  98. _modbusDbContext.SaveChanges();
  99. //判断发送回来的数据是否正确
  100. if(CheckData.CheckResponse(response))
  101. {
  102. if (!CRCUitl.ValidateCRC(response))
  103. {
  104. MessageBox.Show("0x14:CRC校验错误");
  105. return null;
  106. }
  107. }
  108. List<byte> responseList = new List<byte>(response);
  109. //移除前两位
  110. responseList.RemoveRange(0, 3);
  111. //移除后两位校验码
  112. responseList.RemoveRange(responseList.Count - 2, 2);
  113. responseList.Reverse();
  114. //数组反转之后,转为二进制字符
  115. List<string> StringList = responseList.Select(m => Convert.ToString(m, 2)).ToList();
  116. var result = "";
  117. foreach (var item in StringList)
  118. {
  119. result += item.PadLeft(8, '0');
  120. }
  121. //先将字符串转为数组,再反转,再转为字节数组
  122. char[] chars = result.ToArray().Reverse<char>().ToArray();
  123. bool[] ultimately = new bool[numberOfPoints];
  124. for (int i = 0; i < numberOfPoints; i++)
  125. {
  126. if (chars[i] == '1')
  127. {
  128. ultimately[i] = true;
  129. }
  130. else
  131. {
  132. ultimately[i] = false;
  133. }
  134. }
  135. return ultimately;
  136. }
  137. /// <summary>
  138. /// 写入多个线圈操作
  139. /// </summary>
  140. /// <param name="slaveAddress">从站地址</param>
  141. /// <param name="startAddress">起始地址</param>
  142. /// <param name="data">写入的数据</param>
  143. public bool WriteCoil(byte slaveAddress, ushort startAddress, bool[] data)
  144. {
  145. if (!_serialPort.IsOpen)
  146. {
  147. MessageBox.Show("串口没有链接,请先链接");
  148. return false;
  149. }
  150. List<byte> sendByteList = new List<byte>();
  151. //设置从站地址
  152. sendByteList.Add(slaveAddress);
  153. //设置功能码
  154. sendByteList.Add(0x0F);
  155. //设置起始地址的高位和低位
  156. sendByteList.Add(BitConverter.GetBytes(startAddress)[1]);
  157. sendByteList.Add(BitConverter.GetBytes(startAddress)[0]);
  158. //设置写入几个线圈
  159. sendByteList.Add(BitConverter.GetBytes(data.Length)[1]);
  160. sendByteList.Add(BitConverter.GetBytes(data.Length)[0]);
  161. // 计算字节数
  162. int byteCount = (data.Length + 7) / 8;
  163. sendByteList.Add((byte)byteCount);
  164. // 初始化字节数组
  165. byte[] coilBytes = new byte[byteCount];
  166. // 把bool数组转换为字节
  167. for (int i = 0; i < data.Length; i++)
  168. {
  169. int byteIndex = i / 8;
  170. int bitIndex = i % 8;
  171. if (data[i])
  172. {
  173. coilBytes[byteIndex] |= (byte)(1 << bitIndex);
  174. }
  175. }
  176. sendByteList.AddRange(coilBytes);
  177. //获取CRC校验码
  178. byte[] getCRC = sendByteList.ToArray();
  179. byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length);
  180. sendByteList.Add(resultCRC[0]);
  181. sendByteList.Add(resultCRC[1]);
  182. byte[] sendByte = sendByteList.ToArray();
  183. int maxRetries = 3; // 最大重试次数
  184. int attempt = 0;
  185. byte[] response = null;
  186. while (attempt < maxRetries)
  187. {
  188. try
  189. {
  190. // 清空接收缓冲区(避免残留数据干扰)
  191. _serialPort.DiscardInBuffer();
  192. _serialPort.Write(sendByte, 0, sendByte.Length);
  193. Thread.Sleep(300);
  194. response = new byte[_serialPort.BytesToRead];
  195. _serialPort.Read(response, 0, _serialPort.BytesToRead);
  196. if (response[0] != 0x00)
  197. {
  198. break;
  199. }
  200. }
  201. catch (Exception)
  202. {
  203. attempt++;
  204. }
  205. }
  206. //将操作的指令,插入数据库
  207. string RequestStr = ByteArrayToString(sendByte);
  208. string responseStr = ByteArrayToString(response);
  209. ModbusLog m = new ModbusLog();
  210. m.OperationType = "写线圈";
  211. m.ResponseData = responseStr;
  212. m.RequestData = RequestStr;
  213. _modbusDbContext.Add(m);
  214. _modbusDbContext.SaveChanges();
  215. //判断发送回来的数据是否正确
  216. if (CheckData.CheckResponse(response))
  217. {
  218. if (!CRCUitl.ValidateCRC(response))
  219. {
  220. MessageBox.Show("0x14:CRC校验错误");
  221. return false;
  222. }
  223. }
  224. return true;
  225. }
  226. public ushort[] ReadRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  227. {
  228. if (!_serialPort.IsOpen)
  229. {
  230. MessageBox.Show("串口没有链接,请先链接");
  231. return null;
  232. }
  233. List<byte> sendByteList = new List<byte>();
  234. //设置从站地址
  235. sendByteList.Add(slaveAddress);
  236. //设置功能码
  237. sendByteList.Add(0x03);
  238. //设置起始地址的高位和低位
  239. sendByteList.Add(BitConverter.GetBytes(startAddress)[1]);
  240. sendByteList.Add(BitConverter.GetBytes(startAddress)[0]);
  241. //设置读取几个线圈的高位和低位
  242. sendByteList.Add(BitConverter.GetBytes(numberOfPoints)[1]);
  243. sendByteList.Add(BitConverter.GetBytes(numberOfPoints)[0]);
  244. //获取CRC校验码
  245. byte[] getCRC = sendByteList.ToArray();
  246. byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length);
  247. sendByteList.Add(resultCRC[0]);
  248. sendByteList.Add(resultCRC[1]);
  249. byte[] sendByte = sendByteList.ToArray();
  250. if (_serialPort.IsOpen)
  251. {
  252. // 清空接收缓冲区(避免残留数据干扰)
  253. _serialPort.DiscardInBuffer();
  254. _serialPort.Write(sendByte, 0, sendByte.Length);
  255. Thread.Sleep(300);
  256. byte[] response = new byte[_serialPort.BytesToRead];
  257. _serialPort.Read(response, 0, _serialPort.BytesToRead);
  258. return ParseRegistersresponse(sendByte, response, numberOfPoints);
  259. }
  260. return null;
  261. }
  262. public ushort[] ParseRegistersresponse(byte[] sendByte, byte[] response, ushort numberOfPoints)
  263. {
  264. //将操作的指令,插入数据库
  265. string RequestStr = ByteArrayToString(sendByte);
  266. string responseStr = ByteArrayToString(response);
  267. ModbusLog m = new ModbusLog();
  268. m.OperationType = "读寄存器";
  269. m.ResponseData = responseStr;
  270. m.RequestData = responseStr;
  271. _modbusDbContext.Add(m);
  272. _modbusDbContext.SaveChanges();
  273. //判断发送回来的数据是否正确
  274. CheckData.CheckResponse(response);
  275. if (!CRCUitl.ValidateCRC(response))
  276. {
  277. MessageBox.Show("0x14:CRC校验错误");
  278. return null;
  279. }
  280. ushort[] registers = new ushort[numberOfPoints];
  281. for (int i = 0; i < numberOfPoints; i++)
  282. {
  283. int pos = 3 + i * 2;
  284. // 大端序转换 (高位在前)
  285. registers[i] = (ushort)((response[pos] << 8) | response[pos + 1]);
  286. }
  287. return registers;
  288. }
  289. public void WriteRegisters(byte slaveAddress, ushort startAddress, ushort[] data)
  290. {
  291. List<byte> sendByteList = new List<byte>();
  292. //设置从站地址
  293. sendByteList.Add(slaveAddress);
  294. //设置功能码
  295. sendByteList.Add(0x10);
  296. //设置起始地址的高位和低位
  297. sendByteList.Add(BitConverter.GetBytes(startAddress)[1]);
  298. sendByteList.Add(BitConverter.GetBytes(startAddress)[0]);
  299. //设置写入几个线圈
  300. sendByteList.Add(BitConverter.GetBytes(data.Length)[1]);
  301. sendByteList.Add(BitConverter.GetBytes(data.Length)[0]);
  302. // 计算字节数
  303. int byteCount = (data.Length + 7) / 8;
  304. sendByteList.Add((byte)byteCount);
  305. foreach (ushort value in data)
  306. {
  307. byte[] valueBytes = BitConverter.GetBytes(value);
  308. // 大端序:高字节在前,低字节在后
  309. sendByteList.Add(valueBytes[1]);
  310. sendByteList.Add(valueBytes[0]);
  311. }
  312. //获取CRC校验码
  313. byte[] getCRC = sendByteList.ToArray();
  314. byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length);
  315. sendByteList.Add(resultCRC[0]);
  316. sendByteList.Add(resultCRC[1]);
  317. byte[] sendByte = sendByteList.ToArray();
  318. if (_serialPort.IsOpen)
  319. {
  320. // 清空接收缓冲区(避免残留数据干扰)
  321. _serialPort.DiscardInBuffer();
  322. _serialPort.Write(sendByte, 0, sendByte.Length);
  323. Thread.Sleep(300);
  324. byte[] response = new byte[_serialPort.BytesToRead];
  325. _serialPort.Read(response, 0, _serialPort.BytesToRead);
  326. //将操作的指令,插入数据库
  327. string RequestStr = ByteArrayToString(sendByte);
  328. string responseStr = ByteArrayToString(response);
  329. ModbusLog m = new ModbusLog();
  330. m.OperationType = "写寄存器";
  331. m.ResponseData = responseStr;
  332. m.RequestData = responseStr;
  333. _modbusDbContext.Add(m);
  334. _modbusDbContext.SaveChanges();
  335. //判断发送回来的数据是否正确
  336. CheckData.CheckResponse(response);
  337. if (!CRCUitl.ValidateCRC(response))
  338. {
  339. MessageBox.Show("0x14:CRC校验错误");
  340. }
  341. }
  342. }
  343. static string ByteArrayToString(byte[] byteArray)
  344. {
  345. if (byteArray == null || byteArray.Length == 0)
  346. return string.Empty;
  347. System.Text.StringBuilder sb = new System.Text.StringBuilder();
  348. // 处理第一个元素(无前导空格)
  349. sb.Append(byteArray[0].ToString("X2"));
  350. // 处理后续元素(每个前面加空格)
  351. for (int i = 1; i < byteArray.Length; i++)
  352. {
  353. sb.Append(' ');
  354. sb.Append(byteArray[i].ToString("X2"));
  355. }
  356. return sb.ToString();
  357. }
  358. }
  359. }