This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
namespace ASTM_D7896_Tester.Services;
|
||||
|
||||
public interface IPlcCommunicationService
|
||||
{
|
||||
Task<bool> ConnectAsync();
|
||||
Task DisconnectAsync();
|
||||
Task<bool> IsConnectedAsync();
|
||||
Task<float> ReadFloatAsync(int address);
|
||||
Task WriteSingleCoilAsync(int address, bool value);
|
||||
Task WriteSingleRegisterAsync(int address, short value);
|
||||
}
|
||||
39
Services/IPlcService.cs
Normal file
39
Services/IPlcService.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
namespace ASTM_D7896_Tester.Services;
|
||||
|
||||
public interface IPlcService
|
||||
{
|
||||
Task<bool> ConnectAsync();
|
||||
Task DisconnectAsync();
|
||||
Task<bool> IsConnectedAsync();
|
||||
/// <summary> 读取指定工位的压力(浮点数) </summary>
|
||||
/// <param name="stationId">工位号 1~3</param>
|
||||
//Task<float> ReadPressureAsync(int stationId);
|
||||
|
||||
/// <summary> 读取湿膜流量(浮点数) </summary>
|
||||
Task<float> ReadWetFlowAsync(int stationId);
|
||||
|
||||
/// <summary> 读取干膜流量(浮点数) </summary>
|
||||
Task<float> ReadDryFlowAsync(int stationId);
|
||||
|
||||
/// <summary> 写入线圈(如 M 元件) </summary>
|
||||
Task WriteCoilAsync(ushort coilAddress, bool value);
|
||||
|
||||
/// <summary> 写入单个寄存器(16位) </summary>
|
||||
Task WriteRegisterAsync(ushort registerAddress, ushort value);
|
||||
|
||||
/// <summary> 读取线圈状态(如 M 元件的 ON/OFF) </summary>
|
||||
Task<bool> ReadCoilAsync(ushort coilAddress);
|
||||
|
||||
/// <summary> 读取连续多个保持寄存器(16位) </summary>
|
||||
Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count);
|
||||
|
||||
/// <summary> 写入单个保持寄存器(16位) </summary>
|
||||
Task WriteSingleRegisterAsync(ushort registerAddress, ushort value);
|
||||
|
||||
|
||||
Task WriteMultipleRegistersAsync(ushort registerAddress, float value);
|
||||
|
||||
float UshortToFloat(ushort P1, ushort P2);
|
||||
|
||||
Task<float> ReadFloatAsync(ushort startAddress);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ASTM_D7896_Tester.Services;
|
||||
|
||||
public class PlcCommunicationService : IPlcCommunicationService
|
||||
{
|
||||
private bool _isConnected = false;
|
||||
private readonly Random _random = new();
|
||||
|
||||
public Task<bool> ConnectAsync()
|
||||
{
|
||||
// 模拟连接
|
||||
_isConnected = true;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
_isConnected = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<bool> IsConnectedAsync() => Task.FromResult(_isConnected);
|
||||
|
||||
public Task<float> ReadFloatAsync(int address)
|
||||
{
|
||||
// 模拟读取PLC寄存器返回随机值(实际应通过协议读取)
|
||||
// 热导率范围 0.1~1.0 W/m·K,热扩散率范围 0.05~1.5 ×10⁻⁶ m²/s
|
||||
if (address == 40001)
|
||||
return Task.FromResult((float)(0.2 + _random.NextDouble() * 0.8));
|
||||
if (address == 40003)
|
||||
return Task.FromResult((float)(0.1 + _random.NextDouble() * 1.0));
|
||||
if (address == 40005)
|
||||
return Task.FromResult(25.0f); // 测试温度
|
||||
return Task.FromResult(0.0f);
|
||||
}
|
||||
|
||||
public Task WriteSingleCoilAsync(int address, bool value)
|
||||
{
|
||||
// 模拟写入线圈
|
||||
System.Diagnostics.Debug.WriteLine($"Write coil {address} = {value}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task WriteSingleRegisterAsync(int address, short value)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Write register {address} = {value}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
212
Services/PlcService.cs
Normal file
212
Services/PlcService.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
using ASTM_D7896_Tester.Models;
|
||||
using Modbus.Device;
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ASTM_D7896_Tester.Services;
|
||||
|
||||
public class PlcService : IPlcService
|
||||
{
|
||||
private bool _isConnected = false;
|
||||
private readonly AppConfig _config;
|
||||
private TcpClient _tcpClient;
|
||||
private IModbusMaster _master;
|
||||
|
||||
public PlcService(AppConfig config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public Task<bool> ConnectAsync()
|
||||
{
|
||||
// 模拟连接
|
||||
_isConnected = true;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
_isConnected = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<bool> IsConnectedAsync() => Task.FromResult(_isConnected);
|
||||
|
||||
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.PlcConnection.IpAddress, _config.PlcConnection.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.PlcConnection.IpAddress}:{_config.PlcConnection.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<float> ReadDryFlowAsync(int stationId)
|
||||
{
|
||||
ushort startAddress = stationId switch
|
||||
{
|
||||
_ => throw new ArgumentException("Invalid station")
|
||||
};
|
||||
return await ReadFloatAsync(startAddress);
|
||||
}
|
||||
|
||||
|
||||
public async Task WriteCoilAsync(ushort coilAddress, bool value)
|
||||
{
|
||||
await EnsureConnectedAsync();
|
||||
await _master.WriteSingleCoilAsync(_config.PlcConnection.SlaveId, coilAddress, value);
|
||||
}
|
||||
|
||||
public async Task WriteRegisterAsync(ushort registerAddress, ushort value)
|
||||
{
|
||||
await EnsureConnectedAsync();
|
||||
await Task.Delay(100);
|
||||
await _master.WriteSingleRegisterAsync(_config.PlcConnection.SlaveId, registerAddress, value);
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> ReadCoilAsync(ushort coilAddress)
|
||||
{
|
||||
await EnsureConnectedAsync();
|
||||
await Task.Delay(100);
|
||||
bool[] result = await _master?.ReadCoilsAsync(_config.PlcConnection.SlaveId, coilAddress, 1);
|
||||
return result[0];
|
||||
}
|
||||
|
||||
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.PlcConnection.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.PlcConnection.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 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<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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user