using HME_MoistureLossMeter.Models; using Modbus.Device; using System; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; namespace HME_MoistureLossMeter.Services { public class ModbusTcpPlcService : IPlcService, IDisposable { // 寄存器地址常量 - 测试界面 public const ushort ADDR_WATER_TEMP = 1118; // 水浴温度 public const ushort ADDR_CHAMBER_TEMP = 1168; // 箱体温度 public const ushort ADDR_WEIGHT = 1268; // 重量 public const ushort ADDR_AIR_VOLUME = 4110; // 空气体积 public const ushort ADDR_OUTLET_FLOW = 1218; // 出口流量 public const ushort ADDR_PRESET_HOUR = 1681; // 设定测试时 public const ushort ADDR_PRESET_MINUTE = 1680; // 设定测试分 public const ushort ADDR_DISPLAY_HOUR = 3004; // 显示时 public const ushort ADDR_DISPLAY_MINUTE = 3003; // 显示分 public const ushort ADDR_DISPLAY_SECOND = 3002; // 显示秒 public const ushort ADDR_INITIAL_MASS = 206; // 初始质量 public const ushort ADDR_FINAL_MASS = 208; // 测后质量 public const ushort ADDR_MOISTURE_LOSS = 4106; // 水分损失 public const ushort ADDR_BATCH_NO = 3202; // 生产批号 public const ushort ADDR_OPERATOR_ID = 3200; // 操作员编号 public const ushort ADDR_EXPERIMENT_ID = 3204; // 实验编号 public const ushort ADDR_TIDAL_VOLUME = 300; // 潮气量 public const ushort ADDR_FREQUENCY = 210; // 频率 public const ushort ADDR_BREATH_COUNT = 3000; // 呼吸次数 public const ushort ADDR_DRY_AIR_FLOW = 3700; // 通入干燥空气量 // 线圈地址 - 测试界面 public const ushort COIL_RESET = 3; // 复位 M3 public const ushort COIL_TEST = 5; // 测试 M5 public const ushort COIL_STOP = 8; // 停止 M8 public const ushort COIL_CLEAR = 41; // 清零 M41 public const ushort COIL_HEAT = 300; // 加热 M300 public const ushort COIL_P1_RECORD = 91; // P1记录 M91 public const ushort COIL_P2_RECORD = 92; // P2记录 M92 public const ushort COIL_PRINT = 15; // 打印 M15 public const ushort COIL_EXHALE = 51; // 呼气 M51 public const ushort COIL_INHALE = 55; // 吸气 M55 public const ushort COIL_DOWN = 46; // 下降 M46 public const ushort COIL_UP = 47; // 上升 M47 // 寄存器地址 - 手动界面 public const ushort ADDR_MANUAL_SPEED = 218; // 手动速度 D218 public const ushort ADDR_TIDAL_COEFF = 302; // 潮气量系数 D302 public const ushort ADDR_FREQ_COEFF = 282; // 频率系数 D282 // 线圈地址 - 手动界面 public const ushort COIL_LEFT = 16; // 左 M16 public const ushort COIL_RIGHT = 17; // 右 M17 public const ushort COIL_MANUAL_INHALE = 19; // 手动吸 M19 public const ushort COIL_MANUAL_EXHALE = 18; // 手动呼 M18 public const ushort COIL_ZERO = 40; // 校零 M40 private readonly PlcConfiguration _config; private TcpClient _tcpClient; private IModbusMaster _master; public ModbusTcpPlcService(PlcConfiguration config) { _config = config; } public bool IsConnected => _tcpClient != null && _tcpClient.Connected; 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); _master = ModbusIpMaster.CreateIp(_tcpClient); _master.Transport.ReadTimeout = 3000; _master.Transport.WriteTimeout = 3000; 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}),请检查网络和 PLC 状态。"); } public async Task ReadFloatAsync(ushort startAddress) { await EnsureConnectedAsync(); var registers = await ReadHoldingRegistersAsync(startAddress, 2); return UshortToFloat(registers[1], registers[0]); } public async Task ReadInt32Async(ushort startAddress) { await EnsureConnectedAsync(); var regs = await ReadHoldingRegistersAsync(startAddress, 2); return regs[1] << 16 | regs[0]; } public async Task ReadPressureAsync() => await ReadFloatAsync(_config.PressureRegister); public async Task ReadWetFlowAsync(int stationId) { ushort startAddress = stationId switch { 1 => _config.WetFlowRegister, 2 => _config.WetFlowRegister2, 3 => _config.WetFlowRegister3, _ => _config.WetFlowRegister }; return await ReadFloatAsync(startAddress); } public async Task ReadPressureAsync(int stationId) { ushort startAddress = stationId switch { 1 => _config.PressureRegisterStation1, 2 => _config.PressureRegisterStation2, 3 => _config.PressureRegisterStation3, _ => _config.PressureRegisterStation1 }; return await ReadFloatAsync(startAddress); } public async Task ReadCoilAsync(ushort coilAddress) { try { await EnsureConnectedAsync(); bool[] result = await _master.ReadCoilsAsync(_config.SlaveId, coilAddress, 1); return result[0]; } catch { return false; } } public async Task ReadHoldingRegistersAsync(ushort startAddress, ushort count) { await EnsureConnectedAsync(); return await _master.ReadHoldingRegistersAsync(_config.SlaveId, startAddress, count); } public async Task WriteCoilAsync(ushort coilAddress, bool value) { await EnsureConnectedAsync(); await _master.WriteSingleCoilAsync(_config.SlaveId, coilAddress, value); } public async Task WriteRegisterAsync(ushort registerAddress, ushort value) { await EnsureConnectedAsync(); await Task.Delay(50); await _master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value); } public async Task WriteSingleRegisterAsync(ushort registerAddress, ushort value) { await EnsureConnectedAsync(); await Task.Delay(50); await _master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value); } public async Task WriteMultipleRegistersAsync(ushort registerAddress, float value) { await EnsureConnectedAsync(); await Task.Delay(50); await _master.WriteMultipleRegistersAsync(_config.SlaveId, registerAddress, SplitFloatToUShortArray(value)); } public async Task WriteInt32Async(ushort startAddress, int value) { await EnsureConnectedAsync(); ushort[] registers = ConvertIntToRegisters(value); await _master.WriteMultipleRegistersAsync(_config.SlaveId, startAddress, registers); } private ushort[] SplitFloatToUShortArray(float value) { byte[] floatBytes = BitConverter.GetBytes(value); ushort[] ushortArray = new ushort[floatBytes.Length / 2]; for (int i = 0, j = 0; i < floatBytes.Length; i += 2, j++) { ushortArray[j] = BitConverter.ToUInt16(floatBytes, i); } return ushortArray; } private float UshortToFloat(ushort p1, ushort p2) { int intSign, intSignRest, intExponent, intExponentRest; float faResult, faDigit; intSign = p1 / 32768; intSignRest = p1 % 32768; intExponent = intSignRest / 128; intExponentRest = intSignRest % 128; faDigit = (float)(intExponentRest * 65536 + p2) / 8388608; faResult = (float)Math.Pow(-1, intSign) * (float)Math.Pow(2, intExponent - 127) * (faDigit + 1); return faResult; } private ushort[] ConvertIntToRegisters(int value) { byte[] bytes = BitConverter.GetBytes(value); ushort[] registers = new ushort[2]; registers[0] = BitConverter.ToUInt16(bytes, 0); registers[1] = BitConverter.ToUInt16(bytes, 2); return registers; } public void Dispose() { _master?.Dispose(); _tcpClient?.Close(); _tcpClient?.Dispose(); } } }