using Modbus.Device; using Modbus.IO; using MV485.model; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.IO.Ports; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Threading; namespace MV485.helper { public class ModbusRtuClient { private SerialPort _serialPort; private PortModel _portModel; //private IModbusSerialMaster _modbusMaster; private ModbusSerialMaster _modbusMaster; private ModbusSerialTransport _modbusTransport; //private readonly byte _deviceAddress; //private byte _deviceAddress; private CancellationTokenSource _pollingTokenSource; //轮询任务 private CancellationTokenSource _connectionMonitorTokenSource; //串口连接状态监控 private Task _pollingTask; //轮询任务 private Task _connectionMonitorTask; //串口状态任务 private readonly object _lock = new object(); private bool _isConnected; //串口的连接状态 //读写互斥锁 private readonly SemaphoreSlim _modbusLock = new SemaphoreSlim(1, 1); // 允许一个操作执行 //设备地址列表(0x01 ~0xF7) //private List _deviceAddresses = new List(); private ConcurrentDictionary _deviceAddresses; // = new ConcurrentDictionary(); //private ConcurrentBag _deviceAddresses = new ConcurrentBag(); //private readonly object _deviceLock = new object(); // 设备地址列表的锁 private int _readIntervalSeconds; //定时读取的间隔 // 设备数据更新事件 public event Action DataReceived; //事件:串口连接状态变化通知UI public event Action ConnectionStatusChanged; private byte[] sentData; //发送到设备的数据 private byte[] recvData; //从设备接收的数据 public ModbusRtuClient(List deviceAddresses, PortModel portModel) { //加入到线程安全的字典中 _deviceAddresses = new ConcurrentDictionary( deviceAddresses.ToDictionary(addr => addr, _ => true) ); _portModel = portModel; //_serialPort = new SerialPort(portModel.PortName, portModel.BaudRate, portModel.Parity, portModel.DataBits, portModel.StopBits); } public PortModel GetPortMode() { return _portModel; } //读取间隔 public async void StartAsync(int intervalSeconds) { // 假设这个实例是 Modbus 相关的类的对象 await StopAsync(); //先停止可能已有的任务 _readIntervalSeconds = intervalSeconds; try { // 启动串口状态监控线程 _connectionMonitorTokenSource = new CancellationTokenSource(); _connectionMonitorTask = Task.Run(() => MonitorConnectionLoop(_connectionMonitorTokenSource.Token)); // 启动轮询线程(可能间隔很长) _pollingTokenSource = new CancellationTokenSource(); //_pollingTask = Task.Run(() => PollingLoop(_pollingTokenSource.Token)); _pollingTask = Task.Run(() => PollingLoop(_pollingTokenSource.Token)); //.ContinueWith(t => Console.WriteLine($"Polling stopped: {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); } catch (Exception ex) { Console.WriteLine(ex.Message); } } public async Task StopAsync() { // 取消轮询任务 _pollingTokenSource?.Cancel(); _connectionMonitorTokenSource?.Cancel(); // 等待轮询任务完成 if (_pollingTask != null) { try { await _pollingTask; } catch { } } if (_connectionMonitorTask != null) { try { await _connectionMonitorTask; } catch { } } // 释放 Modbus 资源 _modbusMaster?.Dispose(); _modbusTransport?.Dispose(); // 关闭串口 try { if (_serialPort?.IsOpen ?? false) { _serialPort.Close(); UpdateConnectionStatus(false); } } catch (Exception ex) { Console.WriteLine($"关闭串口时发生错误: {ex.Message}"); } finally { _serialPort?.Dispose(); // 确保串口资源最终被释放 } } private async Task PollingLoop(CancellationToken token) { while (!token.IsCancellationRequested) { if (!_isConnected) { await Task.Delay(1000, token); continue; } List addresses = _deviceAddresses.Keys.ToList(); // 线程安全获取设备列表 foreach (var device in addresses) { await _modbusLock.WaitAsync(token); try { WMData wmData = ReadWMData(device); if (wmData != null) { DataReceived?.Invoke(wmData); } } catch (Exception ex) { Console.WriteLine($"读取设备 {device} 数据时发生错误: {ex}"); } finally { _modbusLock.Release(); } // 在每次设备读取后,检查是否取消任务 if (token.IsCancellationRequested) { return; } }//for each // 改进延迟方式,支持任务取消 //for (int i = 0; i < _readIntervalSeconds; i++) //{ // if (token.IsCancellationRequested) return; // await Task.Delay(1000, token); //} await Task.Delay(_readIntervalSeconds * 1000, token); }//while } //监控串口状态的任务 private async Task MonitorConnectionLoop(CancellationToken token) { while (!token.IsCancellationRequested) { if (!_serialPort?.IsOpen ?? true) { TryReconnect(); } await Task.Delay(1000, token); } } private void TryReconnect() { lock (_lock) { if (_serialPort?.IsOpen ?? false) return; try { // 先释放已有资源 _serialPort?.Dispose(); _serialPort = new SerialPort(_portModel.PortName, _portModel.BaudRate, _portModel.Parity, _portModel.DataBits, _portModel.StopBits); _serialPort.Open(); _modbusMaster = ModbusSerialMaster.CreateRtu(_serialPort); _modbusTransport = (ModbusSerialTransport)_modbusMaster.Transport; Console.WriteLine("串口重新连接成功"); UpdateConnectionStatus(true); } catch (Exception ex) { Console.WriteLine($"串口连接失败: {ex.Message}"); UpdateConnectionStatus(false); } } } private void UpdateConnectionStatus(bool isConnected) { if (_isConnected != isConnected) { _isConnected = isConnected; ConnectionStatusChanged?.Invoke(this,isConnected); } } public WMData ReadWMData(byte deviceAddress) { WMData wmData = null; // = new WMData(); try { // 读取保持寄存器功能码 0x03 //ushort[] holdingRegisters = _modbusMaster.ReadHoldingRegisters(_deviceAddress, startAddress, numOfRegisters); // 读取输入寄存器功能码 0x04 //ushort[] inputRegisters = _modbusMaster.ReadInputRegisters(_deviceAddress, startAddress, numOfRegisters); //// 读取累计流量(地址 40001-40004) //ushort[] flowRegisters = _modbusMaster.ReadHoldingRegisters(_deviceAddress, 0x00, 4); //uint totalFlow = (uint)(flowRegisters[0] << 48 | flowRegisters[1] << 32 | flowRegisters[2] << 16 | flowRegisters[3]); //// 读取采样时间(地址 40005-40010) //ushort[] timeRegisters = _modbusMaster.ReadHoldingRegisters(_deviceAddress, 0x04, 6); //int year1 = timeRegisters[0]; //int month1 = timeRegisters[1]; //int day1 = timeRegisters[2]; //int hour1 = timeRegisters[3]; //int minute1 = timeRegisters[4]; //int second1 = timeRegisters[5]; //改为同时读取采样信息(包括流量和时间)(流量和时间也可以分开去读) ushort[] sampleRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0x00, 10); printModbusSRData("流量+采样时间"); //uint totalFlow = (uint)(sampleRegisters[0] << 48 | sampleRegisters[1] << 32 | sampleRegisters[2] << 16 | sampleRegisters[3]); ulong totalFlow = ((ulong)sampleRegisters[0] << 48) | ((ulong)sampleRegisters[1] << 32) | ((ulong)sampleRegisters[2] << 16) | sampleRegisters[3]; int year = sampleRegisters[4]; int month = sampleRegisters[5]; int day = sampleRegisters[6]; int hour = sampleRegisters[7]; int minute = sampleRegisters[8]; int second = sampleRegisters[9]; //D2为模块名称 ushort[] modelNameRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xD2, 1); printModbusSRData("模块名称"); ushort modelName = modelNameRegisters[0]; //一个寄存器的内容 //D4~D5固件版本号 ushort[] firewareRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xD4, 2); printModbusSRData("固件版本"); //uint fireware = (uint)firewareRegisters[0] << 16 | firewareRegisters[1]; string mcuVer = $"{(firewareRegisters[0] & 0xFF).ToString("D2")}." + $"{(firewareRegisters[1] >> 8).ToString("D2")}." + $"{(firewareRegisters[1] & 0xFF).ToString("D2")}"; //E5~E7读取SN ushort[] snRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xE5, 3); // 读取 3 个寄存器 printModbusSRData("设备SN"); // 解析 BCD 码 string deviceSn = Tools.SNBcdToString(snRegisters); Console.WriteLine("设备序列号: " + deviceSn); //F0模块ID,即站地址 ushort[] slaveAddressRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xF0, 1); printModbusSRData("模块ID"); byte slaveAdress = (byte)(slaveAddressRegisters[0] & 0xFF); var modbusData = new WMData { //DeviceAddress = deviceAddress, TotalFlow = totalFlow / 10, // 单位转换 SampleTime = new DateTime(2000 + year, month, day, hour, minute, second), ModelName = "0X" + modelName.ToString("X4"), McuVer = mcuVer, DeviceSn = deviceSn, SlaveAddress = slaveAdress, }; wmData = modbusData; } catch(Exception ex) { //读取数据错误 //Console.WriteLine($"读取设备数据 {_deviceAddress} 数据时发生错误: {ex.Message}"); throw; } return wmData; } public RS485Config ReadRS485Config(byte deviceAddress) { RS485Config rS485Config = null; try { //不允许连读 //F2~F4 (波特率、校验和、停止位)(可读可写)--设备的参数 ushort[] baudRateTypeRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xF2, 1); printModbusSRData("波特率"); byte bauddRateType = (byte)(baudRateTypeRegisters[0] & 0xFF); //F3 ushort[] parityCheckTypeRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xF3, 1); printModbusSRData("校验位"); byte parityCheckType = (byte)(parityCheckTypeRegisters[0] & 0xFF); //F4(这一位,MCU程序中并未起作用) ushort[] stopbitsTypeRegisters = _modbusMaster.ReadHoldingRegisters(deviceAddress, 0xF4, 1); printModbusSRData("停止位"); byte stopbitsType = (byte)(stopbitsTypeRegisters[0] & 0xFF); var configData = new RS485Config() { BaudRateType = bauddRateType, ParityCheckType = parityCheckType, StopBitsType = stopbitsType }; rS485Config = configData; } catch (Exception ex) { Console.WriteLine($"读取设备参数 {deviceAddress} 数据时发生错误: {ex.Message}"); } return rS485Config; } //设置设备地址或模块ID public async Task SetModleID(byte oldAddress,ushort newAddress) { // 判断 newAddress 是否已存在 if (_deviceAddresses.ContainsKey((byte)newAddress)) { Console.WriteLine($"地址 {newAddress} 已存在"); return false; } // 发送 Modbus 命令修改地址 bool blResult = await WriteSingleRegister(oldAddress, 0xF0, newAddress); printModbusSRData("更改模块地址"); // 如果成功,更新设备地址 if (blResult) { if (_deviceAddresses.TryRemove(oldAddress, out _)) // 先删除旧地址 { _deviceAddresses.TryAdd((byte)newAddress, true); // 添加新地址 } } return blResult; } /// /// 写入单个寄存器,同时保证不会和轮询冲突 /// public async Task WriteSingleRegister(byte deviceAddress, ushort registerAddress, ushort value) { if (!_isConnected) { await Task.Delay(1000); } if (!_isConnected || !_serialPort.IsOpen) return false; try { await _modbusLock.WaitAsync(); //获取锁,等待读取完成 _modbusMaster.WriteSingleRegister(deviceAddress, registerAddress, value); // 这里是写入 Modbus 寄存器的逻辑 Console.WriteLine($"写入寄存器 {registerAddress} 值 {value}"); return true; } catch (Exception ex) { Console.WriteLine($"写入寄存器失败: {ex.Message}"); return false; } finally { _modbusLock.Release(); } } // 添加设备(线程安全) public void AddDevice(byte address) { _deviceAddresses[address] = true; } // 移除设备(线程安全) public void RemoveDevice(byte address) { _deviceAddresses.TryRemove(address, out _); } // 获取当前设备列表(线程安全) public List GetDeviceList() { return _deviceAddresses.Keys.ToList(); } //打印Modbus收发数据 private void printModbusSRData(string dataName) { Console.WriteLine(dataName + ":"); // 获取最后一次的发送和接收数据 sentData = _modbusTransport.GetLastSentData(); recvData = _modbusTransport.GetLastReceivedData(); string sendDataHex = BitConverter.ToString(sentData).Replace("-", " "); string recvDataHex = BitConverter.ToString(recvData).Replace("-", " "); Console.WriteLine(sendDataHex); Console.WriteLine(recvDataHex); } } //--------------------------------------------------------------------- }