更新2026
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TabletTester2025.Services
|
||||
{
|
||||
public class BalanceService
|
||||
{
|
||||
// 实际项目中请使用串口通讯
|
||||
public async Task<double> ReadWeightAsync()
|
||||
{
|
||||
await Task.Delay(100);
|
||||
return new Random().NextDouble() * 10; // 模拟重量 0-10g
|
||||
throw new InvalidOperationException("天平真实通讯未接入,禁止返回模拟重量");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Modbus.Device;
|
||||
using Modbus.Device;
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
@@ -7,102 +7,151 @@ using TabletTester2025.Models;
|
||||
|
||||
namespace TabletTester2025.Services
|
||||
{
|
||||
public class ModbusTcpPlcService : IPlcService
|
||||
public class ModbusTcpPlcService : IPlcService, IDisposable
|
||||
{
|
||||
private const int ConnectTimeoutMs = 3000;
|
||||
private const int RetryDelayMs = 1000;
|
||||
private const int DefaultRetryCount = 3;
|
||||
|
||||
private readonly PlcConfiguration _config;
|
||||
private TcpClient _tcpClient;
|
||||
private IModbusMaster _master;
|
||||
private readonly SemaphoreSlim _connectLock = new(1, 1);
|
||||
private TcpClient? _tcpClient;
|
||||
private IModbusMaster? _master;
|
||||
|
||||
public ModbusTcpPlcService(PlcConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async Task ConnectAsync() => await EnsureConnectedAsync();
|
||||
public Task ConnectAsync() => EnsureConnectedAsync();
|
||||
|
||||
public async Task EnsureConnectedAsync(int retryCount = 3)
|
||||
public async Task EnsureConnectedAsync(int retryCount = DefaultRetryCount)
|
||||
{
|
||||
if (_tcpClient != null && _tcpClient.Connected)
|
||||
if (HasOpenConnection)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < retryCount; i++)
|
||||
await _connectLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
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;
|
||||
if (HasOpenConnection)
|
||||
return;
|
||||
}
|
||||
catch (Exception ex) when (i < retryCount - 1)
|
||||
|
||||
Exception? lastError = null;
|
||||
for (int attempt = 1; attempt <= retryCount; attempt++)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"连接失败,500ms后重试... {ex.Message}");
|
||||
await Task.Delay(500);
|
||||
try
|
||||
{
|
||||
CloseConnection();
|
||||
|
||||
var client = new TcpClient();
|
||||
using var cts = new CancellationTokenSource(ConnectTimeoutMs);
|
||||
await client.ConnectAsync(_config.IpAddress, _config.Port).WithCancellation(cts.Token);
|
||||
|
||||
var master = ModbusIpMaster.CreateIp(client);
|
||||
master.Transport.ReadTimeout = 1000;
|
||||
master.Transport.WriteTimeout = 1000;
|
||||
|
||||
_tcpClient = client;
|
||||
_master = master;
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastError = ex;
|
||||
CloseConnection();
|
||||
System.Diagnostics.Debug.WriteLine($"PLC连接失败,第{attempt}/{retryCount}次:{ex.Message}");
|
||||
|
||||
if (attempt < retryCount)
|
||||
await Task.Delay(RetryDelayMs);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"无法连接到 PLC ({_config.IpAddress}:{_config.Port})", lastError);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_connectLock.Release();
|
||||
}
|
||||
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)
|
||||
public Task WriteCoilAsync(ushort coilAddress, bool value)
|
||||
{
|
||||
await EnsureConnectedAsync();
|
||||
await _master.WriteSingleCoilAsync(_config.SlaveId, coilAddress, value);
|
||||
return ExecuteAsync(master => master.WriteSingleCoilAsync(_config.SlaveId, coilAddress, value));
|
||||
}
|
||||
|
||||
public async Task<bool> ReadCoilAsync(ushort coilAddress)
|
||||
{
|
||||
await EnsureConnectedAsync();
|
||||
bool[] result = await _master.ReadCoilsAsync(_config.SlaveId, coilAddress, 1);
|
||||
bool[] result = await ExecuteAsync(master => master.ReadCoilsAsync(_config.SlaveId, coilAddress, 1));
|
||||
return result[0];
|
||||
}
|
||||
|
||||
public async Task WriteRegisterAsync(ushort registerAddress, ushort value)
|
||||
public Task WriteRegisterAsync(ushort registerAddress, ushort value)
|
||||
{
|
||||
await EnsureConnectedAsync();
|
||||
await _master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value);
|
||||
return ExecuteAsync(master => master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value));
|
||||
}
|
||||
|
||||
public async Task WriteFloatAsync(ushort startAddress, float value)
|
||||
public Task WriteFloatAsync(ushort startAddress, float value)
|
||||
{
|
||||
await EnsureConnectedAsync();
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
ushort[] registers =
|
||||
{
|
||||
(ushort)((bytes[2] << 8) | bytes[3]),
|
||||
(ushort)((bytes[0] << 8) | bytes[1])
|
||||
};
|
||||
await _master.WriteMultipleRegistersAsync(_config.SlaveId, startAddress, registers);
|
||||
|
||||
return ExecuteAsync(master => master.WriteMultipleRegistersAsync(_config.SlaveId, startAddress, registers));
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count)
|
||||
public Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count)
|
||||
{
|
||||
return ExecuteAsync(master => master.ReadHoldingRegistersAsync(_config.SlaveId, startAddress, count));
|
||||
}
|
||||
|
||||
public bool IsConnected => HasOpenConnection;
|
||||
|
||||
private bool HasOpenConnection => _tcpClient?.Connected == true && _master != null;
|
||||
|
||||
private async Task ExecuteAsync(Func<IModbusMaster, Task> action)
|
||||
{
|
||||
await ExecuteAsync(async master =>
|
||||
{
|
||||
await action(master);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<T> ExecuteAsync<T>(Func<IModbusMaster, Task<T>> action)
|
||||
{
|
||||
await EnsureConnectedAsync();
|
||||
return await _master.ReadHoldingRegistersAsync(_config.SlaveId, startAddress, count);
|
||||
|
||||
try
|
||||
{
|
||||
if (_master == null)
|
||||
throw new InvalidOperationException("PLC连接未初始化");
|
||||
|
||||
return await action(_master);
|
||||
}
|
||||
catch
|
||||
{
|
||||
CloseConnection();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConnected => _tcpClient != null && _tcpClient.Connected;
|
||||
|
||||
private float UshortToFloat(ushort high, ushort low)
|
||||
private static float UshortToFloat(ushort high, ushort low)
|
||||
{
|
||||
// Modbus 大端模式:高16位在前,低16位在后
|
||||
byte[] bytes = new byte[4];
|
||||
bytes[0] = (byte)(high >> 8);
|
||||
bytes[1] = (byte)(high & 0xFF);
|
||||
@@ -111,11 +160,19 @@ namespace TabletTester2025.Services
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
|
||||
private void CloseConnection()
|
||||
{
|
||||
try { _master?.Dispose(); } catch { }
|
||||
try { _tcpClient?.Close(); } catch { }
|
||||
try { _tcpClient?.Dispose(); } catch { }
|
||||
_master = null;
|
||||
_tcpClient = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_master?.Dispose();
|
||||
_tcpClient?.Close();
|
||||
_tcpClient?.Dispose();
|
||||
CloseConnection();
|
||||
_connectLock.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,11 +181,12 @@ namespace TabletTester2025.Services
|
||||
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))
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s!).TrySetResult(true), tcs))
|
||||
{
|
||||
if (task != await Task.WhenAny(task, tcs.Task))
|
||||
throw new OperationCanceledException(cancellationToken);
|
||||
}
|
||||
|
||||
await task;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user