using Common.Attributes; using NModbus; using NModbus.Serial; using System; using System.IO.Ports; using System.Threading; using System.Threading.Tasks; namespace DeviceCommand.Flexible { /// /// 灵活型 Modbus RTU 串口通信类(免实例化,每次调用临时创建串口连接)。 /// 支持保持寄存器与线圈的读写操作,内部使用通信锁保证同一时刻只有一个事务在执行, /// 适用于偶发性、无需保持长连接的 Modbus RTU 设备读写场景。 /// [ADPCommand] public static class FModbusRTU { // 通信锁:保证同一时刻只有一个 Modbus 事务在执行 private static readonly SemaphoreSlim _commLock = new(1, 1); /// /// 创建串口实例并配置超时参数。 /// private static SerialPort CreatePort( string portName, int baudRate, int dataBits, StopBits stopBits, Parity parity, int readTimeout, int writeTimeout) { return new SerialPort(portName, baudRate, parity, dataBits, stopBits) { ReadTimeout = readTimeout > 0 ? readTimeout : SerialPort.InfiniteTimeout, WriteTimeout = writeTimeout > 0 ? writeTimeout : SerialPort.InfiniteTimeout }; } /// /// 基于指定串口创建 Modbus RTU 主站。 /// private static IModbusMaster CreateMaster(SerialPort port) { return new ModbusFactory().CreateRtuMaster(port); } #region Holding Register /// /// 读取保持寄存器(功能码 03),返回指定数量的寄存器值数组。 /// /// 串口名称,如 "COM1" /// 波特率,如 9600、115200 /// Modbus 从站站号 (1-247) /// 起始寄存器地址 /// 读取的寄存器数量 /// 数据位,默认 8 /// 停止位,默认 1 位 /// 校验位,默认无校验 /// 读取超时时间(毫秒),默认 3000 /// 写入超时时间(毫秒),默认 3000 /// 异步取消令牌 /// 读取到的寄存器值数组 public static async Task 读保持寄存器( string portName, int baudRate, byte slaveAddress, ushort startAddress, ushort numberOfPoints, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { using var port = CreatePort( portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout); port.Open(); var master = CreateMaster(port); using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); if (readTimeout > 0) cts.CancelAfter(readTimeout); return await master .ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints) .WaitAsync(TimeSpan.FromMilliseconds(readTimeout),cts.Token); } finally { _commLock.Release(); } } /// /// 写入单个保持寄存器(功能码 06)。 /// /// 串口名称,如 "COM1" /// 波特率,如 9600、115200 /// Modbus 从站站号 (1-247) /// 要写入的寄存器地址 /// 要写入的寄存器值 (0-65535) /// 数据位,默认 8 /// 停止位,默认 1 位 /// 校验位,默认无校验 /// 读取超时时间(毫秒),默认 3000 /// 写入超时时间(毫秒),默认 3000 /// 异步取消令牌 public static async Task 写单个寄存器( string portName, int baudRate, byte slaveAddress, ushort registerAddress, ushort value, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { using var port = CreatePort( portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout); port.Open(); var master = CreateMaster(port); using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); if (writeTimeout > 0) cts.CancelAfter(writeTimeout); await master .WriteSingleRegisterAsync(slaveAddress, registerAddress, value) .WaitAsync(TimeSpan.FromMilliseconds(writeTimeout),cts.Token); } finally { _commLock.Release(); } } #endregion #region Coil /// /// 读取线圈状态(功能码 01),返回指定数量的线圈开关状态数组。 /// /// 串口名称,如 "COM1" /// 波特率,如 9600、115200 /// Modbus 从站站号 (1-247) /// 起始线圈地址 /// 读取的线圈数量 /// 数据位,默认 8 /// 停止位,默认 1 位 /// 校验位,默认无校验 /// 读取超时时间(毫秒),默认 3000 /// 写入超时时间(毫秒),默认 3000 /// 异步取消令牌 /// 线圈状态数组,true 表示吸合/导通 public static async Task 读线圈( string portName, int baudRate, byte slaveAddress, ushort startAddress, ushort numberOfPoints, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { using var port = CreatePort( portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout); port.Open(); var master = CreateMaster(port); using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); if (readTimeout > 0) cts.CancelAfter(readTimeout); return await master .ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints) .WaitAsync(TimeSpan.FromMilliseconds(readTimeout),cts.Token); } finally { _commLock.Release(); } } /// /// 写入单个线圈(功能码 05),控制单个开关量输出。 /// /// 串口名称,如 "COM1" /// 波特率,如 9600、115200 /// Modbus 从站站号 (1-247) /// 要写入的线圈地址 /// 线圈状态(true: 吸合/导通, false: 断开) /// 数据位,默认 8 /// 停止位,默认 1 位 /// 校验位,默认无校验 /// 读取超时时间(毫秒),默认 3000 /// 写入超时时间(毫秒),默认 3000 /// 异步取消令牌 public static async Task 写单个线圈( string portName, int baudRate, byte slaveAddress, ushort coilAddress, bool value, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { using var port = CreatePort( portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout); port.Open(); var master = CreateMaster(port); using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); if (writeTimeout > 0) cts.CancelAfter(writeTimeout); await master .WriteSingleCoilAsync(slaveAddress, coilAddress, value) .WaitAsync(TimeSpan.FromMilliseconds(writeTimeout), cts.Token); } finally { _commLock.Release(); } } #endregion } }