添加项目文件。
This commit is contained in:
12
Services/IMesService.cs
Normal file
12
Services/IMesService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using HME_MoistureLossMeter.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HME_MoistureLossMeter.Services
|
||||
{
|
||||
public interface IMesService
|
||||
{
|
||||
Task<bool> SendRealtimeDataAsync(RealtimeDataModel data);
|
||||
Task<bool> SendTestResultAsync(TestResultModel result);
|
||||
Task<bool> SendHistoryDataAsync(HistoryDataModel history);
|
||||
}
|
||||
}
|
||||
23
Services/IPlcService.cs
Normal file
23
Services/IPlcService.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HME_MoistureLossMeter.Services
|
||||
{
|
||||
public interface IPlcService
|
||||
{
|
||||
Task EnsureConnectedAsync(int retryCount = 3);
|
||||
Task<float> ReadFloatAsync(ushort startAddress);
|
||||
Task<float> ReadPressureAsync();
|
||||
Task<float> ReadWetFlowAsync(int stationId);
|
||||
Task<float> ReadPressureAsync(int stationId);
|
||||
Task<bool> ReadCoilAsync(ushort coilAddress);
|
||||
Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count);
|
||||
Task WriteCoilAsync(ushort coilAddress, bool value);
|
||||
Task WriteRegisterAsync(ushort registerAddress, ushort value);
|
||||
Task WriteSingleRegisterAsync(ushort registerAddress, ushort value);
|
||||
Task WriteMultipleRegistersAsync(ushort registerAddress, float value);
|
||||
Task<int> ReadInt32Async(ushort startAddress);
|
||||
Task WriteInt32Async(ushort startAddress, int value);
|
||||
bool IsConnected { get; }
|
||||
void Dispose();
|
||||
}
|
||||
}
|
||||
111
Services/MesHttpService.cs
Normal file
111
Services/MesHttpService.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using HME_MoistureLossMeter.Models;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace HME_MoistureLossMeter.Services
|
||||
{
|
||||
public class MesHttpService : IMesService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly MesConfiguration _config;
|
||||
|
||||
public MesHttpService(HttpClient httpClient, MesConfiguration config)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_config = config;
|
||||
|
||||
if (!string.IsNullOrEmpty(_config.ApiKey))
|
||||
{
|
||||
_httpClient.DefaultRequestHeaders.Add("X-API-Key", _config.ApiKey);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SendRealtimeDataAsync(RealtimeDataModel data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(data, new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
});
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("/api/hme/realtime", content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Log.Warning("发送实时数据失败: {StatusCode}, {Reason}",
|
||||
response.StatusCode, response.ReasonPhrase);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "发送实时数据异常");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SendTestResultAsync(TestResultModel result)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(result, new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
});
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("/api/hme/test-result", content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Log.Warning("发送测试结果失败: {StatusCode}, {Reason}",
|
||||
response.StatusCode, response.ReasonPhrase);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "发送测试结果异常");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SendHistoryDataAsync(HistoryDataModel history)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(history, new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
});
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("/api/hme/history", content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Log.Warning("发送历史数据失败: {StatusCode}, {Reason}",
|
||||
response.StatusCode, response.ReasonPhrase);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "发送历史数据异常");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
232
Services/ModbusTcpPlcService.cs
Normal file
232
Services/ModbusTcpPlcService.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user