123 lines
4.3 KiB
C#
123 lines
4.3 KiB
C#
using Modbus.Device;
|
||
using System;
|
||
using System.Net.Sockets;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using TabletTester2025.Models;
|
||
|
||
namespace TabletTester2025.Services
|
||
{
|
||
public class ModbusTcpPlcService : IPlcService
|
||
{
|
||
private readonly PlcConfiguration _config;
|
||
private TcpClient _tcpClient;
|
||
private IModbusMaster _master;
|
||
|
||
public ModbusTcpPlcService(PlcConfiguration config)
|
||
{
|
||
_config = config;
|
||
}
|
||
|
||
public async Task ConnectAsync() => await EnsureConnectedAsync();
|
||
|
||
public async Task EnsureConnectedAsync(int retryCount = 3)
|
||
{
|
||
if (_tcpClient != null && _tcpClient.Connected)
|
||
return;
|
||
|
||
for (int i = 0; i < retryCount; i++)
|
||
{
|
||
try
|
||
{
|
||
_tcpClient?.Close();
|
||
_tcpClient = new TcpClient();
|
||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
|
||
await _tcpClient.ConnectAsync(_config.IpAddress, _config.Port).WithCancellation(cts.Token);
|
||
_master = ModbusIpMaster.CreateIp(_tcpClient);
|
||
_master.Transport.ReadTimeout = 1000;
|
||
_master.Transport.WriteTimeout = 1000;
|
||
return;
|
||
}
|
||
catch (Exception ex) when (i < retryCount - 1)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"连接失败,500ms后重试... {ex.Message}");
|
||
await Task.Delay(500);
|
||
}
|
||
}
|
||
throw new Exception($"无法连接到 PLC ({_config.IpAddress}:{_config.Port})");
|
||
}
|
||
//读取寄存器返回浮点型
|
||
public async Task<float> ReadFloatAsync(ushort startAddress)
|
||
{
|
||
await EnsureConnectedAsync();
|
||
var registers = await ReadHoldingRegistersAsync(startAddress, 2);
|
||
return UshortToFloat(registers[1], registers[0]);
|
||
}
|
||
//读取返回整型
|
||
public async Task<int> ReadIntAsync(ushort startAddress)
|
||
{
|
||
await EnsureConnectedAsync();
|
||
var registers = await ReadHoldingRegistersAsync(startAddress, 1);
|
||
return registers[0];
|
||
}
|
||
|
||
public async Task WriteCoilAsync(ushort coilAddress, bool value)
|
||
{
|
||
await EnsureConnectedAsync();
|
||
await _master.WriteSingleCoilAsync(_config.SlaveId, coilAddress, value);
|
||
}
|
||
|
||
public async Task<bool> ReadCoilAsync(ushort coilAddress)
|
||
{
|
||
await EnsureConnectedAsync();
|
||
bool[] result = await _master.ReadCoilsAsync(_config.SlaveId, coilAddress, 1);
|
||
return result[0];
|
||
}
|
||
|
||
public async Task WriteRegisterAsync(ushort registerAddress, ushort value)
|
||
{
|
||
await EnsureConnectedAsync();
|
||
await _master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value);
|
||
}
|
||
|
||
public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count)
|
||
{
|
||
await EnsureConnectedAsync();
|
||
return await _master.ReadHoldingRegistersAsync(_config.SlaveId, startAddress, count);
|
||
}
|
||
|
||
public bool IsConnected => _tcpClient != null && _tcpClient.Connected;
|
||
|
||
private float UshortToFloat(ushort high, ushort low)
|
||
{
|
||
// Modbus 大端模式:高16位在前,低16位在后
|
||
byte[] bytes = new byte[4];
|
||
bytes[0] = (byte)(high >> 8);
|
||
bytes[1] = (byte)(high & 0xFF);
|
||
bytes[2] = (byte)(low >> 8);
|
||
bytes[3] = (byte)(low & 0xFF);
|
||
return BitConverter.ToSingle(bytes, 0);
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
_master?.Dispose();
|
||
_tcpClient?.Close();
|
||
_tcpClient?.Dispose();
|
||
}
|
||
}
|
||
|
||
public static class TaskExtensions
|
||
{
|
||
public static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
|
||
{
|
||
var tcs = new TaskCompletionSource<bool>();
|
||
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
|
||
{
|
||
if (task != await Task.WhenAny(task, tcs.Task))
|
||
throw new OperationCanceledException(cancellationToken);
|
||
}
|
||
await task;
|
||
}
|
||
}
|
||
} |