2026-06-13 14:16:34 +08:00
|
|
|
|
using Modbus.Device;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Net;
|
|
|
|
|
|
using System.Net.Sockets;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using AciTester.Models;
|
|
|
|
|
|
|
|
|
|
|
|
namespace AciTester.Services
|
|
|
|
|
|
{
|
|
|
|
|
|
public class ModbusTcpPlcService : IPlcService, IDisposable
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly PlcConfiguration _config;
|
|
|
|
|
|
private TcpClient _tcpClient;
|
|
|
|
|
|
private IModbusMaster _master;
|
|
|
|
|
|
|
|
|
|
|
|
public ModbusTcpPlcService(PlcConfiguration config)
|
|
|
|
|
|
{
|
|
|
|
|
|
_config = config;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex) when (i < retryCount - 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
System.Diagnostics.Debug.WriteLine($"连接失败,{500}ms 后重试... {ex.Message}");
|
|
|
|
|
|
await Task.Delay(500);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
throw new Exception($"无法连接到 PLC ({_config.IpAddress}:{_config.Port}),请检查网络和 PLC 状态。");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 读取两个连续的保持寄存器,转换为32位浮点数(假设大端模式)
|
|
|
|
|
|
public async Task<float> ReadFloatAsync(ushort startAddress)
|
|
|
|
|
|
{
|
|
|
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
var registers = await ReadHoldingRegistersAsync(startAddress, 2);
|
|
|
|
|
|
return UshortToFloat(registers[1], registers[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,
|
|
|
|
|
|
};
|
|
|
|
|
|
return await ReadFloatAsync(startAddress);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(100);
|
|
|
|
|
|
await _master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<bool> ReadCoilAsync(ushort coilAddress)
|
|
|
|
|
|
{
|
2026-06-16 21:18:46 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
await Task.Delay(100);
|
|
|
|
|
|
bool[] result = await _master?.ReadCoilsAsync(_config.SlaveId, coilAddress, 1);
|
|
|
|
|
|
return result[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
catch { return false; }
|
2026-06-13 14:16:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsConnected => _tcpClient != null && _tcpClient.Connected;
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count)
|
|
|
|
|
|
{
|
|
|
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
// await Task.Delay(100);
|
|
|
|
|
|
return await _master.ReadHoldingRegistersAsync(_config.SlaveId, startAddress, count);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task WriteSingleRegisterAsync(ushort registerAddress, ushort value)
|
|
|
|
|
|
{
|
|
|
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
int val = (int)value;
|
|
|
|
|
|
await Task.Delay(100);
|
|
|
|
|
|
await _master.WriteMultipleRegistersAsync(1, registerAddress, intToushorts(val));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task WriteMultipleRegistersAsync(ushort registerAddress, float value)
|
|
|
|
|
|
{
|
|
|
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
await Task.Delay(100);
|
|
|
|
|
|
await _master.WriteMultipleRegistersAsync(_config.SlaveId, registerAddress, SplitFloatToUShortArray((float)value));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Int转为ushort数组发送
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="res"></param>
|
|
|
|
|
|
/// <returns>返回ushort数组</returns>
|
|
|
|
|
|
private ushort[] intToushorts(int res)
|
|
|
|
|
|
{
|
|
|
|
|
|
ushort ust1 = (ushort)(res >> 16);
|
|
|
|
|
|
ushort ust2 = (ushort)res;
|
|
|
|
|
|
return new ushort[] { ust2, ust1 };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Float转为Ushort数组发送
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="value"></param>
|
|
|
|
|
|
/// <returns>返回ushort数组</returns>
|
|
|
|
|
|
public 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// ushort转为float类型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="P1"></param>
|
|
|
|
|
|
/// <param name="P2"></param>
|
|
|
|
|
|
/// <returns>float型数据</returns>
|
|
|
|
|
|
public 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 新增读取压力(根据工位)
|
|
|
|
|
|
public async Task<float> ReadPressureAsync(int stationId)
|
|
|
|
|
|
{
|
|
|
|
|
|
ushort startAddress = stationId switch
|
|
|
|
|
|
{
|
|
|
|
|
|
1 => _config.PressureRegisterStation1,
|
|
|
|
|
|
2 => _config.PressureRegisterStation2,
|
|
|
|
|
|
3 => _config.PressureRegisterStation3,
|
|
|
|
|
|
_ => throw new ArgumentException("Invalid station")
|
|
|
|
|
|
};
|
|
|
|
|
|
return await ReadFloatAsync(startAddress);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
_master?.Dispose();
|
|
|
|
|
|
_tcpClient?.Close();
|
|
|
|
|
|
_tcpClient?.Dispose();
|
|
|
|
|
|
}
|
2026-06-16 11:53:02 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<int> ReadInt32Async(ushort startAddress)
|
|
|
|
|
|
{
|
|
|
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
var regs = await ReadHoldingRegistersAsync(startAddress, 2);
|
2026-06-16 20:50:05 +08:00
|
|
|
|
|
|
|
|
|
|
return regs[0]; // 暂时返回大端,您根据日志判断
|
2026-06-16 11:53:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task WriteInt32Async(ushort startAddress, int value)
|
|
|
|
|
|
{
|
|
|
|
|
|
await EnsureConnectedAsync();
|
2026-06-16 20:50:05 +08:00
|
|
|
|
ushort[] dwellTimeRegisters = ConvertIntToRegisters(value);
|
|
|
|
|
|
await _master.WriteMultipleRegistersAsync(1, startAddress, dwellTimeRegisters);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将整数转换为寄存器数组(2个寄存器)
|
|
|
|
|
|
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;
|
2026-06-16 11:53:02 +08:00
|
|
|
|
}
|
2026-06-13 14:16:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|