ModbusRtuClient.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. using Modbus.Device;
  2. using Modbus.IO;
  3. using MV485.model;
  4. using System;
  5. using System.Collections.Concurrent;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.IO.Ports;
  9. using System.Linq;
  10. using System.Reflection;
  11. using System.Text;
  12. using System.Threading;
  13. using System.Threading.Tasks;
  14. using System.Windows.Threading;
  15. namespace MV485.helper
  16. {
  17. public class ModbusRtuClient
  18. {
  19. private SerialPort _serialPort;
  20. private PortModel _portModel;
  21. //private IModbusSerialMaster _modbusMaster;
  22. private ModbusSerialMaster _modbusMaster;
  23. private ModbusSerialTransport _modbusTransport;
  24. //private readonly byte _deviceAddress;
  25. //private byte _deviceAddress;
  26. private CancellationTokenSource _pollingTokenSource; //轮询任务
  27. private CancellationTokenSource _connectionMonitorTokenSource; //串口连接状态监控
  28. private Task _pollingTask; //轮询任务
  29. private Task _connectionMonitorTask; //串口状态任务
  30. private readonly object _lock = new object();
  31. private bool _isConnected; //串口的连接状态
  32. //读写互斥锁
  33. private readonly SemaphoreSlim _modbusLock = new SemaphoreSlim(1, 1); // 允许一个操作执行
  34. //设备地址列表(0x01 ~0xF7)
  35. //private List<byte> _deviceAddresses = new List<byte>();
  36. private ConcurrentDictionary<byte, bool> _deviceAddresses; // = new ConcurrentDictionary<byte, bool>();
  37. //private ConcurrentBag<byte> _deviceAddresses = new ConcurrentBag<byte>();
  38. //private readonly object _deviceLock = new object(); // 设备地址列表的锁
  39. private int _readIntervalSeconds; //定时读取的间隔
  40. // 设备数据更新事件
  41. public event Action<WMData> DataReceived;
  42. //事件:串口连接状态变化通知UI
  43. public event Action<ModbusRtuClient,bool> ConnectionStatusChanged;
  44. private byte[] sentData; //发送到设备的数据
  45. private byte[] recvData; //从设备接收的数据
  46. public ModbusRtuClient(List<byte> deviceAddresses, PortModel portModel)
  47. {
  48. //加入到线程安全的字典中
  49. _deviceAddresses = new ConcurrentDictionary<byte, bool>(
  50. deviceAddresses.ToDictionary(addr => addr, _ => true)
  51. );
  52. _portModel = portModel;
  53. //_serialPort = new SerialPort(portModel.PortName, portModel.BaudRate, portModel.Parity, portModel.DataBits, portModel.StopBits);
  54. }
  55. public PortModel GetPortMode()
  56. {
  57. return _portModel;
  58. }
  59. //读取间隔
  60. public async void StartAsync(int intervalSeconds)
  61. {
  62. // 假设这个实例是 Modbus 相关的类的对象
  63. await StopAsync(); //先停止可能已有的任务
  64. _readIntervalSeconds = intervalSeconds;
  65. try
  66. {
  67. // 启动串口状态监控线程
  68. _connectionMonitorTokenSource = new CancellationTokenSource();
  69. _connectionMonitorTask = Task.Run(() => MonitorConnectionLoop(_connectionMonitorTokenSource.Token));
  70. // 启动轮询线程(可能间隔很长)
  71. _pollingTokenSource = new CancellationTokenSource();
  72. //_pollingTask = Task.Run(() => PollingLoop(_pollingTokenSource.Token));
  73. _pollingTask = Task.Run(() => PollingLoop(_pollingTokenSource.Token));
  74. //.ContinueWith(t => Console.WriteLine($"Polling stopped: {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
  75. }
  76. catch (Exception ex)
  77. {
  78. Console.WriteLine(ex.Message);
  79. }
  80. }
  81. public async Task StopAsync()
  82. {
  83. // 取消轮询任务
  84. _pollingTokenSource?.Cancel();
  85. _connectionMonitorTokenSource?.Cancel();
  86. // 等待轮询任务完成
  87. if (_pollingTask != null)
  88. {
  89. try { await _pollingTask; } catch { }
  90. }
  91. if (_connectionMonitorTask != null)
  92. {
  93. try { await _connectionMonitorTask; } catch { }
  94. }
  95. // 释放 Modbus 资源
  96. _modbusMaster?.Dispose();
  97. _modbusTransport?.Dispose();
  98. // 关闭串口
  99. try
  100. {
  101. if (_serialPort?.IsOpen ?? false)
  102. {
  103. _serialPort.Close();
  104. UpdateConnectionStatus(false);
  105. }
  106. }
  107. catch (Exception ex)
  108. {
  109. Console.WriteLine($"关闭串口时发生错误: {ex.Message}");
  110. }
  111. finally
  112. {
  113. _serialPort?.Dispose(); // 确保串口资源最终被释放
  114. }
  115. }
  116. private async Task PollingLoop(CancellationToken token)
  117. {
  118. while (!token.IsCancellationRequested)
  119. {
  120. if (!_isConnected)
  121. {
  122. await Task.Delay(1000, token);
  123. continue;
  124. }
  125. List<byte> addresses = _deviceAddresses.Keys.ToList(); // 线程安全获取设备列表
  126. foreach (var device in addresses)
  127. {
  128. await _modbusLock.WaitAsync(token);
  129. try
  130. {
  131. WMData wmData = ReadWMData(device);
  132. if (wmData != null)
  133. {
  134. DataReceived?.Invoke(wmData);
  135. }
  136. }
  137. catch (Exception ex)
  138. {
  139. Console.WriteLine($"读取设备 {device} 数据时发生错误: {ex}");
  140. }
  141. finally
  142. {
  143. _modbusLock.Release();
  144. }
  145. // 在每次设备读取后,检查是否取消任务
  146. if (token.IsCancellationRequested)
  147. {
  148. return;
  149. }
  150. }//for each
  151. // 改进延迟方式,支持任务取消
  152. //for (int i = 0; i < _readIntervalSeconds; i++)
  153. //{
  154. // if (token.IsCancellationRequested) return;
  155. // await Task.Delay(1000, token);
  156. //}
  157. await Task.Delay(_readIntervalSeconds * 1000, token);
  158. }//while
  159. }
  160. //监控串口状态的任务
  161. private async Task MonitorConnectionLoop(CancellationToken token)
  162. {
  163. while (!token.IsCancellationRequested)
  164. {
  165. if (!_serialPort?.IsOpen ?? true)
  166. {
  167. TryReconnect();
  168. }
  169. await Task.Delay(1000, token);
  170. }
  171. }
  172. private void TryReconnect()
  173. {
  174. lock (_lock)
  175. {
  176. if (_serialPort?.IsOpen ?? false) return;
  177. try
  178. {
  179. // 先释放已有资源
  180. _serialPort?.Dispose();
  181. _serialPort = new SerialPort(_portModel.PortName, _portModel.BaudRate,
  182. _portModel.Parity, _portModel.DataBits, _portModel.StopBits);
  183. _serialPort.Open();
  184. _modbusMaster = ModbusSerialMaster.CreateRtu(_serialPort);
  185. _modbusTransport = (ModbusSerialTransport)_modbusMaster.Transport;
  186. Console.WriteLine("串口重新连接成功");
  187. UpdateConnectionStatus(true);
  188. }
  189. catch (Exception ex)
  190. {
  191. Console.WriteLine($"串口连接失败: {ex.Message}");
  192. UpdateConnectionStatus(false);
  193. }
  194. }
  195. }
  196. private void UpdateConnectionStatus(bool isConnected)
  197. {
  198. if (_isConnected != isConnected)
  199. {
  200. _isConnected = isConnected;
  201. ConnectionStatusChanged?.Invoke(this,isConnected);
  202. }
  203. }
  204. public WMData ReadWMData(byte deviceAddress)
  205. {
  206. WMData wmData = null; // = new WMData();
  207. try
  208. {
  209. // 读取保持寄存器功能码 0x03
  210. //ushort[] holdingRegisters = _modbusMaster.ReadHoldingRegisters(_deviceAddress, startAddress, numOfRegisters);
  211. // 读取输入寄存器功能码 0x04
  212. //ushort[] inputRegisters = _modbusMaster.ReadInputRegisters(_deviceAddress, startAddress, numOfRegisters);
  213. //// 读取累计流量(地址 40001-40004)
  214. //ushort[] flowRegisters = _modbusMaster.ReadHoldingRegisters(_deviceAddress, 0x00, 4);
  215. //uint totalFlow = (uint)(flowRegisters[0] << 48 | flowRegisters[1] << 32 | flowRegisters[2] << 16 | flowRegisters[3]);
  216. //// 读取采样时间(地址 40005-40010)
  217. //ushort[] timeRegisters = _modbusMaster.ReadHoldingRegisters(_deviceAddress, 0x04, 6);
  218. //int year1 = timeRegisters[0];
  219. //int month1 = timeRegisters[1];
  220. //int day1 = timeRegisters[2];
  221. //int hour1 = timeRegisters[3];
  222. //int minute1 = timeRegisters[4];
  223. //int second1 = timeRegisters[5];
  224. //改为同时读取采样信息(包括流量和时间)(流量和时间也可以分开去读)
  225. ushort[] sampleRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0x00, 10);
  226. printModbusSRData("流量+采样时间");
  227. //uint totalFlow = (uint)(sampleRegisters[0] << 48 | sampleRegisters[1] << 32 | sampleRegisters[2] << 16 | sampleRegisters[3]);
  228. ulong totalFlow = ((ulong)sampleRegisters[0] << 48) |
  229. ((ulong)sampleRegisters[1] << 32) | ((ulong)sampleRegisters[2] << 16) | sampleRegisters[3];
  230. int year = sampleRegisters[4];
  231. int month = sampleRegisters[5];
  232. int day = sampleRegisters[6];
  233. int hour = sampleRegisters[7];
  234. int minute = sampleRegisters[8];
  235. int second = sampleRegisters[9];
  236. //D2为模块名称
  237. ushort[] modelNameRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xD2, 1);
  238. printModbusSRData("模块名称");
  239. ushort modelName = modelNameRegisters[0]; //一个寄存器的内容
  240. //D4~D5固件版本号
  241. ushort[] firewareRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xD4, 2);
  242. printModbusSRData("固件版本");
  243. //uint fireware = (uint)firewareRegisters[0] << 16 | firewareRegisters[1];
  244. string mcuVer = $"{(firewareRegisters[0] & 0xFF).ToString("D2")}." +
  245. $"{(firewareRegisters[1] >> 8).ToString("D2")}." +
  246. $"{(firewareRegisters[1] & 0xFF).ToString("D2")}";
  247. //E5~E7读取SN
  248. ushort[] snRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xE5, 3); // 读取 3 个寄存器
  249. printModbusSRData("设备SN");
  250. // 解析 BCD 码
  251. string deviceSn = Tools.SNBcdToString(snRegisters);
  252. Console.WriteLine("设备序列号: " + deviceSn);
  253. //F0模块ID,即站地址
  254. ushort[] slaveAddressRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xF0, 1);
  255. printModbusSRData("模块ID");
  256. byte slaveAdress = (byte)(slaveAddressRegisters[0] & 0xFF);
  257. var modbusData = new WMData
  258. {
  259. //DeviceAddress = deviceAddress,
  260. TotalFlow = totalFlow / 10, // 单位转换
  261. SampleTime = new DateTime(2000 + year, month, day, hour, minute, second),
  262. ModelName = "0X" + modelName.ToString("X4"),
  263. McuVer = mcuVer,
  264. DeviceSn = deviceSn,
  265. SlaveAddress = slaveAdress,
  266. };
  267. wmData = modbusData;
  268. }
  269. catch(Exception ex)
  270. {
  271. //读取数据错误
  272. //Console.WriteLine($"读取设备数据 {_deviceAddress} 数据时发生错误: {ex.Message}");
  273. throw;
  274. }
  275. return wmData;
  276. }
  277. public RS485Config ReadRS485Config(byte deviceAddress)
  278. {
  279. RS485Config rS485Config = null;
  280. try
  281. {
  282. //不允许连读
  283. //F2~F4 (波特率、校验和、停止位)(可读可写)--设备的参数
  284. ushort[] baudRateTypeRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xF2, 1);
  285. printModbusSRData("波特率");
  286. byte bauddRateType = (byte)(baudRateTypeRegisters[0] & 0xFF);
  287. //F3
  288. ushort[] parityCheckTypeRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xF3, 1);
  289. printModbusSRData("校验位");
  290. byte parityCheckType = (byte)(parityCheckTypeRegisters[0] & 0xFF);
  291. //F4(这一位,MCU程序中并未起作用)
  292. ushort[] stopbitsTypeRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xF4, 1);
  293. printModbusSRData("停止位");
  294. byte stopbitsType = (byte)(stopbitsTypeRegisters[0] & 0xFF);
  295. var configData = new RS485Config()
  296. {
  297. BaudRateType = bauddRateType,
  298. ParityCheckType = parityCheckType,
  299. StopBitsType = stopbitsType
  300. };
  301. rS485Config = configData;
  302. }
  303. catch (Exception ex)
  304. {
  305. Console.WriteLine($"读取设备参数 {deviceAddress} 数据时发生错误: {ex.Message}");
  306. }
  307. return rS485Config;
  308. }
  309. //设置设备地址或模块ID
  310. public async Task<bool> SetModleID(byte oldAddress,ushort newAddress)
  311. {
  312. // 判断 newAddress 是否已存在
  313. if (_deviceAddresses.ContainsKey((byte)newAddress))
  314. {
  315. Console.WriteLine($"地址 {newAddress} 已存在");
  316. return false;
  317. }
  318. // 发送 Modbus 命令修改地址
  319. bool blResult = await WriteSingleRegister(oldAddress, 0xF0, newAddress);
  320. printModbusSRData("更改模块地址");
  321. // 如果成功,更新设备地址
  322. if (blResult)
  323. {
  324. if (_deviceAddresses.TryRemove(oldAddress, out _)) // 先删除旧地址
  325. {
  326. _deviceAddresses.TryAdd((byte)newAddress, true); // 添加新地址
  327. }
  328. }
  329. return blResult;
  330. }
  331. /// <summary>
  332. /// 写入单个寄存器,同时保证不会和轮询冲突
  333. /// </summary>
  334. public async Task<bool> WriteSingleRegister(byte deviceAddress, ushort registerAddress, ushort value)
  335. {
  336. if (!_isConnected)
  337. {
  338. await Task.Delay(1000);
  339. }
  340. if (!_isConnected || !_serialPort.IsOpen) return false;
  341. try
  342. {
  343. await _modbusLock.WaitAsync(); //获取锁,等待读取完成
  344. _modbusMaster.WriteSingleRegister(deviceAddress, registerAddress, value);
  345. // 这里是写入 Modbus 寄存器的逻辑
  346. Console.WriteLine($"写入寄存器 {registerAddress} 值 {value}");
  347. return true;
  348. }
  349. catch (Exception ex)
  350. {
  351. Console.WriteLine($"写入寄存器失败: {ex.Message}");
  352. return false;
  353. }
  354. finally
  355. {
  356. _modbusLock.Release();
  357. }
  358. }
  359. // 添加设备(线程安全)
  360. public void AddDevice(byte address)
  361. {
  362. _deviceAddresses[address] = true;
  363. }
  364. // 移除设备(线程安全)
  365. public void RemoveDevice(byte address)
  366. {
  367. _deviceAddresses.TryRemove(address, out _);
  368. }
  369. // 获取当前设备列表(线程安全)
  370. public List<byte> GetDeviceList()
  371. {
  372. return _deviceAddresses.Keys.ToList();
  373. }
  374. //打印Modbus收发数据
  375. private void printModbusSRData(string dataName)
  376. {
  377. Console.WriteLine(dataName + ":");
  378. // 获取最后一次的发送和接收数据
  379. sentData = _modbusTransport.GetLastSentData();
  380. recvData = _modbusTransport.GetLastReceivedData();
  381. string sendDataHex = BitConverter.ToString(sentData).Replace("-", " ");
  382. string recvDataHex = BitConverter.ToString(recvData).Replace("-", " ");
  383. Console.WriteLine(sendDataHex);
  384. Console.WriteLine(recvDataHex);
  385. }
  386. }
  387. //---------------------------------------------------------------------
  388. }