添加分组
This commit is contained in:
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace Cardiopulmonarybypasssystems.Converters;
|
||||||
|
|
||||||
|
public sealed class TrendPointCollectionConverter : IMultiValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (values.Length < 4
|
||||||
|
|| values[0] is not IEnumerable enumerable
|
||||||
|
|| values[1] is not double width
|
||||||
|
|| values[2] is not double height
|
||||||
|
|| values[3] is not double maxValue
|
||||||
|
|| width <= 0
|
||||||
|
|| height <= 0
|
||||||
|
|| maxValue <= 0)
|
||||||
|
{
|
||||||
|
return new PointCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples = enumerable.Cast<object>()
|
||||||
|
.Select(item => item is double value ? value : System.Convert.ToDouble(item, culture))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (samples.Count == 0)
|
||||||
|
{
|
||||||
|
return new PointCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samples.Count == 1)
|
||||||
|
{
|
||||||
|
return new PointCollection
|
||||||
|
{
|
||||||
|
new System.Windows.Point(0, height - samples[0] / maxValue * height)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var points = new PointCollection(samples.Count);
|
||||||
|
var xStep = width / Math.Max(samples.Count - 1, 1);
|
||||||
|
|
||||||
|
for (var index = 0; index < samples.Count; index++)
|
||||||
|
{
|
||||||
|
var x = xStep * index;
|
||||||
|
var yRatio = Math.Clamp(samples[index] / maxValue, 0d, 1d);
|
||||||
|
var y = height - yRatio * height;
|
||||||
|
points.Add(new System.Windows.Point(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
@@ -4,11 +4,15 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:models="clr-namespace:Cardiopulmonarybypasssystems.Models"
|
xmlns:models="clr-namespace:Cardiopulmonarybypasssystems.Models"
|
||||||
|
xmlns:converters="clr-namespace:Cardiopulmonarybypasssystems.Converters"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="心肺转流检测"
|
Title="心肺转流检测"
|
||||||
Width="1024"
|
Width="1024"
|
||||||
Height="800"
|
Height="800"
|
||||||
WindowStartupLocation="CenterScreen">
|
WindowStartupLocation="CenterScreen">
|
||||||
|
<Window.Resources>
|
||||||
|
<converters:TrendPointCollectionConverter x:Key="TrendPointCollectionConverter" />
|
||||||
|
</Window.Resources>
|
||||||
<Grid Margin="12">
|
<Grid Margin="12">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -398,150 +402,380 @@
|
|||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
<StackPanel Margin="0,6,0,0">
|
<StackPanel Margin="0,6,0,0">
|
||||||
<Border Style="{StaticResource CardBorderStyle}">
|
<Border Style="{StaticResource CardBorderStyle}">
|
||||||
|
<Border.Resources>
|
||||||
|
<DataTemplate x:Key="PumpControlCardTemplate" DataType="{x:Type models:PumpControlChannel}">
|
||||||
|
<Border Width="220" Margin="0,0,10,10" Padding="14" Background="#FFF4F8FA" CornerRadius="14">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="实时总览" />
|
<DockPanel>
|
||||||
<WrapPanel Margin="0,0,0,4">
|
<Ellipse Width="12" Height="12" Margin="0,3,8,0" Fill="{Binding IndicatorColor}" DockPanel.Dock="Left" />
|
||||||
<Border Width="150" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
<TextBlock FontSize="16" FontWeight="SemiBold" Text="{Binding Name}" TextWrapping="Wrap" />
|
||||||
<StackPanel>
|
</DockPanel>
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="阶段" />
|
<TextBlock Margin="0,8,0,0" Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding FlowDisplay}" />
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding CurrentStage}" TextWrapping="Wrap" />
|
<TextBlock Margin="0,4,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding StateText}" />
|
||||||
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="{Binding StateHint}" />
|
||||||
|
<Button Margin="0,10,0,0"
|
||||||
|
Command="{Binding DataContext.TogglePumpControlCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
Content="{Binding ActionText}"
|
||||||
|
Background="#FF4D8C72" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<Border Width="150" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
</DataTemplate>
|
||||||
|
</Border.Resources>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="设备" />
|
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="泵启动控制" />
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding DeviceStatus}" TextWrapping="Wrap" />
|
<UniformGrid Columns="2" Margin="0,10,0,0">
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
<Border Width="150" Margin="0,0,8,8" Padding="14" Background="#FFE7F5F3" CornerRadius="14">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="合格率" />
|
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="24" Text="{Binding ComplianceDisplay}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
<Border Width="150" Margin="0,0,8,8" Padding="14" Background="#FFE9EFF9" CornerRadius="14">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="合格项" />
|
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="24" Text="{Binding QualifiedCount}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
<Border Width="150" Margin="0,0,8,8" Padding="14" Background="#FFFDEBE7" CornerRadius="14">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="预警/不合格" />
|
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="24" Text="{Binding WarningCount}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
<Border Width="150" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="待检" />
|
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding PendingCount}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
<Border Width="150" Margin="0,0,0,8" Style="{StaticResource PanelSectionStyle}">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="告警" />
|
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding AlarmMessages.Count}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</WrapPanel>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<Border Style="{StaticResource CardBorderStyle}">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="流量快照" />
|
|
||||||
<UniformGrid Columns="3" Margin="0,0,0,8">
|
|
||||||
<Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
<Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="主泵" />
|
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="压力降 / 抗塌陷" />
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding PumpFlowDisplay}" />
|
<ItemsControl Margin="0,8,0,0" ItemsSource="{Binding PressureDropPumpControls}" ItemTemplate="{StaticResource PumpControlCardTemplate}">
|
||||||
<TextBlock Margin="0,4,0,6" Style="{StaticResource CaptionStyle}" Text="{Binding PumpFlowLoadDisplay}" />
|
<ItemsControl.ItemsPanel>
|
||||||
<ProgressBar Minimum="0" Maximum="1" Value="{Binding PumpFlowNormalizedValue, Mode=OneWay}" Height="10" Foreground="{StaticResource AccentBrush}" Background="#FFDDE7EC" />
|
<ItemsPanelTemplate>
|
||||||
</StackPanel>
|
<WrapPanel />
|
||||||
</Border>
|
</ItemsPanelTemplate>
|
||||||
<Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
</ItemsControl.ItemsPanel>
|
||||||
<StackPanel>
|
</ItemsControl>
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="引流" />
|
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding DrainageFlowDisplay}" />
|
|
||||||
<TextBlock Margin="0,4,0,6" Style="{StaticResource CaptionStyle}" Text="{Binding DrainageFlowLoadDisplay}" />
|
|
||||||
<ProgressBar Minimum="0" Maximum="1" Value="{Binding DrainageFlowNormalizedValue, Mode=OneWay}" Height="10" Foreground="{StaticResource AccentBrush}" Background="#FFDDE7EC" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<Border Margin="0,0,0,8" Style="{StaticResource PanelSectionStyle}">
|
<Border Margin="0,0,0,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="回输" />
|
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="再循环" />
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding ReturnFlowDisplay}" />
|
<ItemsControl Margin="0,8,0,0" ItemsSource="{Binding RecirculationPumpControls}" ItemTemplate="{StaticResource PumpControlCardTemplate}">
|
||||||
<TextBlock Margin="0,4,0,6" Style="{StaticResource CaptionStyle}" Text="{Binding ReturnFlowLoadDisplay}" />
|
<ItemsControl.ItemsPanel>
|
||||||
<ProgressBar Minimum="0" Maximum="1" Value="{Binding ReturnFlowNormalizedValue, Mode=OneWay}" Height="10" Foreground="{StaticResource AccentBrush}" Background="#FFDDE7EC" />
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border Margin="0,0,8,0" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="抗扭结" />
|
||||||
|
<ItemsControl Margin="0,8,0,0" ItemsSource="{Binding KinkResistancePumpControls}" ItemTemplate="{StaticResource PumpControlCardTemplate}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border Margin="0,0,0,0" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="血细胞破坏" />
|
||||||
|
<ItemsControl Margin="0,8,0,0" ItemsSource="{Binding HemolysisPumpControls}" ItemTemplate="{StaticResource PumpControlCardTemplate}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</UniformGrid>
|
</UniformGrid>
|
||||||
<WrapPanel>
|
|
||||||
<Border Width="180" Margin="0,0,8,8" Padding="14" Background="#FFEAF6F3" CornerRadius="14">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="再循环率" />
|
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding RealtimeRecirculationDisplay}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
<Border Width="180" Margin="0,0,8,8" Padding="14" Background="#FFF6EFE2" CornerRadius="14">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="压力降" />
|
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding DeltaPressureDisplay}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
<Border Width="180" Margin="0,0,0,8" Padding="14" Background="#FFEFF1FA" CornerRadius="14">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="主泵/回输差" />
|
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding FlowImbalanceDisplay}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</WrapPanel>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Border Style="{StaticResource CardBorderStyle}">
|
<Border Style="{StaticResource CardBorderStyle}">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="压力与辅助指标" />
|
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="关键实时读数" />
|
||||||
<WrapPanel Margin="0,0,0,4">
|
<WrapPanel Margin="0,0,0,4">
|
||||||
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="近端压力" />
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="阶段" />
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding Channels[4].DisplayValue}" />
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding CurrentStage}" TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="远端压力" />
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="设备状态" />
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding Channels[3].DisplayValue}" />
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding DeviceStatus}" TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="负压辅助引流" />
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="近端压力" />
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding NegativeAssistPressureDisplay}" />
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding ProximalPressureDisplay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<Border Width="180" Margin="0,0,0,8" Style="{StaticResource PanelSectionStyle}">
|
<Border Width="180" Margin="0,0,0,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="模拟血液温度" />
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="远端压力" />
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding TemperatureDisplay}" />
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding DistalPressureDisplay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
|
|
||||||
<WrapPanel>
|
<WrapPanel>
|
||||||
<Border Width="220" Margin="0,0,8,0" Style="{StaticResource PanelSectionStyle}">
|
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="fHb 趋势" />
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="压力降/抗塌陷" />
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding FreeHemoglobinDisplay}" />
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding PressureDropPumpFlowDisplay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<Border Width="220" Margin="0,0,0,0" Style="{StaticResource PanelSectionStyle}">
|
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource CaptionStyle}" Text="白细胞减少率" />
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="再循环主泵" />
|
||||||
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="22" Text="{Binding WhiteCellLossDisplay}" />
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding RecirculationPumpFlowDisplay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="回流泵" />
|
||||||
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding ReturnFlowDisplay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border Width="180" Margin="0,0,0,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="引流泵" />
|
||||||
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding DrainageFlowDisplay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
|
|
||||||
|
<WrapPanel>
|
||||||
|
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="抗扭结" />
|
||||||
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding KinkResistancePumpFlowDisplay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="血细胞破坏单腔引流" />
|
||||||
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding HemolysisDrainageSingleFlowDisplay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border Width="180" Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="血细胞破坏单腔回输" />
|
||||||
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding HemolysisReturnSingleFlowDisplay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border Width="180" Margin="0,0,0,8" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="血细胞破坏双腔" />
|
||||||
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding HemolysisDualLumenFlowDisplay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</WrapPanel>
|
||||||
|
|
||||||
|
<WrapPanel>
|
||||||
|
<Border Width="180" Margin="0,0,8,0" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="负压辅助" />
|
||||||
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding NegativeAssistPressureDisplay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border Width="180" Margin="0,0,8,0" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="压力降 ΔP" />
|
||||||
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding DeltaPressureDisplay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border Width="180" Margin="0,0,0,0" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="再循环率" />
|
||||||
|
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding RealtimeRecirculationDisplay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</WrapPanel>
|
||||||
|
|
||||||
|
<DockPanel Margin="0,12,0,8">
|
||||||
|
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="趋势图" />
|
||||||
|
<Button DockPanel.Dock="Right"
|
||||||
|
MinWidth="96"
|
||||||
|
Command="{Binding ClearTrendDataCommand}"
|
||||||
|
Content="清空曲线"
|
||||||
|
Background="#FF6B8791" />
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<Grid Margin="0,0,0,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="12" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Border Grid.Column="0" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="压力/ΔP 趋势" />
|
||||||
|
<WrapPanel Margin="0,0,0,8">
|
||||||
|
<Border Width="12" Height="12" Margin="0,2,6,0" Background="#FF0B7A75" CornerRadius="6" />
|
||||||
|
<TextBlock Margin="0,0,12,0" Style="{StaticResource CaptionStyle}" Text="近端压力" />
|
||||||
|
<Border Width="12" Height="12" Margin="0,2,6,0" Background="#FF3C6FB6" CornerRadius="6" />
|
||||||
|
<TextBlock Margin="0,0,12,0" Style="{StaticResource CaptionStyle}" Text="远端压力" />
|
||||||
|
<Border Width="12" Height="12" Margin="0,2,6,0" Background="#FFD38A16" CornerRadius="6" />
|
||||||
|
<TextBlock Style="{StaticResource CaptionStyle}" Text="ΔP" />
|
||||||
|
</WrapPanel>
|
||||||
|
<Grid Height="180" Background="#FFF7FBFC">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Border Grid.Row="0" BorderBrush="#FFE0EAEE" BorderThickness="0,0,0,1" />
|
||||||
|
<Border Grid.Row="1" BorderBrush="#FFE0EAEE" BorderThickness="0,0,0,1" />
|
||||||
|
<Border Grid.Row="2" BorderBrush="#FFE0EAEE" BorderThickness="0,0,0,1" />
|
||||||
|
<Canvas>
|
||||||
|
<Polyline Stroke="#FF0B7A75" StrokeThickness="2">
|
||||||
|
<Polyline.Points>
|
||||||
|
<MultiBinding Converter="{StaticResource TrendPointCollectionConverter}">
|
||||||
|
<Binding Path="ProximalPressureTrendValues" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualWidth" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualHeight" />
|
||||||
|
<Binding Path="PressureTrendMax" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Polyline.Points>
|
||||||
|
</Polyline>
|
||||||
|
<Polyline Stroke="#FF3C6FB6" StrokeThickness="2">
|
||||||
|
<Polyline.Points>
|
||||||
|
<MultiBinding Converter="{StaticResource TrendPointCollectionConverter}">
|
||||||
|
<Binding Path="DistalPressureTrendValues" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualWidth" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualHeight" />
|
||||||
|
<Binding Path="PressureTrendMax" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Polyline.Points>
|
||||||
|
</Polyline>
|
||||||
|
<Polyline Stroke="#FFD38A16" StrokeThickness="2">
|
||||||
|
<Polyline.Points>
|
||||||
|
<MultiBinding Converter="{StaticResource TrendPointCollectionConverter}">
|
||||||
|
<Binding Path="DeltaPressureTrendValues" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualWidth" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualHeight" />
|
||||||
|
<Binding Path="PressureTrendMax" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Polyline.Points>
|
||||||
|
</Polyline>
|
||||||
|
</Canvas>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border Grid.Column="2" Style="{StaticResource PanelSectionStyle}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="{Binding FlowTrendTitle}" />
|
||||||
|
<WrapPanel Margin="0,0,0,8">
|
||||||
|
<Border Width="12" Height="12" Margin="0,2,6,0" Background="#FF0B7A75" CornerRadius="6" />
|
||||||
|
<TextBlock Margin="0,0,12,0" Style="{StaticResource CaptionStyle}" Text="{Binding FlowTrendPrimaryLabel}" />
|
||||||
|
<Border Width="12" Height="12" Margin="0,2,6,0" Background="#FF3C6FB6" CornerRadius="6">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding HasFlowTrendSecondary}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Margin="0,0,12,0" Text="{Binding FlowTrendSecondaryLabel}">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock" BasedOn="{StaticResource CaptionStyle}">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding HasFlowTrendSecondary}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
<Border Width="12" Height="12" Margin="0,2,6,0" Background="#FFD38A16" CornerRadius="6">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding HasFlowTrendTertiary}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Text="{Binding FlowTrendTertiaryLabel}">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock" BasedOn="{StaticResource CaptionStyle}">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding HasFlowTrendTertiary}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</WrapPanel>
|
||||||
|
<Grid Height="180" Background="#FFF7FBFC">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Border Grid.Row="0" BorderBrush="#FFE0EAEE" BorderThickness="0,0,0,1" />
|
||||||
|
<Border Grid.Row="1" BorderBrush="#FFE0EAEE" BorderThickness="0,0,0,1" />
|
||||||
|
<Border Grid.Row="2" BorderBrush="#FFE0EAEE" BorderThickness="0,0,0,1" />
|
||||||
|
<Canvas>
|
||||||
|
<Polyline Stroke="#FF0B7A75" StrokeThickness="2">
|
||||||
|
<Polyline.Points>
|
||||||
|
<MultiBinding Converter="{StaticResource TrendPointCollectionConverter}">
|
||||||
|
<Binding Path="ActiveFlowTrendPrimaryValues" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualWidth" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualHeight" />
|
||||||
|
<Binding Path="FlowTrendMax" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Polyline.Points>
|
||||||
|
</Polyline>
|
||||||
|
<Polyline Stroke="#FF3C6FB6" StrokeThickness="2">
|
||||||
|
<Polyline.Style>
|
||||||
|
<Style TargetType="Polyline">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding HasFlowTrendSecondary}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Polyline.Style>
|
||||||
|
<Polyline.Points>
|
||||||
|
<MultiBinding Converter="{StaticResource TrendPointCollectionConverter}">
|
||||||
|
<Binding Path="ActiveFlowTrendSecondaryValues" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualWidth" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualHeight" />
|
||||||
|
<Binding Path="FlowTrendMax" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Polyline.Points>
|
||||||
|
</Polyline>
|
||||||
|
<Polyline Stroke="#FFD38A16" StrokeThickness="2">
|
||||||
|
<Polyline.Style>
|
||||||
|
<Style TargetType="Polyline">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding HasFlowTrendTertiary}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Polyline.Style>
|
||||||
|
<Polyline.Points>
|
||||||
|
<MultiBinding Converter="{StaticResource TrendPointCollectionConverter}">
|
||||||
|
<Binding Path="ActiveFlowTrendTertiaryValues" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualWidth" />
|
||||||
|
<Binding RelativeSource="{RelativeSource AncestorType=Canvas}" Path="ActualHeight" />
|
||||||
|
<Binding Path="FlowTrendMax" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Polyline.Points>
|
||||||
|
</Polyline>
|
||||||
|
</Canvas>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
58
Cardiopulmonarybypasssystems/Models/PumpControlChannel.cs
Normal file
58
Cardiopulmonarybypasssystems/Models/PumpControlChannel.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace Cardiopulmonarybypasssystems.Models;
|
||||||
|
|
||||||
|
public partial class PumpControlChannel : ObservableObject
|
||||||
|
{
|
||||||
|
private const double FlowEstablishedThreshold = 0.15d;
|
||||||
|
|
||||||
|
public required string Key { get; init; }
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public int StartAddress { get; init; }
|
||||||
|
public int? FlowAddress { get; init; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool isRunning;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private double flowValue;
|
||||||
|
|
||||||
|
public string StartAddressDisplay => $"M{StartAddress}";
|
||||||
|
public string FlowAddressDisplay => FlowAddress.HasValue ? $"D{FlowAddress.Value}" : "-";
|
||||||
|
public bool HasFlowTelemetry => FlowAddress.HasValue;
|
||||||
|
public bool IsFlowEstablished => !HasFlowTelemetry || FlowValue >= FlowEstablishedThreshold;
|
||||||
|
public string StateText => !IsRunning
|
||||||
|
? "停止"
|
||||||
|
: IsFlowEstablished
|
||||||
|
? "运行"
|
||||||
|
: "启动中";
|
||||||
|
public string StateHint => !IsRunning
|
||||||
|
? "泵未启动"
|
||||||
|
: IsFlowEstablished
|
||||||
|
? "流量已建立"
|
||||||
|
: "等待流量建立";
|
||||||
|
public string IndicatorColor => !IsRunning
|
||||||
|
? "#FFC8D4DA"
|
||||||
|
: IsFlowEstablished
|
||||||
|
? "#FF32B06A"
|
||||||
|
: "#FFD38A16";
|
||||||
|
public string FlowDisplay => FlowAddress.HasValue ? $"{FlowValue:F2} L/min" : "-";
|
||||||
|
public string ActionText => IsRunning ? "停止" : "启动";
|
||||||
|
|
||||||
|
partial void OnIsRunningChanged(bool value)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(StateText));
|
||||||
|
OnPropertyChanged(nameof(StateHint));
|
||||||
|
OnPropertyChanged(nameof(IndicatorColor));
|
||||||
|
OnPropertyChanged(nameof(ActionText));
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnFlowValueChanged(double value)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(FlowDisplay));
|
||||||
|
OnPropertyChanged(nameof(IsFlowEstablished));
|
||||||
|
OnPropertyChanged(nameof(StateText));
|
||||||
|
OnPropertyChanged(nameof(StateHint));
|
||||||
|
OnPropertyChanged(nameof(IndicatorColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,5 +5,7 @@ namespace Cardiopulmonarybypasssystems.Services;
|
|||||||
public interface IModbusTelemetryService
|
public interface IModbusTelemetryService
|
||||||
{
|
{
|
||||||
IReadOnlyList<DeviceChannel> GetChannels();
|
IReadOnlyList<DeviceChannel> GetChannels();
|
||||||
|
IReadOnlyList<PumpControlChannel> GetPumpControls();
|
||||||
IReadOnlyList<AlarmMessage> UpdateChannels();
|
IReadOnlyList<AlarmMessage> UpdateChannels();
|
||||||
|
void SetPumpRunning(string pumpKey, bool isRunning);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ 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;
|
||||||
private const ushort PumpFlowRegister = 1040;
|
|
||||||
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;
|
||||||
@@ -19,23 +18,64 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
private static readonly TimeSpan ConnectionAttemptTimeout = TimeSpan.FromMilliseconds(300);
|
private static readonly TimeSpan ConnectionAttemptTimeout = TimeSpan.FromMilliseconds(300);
|
||||||
private static readonly TimeSpan ConnectionRetryInterval = TimeSpan.FromSeconds(5);
|
private static readonly TimeSpan ConnectionRetryInterval = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
private static readonly IReadOnlyDictionary<string, ushort> FlowRegisters = new Dictionary<string, ushort>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
["PressureDropPump"] = 1000,
|
||||||
|
["RecirculationMainPump"] = 1010,
|
||||||
|
["RecirculationReturnPump"] = 1020,
|
||||||
|
["RecirculationDrainagePump"] = 1030,
|
||||||
|
["KinkResistancePump"] = 1040,
|
||||||
|
["HemolysisDrainageSinglePump"] = 1050,
|
||||||
|
["HemolysisReturnSinglePump"] = 1060,
|
||||||
|
["HemolysisDualLumenPump"] = 1070
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly IReadOnlyDictionary<string, string> FlowChannelNames = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
["PressureDropPump"] = "主泵流量",
|
||||||
|
["RecirculationMainPump"] = "再循环主泵流量",
|
||||||
|
["RecirculationReturnPump"] = "动脉回输流量",
|
||||||
|
["RecirculationDrainagePump"] = "静脉引流流量",
|
||||||
|
["KinkResistancePump"] = "抗扭结主泵流量",
|
||||||
|
["HemolysisDrainageSinglePump"] = "血细胞破坏-单腔引流流量",
|
||||||
|
["HemolysisReturnSinglePump"] = "血细胞破坏-单腔回输流量",
|
||||||
|
["HemolysisDualLumenPump"] = "血细胞破坏-双腔流量"
|
||||||
|
};
|
||||||
|
|
||||||
private readonly object _syncRoot = new();
|
private readonly object _syncRoot = new();
|
||||||
private readonly Random _random = new();
|
private readonly Random _random = new();
|
||||||
private readonly ModbusFactory _factory = new();
|
private readonly ModbusFactory _factory = new();
|
||||||
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.82, Min = 0, Max = 7 },
|
new() { Name = "主泵流量", Unit = "L/min", Value = 4.32, Min = 0, Max = 7 },
|
||||||
new() { Name = "静脉引流流量", Unit = "L/min", Value = 4.54, Min = 0, Max = 7 },
|
new() { Name = "再循环主泵流量", Unit = "L/min", Value = 4.86, 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 = 94, Min = 40, Max = 180 },
|
||||||
new() { Name = "近端压力", Unit = "mmHg", Value = 112, Min = 60, Max = 220 },
|
new() { Name = "近端压力", Unit = "mmHg", Value = 112, Min = 60, Max = 220 },
|
||||||
new() { Name = "负压辅助引流", Unit = "kPa", Value = -10.4, Min = -20, Max = 0 },
|
new() { Name = "负压辅助引流", Unit = "kPa", Value = -6.7, 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 = 6.8, 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 =
|
||||||
|
[
|
||||||
|
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 }
|
||||||
|
];
|
||||||
|
|
||||||
private TcpClient? _tcpClient;
|
private TcpClient? _tcpClient;
|
||||||
private IModbusMaster? _master;
|
private IModbusMaster? _master;
|
||||||
@@ -49,19 +89,55 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
return _channels;
|
return _channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<PumpControlChannel> GetPumpControls()
|
||||||
|
{
|
||||||
|
EnsureConnectionScheduled();
|
||||||
|
return _pumpControls;
|
||||||
|
}
|
||||||
|
|
||||||
public IReadOnlyList<AlarmMessage> UpdateChannels()
|
public IReadOnlyList<AlarmMessage> UpdateChannels()
|
||||||
{
|
{
|
||||||
EnsureConnectionScheduled();
|
EnsureConnectionScheduled();
|
||||||
|
|
||||||
lock (_syncRoot)
|
lock (_syncRoot)
|
||||||
{
|
{
|
||||||
var liveReadSucceeded = TryReadProcessChannels();
|
var liveReadSucceeded = TryReadPumpStatesAndFlows();
|
||||||
|
TryReadPressureChannels(liveReadSucceeded);
|
||||||
SimulateAuxiliaryChannels(liveReadSucceeded);
|
SimulateAuxiliaryChannels(liveReadSucceeded);
|
||||||
SyncDerivedChannels(liveReadSucceeded);
|
SyncDerivedChannels();
|
||||||
return BuildAlarms();
|
return BuildAlarms();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetPumpRunning(string pumpKey, bool isRunning)
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
var pump = _pumpControls.FirstOrDefault(item => item.Key == pumpKey);
|
||||||
|
if (pump is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pump.IsRunning = isRunning;
|
||||||
|
|
||||||
|
if (_master is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_master.WriteSingleCoil(SlaveId, (ushort)pump.StartAddress, isRunning);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ReleaseConnection();
|
||||||
|
_nextConnectionAttemptUtc = DateTime.MinValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
lock (_syncRoot)
|
lock (_syncRoot)
|
||||||
@@ -127,59 +203,86 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryReadProcessChannels()
|
private bool TryReadPumpStatesAndFlows()
|
||||||
{
|
{
|
||||||
if (_master is null)
|
if (_master is null)
|
||||||
{
|
{
|
||||||
SimulatePressureChannels();
|
SimulatePumpFlows();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pumpFlowRaw = _master.ReadHoldingRegisters(SlaveId, PumpFlowRegister, 1)[0];
|
var coilStates = _master.ReadCoils(SlaveId, 0, (ushort)_pumpControls.Count);
|
||||||
var proximalRaw = _master.ReadHoldingRegisters(SlaveId, ProximalPressureRegister, 1)[0];
|
for (var index = 0; index < _pumpControls.Count; index++)
|
||||||
var distalRaw = _master.ReadHoldingRegisters(SlaveId, DistalPressureRegister, 1)[0];
|
{
|
||||||
|
_pumpControls[index].IsRunning = coilStates[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pump in _pumpControls.Where(item => item.FlowAddress.HasValue))
|
||||||
|
{
|
||||||
|
var registerValue = _master.ReadHoldingRegisters(SlaveId, (ushort)pump.FlowAddress!.Value, 1)[0];
|
||||||
|
var flowValue = ConvertRegisterToFlow(registerValue);
|
||||||
|
pump.FlowValue = flowValue;
|
||||||
|
SetSmoothedValue(FlowChannelNames[pump.Key], flowValue);
|
||||||
|
}
|
||||||
|
|
||||||
SetSmoothedValue("主泵流量", ConvertRegisterToFlow(pumpFlowRaw));
|
|
||||||
SetSmoothedValue("近端压力", ConvertRegisterToPressure(proximalRaw, Channel("近端压力")));
|
|
||||||
SetSmoothedValue("远端压力", ConvertRegisterToPressure(distalRaw, Channel("远端压力")));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
ReleaseConnection();
|
ReleaseConnection();
|
||||||
_nextConnectionAttemptUtc = DateTime.MinValue;
|
_nextConnectionAttemptUtc = DateTime.MinValue;
|
||||||
SimulatePressureChannels();
|
SimulatePumpFlows();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SimulateAuxiliaryChannels(bool preservePumpFlow)
|
private void TryReadPressureChannels(bool liveReadSucceeded)
|
||||||
{
|
{
|
||||||
foreach (var channel in _channels.Where(channel =>
|
if (_master is null || !liveReadSucceeded)
|
||||||
channel.Name is not "近端压力" and not "远端压力"
|
|
||||||
&& (!preservePumpFlow || channel.Name != "主泵流量")))
|
|
||||||
{
|
{
|
||||||
var offset = channel.Name switch
|
SimulatePressureChannels();
|
||||||
{
|
return;
|
||||||
"主泵流量" => Next(-0.08, 0.08),
|
}
|
||||||
"静脉引流流量" => Next(-0.08, 0.08),
|
|
||||||
"动脉回输流量" => Next(-0.06, 0.06),
|
|
||||||
"负压辅助引流" => Next(-0.6, 0.6),
|
|
||||||
"模拟血液温度" => Next(-0.15, 0.15),
|
|
||||||
"游离血红蛋白" => Next(-0.003, 0.003),
|
|
||||||
_ => Next(-0.5, 0.5)
|
|
||||||
};
|
|
||||||
|
|
||||||
SetSmoothedValue(channel.Name, channel.Value + offset);
|
try
|
||||||
|
{
|
||||||
|
var proximalRaw = _master.ReadHoldingRegisters(SlaveId, ProximalPressureRegister, 1)[0];
|
||||||
|
var distalRaw = _master.ReadHoldingRegisters(SlaveId, DistalPressureRegister, 1)[0];
|
||||||
|
SetSmoothedValue("近端压力", ConvertRegisterToPressure(proximalRaw, Channel("近端压力")));
|
||||||
|
SetSmoothedValue("远端压力", ConvertRegisterToPressure(distalRaw, Channel("远端压力")));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ReleaseConnection();
|
||||||
|
_nextConnectionAttemptUtc = DateTime.MinValue;
|
||||||
|
SimulatePressureChannels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SimulatePumpFlows()
|
||||||
|
{
|
||||||
|
foreach (var pump in _pumpControls.Where(item => item.FlowAddress.HasValue))
|
||||||
|
{
|
||||||
|
var target = pump.IsRunning ? SimulatedRunningTarget(pump.Key) : 0d;
|
||||||
|
var nextValue = pump.IsRunning
|
||||||
|
? target + Next(-0.10, 0.10)
|
||||||
|
: Math.Max(0d, pump.FlowValue * 0.35 + Next(-0.02, 0.02));
|
||||||
|
|
||||||
|
pump.FlowValue = Math.Clamp(nextValue, 0d, 7d);
|
||||||
|
SetSmoothedValue(FlowChannelNames[pump.Key], pump.FlowValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SimulatePressureChannels()
|
private void SimulatePressureChannels()
|
||||||
{
|
{
|
||||||
SetSmoothedValue("近端压力", Channel("近端压力").Value + Next(-3.0, 3.0));
|
var pressurePumpRunning = Pump("PressureDropPump").IsRunning;
|
||||||
SetSmoothedValue("远端压力", Channel("远端压力").Value + Next(-2.5, 2.5));
|
var proximalTarget = pressurePumpRunning ? 112d : 80d;
|
||||||
|
var distalTarget = pressurePumpRunning ? 94d : 68d;
|
||||||
|
|
||||||
|
SetSmoothedValue("近端压力", proximalTarget + Next(-3.0, 3.0));
|
||||||
|
SetSmoothedValue("远端压力", distalTarget + Next(-2.5, 2.5));
|
||||||
|
|
||||||
var proximal = Channel("近端压力");
|
var proximal = Channel("近端压力");
|
||||||
var distal = Channel("远端压力");
|
var distal = Channel("远端压力");
|
||||||
@@ -189,34 +292,34 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SyncDerivedChannels(bool livePumpFlow)
|
private void SimulateAuxiliaryChannels(bool liveReadSucceeded)
|
||||||
{
|
{
|
||||||
var pumpFlow = Channel("主泵流量");
|
var negativePump = Pump("NegativeAssistPump");
|
||||||
|
var negativeTarget = negativePump.IsRunning ? -6.67d : 0d;
|
||||||
|
SetSmoothedValue("负压辅助引流", negativeTarget + Next(-0.5, 0.5));
|
||||||
|
|
||||||
if (livePumpFlow)
|
SetSmoothedValue("模拟血液温度", Channel("模拟血液温度").Value + Next(-0.15, 0.15));
|
||||||
|
SetSmoothedValue("游离血红蛋白", Channel("游离血红蛋白").Value + Next(-0.003, 0.003));
|
||||||
|
SetSmoothedValue("白细胞减少率", Channel("白细胞减少率").Value + Next(-0.4, 0.4));
|
||||||
|
|
||||||
|
if (!liveReadSucceeded)
|
||||||
{
|
{
|
||||||
SetSmoothedValue("动脉回输流量", Math.Max(0d, pumpFlow.Value + Next(-0.08, 0.08)));
|
return;
|
||||||
SetSmoothedValue("静脉引流流量", Math.Max(0d, pumpFlow.Value - Next(0.12, 0.42)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var drainageFlow = Channel("静脉引流流量");
|
|
||||||
var returnFlow = Channel("动脉回输流量");
|
|
||||||
SetSmoothedValue(
|
|
||||||
"静脉引流流量",
|
|
||||||
Math.Min(drainageFlow.Value, returnFlow.Value - Next(0.12, 0.48)));
|
|
||||||
SetSmoothedValue(
|
|
||||||
"主泵流量",
|
|
||||||
(Channel("静脉引流流量").Value + returnFlow.Value) / 2d + Next(-0.05, 0.05));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var recirculationRate = Channel("动脉回输流量").Value <= 0.01
|
foreach (var pump in _pumpControls.Where(item => item.FlowAddress.HasValue))
|
||||||
? 0
|
{
|
||||||
: Math.Clamp(
|
SetSmoothedValue(FlowChannelNames[pump.Key], pump.FlowValue);
|
||||||
(Channel("动脉回输流量").Value - Channel("静脉引流流量").Value)
|
}
|
||||||
/ Channel("动脉回输流量").Value * 100d,
|
}
|
||||||
0d,
|
|
||||||
100d);
|
private void SyncDerivedChannels()
|
||||||
|
{
|
||||||
|
var returnFlow = Channel("动脉回输流量").Value;
|
||||||
|
var drainageFlow = Channel("静脉引流流量").Value;
|
||||||
|
var recirculationRate = returnFlow <= 0.01
|
||||||
|
? 0d
|
||||||
|
: Math.Clamp((returnFlow - drainageFlow) / returnFlow * 100d, 0d, 100d);
|
||||||
SetSmoothedValue("再循环率", recirculationRate);
|
SetSmoothedValue("再循环率", recirculationRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,8 +328,6 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
var alarms = new List<AlarmMessage>();
|
var alarms = new List<AlarmMessage>();
|
||||||
var deltaPressure = Channel("近端压力").Value - Channel("远端压力").Value;
|
var deltaPressure = Channel("近端压力").Value - Channel("远端压力").Value;
|
||||||
var recirculationRate = Channel("再循环率").Value;
|
var recirculationRate = Channel("再循环率").Value;
|
||||||
var pumpFlow = Channel("主泵流量").Value;
|
|
||||||
var returnFlow = Channel("动脉回输流量").Value;
|
|
||||||
|
|
||||||
if (deltaPressure > 24)
|
if (deltaPressure > 24)
|
||||||
{
|
{
|
||||||
@@ -234,7 +335,7 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
{
|
{
|
||||||
Timestamp = DateTime.Now,
|
Timestamp = DateTime.Now,
|
||||||
Level = "高",
|
Level = "高",
|
||||||
Message = $"压力降 ΔP {deltaPressure:F1} mmHg 偏高,请复核近端/远端压力与流量点。"
|
Message = $"压力降 ΔP {deltaPressure:F1} mmHg 偏高,请复核 D{ProximalPressureRegister}/D{DistalPressureRegister}。"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +345,7 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
{
|
{
|
||||||
Timestamp = DateTime.Now,
|
Timestamp = DateTime.Now,
|
||||||
Level = "中",
|
Level = "中",
|
||||||
Message = $"ModbusTcp 未连接,当前主泵流量 D{PumpFlowRegister} 与压力通道使用平滑模拟数据。目标 {IpAddress}:{Port}。"
|
Message = $"ModbusTcp 未连接,当前 M/D 泵控与流量使用本地平滑模拟。目标 {IpAddress}:{Port}。"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,27 +355,17 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
{
|
{
|
||||||
Timestamp = DateTime.Now,
|
Timestamp = DateTime.Now,
|
||||||
Level = "中",
|
Level = "中",
|
||||||
Message = $"再循环率 {recirculationRate:F1}% 偏高,建议复核双腔位置和回流方向。"
|
Message = $"再循环率 {recirculationRate:F1}% 偏高,建议复核回路与泵状态。"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Math.Abs(pumpFlow - returnFlow) > 0.35)
|
foreach (var pump in _pumpControls.Where(item => item.IsRunning && item.FlowAddress.HasValue && item.FlowValue < 0.15d))
|
||||||
{
|
{
|
||||||
alarms.Add(new AlarmMessage
|
alarms.Add(new AlarmMessage
|
||||||
{
|
{
|
||||||
Timestamp = DateTime.Now,
|
Timestamp = DateTime.Now,
|
||||||
Level = "中",
|
Level = "中",
|
||||||
Message = $"主泵/回输流量差 {Math.Abs(pumpFlow - returnFlow):F2} L/min,建议检查流量传感器标定。"
|
Message = $"{pump.Name} 已启动但流量未建立,请检查回路、泵头和传感器。"
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Channel("游离血红蛋白").Value > 0.04)
|
|
||||||
{
|
|
||||||
alarms.Add(new AlarmMessage
|
|
||||||
{
|
|
||||||
Timestamp = DateTime.Now,
|
|
||||||
Level = "中",
|
|
||||||
Message = $"游离血红蛋白 {Channel("游离血红蛋白").Value:F3} g/L,上升趋势需要关注。"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +407,19 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
_tcpClient = null;
|
_tcpClient = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static double SimulatedRunningTarget(string pumpKey) => pumpKey switch
|
||||||
|
{
|
||||||
|
"PressureDropPump" => 4.2d,
|
||||||
|
"RecirculationMainPump" => 4.8d,
|
||||||
|
"RecirculationReturnPump" => 4.7d,
|
||||||
|
"RecirculationDrainagePump" => 4.4d,
|
||||||
|
"KinkResistancePump" => 4.6d,
|
||||||
|
"HemolysisDrainageSinglePump" => 4.3d,
|
||||||
|
"HemolysisReturnSinglePump" => 4.3d,
|
||||||
|
"HemolysisDualLumenPump" => 4.1d,
|
||||||
|
_ => 4.0d
|
||||||
|
};
|
||||||
|
|
||||||
private static double ConvertRegisterToFlow(ushort rawValue) => rawValue * FlowRegisterScale;
|
private static double ConvertRegisterToFlow(ushort rawValue) => rawValue * FlowRegisterScale;
|
||||||
|
|
||||||
private static double ConvertRegisterToPressure(ushort rawValue, DeviceChannel channel)
|
private static double ConvertRegisterToPressure(ushort rawValue, DeviceChannel channel)
|
||||||
@@ -324,6 +428,8 @@ public sealed class MockModbusTelemetryService : IModbusTelemetryService, IDispo
|
|||||||
return Math.Clamp(signedValue, channel.Min, channel.Max);
|
return Math.Clamp(signedValue, channel.Min, channel.Max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PumpControlChannel Pump(string key) => _pumpControls.First(pump => pump.Key == key);
|
||||||
|
|
||||||
private DeviceChannel Channel(string name) => _channels.First(channel => channel.Name == name);
|
private DeviceChannel Channel(string name) => _channels.First(channel => channel.Name == name);
|
||||||
|
|
||||||
private double Next(double min, double max) => min + (_random.NextDouble() * (max - min));
|
private double Next(double min, double max) => min + (_random.NextDouble() * (max - min));
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
private readonly IModbusTelemetryService _telemetryService;
|
private readonly IModbusTelemetryService _telemetryService;
|
||||||
private readonly DispatcherTimer _timer;
|
private readonly DispatcherTimer _timer;
|
||||||
private const double AntiCollapseTargetNegativePressure = -6.67;
|
private const double AntiCollapseTargetNegativePressure = -6.67;
|
||||||
|
private const int TrendHistoryCapacity = 60;
|
||||||
private static readonly string LimitSettingsPath = Path.Combine(
|
private static readonly string LimitSettingsPath = Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
"Cardiopulmonarybypasssystems",
|
"Cardiopulmonarybypasssystems",
|
||||||
@@ -135,6 +136,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
FilteredItemsView = CollectionViewSource.GetDefaultView(InspectionItems);
|
FilteredItemsView = CollectionViewSource.GetDefaultView(InspectionItems);
|
||||||
FilteredItemsView.Filter = MatchesFilteredItem;
|
FilteredItemsView.Filter = MatchesFilteredItem;
|
||||||
Channels = new ObservableCollection<DeviceChannel>(telemetryService.GetChannels());
|
Channels = new ObservableCollection<DeviceChannel>(telemetryService.GetChannels());
|
||||||
|
PumpControls = new ObservableCollection<PumpControlChannel>(telemetryService.GetPumpControls());
|
||||||
TraceEvents = new ObservableCollection<TraceEvent>(repository.GetSeedTraceEvents());
|
TraceEvents = new ObservableCollection<TraceEvent>(repository.GetSeedTraceEvents());
|
||||||
AlarmMessages = new ObservableCollection<AlarmMessage>();
|
AlarmMessages = new ObservableCollection<AlarmMessage>();
|
||||||
ResultStatusOptions = new ObservableCollection<string>(["待检", "合格", "预警", "不合格"]);
|
ResultStatusOptions = new ObservableCollection<string>(["待检", "合格", "预警", "不合格"]);
|
||||||
@@ -187,12 +189,28 @@ public partial class MainViewModel : ObservableObject
|
|||||||
public ObservableCollection<InspectionItem> InspectionItems { get; }
|
public ObservableCollection<InspectionItem> InspectionItems { get; }
|
||||||
public ICollectionView FilteredItemsView { get; }
|
public ICollectionView FilteredItemsView { get; }
|
||||||
public ObservableCollection<DeviceChannel> Channels { get; }
|
public ObservableCollection<DeviceChannel> Channels { get; }
|
||||||
|
public ObservableCollection<PumpControlChannel> PumpControls { get; }
|
||||||
|
public IEnumerable<PumpControlChannel> PressureDropPumpControls => PumpControlsFor("NegativeAssistPump", "PressureDropPump");
|
||||||
|
public IEnumerable<PumpControlChannel> RecirculationPumpControls => PumpControlsFor("RecirculationMainPump", "RecirculationReturnPump", "RecirculationDrainagePump");
|
||||||
|
public IEnumerable<PumpControlChannel> KinkResistancePumpControls => PumpControlsFor("KinkResistancePump");
|
||||||
|
public IEnumerable<PumpControlChannel> HemolysisPumpControls => PumpControlsFor("HemolysisDrainageSinglePump", "HemolysisReturnSinglePump", "HemolysisDualLumenPump");
|
||||||
public ObservableCollection<TraceEvent> TraceEvents { get; }
|
public ObservableCollection<TraceEvent> TraceEvents { get; }
|
||||||
public ObservableCollection<AlarmMessage> AlarmMessages { get; }
|
public ObservableCollection<AlarmMessage> AlarmMessages { get; }
|
||||||
public ObservableCollection<string> ResultStatusOptions { get; }
|
public ObservableCollection<string> ResultStatusOptions { get; }
|
||||||
public ObservableCollection<PressureDropPointEntry> PressureDropEntries { get; }
|
public ObservableCollection<PressureDropPointEntry> PressureDropEntries { get; }
|
||||||
public ObservableCollection<KinkResistancePointEntry> KinkResistanceEntries { get; }
|
public ObservableCollection<KinkResistancePointEntry> KinkResistanceEntries { get; }
|
||||||
public ObservableCollection<RecirculationPointEntry> RecirculationEntries { get; }
|
public ObservableCollection<RecirculationPointEntry> RecirculationEntries { get; }
|
||||||
|
public ObservableCollection<double> ProximalPressureTrendValues { get; } = [];
|
||||||
|
public ObservableCollection<double> DistalPressureTrendValues { get; } = [];
|
||||||
|
public ObservableCollection<double> DeltaPressureTrendValues { get; } = [];
|
||||||
|
public ObservableCollection<double> PressureDropPumpTrendValues { get; } = [];
|
||||||
|
public ObservableCollection<double> RecirculationMainPumpTrendValues { get; } = [];
|
||||||
|
public ObservableCollection<double> RecirculationReturnPumpTrendValues { get; } = [];
|
||||||
|
public ObservableCollection<double> RecirculationDrainagePumpTrendValues { get; } = [];
|
||||||
|
public ObservableCollection<double> KinkResistancePumpTrendValues { get; } = [];
|
||||||
|
public ObservableCollection<double> HemolysisDrainageSingleTrendValues { get; } = [];
|
||||||
|
public ObservableCollection<double> HemolysisReturnSingleTrendValues { get; } = [];
|
||||||
|
public ObservableCollection<double> HemolysisDualLumenTrendValues { get; } = [];
|
||||||
public ObservableCollection<string> ItemFilterOptions { get; } = new(["全部", "待填写", "已完成", "实时监控", "手动填写"]);
|
public ObservableCollection<string> ItemFilterOptions { get; } = new(["全部", "待填写", "已完成", "实时监控", "手动填写"]);
|
||||||
public bool HasFilteredItems => !FilteredItemsView.IsEmpty;
|
public bool HasFilteredItems => !FilteredItemsView.IsEmpty;
|
||||||
public IEnumerable<DeviceChannel> FlowSensorChannels => Channels.Where(IsFlowSensorChannel);
|
public IEnumerable<DeviceChannel> FlowSensorChannels => Channels.Where(IsFlowSensorChannel);
|
||||||
@@ -207,6 +225,61 @@ public partial class MainViewModel : ObservableObject
|
|||||||
public string PumpFlowDisplay => $"{PumpFlow:F2} L/min";
|
public string PumpFlowDisplay => $"{PumpFlow:F2} L/min";
|
||||||
public string DrainageFlowDisplay => $"{DrainageFlow:F2} L/min";
|
public string DrainageFlowDisplay => $"{DrainageFlow:F2} L/min";
|
||||||
public string ReturnFlowDisplay => $"{ReturnFlow:F2} L/min";
|
public string ReturnFlowDisplay => $"{ReturnFlow:F2} L/min";
|
||||||
|
public string PressureDropPumpFlowDisplay => $"{PressureDropPumpFlow:F2} L/min";
|
||||||
|
public string RecirculationPumpFlowDisplay => $"{RecirculationPumpFlow:F2} L/min";
|
||||||
|
public string KinkResistancePumpFlowDisplay => $"{KinkResistancePumpFlow:F2} L/min";
|
||||||
|
public string HemolysisDrainageSingleFlowDisplay => $"{HemolysisDrainageSingleFlow:F2} L/min";
|
||||||
|
public string HemolysisReturnSingleFlowDisplay => $"{HemolysisReturnSingleFlow:F2} L/min";
|
||||||
|
public string HemolysisDualLumenFlowDisplay => $"{HemolysisDualLumenFlow:F2} L/min";
|
||||||
|
public string FlowTrendTitle => SelectedItem?.Clause switch
|
||||||
|
{
|
||||||
|
"4.3.3" => "再循环流量趋势",
|
||||||
|
"4.2.3" => "抗扭结流量趋势",
|
||||||
|
"4.3.4" => "血细胞破坏流量趋势",
|
||||||
|
_ => "压力降/抗塌陷流量趋势"
|
||||||
|
};
|
||||||
|
public string FlowTrendPrimaryLabel => SelectedItem?.Clause switch
|
||||||
|
{
|
||||||
|
"4.3.3" => "主泵",
|
||||||
|
"4.2.3" => "抗扭结泵",
|
||||||
|
"4.3.4" => "单腔引流",
|
||||||
|
_ => "主泵"
|
||||||
|
};
|
||||||
|
public string FlowTrendSecondaryLabel => SelectedItem?.Clause switch
|
||||||
|
{
|
||||||
|
"4.3.3" => "回流",
|
||||||
|
"4.3.4" => "单腔回输",
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
public string FlowTrendTertiaryLabel => SelectedItem?.Clause switch
|
||||||
|
{
|
||||||
|
"4.3.3" => "引流",
|
||||||
|
"4.3.4" => "双腔",
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
public bool HasFlowTrendSecondary => !string.IsNullOrWhiteSpace(FlowTrendSecondaryLabel);
|
||||||
|
public bool HasFlowTrendTertiary => !string.IsNullOrWhiteSpace(FlowTrendTertiaryLabel);
|
||||||
|
public ObservableCollection<double> ActiveFlowTrendPrimaryValues => SelectedItem?.Clause switch
|
||||||
|
{
|
||||||
|
"4.3.3" => RecirculationMainPumpTrendValues,
|
||||||
|
"4.2.3" => KinkResistancePumpTrendValues,
|
||||||
|
"4.3.4" => HemolysisDrainageSingleTrendValues,
|
||||||
|
_ => PressureDropPumpTrendValues
|
||||||
|
};
|
||||||
|
public ObservableCollection<double> ActiveFlowTrendSecondaryValues => SelectedItem?.Clause switch
|
||||||
|
{
|
||||||
|
"4.3.3" => RecirculationReturnPumpTrendValues,
|
||||||
|
"4.3.4" => HemolysisReturnSingleTrendValues,
|
||||||
|
_ => []
|
||||||
|
};
|
||||||
|
public ObservableCollection<double> ActiveFlowTrendTertiaryValues => SelectedItem?.Clause switch
|
||||||
|
{
|
||||||
|
"4.3.3" => RecirculationDrainagePumpTrendValues,
|
||||||
|
"4.3.4" => HemolysisDualLumenTrendValues,
|
||||||
|
_ => []
|
||||||
|
};
|
||||||
|
public double PressureTrendMax => MaxTrendValue([ProximalPressureTrendValues, DistalPressureTrendValues, DeltaPressureTrendValues], 40d);
|
||||||
|
public double FlowTrendMax => MaxTrendValue([ActiveFlowTrendPrimaryValues, ActiveFlowTrendSecondaryValues, ActiveFlowTrendTertiaryValues], Math.Max(RatedMaxFlow, 1d));
|
||||||
public string ProximalPressureDisplay => $"{ChannelValue("近端压力"):F1} mmHg";
|
public string ProximalPressureDisplay => $"{ChannelValue("近端压力"):F1} mmHg";
|
||||||
public string DistalPressureDisplay => $"{ChannelValue("远端压力"):F1} mmHg";
|
public string DistalPressureDisplay => $"{ChannelValue("远端压力"):F1} mmHg";
|
||||||
public string FlowImbalanceDisplay => $"{Math.Abs(PumpFlow - ReturnFlow):F2} L/min";
|
public string FlowImbalanceDisplay => $"{Math.Abs(PumpFlow - ReturnFlow):F2} L/min";
|
||||||
@@ -265,6 +338,12 @@ public partial class MainViewModel : ObservableObject
|
|||||||
public string RecirculationSamplingSummary => BuildRecirculationSamplingSummary();
|
public string RecirculationSamplingSummary => BuildRecirculationSamplingSummary();
|
||||||
public string RecirculationLimitDisplay => $"制造商声明限值:R ≤ {RecirculationAllowedLimit:F1}%";
|
public string RecirculationLimitDisplay => $"制造商声明限值:R ≤ {RecirculationAllowedLimit:F1}%";
|
||||||
|
|
||||||
|
public double PressureDropPumpFlow => ChannelValue("主泵流量");
|
||||||
|
public double RecirculationPumpFlow => ChannelValue("再循环主泵流量");
|
||||||
|
public double KinkResistancePumpFlow => ChannelValue("抗扭结主泵流量");
|
||||||
|
public double HemolysisDrainageSingleFlow => ChannelValue("血细胞破坏-单腔引流流量");
|
||||||
|
public double HemolysisReturnSingleFlow => ChannelValue("血细胞破坏-单腔回输流量");
|
||||||
|
public double HemolysisDualLumenFlow => ChannelValue("血细胞破坏-双腔流量");
|
||||||
public double PumpFlow => ChannelValue("主泵流量");
|
public double PumpFlow => ChannelValue("主泵流量");
|
||||||
public double DrainageFlow => ChannelValue("静脉引流流量");
|
public double DrainageFlow => ChannelValue("静脉引流流量");
|
||||||
public double ReturnFlow => ChannelValue("动脉回输流量");
|
public double ReturnFlow => ChannelValue("动脉回输流量");
|
||||||
@@ -323,6 +402,16 @@ public partial class MainViewModel : ObservableObject
|
|||||||
OnPropertyChanged(nameof(RealtimeMeasurementHint));
|
OnPropertyChanged(nameof(RealtimeMeasurementHint));
|
||||||
OnPropertyChanged(nameof(SelectedItemLiveDisplay));
|
OnPropertyChanged(nameof(SelectedItemLiveDisplay));
|
||||||
OnPropertyChanged(nameof(SelectedItemLiveHint));
|
OnPropertyChanged(nameof(SelectedItemLiveHint));
|
||||||
|
OnPropertyChanged(nameof(FlowTrendTitle));
|
||||||
|
OnPropertyChanged(nameof(FlowTrendPrimaryLabel));
|
||||||
|
OnPropertyChanged(nameof(FlowTrendSecondaryLabel));
|
||||||
|
OnPropertyChanged(nameof(FlowTrendTertiaryLabel));
|
||||||
|
OnPropertyChanged(nameof(HasFlowTrendSecondary));
|
||||||
|
OnPropertyChanged(nameof(HasFlowTrendTertiary));
|
||||||
|
OnPropertyChanged(nameof(ActiveFlowTrendPrimaryValues));
|
||||||
|
OnPropertyChanged(nameof(ActiveFlowTrendSecondaryValues));
|
||||||
|
OnPropertyChanged(nameof(ActiveFlowTrendTertiaryValues));
|
||||||
|
OnPropertyChanged(nameof(FlowTrendMax));
|
||||||
if (value is not null)
|
if (value is not null)
|
||||||
{
|
{
|
||||||
LoadSelectedItemDraft(value);
|
LoadSelectedItemDraft(value);
|
||||||
@@ -346,6 +435,43 @@ public partial class MainViewModel : ObservableObject
|
|||||||
ItemSearchText = string.Empty;
|
ItemSearchText = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void TogglePumpControl(PumpControlChannel? pump)
|
||||||
|
{
|
||||||
|
if (pump is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextState = !pump.IsRunning;
|
||||||
|
_telemetryService.SetPumpRunning(pump.Key, nextState);
|
||||||
|
pump.IsRunning = nextState;
|
||||||
|
LatestAction = $"{pump.Name} 已{(nextState ? "启动" : "停止")}。";
|
||||||
|
TraceEvents.Insert(0, NewTrace("泵控", $"{pump.Name} => {(nextState ? "启动" : "停止")}"));
|
||||||
|
RefreshTelemetry();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void ClearTrendData()
|
||||||
|
{
|
||||||
|
ClearTrendSeries(
|
||||||
|
ProximalPressureTrendValues,
|
||||||
|
DistalPressureTrendValues,
|
||||||
|
DeltaPressureTrendValues,
|
||||||
|
PressureDropPumpTrendValues,
|
||||||
|
RecirculationMainPumpTrendValues,
|
||||||
|
RecirculationReturnPumpTrendValues,
|
||||||
|
RecirculationDrainagePumpTrendValues,
|
||||||
|
KinkResistancePumpTrendValues,
|
||||||
|
HemolysisDrainageSingleTrendValues,
|
||||||
|
HemolysisReturnSingleTrendValues,
|
||||||
|
HemolysisDualLumenTrendValues);
|
||||||
|
|
||||||
|
LatestAction = "已清空实时趋势曲线。";
|
||||||
|
TraceEvents.Insert(0, NewTrace("趋势图", "已清空实时趋势曲线"));
|
||||||
|
RaiseTrendPropertyChanges();
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void CapturePressureDrop50() => CapturePressureDropSample("50%");
|
private void CapturePressureDrop50() => CapturePressureDropSample("50%");
|
||||||
|
|
||||||
@@ -636,6 +762,12 @@ public partial class MainViewModel : ObservableObject
|
|||||||
OnPropertyChanged(nameof(PumpFlowDisplay));
|
OnPropertyChanged(nameof(PumpFlowDisplay));
|
||||||
OnPropertyChanged(nameof(DrainageFlowDisplay));
|
OnPropertyChanged(nameof(DrainageFlowDisplay));
|
||||||
OnPropertyChanged(nameof(ReturnFlowDisplay));
|
OnPropertyChanged(nameof(ReturnFlowDisplay));
|
||||||
|
OnPropertyChanged(nameof(PressureDropPumpFlowDisplay));
|
||||||
|
OnPropertyChanged(nameof(RecirculationPumpFlowDisplay));
|
||||||
|
OnPropertyChanged(nameof(KinkResistancePumpFlowDisplay));
|
||||||
|
OnPropertyChanged(nameof(HemolysisDrainageSingleFlowDisplay));
|
||||||
|
OnPropertyChanged(nameof(HemolysisReturnSingleFlowDisplay));
|
||||||
|
OnPropertyChanged(nameof(HemolysisDualLumenFlowDisplay));
|
||||||
OnPropertyChanged(nameof(ProximalPressureDisplay));
|
OnPropertyChanged(nameof(ProximalPressureDisplay));
|
||||||
OnPropertyChanged(nameof(DistalPressureDisplay));
|
OnPropertyChanged(nameof(DistalPressureDisplay));
|
||||||
OnPropertyChanged(nameof(RealtimeRecirculationDisplay));
|
OnPropertyChanged(nameof(RealtimeRecirculationDisplay));
|
||||||
@@ -662,9 +794,11 @@ public partial class MainViewModel : ObservableObject
|
|||||||
OnPropertyChanged(nameof(ReturnFlowNormalizedValue));
|
OnPropertyChanged(nameof(ReturnFlowNormalizedValue));
|
||||||
OnPropertyChanged(nameof(FlowSensorChannels));
|
OnPropertyChanged(nameof(FlowSensorChannels));
|
||||||
OnPropertyChanged(nameof(OtherChannels));
|
OnPropertyChanged(nameof(OtherChannels));
|
||||||
|
OnPropertyChanged(nameof(PumpControls));
|
||||||
OnPropertyChanged(nameof(SelectedItemLiveDisplay));
|
OnPropertyChanged(nameof(SelectedItemLiveDisplay));
|
||||||
OnPropertyChanged(nameof(SelectedItemLiveHint));
|
OnPropertyChanged(nameof(SelectedItemLiveHint));
|
||||||
|
|
||||||
|
CaptureTrendSamples();
|
||||||
SyncRealtimeItems();
|
SyncRealtimeItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,6 +810,70 @@ public partial class MainViewModel : ObservableObject
|
|||||||
ComplianceRate = InspectionItems.Count == 0 ? 0 : QualifiedCount * 100d / InspectionItems.Count;
|
ComplianceRate = InspectionItems.Count == 0 ? 0 : QualifiedCount * 100d / InspectionItems.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CaptureTrendSamples()
|
||||||
|
{
|
||||||
|
AppendTrendValue(ProximalPressureTrendValues, ChannelValue("近端压力"));
|
||||||
|
AppendTrendValue(DistalPressureTrendValues, ChannelValue("远端压力"));
|
||||||
|
AppendTrendValue(DeltaPressureTrendValues, DeltaPressure);
|
||||||
|
AppendTrendValue(PressureDropPumpTrendValues, PressureDropPumpFlow);
|
||||||
|
AppendTrendValue(RecirculationMainPumpTrendValues, RecirculationPumpFlow);
|
||||||
|
AppendTrendValue(RecirculationReturnPumpTrendValues, ReturnFlow);
|
||||||
|
AppendTrendValue(RecirculationDrainagePumpTrendValues, DrainageFlow);
|
||||||
|
AppendTrendValue(KinkResistancePumpTrendValues, KinkResistancePumpFlow);
|
||||||
|
AppendTrendValue(HemolysisDrainageSingleTrendValues, HemolysisDrainageSingleFlow);
|
||||||
|
AppendTrendValue(HemolysisReturnSingleTrendValues, HemolysisReturnSingleFlow);
|
||||||
|
AppendTrendValue(HemolysisDualLumenTrendValues, HemolysisDualLumenFlow);
|
||||||
|
|
||||||
|
RaiseTrendPropertyChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendTrendValue(ObservableCollection<double> series, double value)
|
||||||
|
{
|
||||||
|
series.Add(value);
|
||||||
|
while (series.Count > TrendHistoryCapacity)
|
||||||
|
{
|
||||||
|
series.RemoveAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double MaxTrendValue(IEnumerable<IEnumerable<double>> seriesGroup, double fallback)
|
||||||
|
{
|
||||||
|
var max = seriesGroup
|
||||||
|
.SelectMany(series => series.DefaultIfEmpty(0d))
|
||||||
|
.DefaultIfEmpty(fallback)
|
||||||
|
.Max();
|
||||||
|
|
||||||
|
return Math.Max(fallback, max) * 1.1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearTrendSeries(params ObservableCollection<double>[] seriesGroup)
|
||||||
|
{
|
||||||
|
foreach (var series in seriesGroup)
|
||||||
|
{
|
||||||
|
series.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseTrendPropertyChanges()
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(ProximalPressureTrendValues));
|
||||||
|
OnPropertyChanged(nameof(DistalPressureTrendValues));
|
||||||
|
OnPropertyChanged(nameof(DeltaPressureTrendValues));
|
||||||
|
OnPropertyChanged(nameof(PressureDropPumpTrendValues));
|
||||||
|
OnPropertyChanged(nameof(RecirculationMainPumpTrendValues));
|
||||||
|
OnPropertyChanged(nameof(RecirculationReturnPumpTrendValues));
|
||||||
|
OnPropertyChanged(nameof(RecirculationDrainagePumpTrendValues));
|
||||||
|
OnPropertyChanged(nameof(KinkResistancePumpTrendValues));
|
||||||
|
OnPropertyChanged(nameof(HemolysisDrainageSingleTrendValues));
|
||||||
|
OnPropertyChanged(nameof(HemolysisReturnSingleTrendValues));
|
||||||
|
OnPropertyChanged(nameof(HemolysisDualLumenTrendValues));
|
||||||
|
OnPropertyChanged(nameof(ActiveFlowTrendPrimaryValues));
|
||||||
|
OnPropertyChanged(nameof(ActiveFlowTrendSecondaryValues));
|
||||||
|
OnPropertyChanged(nameof(ActiveFlowTrendTertiaryValues));
|
||||||
|
OnPropertyChanged(nameof(PressureTrendMax));
|
||||||
|
OnPropertyChanged(nameof(FlowTrendMax));
|
||||||
|
}
|
||||||
|
|
||||||
private void RefreshFilteredItemsView()
|
private void RefreshFilteredItemsView()
|
||||||
{
|
{
|
||||||
FilteredItemsView.Refresh();
|
FilteredItemsView.Refresh();
|
||||||
@@ -811,6 +1009,17 @@ public partial class MainViewModel : ObservableObject
|
|||||||
|
|
||||||
private double ChannelValue(string name) => Channels.First(channel => channel.Name == name).Value;
|
private double ChannelValue(string name) => Channels.First(channel => channel.Name == name).Value;
|
||||||
private double ChannelNormalizedValue(string name) => Channels.First(channel => channel.Name == name).NormalizedValue;
|
private double ChannelNormalizedValue(string name) => Channels.First(channel => channel.Name == name).NormalizedValue;
|
||||||
|
private IEnumerable<PumpControlChannel> PumpControlsFor(params string[] pumpKeys)
|
||||||
|
{
|
||||||
|
var orderLookup = pumpKeys
|
||||||
|
.Select((key, index) => new { key, index })
|
||||||
|
.ToDictionary(item => item.key, item => item.index);
|
||||||
|
|
||||||
|
return PumpControls
|
||||||
|
.Where(pump => orderLookup.ContainsKey(pump.Key))
|
||||||
|
.OrderBy(pump => orderLookup[pump.Key]);
|
||||||
|
}
|
||||||
|
|
||||||
private List<InspectionItem> ActiveItemScope() => FilteredItemsView.Cast<InspectionItem>().ToList();
|
private List<InspectionItem> ActiveItemScope() => FilteredItemsView.Cast<InspectionItem>().ToList();
|
||||||
|
|
||||||
private bool MatchesFilteredItem(object item) =>
|
private bool MatchesFilteredItem(object item) =>
|
||||||
@@ -865,8 +1074,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
return keywordIndex == keyword.Length;
|
return keywordIndex == keyword.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsFlowSensorChannel(DeviceChannel channel) =>
|
private static bool IsFlowSensorChannel(DeviceChannel channel) => channel.Unit == "L/min";
|
||||||
channel.Name is "主泵流量" or "静脉引流流量" or "动脉回输流量";
|
|
||||||
|
|
||||||
private string BuildSelectedItemLiveDisplay()
|
private string BuildSelectedItemLiveDisplay()
|
||||||
{
|
{
|
||||||
@@ -893,7 +1101,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
$"条件:血液/模拟血液 2.0~3.5 mPa·s / 当前温度 {TemperatureDisplay} / 40±1 °C\n" +
|
$"条件:血液/模拟血液 2.0~3.5 mPa·s / 当前温度 {TemperatureDisplay} / 40±1 °C\n" +
|
||||||
$"流量点:{KinkResistanceFlowPointDisplay}\n" +
|
$"流量点:{KinkResistanceFlowPointDisplay}\n" +
|
||||||
$"{KinkResistanceMandrelDiameterDisplay}\n" +
|
$"{KinkResistanceMandrelDiameterDisplay}\n" +
|
||||||
$"当前主泵:{PumpFlow:F2} L/min(寄存器 D1040 平滑值)\n" +
|
$"当前主泵:{KinkResistancePumpFlow:F2} L/min\n" +
|
||||||
$"{BuildKinkResistanceSamplingSummary()}";
|
$"{BuildKinkResistanceSamplingSummary()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -948,7 +1156,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
var entry = KinkResistanceEntries.First(item => item.Label == label);
|
var entry = KinkResistanceEntries.First(item => item.Label == label);
|
||||||
entry.BaselineFlow = PumpFlow;
|
entry.BaselineFlow = KinkResistancePumpFlow;
|
||||||
entry.Temperature = ChannelValue("模拟血液温度");
|
entry.Temperature = ChannelValue("模拟血液温度");
|
||||||
entry.BaselineCapturedAt = DateTime.Now;
|
entry.BaselineCapturedAt = DateTime.Now;
|
||||||
|
|
||||||
@@ -976,7 +1184,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.KinkedFlow = PumpFlow;
|
entry.KinkedFlow = KinkResistancePumpFlow;
|
||||||
entry.Temperature = ChannelValue("模拟血液温度");
|
entry.Temperature = ChannelValue("模拟血液温度");
|
||||||
entry.KinkedCapturedAt = DateTime.Now;
|
entry.KinkedCapturedAt = DateTime.Now;
|
||||||
|
|
||||||
@@ -1337,7 +1545,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
$"条件:试验液体为水 + 示踪粒子 / 当前温度 {TemperatureDisplay}\n" +
|
$"条件:试验液体为水 + 示踪粒子 / 当前温度 {TemperatureDisplay}\n" +
|
||||||
$"目标流量点:{RecirculationFlowPointDisplay}\n" +
|
$"目标流量点:{RecirculationFlowPointDisplay}\n" +
|
||||||
$"{RecirculationLimitDisplay}\n" +
|
$"{RecirculationLimitDisplay}\n" +
|
||||||
$"当前:主泵 {PumpFlow:F2} L/min / 引流 {DrainageFlow:F2} L/min / 回输 {ReturnFlow:F2} L/min / 在线参考 {RecirculationRate:F1}%\n" +
|
$"当前:主泵 {RecirculationPumpFlow:F2} L/min / 引流 {DrainageFlow:F2} L/min / 回输 {ReturnFlow:F2} L/min / 在线参考 {RecirculationRate:F1}%\n" +
|
||||||
$"{BuildRecirculationSamplingSummary()}";
|
$"{BuildRecirculationSamplingSummary()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1414,7 +1622,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
var entry = RecirculationEntries.First(item => item.Label == label);
|
var entry = RecirculationEntries.First(item => item.Label == label);
|
||||||
entry.ActualPumpFlow = PumpFlow;
|
entry.ActualPumpFlow = RecirculationPumpFlow;
|
||||||
entry.DrainageFlow = DrainageFlow;
|
entry.DrainageFlow = DrainageFlow;
|
||||||
entry.ReturnFlow = ReturnFlow;
|
entry.ReturnFlow = ReturnFlow;
|
||||||
entry.OnlineEstimate = RecirculationRate;
|
entry.OnlineEstimate = RecirculationRate;
|
||||||
@@ -1428,7 +1636,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
_lastAutoRecirculationResult = resultText;
|
_lastAutoRecirculationResult = resultText;
|
||||||
_lastAutoRecirculationNote = noteText;
|
_lastAutoRecirculationNote = noteText;
|
||||||
LatestAction = $"已采集再循环 {entry.Label} 流量点快照,当前在线参考 {RecirculationRate:F1}%。";
|
LatestAction = $"已采集再循环 {entry.Label} 流量点快照,当前在线参考 {RecirculationRate:F1}%。";
|
||||||
TraceEvents.Insert(0, NewTrace("再循环采样", $"{entry.Label} / 主泵 {PumpFlow:F2} L/min / 在线参考 {RecirculationRate:F1}%"));
|
TraceEvents.Insert(0, NewTrace("再循环采样", $"{entry.Label} / 主泵 {RecirculationPumpFlow:F2} L/min / 在线参考 {RecirculationRate:F1}%"));
|
||||||
|
|
||||||
OnPropertyChanged(nameof(RecirculationSamplingSummary));
|
OnPropertyChanged(nameof(RecirculationSamplingSummary));
|
||||||
OnPropertyChanged(nameof(SelectedItemLiveDisplay));
|
OnPropertyChanged(nameof(SelectedItemLiveDisplay));
|
||||||
|
|||||||
Reference in New Issue
Block a user