diff --git a/Cardiopulmonarybypasssystems/MainWindow.xaml b/Cardiopulmonarybypasssystems/MainWindow.xaml
index 7cf6922..9e7c13b 100644
--- a/Cardiopulmonarybypasssystems/MainWindow.xaml
+++ b/Cardiopulmonarybypasssystems/MainWindow.xaml
@@ -14,6 +14,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -202,6 +281,16 @@
Cursor="Hand"
PreviewMouseLeftButtonDown="PumpSetpointTextBox_OnPreviewMouseLeftButtonDown"
GotKeyboardFocus="PumpSetpointTextBox_OnGotKeyboardFocus" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1070,34 +1258,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -1105,7 +1267,7 @@
+ ItemTemplate="{StaticResource ProjectRs485PumpCardTemplate}">
@@ -1133,30 +1295,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -1178,7 +1316,7 @@
+ ItemTemplate="{StaticResource ProjectRs485PumpCardTemplate}">
@@ -1224,70 +1362,38 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1312,30 +1418,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ ItemTemplate="{StaticResource ProjectRs485PumpCardTemplate}">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -1472,7 +1528,7 @@
+ ItemTemplate="{StaticResource ProjectRs485PumpCardTemplate}">
@@ -1495,18 +1551,27 @@
-
-
+
@@ -1983,13 +2048,21 @@
-
+
+
diff --git a/Cardiopulmonarybypasssystems/MainWindow.xaml.cs b/Cardiopulmonarybypasssystems/MainWindow.xaml.cs
index d20319e..ff09d3e 100644
--- a/Cardiopulmonarybypasssystems/MainWindow.xaml.cs
+++ b/Cardiopulmonarybypasssystems/MainWindow.xaml.cs
@@ -21,6 +21,7 @@ public partial class MainWindow : Window
private readonly List _projectInspectionButtons = [];
private object? _projectInspectionContent;
private object? _realtimeContent;
+ private object? _tightnessTestContent;
private object? _manualSupplementContent;
private object? _configurationContent;
private object? _traceContent;
@@ -85,6 +86,7 @@ public partial class MainWindow : Window
{
_realtimeContent = ExtractTabContent(RealtimeDataTab);
_projectInspectionContent = ExtractTabContent(ProjectListTab);
+ _tightnessTestContent = ExtractTabContent(TightnessTestTab);
_manualSupplementContent = ExtractTabContent(ManualSupplementTab);
_configurationContent = ExtractTabContent(ConfigurationTab);
_traceContent = ExtractTabContent(TraceTab);
@@ -126,6 +128,7 @@ public partial class MainWindow : Window
_projectInspectionButtons.Add(button);
}
+ AddNavigationButton("密合性", new NavigationTarget(NavigationPage.TightnessTest, null));
AddNavigationButton("项目补充", new NavigationTarget(NavigationPage.ManualSupplement, null));
AddNavigationButton("配置", new NavigationTarget(NavigationPage.Configuration, null));
AddNavigationButton("追溯", new NavigationTarget(NavigationPage.Trace, null));
@@ -188,9 +191,19 @@ public partial class MainWindow : Window
{
_currentPage = page;
_currentProjectItem = null;
+
+ if (page == NavigationPage.TightnessTest
+ && DataContext is MainViewModel viewModel
+ && viewModel.TightnessInspectionItem is not null
+ && !ReferenceEquals(viewModel.SelectedItem, viewModel.TightnessInspectionItem))
+ {
+ viewModel.SelectedItem = viewModel.TightnessInspectionItem;
+ }
+
MainContentHost.Content = page switch
{
NavigationPage.RealtimeData => _realtimeContent,
+ NavigationPage.TightnessTest => _tightnessTestContent,
NavigationPage.ManualSupplement => _manualSupplementContent,
NavigationPage.Configuration => _configurationContent,
NavigationPage.Trace => _traceContent,
@@ -254,6 +267,7 @@ public partial class MainWindow : Window
{
RealtimeData,
ProjectItem,
+ TightnessTest,
ManualSupplement,
Configuration,
Trace
diff --git a/Cardiopulmonarybypasssystems/Models/PumpControlChannel.cs b/Cardiopulmonarybypasssystems/Models/PumpControlChannel.cs
index 80b7325..203b9e8 100644
--- a/Cardiopulmonarybypasssystems/Models/PumpControlChannel.cs
+++ b/Cardiopulmonarybypasssystems/Models/PumpControlChannel.cs
@@ -109,6 +109,18 @@ public partial class PumpControlChannel : ObservableObject
[ObservableProperty]
private bool isBatchSelected;
+ [ObservableProperty]
+ private bool isFlowStabilizationEnabled;
+
+ [ObservableProperty]
+ private DateTime lastFlowStabilizationAdjustmentUtc = DateTime.MinValue;
+
+ [ObservableProperty]
+ private int flowStabilizationRawSetpoint;
+
+ [ObservableProperty]
+ private string flowStabilizationStatusText = "稳流未启用";
+
public string StartAddressDisplay => $"M{StartAddress}";
public string FlowAddressDisplay => FlowAddress.HasValue ? $"D{FlowAddress.Value}" : "-";
public bool HasFlowTelemetry => FlowAddress.HasValue;
@@ -224,6 +236,15 @@ public partial class PumpControlChannel : ObservableObject
: "未配置流量换算系数";
public string Rs485ReadActionText => IsRs485Busy ? "处理中" : "读取";
public string Rs485WriteActionText => IsRs485Busy ? "处理中" : "写入";
+ public bool CanUseFlowStabilization => SupportsRs485DirectControl && Key != "KinkResistancePump";
+ public string FlowStabilizationToggleHint => CanUseFlowStabilization
+ ? "按实际流量小步调节泵速"
+ : "抗扭结采样要求保持固定转速";
+ public string FlowStabilizationStateText => !CanUseFlowStabilization
+ ? "固定转速"
+ : IsFlowStabilizationEnabled
+ ? FlowStabilizationStatusText
+ : "稳流未启用";
public string SetpointStatusForeground => ResolveSetpointStatusForeground();
public string SetpointStatusBackground => ResolveSetpointStatusBackground();
@@ -241,6 +262,7 @@ public partial class PumpControlChannel : ObservableObject
OnPropertyChanged(nameof(CanToggleRs485Action));
OnPropertyChanged(nameof(ToggleActionHint));
OnPropertyChanged(nameof(CardPrimaryDisplay));
+ OnPropertyChanged(nameof(FlowStabilizationStateText));
}
partial void OnFlowValueChanged(double value)
@@ -251,6 +273,7 @@ public partial class PumpControlChannel : ObservableObject
OnPropertyChanged(nameof(StateHint));
OnPropertyChanged(nameof(IndicatorColor));
OnPropertyChanged(nameof(CardPrimaryDisplay));
+ OnPropertyChanged(nameof(FlowStabilizationStateText));
}
partial void OnStateAvailableChanged(bool value)
@@ -260,6 +283,7 @@ public partial class PumpControlChannel : ObservableObject
OnPropertyChanged(nameof(IndicatorColor));
OnPropertyChanged(nameof(ToggleButtonText));
OnPropertyChanged(nameof(CardPrimaryDisplay));
+ OnPropertyChanged(nameof(FlowStabilizationStateText));
}
partial void OnFlowAvailableChanged(bool value)
@@ -270,6 +294,7 @@ public partial class PumpControlChannel : ObservableObject
OnPropertyChanged(nameof(StateHint));
OnPropertyChanged(nameof(IndicatorColor));
OnPropertyChanged(nameof(CardPrimaryDisplay));
+ OnPropertyChanged(nameof(FlowStabilizationStateText));
}
partial void OnRs485EnabledChanged(bool value)
@@ -280,11 +305,33 @@ public partial class PumpControlChannel : ObservableObject
OnPropertyChanged(nameof(CalibrationStatusText));
OnPropertyChanged(nameof(SetpointReadbackDisplay));
OnPropertyChanged(nameof(RawSetpointDisplay));
+ OnPropertyChanged(nameof(CanUseFlowStabilization));
+ OnPropertyChanged(nameof(FlowStabilizationToggleHint));
+ OnPropertyChanged(nameof(FlowStabilizationStateText));
}
partial void OnRs485SlaveAddressChanged(byte value) => OnPropertyChanged(nameof(Rs485SlaveAddressDisplay));
- partial void OnRs485MotorControlRegisterChanged(ushort value) => OnPropertyChanged(nameof(SupportsRs485DirectControl));
+ partial void OnRs485MotorControlRegisterChanged(ushort value)
+ {
+ OnPropertyChanged(nameof(SupportsRs485DirectControl));
+ OnPropertyChanged(nameof(CanUseFlowStabilization));
+ OnPropertyChanged(nameof(FlowStabilizationToggleHint));
+ OnPropertyChanged(nameof(FlowStabilizationStateText));
+ }
+
+ partial void OnIsFlowStabilizationEnabledChanged(bool value)
+ {
+ if (!value)
+ {
+ FlowStabilizationStatusText = "稳流未启用";
+ }
+
+ OnPropertyChanged(nameof(FlowStabilizationStateText));
+ }
+
+ partial void OnFlowStabilizationStatusTextChanged(string value) =>
+ OnPropertyChanged(nameof(FlowStabilizationStateText));
partial void OnRs485RawPerLitrePerMinuteChanged(double value)
{
@@ -327,6 +374,7 @@ public partial class PumpControlChannel : ObservableObject
ConfirmedSetpointAvailable = false;
ConfirmedRawSetpointValue = 0;
ConfirmedSetpointFlowValue = 0;
+ FlowStabilizationRawSetpoint = 0;
}
partial void OnSetpointAvailableChanged(bool value)
diff --git a/Cardiopulmonarybypasssystems/Models/Rs485PumpBindingSettings.cs b/Cardiopulmonarybypasssystems/Models/Rs485PumpBindingSettings.cs
index c988b5b..5a6c1b1 100644
--- a/Cardiopulmonarybypasssystems/Models/Rs485PumpBindingSettings.cs
+++ b/Cardiopulmonarybypasssystems/Models/Rs485PumpBindingSettings.cs
@@ -16,5 +16,5 @@ public sealed class Rs485PumpBindingSettings
public double RawPerLitrePerMinute { get; set; }
public double RawOffset { get; set; }
public double MinFlowLpm { get; set; }
- public double MaxFlowLpm { get; set; } = 7.0;
+ public double MaxFlowLpm { get; set; } = 20.0;
}
diff --git a/Cardiopulmonarybypasssystems/Services/IRs485PumpFlowService.cs b/Cardiopulmonarybypasssystems/Services/IRs485PumpFlowService.cs
index ab43e26..ac25d56 100644
--- a/Cardiopulmonarybypasssystems/Services/IRs485PumpFlowService.cs
+++ b/Cardiopulmonarybypasssystems/Services/IRs485PumpFlowService.cs
@@ -8,6 +8,7 @@ public interface IRs485PumpFlowService
Rs485PumpFlowOperationResult ReadPumpPreset(Rs485PumpFlowRequest request);
IReadOnlyList ReadPumpRuntimeStates(IReadOnlyList requests);
Rs485PumpFlowOperationResult WritePumpPreset(Rs485PumpFlowRequest request, ushort rawForwardSpeed);
+ Rs485PumpFlowOperationResult WritePumpMotorCommand(Rs485PumpFlowRequest request, short rawMotorSpeed);
Rs485PumpFlowOperationResult StartPump(Rs485PumpFlowRequest request, short rawMotorSpeed);
Rs485PumpFlowOperationResult StopPump(Rs485PumpFlowRequest request);
}
diff --git a/Cardiopulmonarybypasssystems/Services/Rs485PumpFlowService.cs b/Cardiopulmonarybypasssystems/Services/Rs485PumpFlowService.cs
index 36aea51..75b6ee8 100644
--- a/Cardiopulmonarybypasssystems/Services/Rs485PumpFlowService.cs
+++ b/Cardiopulmonarybypasssystems/Services/Rs485PumpFlowService.cs
@@ -278,6 +278,48 @@ public sealed class Rs485PumpFlowService : IRs485PumpFlowService
});
}
+ public Rs485PumpFlowOperationResult WritePumpMotorCommand(Rs485PumpFlowRequest request, short rawMotorSpeed)
+ {
+ return Execute(request, master =>
+ {
+ var slave = request.PumpSettings.SlaveAddress;
+ if (rawMotorSpeed <= 0)
+ {
+ return Failure("RS485 稳流调节失败:控制值必须大于 0");
+ }
+
+ if (!TryWriteSignedRegister(master, slave, request.PumpSettings.MotorControlRegister, rawMotorSpeed, out var writeError))
+ {
+ return Failure($"RS485 稳流调节失败:{writeError}");
+ }
+
+ if (PostCommandVerifyDelayMs > 0)
+ {
+ Thread.Sleep(PostCommandVerifyDelayMs);
+ }
+
+ if (TryReadRegister(master, slave, request.PumpSettings.MotorControlRegister, out var motorCommandReadback, out _)
+ && motorCommandReadback != unchecked((ushort)rawMotorSpeed))
+ {
+ return Failure($"RS485 稳流调节失败:控制寄存器回读 {motorCommandReadback},目标 {rawMotorSpeed}");
+ }
+
+ ushort? runStatus = null;
+ if (TryReadRegister(master, slave, request.PumpSettings.RunStatusRegister, out var runStatusValue, out _))
+ {
+ runStatus = runStatusValue;
+ }
+
+ return new Rs485PumpFlowOperationResult
+ {
+ Success = true,
+ Message = $"RS485 稳流调节成功,控制值 {rawMotorSpeed}",
+ RunStatus = runStatus,
+ RawForwardSpeed = (ushort)rawMotorSpeed
+ };
+ });
+ }
+
public Rs485PumpFlowOperationResult StopPump(Rs485PumpFlowRequest request)
{
return Execute(request, master =>
diff --git a/Cardiopulmonarybypasssystems/ViewModels/MainViewModel.Rs485.cs b/Cardiopulmonarybypasssystems/ViewModels/MainViewModel.Rs485.cs
index 70e0e48..c7924e9 100644
--- a/Cardiopulmonarybypasssystems/ViewModels/MainViewModel.Rs485.cs
+++ b/Cardiopulmonarybypasssystems/ViewModels/MainViewModel.Rs485.cs
@@ -41,8 +41,12 @@ public partial class MainViewModel
private const double DefaultRs485RawPerLitrePerMinute = 100d;
private const double DefaultRs485RawOffset = 0d;
private const int MaxRs485MotorCommand = short.MaxValue;
+ private const double FlowStabilizationDeadbandLpm = 0.05d;
+ private const int FlowStabilizationMaxRawStep = 5;
+ private const double FlowStabilizationMaxRelativeTrim = 0.20d;
private const string PressureDropRs485PumpKey = "PressureDropPump";
private const string KinkResistanceRs485PumpKey = "KinkResistancePump";
+ private static readonly TimeSpan FlowStabilizationAdjustmentInterval = TimeSpan.FromSeconds(2);
private static readonly string[] HemolysisRs485PumpKeys =
[
"HemolysisDrainageSinglePump",
@@ -220,7 +224,7 @@ public partial class MainViewModel
RawPerLitrePerMinute = DefaultRs485RawPerLitrePerMinute,
RawOffset = DefaultRs485RawOffset,
MinFlowLpm = 0,
- MaxFlowLpm = 7.0
+ MaxFlowLpm = 20.0
});
}
@@ -249,7 +253,7 @@ public partial class MainViewModel
? binding.RawOffset
: DefaultRs485RawOffset;
pump.Rs485MinFlowLpm = binding.MinFlowLpm;
- pump.Rs485MaxFlowLpm = binding.MaxFlowLpm <= 0 ? Math.Max(RatedMaxFlow, 7.0) : binding.MaxFlowLpm;
+ pump.Rs485MaxFlowLpm = binding.MaxFlowLpm <= 0 ? Math.Max(RatedMaxFlow, 20.0) : binding.MaxFlowLpm;
if (string.IsNullOrWhiteSpace(pump.PendingSetpointText))
{
@@ -552,6 +556,132 @@ public partial class MainViewModel
}
}
+ private async Task MaintainRs485FlowStabilizationAsync()
+ {
+ var adjustablePumps = ActiveRs485FlowPumpControls
+ .Where(ShouldMaintainFlowStabilization)
+ .OrderBy(item => item.Rs485SlaveAddress)
+ .ToList();
+
+ foreach (var pump in adjustablePumps)
+ {
+ await TryAdjustRs485FlowStabilizationAsync(pump);
+ }
+ }
+
+ private bool ShouldMaintainFlowStabilization(PumpControlChannel pump)
+ {
+ if (!pump.IsFlowStabilizationEnabled)
+ {
+ return false;
+ }
+
+ if (!pump.CanUseFlowStabilization)
+ {
+ pump.FlowStabilizationStatusText = "当前项目要求固定转速";
+ return false;
+ }
+
+ if (pump.IsRs485Busy)
+ {
+ pump.FlowStabilizationStatusText = "等待当前 RS485 操作完成";
+ return false;
+ }
+
+ if (!pump.IsRunning || pump.PendingRs485RunningState == false)
+ {
+ pump.FlowStabilizationStatusText = "等待泵运行";
+ return false;
+ }
+
+ if (!pump.FlowAvailable)
+ {
+ pump.FlowStabilizationStatusText = "等待流量反馈";
+ return false;
+ }
+
+ if (!pump.ConfirmedSetpointAvailable || pump.ConfirmedSetpointFlowValue <= 0)
+ {
+ pump.FlowStabilizationStatusText = "等待确认目标流量";
+ return false;
+ }
+
+ if (DateTime.UtcNow - pump.LastFlowStabilizationAdjustmentUtc < FlowStabilizationAdjustmentInterval)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private async Task TryAdjustRs485FlowStabilizationAsync(PumpControlChannel pump)
+ {
+ var targetFlow = pump.ConfirmedSetpointFlowValue;
+ var flowError = targetFlow - pump.FlowValue;
+ if (Math.Abs(flowError) <= FlowStabilizationDeadbandLpm)
+ {
+ pump.FlowStabilizationStatusText = $"稳流保持:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min";
+ pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
+ return;
+ }
+
+ var targetRaw = pump.ConfirmedRawSetpointValue > 0
+ ? pump.ConfirmedRawSetpointValue
+ : ConvertFlowToRawSpeed(pump, targetFlow);
+ if (targetRaw <= 0)
+ {
+ pump.FlowStabilizationStatusText = "目标流量换算值无效";
+ pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
+ return;
+ }
+
+ var currentRaw = pump.FlowStabilizationRawSetpoint > 0
+ ? pump.FlowStabilizationRawSetpoint
+ : pump.RawSetpointValue > 0
+ ? pump.RawSetpointValue
+ : targetRaw;
+ var maxTrim = Math.Max(FlowStabilizationMaxRawStep, (int)Math.Round(targetRaw * FlowStabilizationMaxRelativeTrim, MidpointRounding.AwayFromZero));
+ var minRaw = Math.Max(1, targetRaw - maxTrim);
+ var maxRaw = Math.Min(MaxRs485MotorCommand, targetRaw + maxTrim);
+ var rawStep = Math.Sign(flowError) * FlowStabilizationMaxRawStep;
+ var nextRaw = Math.Clamp(currentRaw + rawStep, minRaw, maxRaw);
+
+ if (nextRaw == currentRaw)
+ {
+ pump.FlowStabilizationStatusText = $"稳流已到调节限幅:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min";
+ pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
+ return;
+ }
+
+ if (!TryBeginRs485PumpOperation(pump, "稳流调节"))
+ {
+ return;
+ }
+
+ try
+ {
+ var request = BuildRs485Request(pump);
+ var result = await Task.Run(() => _rs485PumpFlowService.WritePumpMotorCommand(request, (short)nextRaw));
+ if (!result.Success)
+ {
+ pump.FlowStabilizationStatusText = result.Message;
+ Rs485StatusText = result.Message;
+ TraceEvents.Insert(0, NewTrace("RS485 稳流调节失败", $"{pump.Name} / {result.Message}"));
+ return;
+ }
+
+ pump.FlowStabilizationRawSetpoint = nextRaw;
+ CacheResolvedRs485Setpoint(pump, nextRaw);
+ ApplyPostCommandPumpState(pump, result, expectedRunning: true);
+ pump.FlowStabilizationStatusText = $"稳流调节:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min / 控制值 {nextRaw}";
+ }
+ finally
+ {
+ pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
+ EndRs485PumpOperation(pump);
+ }
+ }
+
private void RaiseRs485CalibrationSummaryChanges()
{
OnPropertyChanged(nameof(Rs485EnabledPumpCount));
@@ -1349,6 +1479,7 @@ public partial class MainViewModel
pump.ConfirmedSetpointAvailable = true;
pump.ConfirmedRawSetpointValue = rawMotorSpeed;
pump.ConfirmedSetpointFlowValue = flowLpm;
+ pump.FlowStabilizationRawSetpoint = rawMotorSpeed;
}
private bool TryBeginRs485PumpOperation(PumpControlChannel pump, string operationName)
diff --git a/Cardiopulmonarybypasssystems/ViewModels/MainViewModel.cs b/Cardiopulmonarybypasssystems/ViewModels/MainViewModel.cs
index 4fe09a0..773e275 100644
--- a/Cardiopulmonarybypasssystems/ViewModels/MainViewModel.cs
+++ b/Cardiopulmonarybypasssystems/ViewModels/MainViewModel.cs
@@ -423,6 +423,8 @@ public partial class MainViewModel : ObservableObject, IDisposable
public bool HasFilteredItems => !FilteredItemsView.IsEmpty;
public bool HasManualSupplementItems => !ManualSupplementItemsView.IsEmpty;
public bool HasSelectedItem => SelectedItem is not null;
+ public InspectionItem? TightnessInspectionItem => InspectionItems.FirstOrDefault(IsTightnessInspectionItem);
+ public bool HasTightnessInspectionItem => TightnessInspectionItem is not null;
public IEnumerable FlowSensorChannels => Channels.Where(IsFlowSensorChannel);
public IEnumerable OtherChannels => Channels.Where(channel => !IsFlowSensorChannel(channel));
public bool IsTelemetryOnline => _isTelemetryOnline;
@@ -544,7 +546,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
public bool HasItemSearchText => !string.IsNullOrWhiteSpace(ItemSearchText);
public int RealtimeMonitorCount => InspectionItems.Count(item => item.CaptureMode == InspectionItemCaptureMode.RealtimeMonitor);
public int RealtimeAssistCount => InspectionItems.Count(item => item.CaptureMode == InspectionItemCaptureMode.RealtimeAssist);
- public int ManualEntryCount => InspectionItems.Count(item => item.CaptureMode == InspectionItemCaptureMode.ManualEntry);
+ public int ManualEntryCount => InspectionItems.Count(item => item.CaptureMode == InspectionItemCaptureMode.ManualEntry && !IsTightnessInspectionItem(item));
public string SelectedItemCaptureModeText => SelectedItem?.CaptureModeText ?? "未选择";
public string SelectedItemMeasurementSource => SelectedItem?.MeasurementSource ?? "-";
public bool SelectedItemUsesRealtimeValue => SelectedItem?.CaptureMode == InspectionItemCaptureMode.RealtimeMonitor;
@@ -566,6 +568,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
public string KinkResistanceMandrelDiameterDisplay => $"圆角模板直径:{Math.Max(KinkResistanceOuterDiameter, 0d) * 4d:F1} mm(外径 {KinkResistanceOuterDiameter:F1} mm × 4)";
public bool IsPressureDropSelected => SelectedItem?.Clause == "4.3.1";
public string PressureDropSamplingSummary => BuildPressureDropSamplingSummary();
+ public bool IsTightnessSelected => SelectedItem is not null && IsTightnessInspectionItem(SelectedItem);
public bool IsKinkResistanceSelected => SelectedItem?.Clause == "4.2.3";
public string KinkResistanceSamplingSummary => BuildKinkResistanceSamplingSummary();
public bool IsAntiCollapseSelected => SelectedItem?.Clause == "4.3.2";
@@ -692,6 +695,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
OnPropertyChanged(nameof(SelectedItemUsesRealtimeValue));
OnPropertyChanged(nameof(IsPressureDropSelected));
OnPropertyChanged(nameof(PressureDropSamplingSummary));
+ OnPropertyChanged(nameof(IsTightnessSelected));
OnPropertyChanged(nameof(IsKinkResistanceSelected));
OnPropertyChanged(nameof(KinkResistanceFlowPointDisplay));
OnPropertyChanged(nameof(KinkResistanceMandrelDiameterDisplay));
@@ -1408,6 +1412,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
var snapshot = await Task.Run(_telemetryService.UpdateChannels);
ApplyTelemetrySnapshot(snapshot);
await RefreshRs485RuntimeStateSilentlyAsync();
+ await MaintainRs485FlowStabilizationAsync();
_lastTelemetryRefreshFailureMessage = string.Empty;
RefreshTelemetryPanel();
RefreshDeviceStatus();
@@ -1635,7 +1640,8 @@ public partial class MainViewModel : ObservableObject, IDisposable
var projectCheckItems = FilteredItemsView.Cast().ToList();
var manualSupplementItems = ManualSupplementItemsView.Cast().ToList();
var selectedItemStillVisible = SelectedItem is not null
- && ((SelectedItem.CaptureMode == InspectionItemCaptureMode.ManualEntry && manualSupplementItems.Contains(SelectedItem))
+ && ((IsTightnessInspectionItem(SelectedItem))
+ || (SelectedItem.CaptureMode == InspectionItemCaptureMode.ManualEntry && manualSupplementItems.Contains(SelectedItem))
|| (SelectedItem.CaptureMode != InspectionItemCaptureMode.ManualEntry && projectCheckItems.Contains(SelectedItem)));
if (selectedItemStillVisible)
@@ -2132,8 +2138,13 @@ public partial class MainViewModel : ObservableObject, IDisposable
private bool MatchesManualSupplementItem(object item) =>
item is InspectionItem inspectionItem
&& inspectionItem.CaptureMode == InspectionItemCaptureMode.ManualEntry
+ && !IsTightnessInspectionItem(inspectionItem)
&& MatchesItemSearch(inspectionItem);
+ private static bool IsTightnessInspectionItem(InspectionItem item) =>
+ string.Equals(item.Clause, "4.2.1", StringComparison.Ordinal)
+ && string.Equals(item.Item, "血液通道密合性", StringComparison.Ordinal);
+
private bool MatchesItemSearch(InspectionItem item)
{
if (string.IsNullOrWhiteSpace(ItemSearchText))
diff --git a/Cardiopulmonarybypasssystems/manufacturer-limits.json b/Cardiopulmonarybypasssystems/manufacturer-limits.json
index 34ff8f9..83fd8c2 100644
--- a/Cardiopulmonarybypasssystems/manufacturer-limits.json
+++ b/Cardiopulmonarybypasssystems/manufacturer-limits.json
@@ -37,7 +37,7 @@
"RawPerLitrePerMinute": 100,
"RawOffset": 0,
"MinFlowLpm": 0,
- "MaxFlowLpm": 7
+ "MaxFlowLpm": 20
},
{
"PumpKey": "RecirculationMainPump",
@@ -54,7 +54,7 @@
"RawPerLitrePerMinute": 100,
"RawOffset": 0,
"MinFlowLpm": 0,
- "MaxFlowLpm": 7
+ "MaxFlowLpm": 20
},
{
"PumpKey": "RecirculationReturnPump",
@@ -71,7 +71,7 @@
"RawPerLitrePerMinute": 100,
"RawOffset": 0,
"MinFlowLpm": 0,
- "MaxFlowLpm": 7
+ "MaxFlowLpm": 20
},
{
"PumpKey": "RecirculationDrainagePump",
@@ -88,7 +88,7 @@
"RawPerLitrePerMinute": 100,
"RawOffset": 0,
"MinFlowLpm": 0,
- "MaxFlowLpm": 7
+ "MaxFlowLpm": 20
},
{
"PumpKey": "KinkResistancePump",
@@ -105,7 +105,7 @@
"RawPerLitrePerMinute": 100,
"RawOffset": 0,
"MinFlowLpm": 0,
- "MaxFlowLpm": 7
+ "MaxFlowLpm": 20
},
{
"PumpKey": "HemolysisDrainageSinglePump",
@@ -122,7 +122,7 @@
"RawPerLitrePerMinute": 100,
"RawOffset": 0,
"MinFlowLpm": 0,
- "MaxFlowLpm": 7
+ "MaxFlowLpm": 20
},
{
"PumpKey": "HemolysisReturnSinglePump",
@@ -139,7 +139,7 @@
"RawPerLitrePerMinute": 100,
"RawOffset": 0,
"MinFlowLpm": 0,
- "MaxFlowLpm": 7
+ "MaxFlowLpm": 20
},
{
"PumpKey": "HemolysisDualLumenPump",
@@ -156,7 +156,7 @@
"RawPerLitrePerMinute": 100,
"RawOffset": 0,
"MinFlowLpm": 0,
- "MaxFlowLpm": 7
+ "MaxFlowLpm": 20
}
]
-}
\ No newline at end of file
+}