using Common.Attributes; using NModbus; using System; using System.Collections.Concurrent; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace DeviceCommand.Flexible { /// /// 灵活型 Modbus TCP 通信类(免实例化,每次调用临时建立 TCP 连接)。 /// 支持保持寄存器与线圈的读写操作,内部使用按端点粒度的通信锁保证同一端点同一时刻只有一个事务在执行, /// 不同端点之间互不阻塞,适用于偶发性、无需保持长连接的 Modbus TCP 设备读写场景。 /// [ACPCommand] public static class FModbusTCP { // 按端点粒度的通信锁:同一端点同一时刻只有一个事务在执行,不同端点互不阻塞 private static readonly ConcurrentDictionary _commLocks = new(); /// 获取指定端点的通信锁(不存在则自动创建) private static SemaphoreSlim GetLock(string ipAddress, int port) => _commLocks.GetOrAdd($"{ipAddress}:{port}", _ => new SemaphoreSlim(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 GetLock(ipAddress, port).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 { GetLock(ipAddress, port).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 GetLock(ipAddress, port).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 { GetLock(ipAddress, port).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 GetLock(ipAddress, port).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 { GetLock(ipAddress, port).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 GetLock(ipAddress, port).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 { GetLock(ipAddress, port).Release(); } } #endregion } }