更新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();
services.AddLogging(builder => builder.AddSerilog(dispose: false));
services.AddSingleton<DeviceSettings>();
services.AddSingleton(DeviceSettingsStore.Load());
services.AddSingleton<TestFormulaService>();
services.AddSingleton<TestRunRepository>();
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 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)

View File

@@ -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 响应头不正确。");
}

View File

@@ -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,