using NModbus; using System; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; namespace DeviceCommand.Base { public class ModbusTcp : IModbusDevice { public string IPAddress { get; private set; } = "127.0.0.1"; public int Port { get; private set; } = 502; public int SendTimeout { get; private set; } = 3000; public int ReceiveTimeout { get; private set; } = 3000; private TcpClient _tcpClient; public IModbusMaster Modbus { get; private set; } public bool IsConnected => _tcpClient?.Connected ?? false; protected readonly SemaphoreSlim _commLock = new(1, 1); public ModbusTcp() { _tcpClient = new TcpClient(); } public void ConfigureDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000) { IPAddress = ipAddress; Port = port; SendTimeout = sendTimeout; ReceiveTimeout = receiveTimeout; } public virtual async Task ConnectAsync(CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 开始连接 - IP: {IPAddress}, 端口: {Port}"); System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 超时设置 - 发送: {SendTimeout}ms, 接收: {ReceiveTimeout}ms"); if (_tcpClient.Connected) { var remoteEndPoint = (IPEndPoint)_tcpClient.Client.RemoteEndPoint!; string currentIp = remoteEndPoint.Address.MapToIPv4().ToString(); int currentPort = remoteEndPoint.Port; System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 已有连接: {currentIp}:{currentPort}"); if (currentIp == IPAddress && currentPort == Port) { System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 参数匹配,复用现有连接"); return true; } else { System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 参数不匹配,需要重新连接"); } } System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 关闭并释放旧连接"); _tcpClient.Close(); _tcpClient.Dispose(); _tcpClient = new TcpClient(); System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 调用 ConnectAsync({IPAddress}, {Port})"); await _tcpClient.ConnectAsync(IPAddress, Port, ct); System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 创建ModbusMaster"); Modbus = new ModbusFactory().CreateMaster(_tcpClient); bool isConnected = _tcpClient.Connected; System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 连接结果: {(isConnected ? "成功" : "失败")}"); return isConnected; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 连接异常: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 异常类型: {ex.GetType().Name}"); System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 异常堆栈: {ex.StackTrace}"); return false; } finally { _commLock.Release(); } } public virtual void Close() { if (_tcpClient.Connected) _tcpClient.Close(); } public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { // 修复:FromMinutes 改为 FromMilliseconds await Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value) .WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct); } finally { _commLock.Release(); } } public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { await Modbus.WriteMultipleRegistersAsync(slaveAddress, startAddress, values) .WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct); } finally { _commLock.Release(); } } public async Task ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { return await Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints) .WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct); } finally { _commLock.Release(); } } public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { await Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value) .WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct); } finally { _commLock.Release(); } } public async Task ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { return await Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints) .WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct); } finally { _commLock.Release(); } } public async Task ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { return await Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints) .WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct); } finally { _commLock.Release(); } } public void Dispose() { _tcpClient?.Dispose(); _commLock?.Dispose(); } } }