135 lines
4.2 KiB
C#
135 lines
4.2 KiB
C#
using Modbus.Device;
|
|
using RobotNet10.Script.IO;
|
|
using System.Net.Sockets;
|
|
|
|
namespace RobotNet10.ScriptEngine.IO;
|
|
|
|
/// <summary>
|
|
/// Implementation of ModbusTCP connection using NModbus4 library.
|
|
/// </summary>
|
|
public class ModbusTcpConnection(string ipAddress, int port = 502, byte slaveId = 1, int connectTimeoutMs = 5000) : IModbusTcpConnection
|
|
{
|
|
private TcpClient? _tcpClient;
|
|
private ModbusMaster? _modbusMaster;
|
|
private bool _disposed;
|
|
|
|
public string IpAddress { get; } = ipAddress;
|
|
public int Port { get; } = port;
|
|
public byte SlaveId { get; } = slaveId;
|
|
public int ConnectTimeoutMs { get; } = connectTimeoutMs;
|
|
public bool IsConnected { get; private set; } = false;
|
|
|
|
public async Task ConnectAsync()
|
|
{
|
|
if (IsConnected)
|
|
return;
|
|
|
|
try
|
|
{
|
|
_tcpClient = new TcpClient();
|
|
|
|
// Sử dụng ConnectTimeout
|
|
using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(ConnectTimeoutMs)))
|
|
{
|
|
await _tcpClient.ConnectAsync(IpAddress, Port).WaitAsync(cts.Token);
|
|
}
|
|
|
|
_modbusMaster = ModbusIpMaster.CreateIp(_tcpClient);
|
|
IsConnected = true;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
Disconnect();
|
|
throw new TimeoutException($"Connection to {IpAddress}:{Port} timed out after {ConnectTimeoutMs}ms");
|
|
}
|
|
catch
|
|
{
|
|
Disconnect();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public Task DisconnectAsync()
|
|
{
|
|
Disconnect();
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private void Disconnect()
|
|
{
|
|
IsConnected = false;
|
|
_modbusMaster?.Dispose();
|
|
_modbusMaster = null;
|
|
_tcpClient?.Close();
|
|
_tcpClient?.Dispose();
|
|
_tcpClient = null;
|
|
}
|
|
|
|
public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort numberOfPoints)
|
|
{
|
|
EnsureConnected();
|
|
return await Task.Run(() => _modbusMaster!.ReadHoldingRegisters(SlaveId, startAddress, numberOfPoints));
|
|
}
|
|
|
|
public async Task<ushort[]> ReadInputRegistersAsync(ushort startAddress, ushort numberOfPoints)
|
|
{
|
|
EnsureConnected();
|
|
return await Task.Run(() => _modbusMaster!.ReadInputRegisters(SlaveId, startAddress, numberOfPoints));
|
|
}
|
|
|
|
public async Task<bool[]> ReadCoilsAsync(ushort startAddress, ushort numberOfPoints)
|
|
{
|
|
EnsureConnected();
|
|
return await Task.Run(() => _modbusMaster!.ReadCoils(SlaveId, startAddress, numberOfPoints));
|
|
}
|
|
|
|
public async Task<bool[]> ReadDiscreteInputsAsync(ushort startAddress, ushort numberOfPoints)
|
|
{
|
|
EnsureConnected();
|
|
return await Task.Run(() => _modbusMaster!.ReadInputs(SlaveId, startAddress, numberOfPoints));
|
|
}
|
|
|
|
public async Task WriteSingleCoilAsync(ushort coilAddress, bool value)
|
|
{
|
|
EnsureConnected();
|
|
await Task.Run(() => _modbusMaster!.WriteSingleCoil(SlaveId, coilAddress, value));
|
|
}
|
|
|
|
public async Task WriteMultipleCoilsAsync(ushort startAddress, bool[] values)
|
|
{
|
|
EnsureConnected();
|
|
await Task.Run(() => _modbusMaster!.WriteMultipleCoils(SlaveId, startAddress, values));
|
|
}
|
|
|
|
public async Task WriteSingleRegisterAsync(ushort registerAddress, ushort value)
|
|
{
|
|
EnsureConnected();
|
|
await Task.Run(() => _modbusMaster!.WriteSingleRegister(SlaveId, registerAddress, value));
|
|
}
|
|
|
|
public async Task WriteMultipleRegistersAsync(ushort startAddress, ushort[] values)
|
|
{
|
|
EnsureConnected();
|
|
await Task.Run(() => _modbusMaster!.WriteMultipleRegisters(SlaveId, startAddress, values));
|
|
}
|
|
|
|
private void EnsureConnected()
|
|
{
|
|
if (!IsConnected || _modbusMaster == null)
|
|
{
|
|
throw new InvalidOperationException("ModbusTCP connection is not connected. Call ConnectAsync() first.");
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
Disconnect();
|
|
_disposed = true;
|
|
}
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|
|
|