123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- 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<byte> _deviceAddresses = new List<byte>();
- private ConcurrentDictionary<byte, bool> _deviceAddresses; // = new ConcurrentDictionary<byte, bool>();
- //private ConcurrentBag<byte> _deviceAddresses = new ConcurrentBag<byte>();
- //private readonly object _deviceLock = new object(); // 设备地址列表的锁
- private int _readIntervalSeconds; //定时读取的间隔
- // 设备数据更新事件
- public event Action<WMData> DataReceived;
- //事件:串口连接状态变化通知UI
- public event Action<ModbusRtuClient,bool> ConnectionStatusChanged;
- private byte[] sentData; //发送到设备的数据
- private byte[] recvData; //从设备接收的数据
-
- public ModbusRtuClient(List<byte> deviceAddresses, PortModel portModel)
- {
- //加入到线程安全的字典中
- _deviceAddresses = new ConcurrentDictionary<byte, bool>(
- 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<byte> 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<bool> 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;
- }
- /// <summary>
- /// 写入单个寄存器,同时保证不会和轮询冲突
- /// </summary>
- public async Task<bool> 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<byte> 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);
- }
- }
- //---------------------------------------------------------------------
- }
|