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.

522 regels
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. using static System.Runtime.InteropServices.JavaScript.JSType;
  13. namespace ModbusDemo.Device
  14. {
  15. public class ModbusRTU : IModbusRTU
  16. {
  17. //串口
  18. private SerialPort _serialPort;
  19. //用于操作数据库
  20. private ModbusDbContext _modbusDbContext;
  21. //TODO,修改
  22. //private SerialPortAdapter _portAdapter;
  23. public ModbusRTU(SerialPort serialPort, ModbusDbContext modbusDbContext)
  24. {
  25. _serialPort = serialPort;
  26. _modbusDbContext = modbusDbContext;
  27. //_portAdapter = new SerialPortAdapter(serialPort);
  28. }
  29. /// <summary>
  30. /// 用来发送读写线圈的指令
  31. /// </summary>
  32. /// <param name="slaveAddress"></param>
  33. /// <param name="startAddress"></param>
  34. /// <param name="numberOfPoints"></param>
  35. /// <returns></returns>
  36. public bool[] ReadCoil(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  37. {
  38. bool[] resultValue = null;
  39. if (!_serialPort.IsOpen)
  40. {
  41. MessageBox.Show("串口没有链接,请先链接");
  42. return resultValue;
  43. }
  44. //构建发送报文
  45. byte[] sendMessage = ConstructMessage<ushort>(0x01, slaveAddress, startAddress, numberOfPoints,ProcessReadData);
  46. //发送报文
  47. byte[] responseMessage = SendMessage(sendMessage);
  48. //判断发送过来的字节是否正确
  49. if (CheckResponse(responseMessage))
  50. {
  51. // 移除前3个字节和后2个校验码字节,并反转数组
  52. var processedBytes = responseMessage
  53. .Skip(3) // 移除前3个字节
  54. .Take(responseMessage.Count() - 5) // 保留中间部分 (总长度-5)
  55. .Reverse() // 反转字节顺序
  56. .ToList();
  57. // 将每个字节转换为8位二进制字符串,不足8位时左边补0
  58. string binaryString = string.Concat(
  59. processedBytes.Select(b => Convert.ToString(b, 2).PadLeft(8, '0'))
  60. );
  61. // 反转二进制字符串中的所有位,并转换为布尔数组
  62. resultValue = binaryString
  63. .Reverse() // 反转所有位的顺序
  64. .Take(numberOfPoints) // 取指定数量的位
  65. .Select(c => c == '1') // 将字符'1'转换为true,其他转换为false
  66. .ToArray();
  67. }
  68. //将数据插入到数据库
  69. InsertDatabase(sendMessage, responseMessage,"读线圈");
  70. return resultValue;
  71. }
  72. /// <summary>
  73. /// 写入多个线圈操作
  74. /// </summary>
  75. /// <param name="slaveAddress">从站地址</param>
  76. /// <param name="startAddress">起始地址</param>
  77. /// <param name="data">写入的数据</param>
  78. public void WriteCoil(byte slaveAddress, ushort startAddress, bool[] data)
  79. {
  80. if (!_serialPort.IsOpen)
  81. {
  82. MessageBox.Show("串口没有链接,请先链接");
  83. return;
  84. }
  85. //构建报文
  86. byte[] sendMessage = ConstructMessage<bool[]>(0x0F, slaveAddress, startAddress, data, ProcessCoilData);
  87. //发送报文
  88. byte[] responseMessage = SendMessage(sendMessage);
  89. //将数据插入到数据库
  90. InsertDatabase(sendMessage, responseMessage,"写线圈");
  91. //判断发送回来的数据是否正确
  92. CheckResponse(responseMessage);
  93. }
  94. public ushort[] ReadRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  95. {
  96. ushort[] resultValue = null;
  97. if (!_serialPort.IsOpen)
  98. {
  99. MessageBox.Show("串口没有链接,请先链接");
  100. return resultValue;
  101. }
  102. //构建发送报文
  103. byte[] sendMessage = ConstructMessage<ushort>(0x03, slaveAddress, startAddress, numberOfPoints, ProcessReadData);
  104. //发送报文
  105. byte[] responseMessage = SendMessage(sendMessage);
  106. //将数据插入到数据库
  107. InsertDatabase(sendMessage, responseMessage,"读寄存器");
  108. //判断发送回来的数据是否正确
  109. if (CheckResponse(responseMessage))
  110. {
  111. ushort[] registers = new ushort[numberOfPoints];
  112. for (int i = 0; i < numberOfPoints; i++)
  113. {
  114. int pos = 3 + i * 2;
  115. // 大端序转换 (高位在前)
  116. registers[i] = (ushort)((responseMessage[pos] << 8) | responseMessage[pos + 1]);
  117. }
  118. resultValue = registers;
  119. }
  120. return resultValue;
  121. }
  122. /// <summary>
  123. /// 写寄存器
  124. /// </summary>
  125. /// <param name="slaveAddress"></param>
  126. /// <param name="startAddress"></param>
  127. /// <param name="data"></param>
  128. public void WriteRegisters(byte slaveAddress, ushort startAddress, ushort[] data)
  129. {
  130. if (!_serialPort.IsOpen)
  131. {
  132. MessageBox.Show("串口没有链接,请先链接");
  133. return;
  134. }
  135. //构建报文
  136. byte[] sendMessage = ConstructMessage<ushort[]>(0x10, slaveAddress, startAddress, data, ProcessRegisterData);
  137. //发送报文
  138. byte[] responseMessage = SendMessage(sendMessage);
  139. //将数据插入到数据库
  140. InsertDatabase(sendMessage, responseMessage, "写寄存器");
  141. //判断发送回来的数据是否正确
  142. CheckResponse(responseMessage);
  143. }
  144. ///
  145. // 定义委托处理不同数据类型的转换
  146. private delegate void DataProcessor<T>(List<byte> sendByteList, T data);
  147. /// <summary>
  148. /// 生成发送报文
  149. /// </summary>
  150. /// <typeparam name="T"></typeparam>
  151. /// <param name="Function"></param>
  152. /// <param name="slaveAddress"></param>
  153. /// <param name="startAddress"></param>
  154. /// <param name="data"></param>
  155. /// <param name="processor"></param>
  156. /// <returns></returns>
  157. private byte[] ConstructMessage<T>(byte Function, byte slaveAddress, ushort startAddress, T data, DataProcessor<T> processor)
  158. {
  159. byte[] resultValue = null;
  160. var sendByteList = new List<byte>
  161. {
  162. slaveAddress, // 从站地址
  163. Function // 功能码
  164. };
  165. // 添加起始地址(高字节在前,低字节在后)
  166. sendByteList.AddRange(BitConverter.GetBytes(startAddress).Reverse());
  167. // 调用处理器处理具体数据
  168. processor(sendByteList, data);
  169. //获取CRC校验码
  170. byte[] getCRC = sendByteList.ToArray();
  171. byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length);
  172. sendByteList.Add(resultCRC[0]);
  173. sendByteList.Add(resultCRC[1]);
  174. resultValue = sendByteList.ToArray();
  175. return resultValue;
  176. }
  177. /// <summary>
  178. /// 处理写入线圈数据
  179. /// </summary>
  180. /// <param name="sendByteList"></param>
  181. /// <param name="data"></param>
  182. private void ProcessCoilData(List<byte> sendByteList, bool[] data)
  183. {
  184. // 添加线圈数量(高字节在前,低字节在后)
  185. ushort coilCount = (ushort)data.Length;
  186. sendByteList.AddRange(BitConverter.GetBytes(coilCount).Reverse());
  187. // 计算所需字节数(每8个线圈占1字节)
  188. int byteCount = (coilCount + 7) / 8;
  189. sendByteList.Add((byte)byteCount);
  190. // 将布尔数组转换为字节数组(每个位表示一个线圈状态)
  191. byte[] result = new byte[byteCount];
  192. for (int i = 0; i < data.Length; i++)
  193. {
  194. if (data[i])
  195. {
  196. int byteIndex = i / 8;
  197. int bitIndex = i % 8;
  198. result[byteIndex] |= (byte)(1 << bitIndex);
  199. }
  200. }
  201. sendByteList.AddRange(result);
  202. }
  203. /// <summary>
  204. /// 处理读取几个线圈或者寄存器的转换
  205. /// </summary>
  206. /// <param name="sendByteList"></param>
  207. /// <param name="data"></param>
  208. private void ProcessReadData(List<byte> sendByteList, ushort data)
  209. {
  210. //设置读取几个线圈的高位和低位
  211. sendByteList.AddRange(BitConverter.GetBytes(data).Reverse());
  212. }
  213. /// <summary>
  214. /// 解析写寄存器的值
  215. /// </summary>
  216. /// <param name="sendByteList"></param>
  217. /// <param name="data"></param>
  218. private void ProcessRegisterData(List<byte> sendByteList, ushort[] data)
  219. {
  220. // 添加寄存器数量
  221. sendByteList.AddRange(BitConverter.GetBytes((ushort)data.Length).Reverse());
  222. // 计算字节数
  223. int byteCount = data.Length * 2;
  224. sendByteList.Add((byte)byteCount);
  225. // 添加寄存器数据
  226. foreach (ushort value in data)
  227. {
  228. sendByteList.AddRange(BitConverter.GetBytes(value).Reverse());
  229. }
  230. }
  231. /// <summary>
  232. /// 读寄存器操作
  233. /// </summary>
  234. /// <param name="slaveAddress"></param>
  235. /// <param name="startAddress"></param>
  236. /// <param name="numberOfPoints"></param>
  237. /// <returns></returns>
  238. /// <summary>
  239. /// 将构建好的报文发送出去
  240. /// </summary>
  241. /// <param name="sendByte"></param>
  242. /// <returns></returns>
  243. public byte[] SendMessage(byte[] sendByte)
  244. {
  245. int maxRetries = 3; // 最大重试次数
  246. int attempt = 0;
  247. byte[] response = null;
  248. List<byte> responseData = new List<byte>();
  249. while (attempt < maxRetries)
  250. {
  251. if (_serialPort.IsOpen)
  252. {
  253. // 每次重试前清空缓冲区
  254. responseData.Clear();
  255. // 清除输入缓冲区残留数据
  256. _serialPort.DiscardInBuffer();
  257. }
  258. try
  259. {
  260. _serialPort.Write(sendByte, 0, sendByte.Length);
  261. //使用时间去轮询查询数据
  262. DateTime startTime = DateTime.Now;
  263. while ((DateTime.Now - startTime).TotalMilliseconds < _serialPort.ReadTimeout)
  264. {
  265. int bytesToRead = _serialPort.BytesToRead;
  266. if (bytesToRead > 0)
  267. {
  268. byte[] buffer = new byte[bytesToRead];
  269. int bytesRead = _serialPort.Read(buffer, 0, bytesToRead);
  270. responseData.AddRange(buffer.Take(bytesRead));
  271. }
  272. //延迟20毫秒,节约cpu资源
  273. Thread.Sleep(20);
  274. }
  275. response = responseData.ToArray();
  276. if (response.Length == 0)
  277. {
  278. attempt++;
  279. continue;
  280. }
  281. return response;
  282. }
  283. catch (Exception)
  284. {
  285. }
  286. finally
  287. {
  288. attempt++;
  289. }
  290. }
  291. return null;
  292. }
  293. /// <summary>
  294. /// 将这次报文信息插入到数据库中
  295. /// </summary>
  296. /// <param name="sendByte"></param>
  297. /// <param name="response"></param>
  298. private void InsertDatabase(byte[] sendByte, byte[] response, string operationType)
  299. {
  300. try
  301. {
  302. //将操作的指令,插入数据库
  303. string RequestStr = ByteArrayToString(sendByte);
  304. string responseStr = ByteArrayToString(response);
  305. ModbusLog modbusLog = new ModbusLog();
  306. modbusLog.OperationType = operationType;
  307. modbusLog.ResponseData = responseStr;
  308. modbusLog.RequestData = RequestStr;
  309. _modbusDbContext.Add(modbusLog);
  310. _modbusDbContext.SaveChanges();
  311. }
  312. catch (Exception)
  313. {
  314. MessageBox.Show(ErrorCode.ErrorCode.DatabaseException.ToString());
  315. }
  316. }
  317. /// <summary>
  318. /// 将数组转换为字符串
  319. /// </summary>
  320. /// <param name="byteArray"></param>
  321. /// <returns></returns>
  322. static string ByteArrayToString(byte[] byteArray)
  323. {
  324. if (byteArray == null || byteArray.Length == 0)
  325. return string.Empty;
  326. StringBuilder sb = new StringBuilder();
  327. // 处理第一个元素(无前导空格)
  328. sb.Append(byteArray[0].ToString("X2"));
  329. // 处理后续元素(每个前面加空格)
  330. for (int i = 1; i < byteArray.Length; i++)
  331. {
  332. sb.Append(' ');
  333. sb.Append(byteArray[i].ToString("X2"));
  334. }
  335. return sb.ToString();
  336. }
  337. /// <summary>
  338. /// 检测返回的数据格式是否正确
  339. /// </summary>
  340. /// <param name="response"></param>
  341. /// <returns></returns>
  342. public static bool CheckResponse(byte[] response)
  343. {
  344. if (response == null || response.Length == 0)
  345. {
  346. MessageBox.Show("返回数据为空", "error", MessageBoxButton.OK, MessageBoxImage.Error);
  347. return false;
  348. }
  349. // 检查数组长度是否足够
  350. if (response.Length > 3)
  351. {
  352. byte secondByte = response[1]; // 获取第二个字节(索引为1)
  353. // 使用掩码0xF0获取高4位,并右移4位
  354. int highNibble = (secondByte & 0xF0) >> 4;
  355. // 判断高4位是否等于8
  356. if (highNibble == 8)
  357. {
  358. var error = ErrorCode.ErrorCode.FromByte(response[2]);
  359. MessageBox.Show(error.ToString());
  360. return false;
  361. }
  362. }
  363. else
  364. {
  365. return false;
  366. }
  367. if (!ValidateCRC(response))
  368. {
  369. MessageBox.Show(ErrorCode.ErrorCode.CrcCheckError.ToString());
  370. return false;
  371. }
  372. return true;
  373. }
  374. //查表法计算CRC
  375. private static readonly byte[] aucCRCHi = {
  376. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  377. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  378. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  379. 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  380. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  381. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  382. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  383. 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  384. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  385. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  386. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  387. 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  388. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  389. 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  390. 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  391. 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  392. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  393. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  394. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  395. 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  396. 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  397. 0x00, 0xC1, 0x81, 0x40
  398. };
  399. private static readonly byte[] aucCRCLo = {
  400. 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
  401. 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
  402. 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
  403. 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
  404. 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
  405. 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
  406. 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
  407. 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
  408. 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
  409. 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
  410. 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
  411. 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
  412. 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
  413. 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
  414. 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
  415. 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
  416. 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
  417. 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
  418. 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
  419. 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
  420. 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
  421. 0x41, 0x81, 0x80, 0x40
  422. };
  423. /// <summary>
  424. /// CRC校验
  425. /// </summary>
  426. /// <param name="pucFrame">字节数组</param>
  427. /// <param name="usLen">验证长度</param>
  428. /// <returns>2个字节</returns>
  429. public static byte[] CalculateCRC(byte[] pucFrame, int usLen)
  430. {
  431. int i = 0;
  432. byte[] res = new byte[2] { 0xFF, 0xFF };
  433. ushort iIndex;
  434. while (usLen-- > 0)
  435. {
  436. iIndex = (ushort)(res[0] ^ pucFrame[i++]);
  437. res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
  438. res[1] = aucCRCLo[iIndex];
  439. }
  440. return res;
  441. }
  442. /// <summary>
  443. /// 用来计算CRC是否合理
  444. /// </summary>
  445. /// <param name="fullFrame"></param>
  446. /// <returns></returns>
  447. public static bool ValidateCRC(byte[] fullFrame)
  448. {
  449. // 报文必须至少包含2个字节的CRC校验码
  450. if (fullFrame.Length < 2)
  451. return false;
  452. // 计算数据部分的长度(排除最后2个CRC字节)
  453. int dataLength = fullFrame.Length - 2;
  454. // 提取报文中包含的CRC校验码(传输顺序:低字节在前,高字节在后)
  455. byte receivedCrcLo = fullFrame[fullFrame.Length - 2];
  456. byte receivedCrcHi = fullFrame[fullFrame.Length - 1];
  457. // 计算数据部分的CRC(返回顺序:高字节在前,低字节在后)
  458. byte[] calculatedCrc = CalculateCRC(fullFrame, dataLength);
  459. // 比较计算出的CRC和接收到的CRC
  460. return (calculatedCrc[calculatedCrc.Length - 1] == receivedCrcHi) &&
  461. (calculatedCrc[calculatedCrc.Length - 2] == receivedCrcLo);
  462. }
  463. }
  464. }