Files
New-version-HME-moisture-lo…/Services/ModbusTcpPlcService.cs
2026-06-17 15:04:35 +08:00

232 lines
9.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<float> ReadFloatAsync(ushort startAddress)
{
await EnsureConnectedAsync();
var registers = await ReadHoldingRegistersAsync(startAddress, 2);
return UshortToFloat(registers[1], registers[0]);
}
public async Task<int> ReadInt32Async(ushort startAddress)
{
await EnsureConnectedAsync();
var regs = await ReadHoldingRegistersAsync(startAddress, 2);
return regs[1] << 16 | regs[0];
}
public async Task<float> ReadPressureAsync() =>
await ReadFloatAsync(_config.PressureRegister);
public async Task<float> 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<float> 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<bool> ReadCoilAsync(ushort coilAddress)
{
try
{
await EnsureConnectedAsync();
bool[] result = await _master.ReadCoilsAsync(_config.SlaveId, coilAddress, 1);
return result[0];
}
catch { return false; }
}
public async Task<ushort[]> 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();
}
}
}