最终测试
This commit is contained in:
@@ -4,6 +4,7 @@ namespace Cardiopulmonarybypasssystems.Services;
|
|||||||
|
|
||||||
public interface IModbusTelemetryService
|
public interface IModbusTelemetryService
|
||||||
{
|
{
|
||||||
|
bool IsLiveConnected { get; }
|
||||||
IReadOnlyList<DeviceChannel> GetChannels();
|
IReadOnlyList<DeviceChannel> GetChannels();
|
||||||
IReadOnlyList<PumpControlChannel> GetPumpControls();
|
IReadOnlyList<PumpControlChannel> GetPumpControls();
|
||||||
IReadOnlyList<ValveControlChannel> GetValveControls();
|
IReadOnlyList<ValveControlChannel> GetValveControls();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
private const string IpAddress = "192.168.1.10";
|
private const string IpAddress = "192.168.1.10";
|
||||||
private const int Port = 502;
|
private const int Port = 502;
|
||||||
private const byte SlaveId = 1;
|
private const byte SlaveId = 1;
|
||||||
|
// Keep distinct pressure registers until the distal PLC address is confirmed.
|
||||||
private const ushort ProximalPressureRegister = 1330;
|
private const ushort ProximalPressureRegister = 1330;
|
||||||
private const ushort DistalPressureRegister = 1380;
|
private const ushort DistalPressureRegister = 1380;
|
||||||
private const double FlowRegisterScale = 0.01d;
|
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 Dictionary<string, Queue<double>> _channelWindows = new(StringComparer.Ordinal);
|
||||||
private readonly List<DeviceChannel> _channels =
|
private readonly List<DeviceChannel> _channels =
|
||||||
[
|
[
|
||||||
new() { Name = "主泵流量", Unit = "L/min", Value = 4.32, Min = 0, Max = 7 },
|
new() { Name = "主泵流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
|
||||||
new() { Name = "再循环主泵流量", Unit = "L/min", Value = 4.86, Min = 0, Max = 7 },
|
new() { Name = "再循环主泵流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
|
||||||
new() { Name = "动脉回输流量", Unit = "L/min", Value = 4.74, Min = 0, Max = 7 },
|
new() { Name = "动脉回输流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
|
||||||
new() { Name = "静脉引流流量", Unit = "L/min", Value = 4.46, Min = 0, Max = 7 },
|
new() { Name = "静脉引流流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
|
||||||
new() { Name = "抗扭结主泵流量", Unit = "L/min", Value = 4.68, Min = 0, Max = 7 },
|
new() { Name = "抗扭结主泵流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
|
||||||
new() { Name = "血细胞破坏-单腔引流/回输流量", Unit = "L/min", Value = 4.25, Min = 0, Max = 7 },
|
new() { Name = "血细胞破坏-单腔引流/回输流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
|
||||||
new() { Name = "双腔插管试验回路流量", Unit = "L/min", Value = 4.30, Min = 0, Max = 7 },
|
new() { Name = "双腔插管试验回路流量", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
|
||||||
new() { Name = "双腔插管试验回路流量(两个管腔)", Unit = "L/min", Value = 4.12, Min = 0, Max = 7 },
|
new() { Name = "双腔插管试验回路流量(两个管腔)", Unit = "L/min", Value = 0, Min = 0, Max = 7 },
|
||||||
new() { Name = "远端压力", Unit = "mmHg", Value = 94, Min = 40, Max = 180 },
|
new() { Name = "远端压力", Unit = "mmHg", Value = 68, Min = 40, Max = 180 },
|
||||||
new() { Name = "近端压力", Unit = "mmHg", Value = 112, Min = 60, Max = 220 },
|
new() { Name = "近端压力", Unit = "mmHg", Value = 80, Min = 60, Max = 220 },
|
||||||
new() { Name = "负压辅助引流", Unit = "kPa", Value = -6.7, Min = -20, Max = 0 },
|
new() { Name = "负压辅助引流", Unit = "kPa", Value = 0, Min = -20, Max = 0 },
|
||||||
new() { Name = "模拟血液温度", Unit = "°C", Value = 37.1, Min = 34, Max = 40 },
|
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 = "g/L", Value = 0.028, Min = 0, Max = 0.08 },
|
||||||
new() { Name = "白细胞减少率", Unit = "%", Value = 7.1, Min = 0, Max = 20 }
|
new() { Name = "白细胞减少率", Unit = "%", Value = 7.1, Min = 0, Max = 20 }
|
||||||
];
|
];
|
||||||
private readonly List<PumpControlChannel> _pumpControls =
|
private readonly List<PumpControlChannel> _pumpControls =
|
||||||
[
|
[
|
||||||
new() { Key = "NegativeAssistPump", Name = "负压泵", StartAddress = 0 },
|
new() { Key = "NegativeAssistPump", Name = "负压泵", StartAddress = 0 },
|
||||||
new() { Key = "PressureDropPump", Name = "压力降/抗塌陷泵", StartAddress = 1, FlowAddress = 1000, IsRunning = true },
|
new() { Key = "PressureDropPump", Name = "压力降/抗塌陷泵", StartAddress = 1, FlowAddress = FlowRegisters["PressureDropPump"] },
|
||||||
new() { Key = "RecirculationMainPump", Name = "再循环主泵", StartAddress = 2, FlowAddress = 1010, IsRunning = true },
|
new() { Key = "RecirculationMainPump", Name = "再循环主泵", StartAddress = 2, FlowAddress = FlowRegisters["RecirculationMainPump"] },
|
||||||
new() { Key = "RecirculationReturnPump", Name = "回流泵", StartAddress = 3, FlowAddress = 1020, IsRunning = true },
|
new() { Key = "RecirculationReturnPump", Name = "回流泵", StartAddress = 3, FlowAddress = FlowRegisters["RecirculationReturnPump"] },
|
||||||
new() { Key = "RecirculationDrainagePump", Name = "引流泵", StartAddress = 4, FlowAddress = 1030, IsRunning = true },
|
new() { Key = "RecirculationDrainagePump", Name = "引流泵", StartAddress = 4, FlowAddress = FlowRegisters["RecirculationDrainagePump"] },
|
||||||
new() { Key = "KinkResistancePump", Name = "抗扭结泵", StartAddress = 5, FlowAddress = 1040, IsRunning = true },
|
new() { Key = "KinkResistancePump", Name = "抗扭结泵", StartAddress = 5, FlowAddress = FlowRegisters["KinkResistancePump"] },
|
||||||
new() { Key = "HemolysisDrainageSinglePump", Name = "血细胞破坏-单腔引流/回输泵", StartAddress = 6, FlowAddress = 1050 },
|
new() { Key = "HemolysisDrainageSinglePump", Name = "血细胞破坏-单腔引流/回输泵", StartAddress = 6, FlowAddress = FlowRegisters["HemolysisDrainageSinglePump"] },
|
||||||
new() { Key = "HemolysisReturnSinglePump", Name = "双腔插管试验回路泵", StartAddress = 7, FlowAddress = 1060 },
|
new() { Key = "HemolysisReturnSinglePump", Name = "双腔插管试验回路泵", StartAddress = 7, FlowAddress = FlowRegisters["HemolysisReturnSinglePump"] },
|
||||||
new() { Key = "HemolysisDualLumenPump", Name = "双腔插管试验回路泵(两个管腔)", StartAddress = 8, FlowAddress = 1070 }
|
new() { Key = "HemolysisDualLumenPump", Name = "双腔插管试验回路泵(两个管腔)", StartAddress = 8, FlowAddress = FlowRegisters["HemolysisDualLumenPump"] }
|
||||||
];
|
];
|
||||||
private readonly List<ValveControlChannel> _valveControls =
|
private readonly List<ValveControlChannel> _valveControls =
|
||||||
[
|
[
|
||||||
@@ -88,6 +89,21 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
private Task? _connectionTask;
|
private Task? _connectionTask;
|
||||||
private DateTime _nextConnectionAttemptUtc = DateTime.MinValue;
|
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()
|
public IReadOnlyList<DeviceChannel> GetChannels()
|
||||||
{
|
{
|
||||||
EnsureConnectionScheduled();
|
EnsureConnectionScheduled();
|
||||||
@@ -222,6 +238,7 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
tcpClient.ReceiveTimeout = (int)ConnectionAttemptTimeout.TotalMilliseconds;
|
tcpClient.ReceiveTimeout = (int)ConnectionAttemptTimeout.TotalMilliseconds;
|
||||||
tcpClient.SendTimeout = (int)ConnectionAttemptTimeout.TotalMilliseconds;
|
tcpClient.SendTimeout = (int)ConnectionAttemptTimeout.TotalMilliseconds;
|
||||||
var master = _factory.CreateMaster(tcpClient);
|
var master = _factory.CreateMaster(tcpClient);
|
||||||
|
ApplySafeStartupState(master);
|
||||||
|
|
||||||
lock (_syncRoot)
|
lock (_syncRoot)
|
||||||
{
|
{
|
||||||
@@ -253,10 +270,15 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var coilStates = _master.ReadCoils(SlaveId, 0, (ushort)_pumpControls.Count);
|
var coilStates = _master.ReadCoils(SlaveId, 0, (ushort)(HighestConfiguredCoilAddress + 1));
|
||||||
for (var index = 0; index < _pumpControls.Count; index++)
|
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))
|
foreach (var pump in _pumpControls.Where(item => item.FlowAddress.HasValue))
|
||||||
@@ -439,6 +461,53 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
return window;
|
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()
|
private void ReleaseConnection()
|
||||||
{
|
{
|
||||||
_master?.Dispose();
|
_master?.Dispose();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -52,7 +52,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
private string batchNumber = $"LOT-{DateTime.Now:yyyyMMdd}-01";
|
private string batchNumber = $"LOT-{DateTime.Now:yyyyMMdd}-01";
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string deviceStatus = "在线";
|
private string deviceStatus = "连接中";
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool acquisitionRunning = true;
|
private bool acquisitionRunning = true;
|
||||||
@@ -209,6 +209,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
RefreshTelemetryPanel();
|
RefreshTelemetryPanel();
|
||||||
|
RefreshDeviceStatus();
|
||||||
RefreshComputedState();
|
RefreshComputedState();
|
||||||
|
|
||||||
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
|
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
|
||||||
@@ -668,7 +669,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
private void ToggleAcquisition()
|
private void ToggleAcquisition()
|
||||||
{
|
{
|
||||||
AcquisitionRunning = !AcquisitionRunning;
|
AcquisitionRunning = !AcquisitionRunning;
|
||||||
DeviceStatus = AcquisitionRunning ? "在线" : "采集暂停";
|
RefreshDeviceStatus();
|
||||||
LatestAction = AcquisitionRunning ? "继续采集实时数据,供检测参考。" : "已暂停实时采集。";
|
LatestAction = AcquisitionRunning ? "继续采集实时数据,供检测参考。" : "已暂停实时采集。";
|
||||||
|
|
||||||
if (AcquisitionRunning)
|
if (AcquisitionRunning)
|
||||||
@@ -857,6 +858,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
RefreshTelemetryPanel();
|
RefreshTelemetryPanel();
|
||||||
|
RefreshDeviceStatus();
|
||||||
RefreshComputedState();
|
RefreshComputedState();
|
||||||
RefreshFilteredItemsView();
|
RefreshFilteredItemsView();
|
||||||
}
|
}
|
||||||
@@ -915,6 +917,17 @@ public partial class MainViewModel : ObservableObject
|
|||||||
SyncRealtimeItems();
|
SyncRealtimeItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RefreshDeviceStatus()
|
||||||
|
{
|
||||||
|
if (!AcquisitionRunning)
|
||||||
|
{
|
||||||
|
DeviceStatus = DetectionCompleted ? "采集停止" : "采集暂停";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceStatus = _telemetryService.IsLiveConnected ? "PLC在线" : "PLC离线(模拟)";
|
||||||
|
}
|
||||||
|
|
||||||
private void RefreshComputedState()
|
private void RefreshComputedState()
|
||||||
{
|
{
|
||||||
QualifiedCount = InspectionItems.Count(r => r.Status == InspectionItemStatus.Qualified);
|
QualifiedCount = InspectionItems.Count(r => r.Status == InspectionItemStatus.Qualified);
|
||||||
|
|||||||
Reference in New Issue
Block a user