更新20260616

This commit is contained in:
GukSang.Jin
2026-06-16 18:06:02 +08:00
parent 6f58bba5c3
commit 1ffd92ce58
5 changed files with 117 additions and 43 deletions

View File

@@ -47,7 +47,7 @@ namespace FootwearTest
{ {
var services = new ServiceCollection(); var services = new ServiceCollection();
services.AddLogging(builder => builder.AddSerilog(dispose: false)); services.AddLogging(builder => builder.AddSerilog(dispose: false));
services.AddSingleton<DeviceSettings>(); services.AddSingleton(DeviceSettingsStore.Load());
services.AddSingleton<TestFormulaService>(); services.AddSingleton<TestFormulaService>();
services.AddSingleton<TestRunRepository>(); services.AddSingleton<TestRunRepository>();
services.AddSingleton<ReportService>(); services.AddSingleton<ReportService>();

View 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");
}
}

View File

@@ -11,6 +11,7 @@ public sealed class HybridDeviceClient : IDeviceClient
private readonly ModbusTcpDeviceClient _plcClient; private readonly ModbusTcpDeviceClient _plcClient;
private readonly ModbusRtuDeviceClient _instrumentClient; private readonly ModbusRtuDeviceClient _instrumentClient;
private readonly ILogger<HybridDeviceClient> _logger; private readonly ILogger<HybridDeviceClient> _logger;
private readonly SemaphoreSlim _communicationLock = new(1, 1);
public HybridDeviceClient( public HybridDeviceClient(
ModbusTcpDeviceClient plcClient, ModbusTcpDeviceClient plcClient,
@@ -32,6 +33,9 @@ public sealed class HybridDeviceClient : IDeviceClient
public event EventHandler<DeviceSnapshot>? SnapshotUpdated; public event EventHandler<DeviceSnapshot>? SnapshotUpdated;
public async Task ConnectAsync(CancellationToken cancellationToken = default) public async Task ConnectAsync(CancellationToken cancellationToken = default)
{
await _communicationLock.WaitAsync(cancellationToken);
try
{ {
await _plcClient.ConnectAsync(cancellationToken); await _plcClient.ConnectAsync(cancellationToken);
try try
@@ -51,14 +55,30 @@ public sealed class HybridDeviceClient : IDeviceClient
SnapshotUpdated?.Invoke(this, LastSnapshot); SnapshotUpdated?.Invoke(this, LastSnapshot);
_logger.LogInformation("Hybrid device communication connected. {ConnectionText}", ConnectionText); _logger.LogInformation("Hybrid device communication connected. {ConnectionText}", ConnectionText);
} }
finally
{
_communicationLock.Release();
}
}
public async Task DisconnectAsync(CancellationToken cancellationToken = default) public async Task DisconnectAsync(CancellationToken cancellationToken = default)
{
await _communicationLock.WaitAsync(cancellationToken);
try
{ {
await _instrumentClient.DisconnectAsync(cancellationToken); await _instrumentClient.DisconnectAsync(cancellationToken);
await _plcClient.DisconnectAsync(cancellationToken); await _plcClient.DisconnectAsync(cancellationToken);
} }
finally
{
_communicationLock.Release();
}
}
public async Task<DeviceSnapshot> ReadSnapshotAsync(CancellationToken cancellationToken = default) public async Task<DeviceSnapshot> ReadSnapshotAsync(CancellationToken cancellationToken = default)
{
await _communicationLock.WaitAsync(cancellationToken);
try
{ {
var plcSnapshot = await _plcClient.ReadSnapshotAsync(cancellationToken); var plcSnapshot = await _plcClient.ReadSnapshotAsync(cancellationToken);
var instrumentSnapshot = await _instrumentClient.ReadSnapshotAsync(cancellationToken); var instrumentSnapshot = await _instrumentClient.ReadSnapshotAsync(cancellationToken);
@@ -69,12 +89,20 @@ public sealed class HybridDeviceClient : IDeviceClient
SnapshotUpdated?.Invoke(this, LastSnapshot); SnapshotUpdated?.Invoke(this, LastSnapshot);
return LastSnapshot; return LastSnapshot;
} }
finally
{
_communicationLock.Release();
}
}
public async Task SetOutputsAsync( public async Task SetOutputsAsync(
bool pumpRunning, bool pumpRunning,
bool fanRunning, bool fanRunning,
bool heaterRunning, bool heaterRunning,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{
await _communicationLock.WaitAsync(cancellationToken);
try
{ {
await _plcClient.SetOutputsAsync(pumpRunning, fanRunning, heaterRunning, cancellationToken); await _plcClient.SetOutputsAsync(pumpRunning, fanRunning, heaterRunning, cancellationToken);
LastSnapshot = LastSnapshot with LastSnapshot = LastSnapshot with
@@ -87,6 +115,11 @@ public sealed class HybridDeviceClient : IDeviceClient
}; };
SnapshotUpdated?.Invoke(this, LastSnapshot); SnapshotUpdated?.Invoke(this, LastSnapshot);
} }
finally
{
_communicationLock.Release();
}
}
private static DeviceSnapshot MergeSnapshots(DeviceSnapshot plcSnapshot, DeviceSnapshot instrumentSnapshot) private static DeviceSnapshot MergeSnapshots(DeviceSnapshot plcSnapshot, DeviceSnapshot instrumentSnapshot)
{ {

View File

@@ -167,15 +167,7 @@ public sealed class ModbusTcpDeviceClient : IDeviceClient
Span<byte> bytes = stackalloc byte[4]; Span<byte> bytes = stackalloc byte[4];
BinaryPrimitives.WriteUInt16BigEndian(bytes[..2], registers[0]); BinaryPrimitives.WriteUInt16BigEndian(bytes[..2], registers[0]);
BinaryPrimitives.WriteUInt16BigEndian(bytes[2..], registers[1]); BinaryPrimitives.WriteUInt16BigEndian(bytes[2..], registers[1]);
return BinaryPrimitives.ReadSingleLittleEndian(bytes);
if (BitConverter.IsLittleEndian)
{
return BitConverter.ToSingle(bytes);
}
var buffer = bytes.ToArray();
Array.Reverse(buffer);
return BitConverter.ToSingle(buffer, 0);
} }
private async Task<ushort[]> ReadHoldingRegistersAsync( private async Task<ushort[]> ReadHoldingRegistersAsync(
@@ -246,7 +238,7 @@ public sealed class ModbusTcpDeviceClient : IDeviceClient
var responseTransactionId = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(0, 2)); var responseTransactionId = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(0, 2));
var protocolId = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(2, 2)); var protocolId = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(2, 2));
var length = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(4, 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 响应头不正确。"); throw new InvalidDataException("PLC Modbus TCP MBAP 响应头不正确。");
} }

View File

@@ -152,7 +152,8 @@ public partial class SettingsViewModel : ViewModelBase
_settings.PumpCoilAddress = PumpCoilAddress; _settings.PumpCoilAddress = PumpCoilAddress;
_settings.FanCoilAddress = FanCoilAddress; _settings.FanCoilAddress = FanCoilAddress;
_settings.HeaterCoilAddress = HeaterCoilAddress; _settings.HeaterCoilAddress = HeaterCoilAddress;
SaveStatus = "参数已保存,新的试验流程将使用当前设置"; DeviceSettingsStore.Save(_settings);
SaveStatus = "参数已保存,重新连接设备后生效";
_logger.LogInformation( _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}", "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, UseSimulator,