更新20260616
This commit is contained in:
@@ -47,7 +47,7 @@ namespace FootwearTest
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging(builder => builder.AddSerilog(dispose: false));
|
||||
services.AddSingleton<DeviceSettings>();
|
||||
services.AddSingleton(DeviceSettingsStore.Load());
|
||||
services.AddSingleton<TestFormulaService>();
|
||||
services.AddSingleton<TestRunRepository>();
|
||||
services.AddSingleton<ReportService>();
|
||||
|
||||
48
FootwearTest/Services/DeviceSettingsStore.cs
Normal file
48
FootwearTest/Services/DeviceSettingsStore.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FootwearTest.Services;
|
||||
|
||||
public static class DeviceSettingsStore
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public static DeviceSettings Load()
|
||||
{
|
||||
var path = GetSettingsPath();
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return new DeviceSettings();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(path);
|
||||
return JsonSerializer.Deserialize<DeviceSettings>(json, JsonOptions) ?? new DeviceSettings();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new DeviceSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save(DeviceSettings settings)
|
||||
{
|
||||
var path = GetSettingsPath();
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||
var json = JsonSerializer.Serialize(settings, JsonOptions);
|
||||
File.WriteAllText(path, json);
|
||||
}
|
||||
|
||||
public static string GetSettingsPath()
|
||||
{
|
||||
var folder = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"FootwearTest");
|
||||
return Path.Combine(folder, "device-settings.json");
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ public sealed class HybridDeviceClient : IDeviceClient
|
||||
private readonly ModbusTcpDeviceClient _plcClient;
|
||||
private readonly ModbusRtuDeviceClient _instrumentClient;
|
||||
private readonly ILogger<HybridDeviceClient> _logger;
|
||||
private readonly SemaphoreSlim _communicationLock = new(1, 1);
|
||||
|
||||
public HybridDeviceClient(
|
||||
ModbusTcpDeviceClient plcClient,
|
||||
@@ -33,41 +34,65 @@ public sealed class HybridDeviceClient : IDeviceClient
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _plcClient.ConnectAsync(cancellationToken);
|
||||
await _communicationLock.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
await _instrumentClient.ConnectAsync(cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _plcClient.DisconnectAsync(cancellationToken);
|
||||
throw;
|
||||
}
|
||||
await _plcClient.ConnectAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
await _instrumentClient.ConnectAsync(cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _plcClient.DisconnectAsync(cancellationToken);
|
||||
throw;
|
||||
}
|
||||
|
||||
LastSnapshot = MergeSnapshots(_plcClient.LastSnapshot, _instrumentClient.LastSnapshot) with
|
||||
LastSnapshot = MergeSnapshots(_plcClient.LastSnapshot, _instrumentClient.LastSnapshot) with
|
||||
{
|
||||
AlarmText = "PLC TCP 与 485 仪表已连接"
|
||||
};
|
||||
SnapshotUpdated?.Invoke(this, LastSnapshot);
|
||||
_logger.LogInformation("Hybrid device communication connected. {ConnectionText}", ConnectionText);
|
||||
}
|
||||
finally
|
||||
{
|
||||
AlarmText = "PLC TCP 与 485 仪表已连接"
|
||||
};
|
||||
SnapshotUpdated?.Invoke(this, LastSnapshot);
|
||||
_logger.LogInformation("Hybrid device communication connected. {ConnectionText}", ConnectionText);
|
||||
_communicationLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _instrumentClient.DisconnectAsync(cancellationToken);
|
||||
await _plcClient.DisconnectAsync(cancellationToken);
|
||||
await _communicationLock.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
await _instrumentClient.DisconnectAsync(cancellationToken);
|
||||
await _plcClient.DisconnectAsync(cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_communicationLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DeviceSnapshot> ReadSnapshotAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var plcSnapshot = await _plcClient.ReadSnapshotAsync(cancellationToken);
|
||||
var instrumentSnapshot = await _instrumentClient.ReadSnapshotAsync(cancellationToken);
|
||||
LastSnapshot = MergeSnapshots(plcSnapshot, instrumentSnapshot) with
|
||||
await _communicationLock.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
AlarmText = "PLC TCP 与 485 仪表读取正常"
|
||||
};
|
||||
SnapshotUpdated?.Invoke(this, LastSnapshot);
|
||||
return LastSnapshot;
|
||||
var plcSnapshot = await _plcClient.ReadSnapshotAsync(cancellationToken);
|
||||
var instrumentSnapshot = await _instrumentClient.ReadSnapshotAsync(cancellationToken);
|
||||
LastSnapshot = MergeSnapshots(plcSnapshot, instrumentSnapshot) with
|
||||
{
|
||||
AlarmText = "PLC TCP 与 485 仪表读取正常"
|
||||
};
|
||||
SnapshotUpdated?.Invoke(this, LastSnapshot);
|
||||
return LastSnapshot;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_communicationLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetOutputsAsync(
|
||||
@@ -76,16 +101,24 @@ public sealed class HybridDeviceClient : IDeviceClient
|
||||
bool heaterRunning,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _plcClient.SetOutputsAsync(pumpRunning, fanRunning, heaterRunning, cancellationToken);
|
||||
LastSnapshot = LastSnapshot with
|
||||
await _communicationLock.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
PumpRunning = pumpRunning,
|
||||
FanRunning = fanRunning,
|
||||
HeaterRunning = heaterRunning,
|
||||
AlarmText = "PLC TCP 输出写入正常"
|
||||
};
|
||||
SnapshotUpdated?.Invoke(this, LastSnapshot);
|
||||
await _plcClient.SetOutputsAsync(pumpRunning, fanRunning, heaterRunning, cancellationToken);
|
||||
LastSnapshot = LastSnapshot with
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
PumpRunning = pumpRunning,
|
||||
FanRunning = fanRunning,
|
||||
HeaterRunning = heaterRunning,
|
||||
AlarmText = "PLC TCP 输出写入正常"
|
||||
};
|
||||
SnapshotUpdated?.Invoke(this, LastSnapshot);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_communicationLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static DeviceSnapshot MergeSnapshots(DeviceSnapshot plcSnapshot, DeviceSnapshot instrumentSnapshot)
|
||||
|
||||
@@ -167,15 +167,7 @@ public sealed class ModbusTcpDeviceClient : IDeviceClient
|
||||
Span<byte> bytes = stackalloc byte[4];
|
||||
BinaryPrimitives.WriteUInt16BigEndian(bytes[..2], registers[0]);
|
||||
BinaryPrimitives.WriteUInt16BigEndian(bytes[2..], registers[1]);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
return BitConverter.ToSingle(bytes);
|
||||
}
|
||||
|
||||
var buffer = bytes.ToArray();
|
||||
Array.Reverse(buffer);
|
||||
return BitConverter.ToSingle(buffer, 0);
|
||||
return BinaryPrimitives.ReadSingleLittleEndian(bytes);
|
||||
}
|
||||
|
||||
private async Task<ushort[]> ReadHoldingRegistersAsync(
|
||||
@@ -246,7 +238,7 @@ public sealed class ModbusTcpDeviceClient : IDeviceClient
|
||||
var responseTransactionId = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(0, 2));
|
||||
var protocolId = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(2, 2));
|
||||
var length = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(4, 2));
|
||||
if (responseTransactionId != transactionId || protocolId != 0 || length == 0)
|
||||
if (responseTransactionId != transactionId || protocolId != 0 || length == 0 || header[6] != _settings.PlcSlaveId)
|
||||
{
|
||||
throw new InvalidDataException("PLC Modbus TCP MBAP 响应头不正确。");
|
||||
}
|
||||
|
||||
@@ -152,7 +152,8 @@ public partial class SettingsViewModel : ViewModelBase
|
||||
_settings.PumpCoilAddress = PumpCoilAddress;
|
||||
_settings.FanCoilAddress = FanCoilAddress;
|
||||
_settings.HeaterCoilAddress = HeaterCoilAddress;
|
||||
SaveStatus = "参数已保存,新的试验流程将使用当前设置";
|
||||
DeviceSettingsStore.Save(_settings);
|
||||
SaveStatus = "参数已保存,重新连接设备后生效";
|
||||
_logger.LogInformation(
|
||||
"Settings saved. UseSimulator={UseSimulator}, PlcHost={PlcHost}, PlcPort={PlcPort}, SerialPort={SerialPort}, BaudRate={BaudRate}, FootAreaSquareMeters={FootArea}, PumpSpeed={PumpSpeed}, FootTemperatureRegister={FootTemperatureRegister}, VoltageRegister={VoltageRegister}, VoltageDecimalPointRegister={VoltageDecimalPointRegister}, CurrentRegister={CurrentRegister}, CurrentDecimalPointRegister={CurrentDecimalPointRegister}, BalanceRegister={BalanceRegister}, BalanceDecimalPointRegister={BalanceDecimalPointRegister}",
|
||||
UseSimulator,
|
||||
|
||||
Reference in New Issue
Block a user