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 ReadFloatAsync(ushort startAddress) { await EnsureConnectedAsync(); var registers = await ReadHoldingRegistersAsync(startAddress, 2); return UshortToFloat(registers[1], registers[0]); } public async Task WriteCoilAsync(ushort coilAddress, bool value) { await EnsureConnectedAsync(); await _master.WriteSingleCoilAsync(_config.SlaveId, coilAddress, value); } public async Task 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 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(); using (cancellationToken.Register(s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) { if (task != await Task.WhenAny(task, tcs.Task)) throw new OperationCanceledException(cancellationToken); } await task; } } }