using Common.Attributes; using NModbus; using System; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace DeviceCommand.Flexible { /// /// 灵活型 Modbus TCP 通信类(免实例化,每次调用临时建立 TCP 连接)。 /// 支持保持寄存器与线圈的读写操作,内部使用通信锁保证同一时刻只有一个事务在执行, /// 适用于偶发性、无需保持长连接的 Modbus TCP 设备读写场景。 /// [ADPCommand] public static class FModbusTCP { // 通信锁:保证同一时刻只有一个 Modbus 事务在执行 private static readonly SemaphoreSlim _commLock = new(1, 1); /// /// 建立 TCP 连接并创建 Modbus TCP 主站。 /// private static async Task ConnectAsync(string ipAddress, int port, int sendTimeout, int receiveTimeout, CancellationToken ct) { var tcpClient = new TcpClient(); using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); if (sendTimeout > 0) cts.CancelAfter(sendTimeout); await tcpClient.ConnectAsync(ipAddress, port, cts.Token); return new ModbusFactory().CreateMaster(tcpClient); } #region Holding Registers /// /// 读取保持寄存器(功能码 03),返回指定数量的寄存器值数组。 /// /// 设备 IP 地址 /// Modbus TCP 端口号,默认常用 502 /// Modbus 从站站号(单元标识符) /// 起始寄存器地址 /// 读取的寄存器数量 /// 发送/连接超时时间(毫秒),默认 3000 /// 接收超时时间(毫秒),默认 3000 /// 异步取消令牌 /// 读取到的寄存器值数组 public static async Task 读保持寄存器( string ipAddress, int port, byte slaveAddress, ushort startAddress, ushort numberOfPoints, int sendTimeout = 3000, int receiveTimeout = 3000, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable; var result = await ((IModbusMaster)master) .ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints) .WaitAsync(TimeSpan.FromMilliseconds(receiveTimeout), ct); return result; } finally { _commLock.Release(); } } /// /// 写入单个保持寄存器(功能码 06)。 /// /// 设备 IP 地址 /// Modbus TCP 端口号,默认常用 502 /// Modbus 从站站号(单元标识符) /// 要写入的寄存器地址 /// 要写入的寄存器值 (0-65535) /// 发送/连接超时时间(毫秒),默认 3000 /// 接收超时时间(毫秒),默认 3000 /// 异步取消令牌 public static async Task 写单个寄存器( string ipAddress, int port, byte slaveAddress, ushort registerAddress, ushort value, int sendTimeout = 3000, int receiveTimeout = 3000, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable; await ((IModbusMaster)master) .WriteSingleRegisterAsync(slaveAddress, registerAddress, value) .WaitAsync(TimeSpan.FromMilliseconds(sendTimeout),ct); } finally { _commLock.Release(); } } #endregion #region Coils /// /// 读取线圈状态(功能码 01),返回指定数量的线圈开关状态数组。 /// /// 设备 IP 地址 /// Modbus TCP 端口号,默认常用 502 /// Modbus 从站站号(单元标识符) /// 起始线圈地址 /// 读取的线圈数量 /// 发送/连接超时时间(毫秒),默认 3000 /// 接收超时时间(毫秒),默认 3000 /// 异步取消令牌 /// 线圈状态数组,true 表示吸合/导通 public static async Task 读线圈( string ipAddress, int port, byte slaveAddress, ushort startAddress, ushort numberOfPoints, int sendTimeout = 3000, int receiveTimeout = 3000, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable; var result = await ((IModbusMaster)master) .ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints) .WaitAsync(TimeSpan.FromMilliseconds(receiveTimeout), ct); return result; } finally { _commLock.Release(); } } /// /// 写入单个线圈(功能码 05),控制单个开关量输出。 /// /// 设备 IP 地址 /// Modbus TCP 端口号,默认常用 502 /// Modbus 从站站号(单元标识符) /// 要写入的线圈地址 /// 线圈状态(true: 吸合/导通, false: 断开) /// 发送/连接超时时间(毫秒),默认 3000 /// 接收超时时间(毫秒),默认 3000 /// 异步取消令牌 public static async Task 写单个线圈( string ipAddress, int port, byte slaveAddress, ushort coilAddress, bool value, int sendTimeout = 3000, int receiveTimeout = 3000, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable; await ((IModbusMaster)master) .WriteSingleCoilAsync(slaveAddress, coilAddress, value) .WaitAsync(TimeSpan.FromMilliseconds(sendTimeout),ct); } finally { _commLock.Release(); } } #endregion } }