Files
IOT/DeviceCommand/Base/ModbusTcp.cs

186 lines
6.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<bool> 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<ushort[]> 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<bool[]> 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<ushort[]> 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();
}
}
}