最终测试

This commit is contained in:
GukSang.Jin
2026-03-14 15:21:24 +08:00
parent 6e95eb188f
commit 7da7c93135
3 changed files with 109 additions and 26 deletions

View File

@@ -4,6 +4,7 @@ namespace Cardiopulmonarybypasssystems.Services;
public interface IModbusTelemetryService
{
bool IsLiveConnected { get; }
IReadOnlyList<DeviceChannel> GetChannels();
IReadOnlyList<PumpControlChannel> GetPumpControls();
IReadOnlyList<ValveControlChannel> GetValveControls();

View File

@@ -11,6 +11,7 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
private const string IpAddress = "192.168.1.10";
private const int Port = 502;
private const byte SlaveId = 1;
// Keep distinct pressure registers until the distal PLC address is confirmed.
private const ushort ProximalPressureRegister = 1330;
private const ushort DistalPressureRegister = 1380;
private const double FlowRegisterScale = 0.01d;
@@ -48,33 +49,33 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
private readonly Dictionary<string, Queue<double>> _channelWindows = new(StringComparer.Ordinal);
private readonly List<DeviceChannel> _channels =
[
new() { Name = "主泵流量", Unit = "L/min", Value = 4.32, Min = 0, Max = 7 },
new() { Name = "再循环主泵流量", Unit = "L/min", Value = 4.86, Min = 0, Max = 7 },
new() { Name = "动脉回输流量", Unit = "L/min", Value = 4.74, Min = 0, Max = 7 },
new() { Name = "静脉引流流量", Unit = "L/min", Value = 4.46, Min = 0, Max = 7 },
new() { Name = "抗扭结主泵流量", Unit = "L/min", Value = 4.68, Min = 0, Max = 7 },
new() { Name = "血细胞破坏-单腔引流/回输流量", Unit = "L/min", Value = 4.25, Min = 0, Max = 7 },
new() { Name = "双腔插管试验回路流量", Unit = "L/min", Value = 4.30, Min = 0, Max = 7 },
new() { Name = "双腔插管试验回路流量(两个管腔)", Unit = "L/min", Value = 4.12, Min = 0, Max = 7 },
new() { Name = "远端压力", Unit = "mmHg", Value = 94, Min = 40, Max = 180 },
new() { Name = "近端压力", Unit = "mmHg", Value = 112, Min = 60, Max = 220 },
new() { Name = "负压辅助引流", Unit = "kPa", Value = -6.7, Min = -20, Max = 0 },
new() { Name = "主泵流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
new() { Name = "再循环主泵流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
new() { Name = "动脉回输流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
new() { Name = "静脉引流流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
new() { Name = "抗扭结主泵流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
new() { Name = "血细胞破坏-单腔引流/回输流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
new() { Name = "双腔插管试验回路流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
new() { Name = "双腔插管试验回路流量(两个管腔)", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
new() { Name = "远端压力", Unit = "mmHg", Value = 68, Min = 40, Max = 180 },
new() { Name = "近端压力", Unit = "mmHg", Value = 80, Min = 60, Max = 220 },
new() { Name = "负压辅助引流", Unit = "kPa", Value = 0, Min = -20, Max = 0 },
new() { Name = "模拟血液温度", Unit = "°C", Value = 37.1, Min = 34, Max = 40 },
new() { Name = "再循环率", Unit = "%", Value = 6.8, Min = 0, Max = 20 },
new() { Name = "再循环率", Unit = "%", Value = 0, Min = 0, Max = 20 },
new() { Name = "游离血红蛋白", Unit = "g/L", Value = 0.028, Min = 0, Max = 0.08 },
new() { Name = "白细胞减少率", Unit = "%", Value = 7.1, Min = 0, Max = 20 }
];
private readonly List<PumpControlChannel> _pumpControls =
[
new() { Key = "NegativeAssistPump", Name = "负压泵", StartAddress = 0 },
new() { Key = "PressureDropPump", Name = "压力降/抗塌陷泵", StartAddress = 1, FlowAddress = 1000, IsRunning = true },
new() { Key = "RecirculationMainPump", Name = "再循环主泵", StartAddress = 2, FlowAddress = 1010, IsRunning = true },
new() { Key = "RecirculationReturnPump", Name = "回流泵", StartAddress = 3, FlowAddress = 1020, IsRunning = true },
new() { Key = "RecirculationDrainagePump", Name = "引流泵", StartAddress = 4, FlowAddress = 1030, IsRunning = true },
new() { Key = "KinkResistancePump", Name = "抗扭结泵", StartAddress = 5, FlowAddress = 1040, IsRunning = true },
new() { Key = "HemolysisDrainageSinglePump", Name = "血细胞破坏-单腔引流/回输泵", StartAddress = 6, FlowAddress = 1050 },
new() { Key = "HemolysisReturnSinglePump", Name = "双腔插管试验回路泵", StartAddress = 7, FlowAddress = 1060 },
new() { Key = "HemolysisDualLumenPump", Name = "双腔插管试验回路泵(两个管腔)", StartAddress = 8, FlowAddress = 1070 }
new() { Key = "PressureDropPump", Name = "压力降/抗塌陷泵", StartAddress = 1, FlowAddress = FlowRegisters["PressureDropPump"] },
new() { Key = "RecirculationMainPump", Name = "再循环主泵", StartAddress = 2, FlowAddress = FlowRegisters["RecirculationMainPump"] },
new() { Key = "RecirculationReturnPump", Name = "回流泵", StartAddress = 3, FlowAddress = FlowRegisters["RecirculationReturnPump"] },
new() { Key = "RecirculationDrainagePump", Name = "引流泵", StartAddress = 4, FlowAddress = FlowRegisters["RecirculationDrainagePump"] },
new() { Key = "KinkResistancePump", Name = "抗扭结泵", StartAddress = 5, FlowAddress = FlowRegisters["KinkResistancePump"] },
new() { Key = "HemolysisDrainageSinglePump", Name = "血细胞破坏-单腔引流/回输泵", StartAddress = 6, FlowAddress = FlowRegisters["HemolysisDrainageSinglePump"] },
new() { Key = "HemolysisReturnSinglePump", Name = "双腔插管试验回路泵", StartAddress = 7, FlowAddress = FlowRegisters["HemolysisReturnSinglePump"] },
new() { Key = "HemolysisDualLumenPump", Name = "双腔插管试验回路泵(两个管腔)", StartAddress = 8, FlowAddress = FlowRegisters["HemolysisDualLumenPump"] }
];
private readonly List<ValveControlChannel> _valveControls =
[
@@ -88,6 +89,21 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
private Task? _connectionTask;
private DateTime _nextConnectionAttemptUtc = DateTime.MinValue;
private int HighestConfiguredCoilAddress => Math.Max(
_pumpControls.Max(item => item.StartAddress),
_valveControls.Max(item => item.StartAddress));
public bool IsLiveConnected
{
get
{
lock (_syncRoot)
{
return _master is not null && _tcpClient?.Connected == true;
}
}
}
public IReadOnlyList<DeviceChannel> GetChannels()
{
EnsureConnectionScheduled();
@@ -222,6 +238,7 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
tcpClient.ReceiveTimeout = (int)ConnectionAttemptTimeout.TotalMilliseconds;
tcpClient.SendTimeout = (int)ConnectionAttemptTimeout.TotalMilliseconds;
var master = _factory.CreateMaster(tcpClient);
ApplySafeStartupState(master);
lock (_syncRoot)
{
@@ -253,10 +270,15 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
try
{
var coilStates = _master.ReadCoils(SlaveId, 0, (ushort)_pumpControls.Count);
for (var index = 0; index < _pumpControls.Count; index++)
var coilStates = _master.ReadCoils(SlaveId, 0, (ushort)(HighestConfiguredCoilAddress + 1));
foreach (var pump in _pumpControls)
{
_pumpControls[index].IsRunning = coilStates[index];
pump.IsRunning = coilStates[pump.StartAddress];
}
foreach (var valve in _valveControls)
{
valve.IsOpen = coilStates[valve.StartAddress];
}
foreach (var pump in _pumpControls.Where(item => item.FlowAddress.HasValue))
@@ -439,6 +461,53 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
return window;
}
private void ApplySafeStartupState(IModbusMaster master)
{
foreach (var pump in _pumpControls)
{
master.WriteSingleCoil(SlaveId, (ushort)pump.StartAddress, false);
}
foreach (var valve in _valveControls)
{
master.WriteSingleCoil(SlaveId, (ushort)valve.StartAddress, false);
}
ApplyLocalSafeState();
}
private void ApplyLocalSafeState()
{
foreach (var pump in _pumpControls)
{
pump.IsRunning = false;
pump.FlowValue = 0d;
}
foreach (var valve in _valveControls)
{
valve.IsOpen = false;
}
foreach (var channelName in FlowChannelNames.Values)
{
SetChannelValueDirect(channelName, 0d);
}
SetChannelValueDirect("负压辅助引流", 0d);
SetChannelValueDirect("再循环率", 0d);
}
private void SetChannelValueDirect(string channelName, double nextValue)
{
var channel = Channel(channelName);
var clampedValue = Math.Clamp(nextValue, channel.Min, channel.Max);
var window = Window(channelName);
window.Clear();
window.Enqueue(clampedValue);
channel.Value = clampedValue;
}
private void ReleaseConnection()
{
_master?.Dispose();

View File

@@ -1,4 +1,4 @@
using System.Collections.ObjectModel;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Text.Json;
@@ -52,7 +52,7 @@ public partial class MainViewModel : ObservableObject
private string batchNumber = $"LOT-{DateTime.Now:yyyyMMdd}-01";
[ObservableProperty]
private string deviceStatus = "在线";
private string deviceStatus = "连接中";
[ObservableProperty]
private bool acquisitionRunning = true;
@@ -209,6 +209,7 @@ public partial class MainViewModel : ObservableObject
}
RefreshTelemetryPanel();
RefreshDeviceStatus();
RefreshComputedState();
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
@@ -668,7 +669,7 @@ public partial class MainViewModel : ObservableObject
private void ToggleAcquisition()
{
AcquisitionRunning = !AcquisitionRunning;
DeviceStatus = AcquisitionRunning ? "在线" : "采集暂停";
RefreshDeviceStatus();
LatestAction = AcquisitionRunning ? "继续采集实时数据,供检测参考。" : "已暂停实时采集。";
if (AcquisitionRunning)
@@ -857,6 +858,7 @@ public partial class MainViewModel : ObservableObject
}
RefreshTelemetryPanel();
RefreshDeviceStatus();
RefreshComputedState();
RefreshFilteredItemsView();
}
@@ -915,6 +917,17 @@ public partial class MainViewModel : ObservableObject
SyncRealtimeItems();
}
private void RefreshDeviceStatus()
{
if (!AcquisitionRunning)
{
DeviceStatus = DetectionCompleted ? "采集停止" : "采集暂停";
return;
}
DeviceStatus = _telemetryService.IsLiveConnected ? "PLC在线" : "PLC离线模拟";
}
private void RefreshComputedState()
{
QualifiedCount = InspectionItems.Count(r => r.Status == InspectionItemStatus.Qualified);