This commit is contained in:
GukSang.Jin
2026-04-14 12:01:15 +08:00
parent e3d6c69f88
commit 99bcf8d334
5 changed files with 1091 additions and 101 deletions

View File

@@ -118,7 +118,7 @@
<TabItem Header="实时数据">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="0,6,0,0">
<Border Style="{StaticResource CardBorderStyle}" Padding="14">
<Border Style="{StaticResource CardBorderStyle}" Padding="14" Visibility="Collapsed">
<Border.Resources>
<DataTemplate x:Key="Rs485QuickPumpCardTemplate" DataType="{x:Type models:PumpControlChannel}">
<Border Width="248"
@@ -129,14 +129,12 @@
BorderBrush="#FFD8E4E8"
BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="84" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<DockPanel LastChildFill="False">
<StackPanel DockPanel.Dock="Left">
<CheckBox Content="选择"
IsChecked="{Binding IsBatchSelected, UpdateSourceTrigger=PropertyChanged}" />
@@ -156,24 +154,11 @@
Text="{Binding Rs485SlaveAddress, StringFormat=从站 {0}}" />
</Border>
</DockPanel>
<Grid Grid.Row="1" Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
<TextBox Margin="0,10,0,0"
Height="34"
VerticalContentAlignment="Center"
Text="{Binding PendingSetpointText, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="1"
MinWidth="76"
Margin="8,0,0,0"
Padding="8,2"
Command="{Binding DataContext.WritePumpSetpointCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="写入" />
</Grid>
<UniformGrid Grid.Row="2" Margin="0,10,0,0" Columns="3">
<UniformGrid Margin="0,10,0,0" Columns="3">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="目标" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding PendingSetpointText, StringFormat={}{0} L/min}" />
@@ -187,8 +172,7 @@
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding FlowDisplay}" />
</StackPanel>
</UniformGrid>
<Border Grid.Row="3"
Margin="0,10,0,0"
<Border Margin="0,10,0,0"
Padding="10,6"
Background="{Binding SetpointStatusBackground}"
CornerRadius="8">
@@ -204,8 +188,17 @@
TextWrapping="Wrap" />
</StackPanel>
</Border>
<StackPanel Grid.Row="4" Margin="0,10,0,0">
</StackPanel>
<StackPanel Grid.Column="1"
Margin="10,0,0,0"
VerticalAlignment="Stretch">
<Button Height="34"
Padding="12,4"
Command="{Binding DataContext.WritePumpSetpointCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="写入" />
<Button Height="34"
Margin="0,8,0,0"
Padding="12,4"
Command="{Binding DataContext.StartSingleRs485PumpCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
@@ -353,14 +346,47 @@
</Border>
</DataTemplate>
</Border.Resources>
<StackPanel Visibility="Collapsed">
<DockPanel LastChildFill="False">
<TextBlock DockPanel.Dock="Left"
FontSize="18"
FontWeight="Bold"
Text="执行机构控制" />
<Button DockPanel.Dock="Right"
MinWidth="132"
Height="34"
Padding="10,4"
Command="{Binding ToggleRealtimeActuatorControlsCommand}"
Content="{Binding RealtimeActuatorControlsToggleText}"
Background="#FF6B8791" />
</DockPanel>
<StackPanel>
<TextBlock FontSize="18" FontWeight="Bold" Text="执行机构控制" />
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding RealtimeActuatorControlsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Border Margin="0,10,0,12"
Padding="12"
Background="#FFF3F7F8"
CornerRadius="10"
BorderBrush="#FFD5E2E6"
BorderThickness="1">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasRealtimeRs485FlowPumpControls}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel>
<DockPanel LastChildFill="False">
<TextBlock DockPanel.Dock="Left"
@@ -432,7 +458,7 @@
Style="{StaticResource CaptionStyle}"
Text="{Binding SelectedRs485PumpCount, StringFormat=当前已选中 {0} 台泵}" />
<ItemsControl Margin="0,4,0,0"
ItemsSource="{Binding ActiveRs485FlowPumpControls}"
ItemsSource="{Binding RealtimeRs485FlowPumpControls}"
ItemTemplate="{StaticResource Rs485QuickPumpCardTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
@@ -443,15 +469,7 @@
</StackPanel>
</Border>
<WrapPanel Margin="0,10,0,0">
<ItemsControl ItemsSource="{Binding NegativeAssistPumpControls}"
ItemTemplate="{StaticResource PumpControlCardTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl ItemsSource="{Binding ValveControls}"
<ItemsControl ItemsSource="{Binding RealtimeValveControls}"
ItemTemplate="{StaticResource ValveControlCardTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
@@ -461,6 +479,7 @@
</ItemsControl>
</WrapPanel>
</StackPanel>
</StackPanel>
</Border>
<Border Style="{StaticResource CardBorderStyle}" Padding="14">
@@ -1268,6 +1287,27 @@
<Setter Property="FontSize" Value="14" />
</Style>
</WrapPanel.Resources>
<Button Command="{Binding ToggleValveControlCommand}"
CommandParameter="{Binding CirculatingWaterControl}"
Content="{Binding CirculatingWaterControl.CirculatingWaterToggleText}"
Background="#FFD64545"
IsEnabled="{Binding CanModifySession}">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="MinWidth" Value="128" />
<Setter Property="MinHeight" Value="34" />
<Setter Property="Padding" Value="10,6" />
<Setter Property="Margin" Value="0,0,6,6" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasCirculatingWaterControl}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Click="ReturnToProjectListButton_OnClick"
Content="返回项目检查"
Background="#FF6B8791" />
@@ -1302,6 +1342,724 @@
TextWrapping="Wrap" />
</StackPanel>
</Border>
<Border>
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource CardBorderStyle}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasSelectedItem}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsKinkResistanceSelected}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding HasKinkResistanceRs485FlowPumpControls}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Border.Resources>
<DataTemplate x:Key="KinkResistanceRs485PumpCardTemplate" DataType="{x:Type models:PumpControlChannel}">
<Border MinWidth="420"
Margin="0,0,10,10"
Padding="12"
Background="#FFF8FBFC"
CornerRadius="12"
BorderBrush="#FFD8E4E8"
BorderThickness="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="96" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock FontSize="14"
FontWeight="Bold"
Text="{Binding Name}"
TextWrapping="Wrap" />
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Height="34"
VerticalContentAlignment="Center"
Text="{Binding PendingSetpointText, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="1"
MinWidth="76"
Margin="8,0,0,0"
Padding="8,2"
Command="{Binding DataContext.WritePumpSetpointCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="写入" />
</Grid>
<UniformGrid Margin="0,10,0,0" Columns="3">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="目标" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding PendingSetpointText, StringFormat={}{0} L/min}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="读回" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding SetpointReadbackDisplay}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="实时" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding FlowDisplay}" />
</StackPanel>
</UniformGrid>
<Border Margin="0,10,0,0"
Padding="10,6"
Background="{Binding SetpointStatusBackground}"
CornerRadius="8">
<TextBlock FontSize="12"
FontWeight="Bold"
Foreground="{Binding SetpointStatusForeground}"
Text="{Binding Rs485RunStateText}" />
</Border>
</StackPanel>
<StackPanel Grid.Column="1"
Margin="12,0,0,0"
VerticalAlignment="Stretch">
<Button Height="34"
MinWidth="84"
Padding="12,4"
Margin="0,44,0,0"
Command="{Binding DataContext.StartSingleRs485PumpCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="启动"
IsEnabled="{Binding CanStartRs485Action}"
Background="#FF2B8F6A"
ToolTipService.ShowOnDisabled="True"
ToolTip="{Binding StartActionHint}" />
<Button Height="34"
MinWidth="84"
Margin="0,8,0,0"
Padding="12,4"
Command="{Binding DataContext.StopSingleRs485PumpCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="停止"
IsEnabled="{Binding CanStopRs485Action}"
Background="#FFB85C38"
ToolTipService.ShowOnDisabled="True"
ToolTip="{Binding StopActionHint}" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</Border.Resources>
<StackPanel>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="12" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="0"
ItemsSource="{Binding KinkResistanceRs485FlowPumpControls}"
ItemTemplate="{StaticResource KinkResistanceRs485PumpCardTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</StackPanel>
</Border>
<Border>
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource CardBorderStyle}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasSelectedItem}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsPressureDropSelected}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding HasPressureDropRs485FlowPumpControls}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Border.Resources>
<DataTemplate x:Key="PressureDropRs485QuickPumpCardTemplate" DataType="{x:Type models:PumpControlChannel}">
<Border MinWidth="420"
Margin="0,0,10,10"
Padding="12"
Background="#FFF8FBFC"
CornerRadius="12"
BorderBrush="#FFD8E4E8"
BorderThickness="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="96" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock FontSize="14"
FontWeight="Bold"
Text="{Binding Name}"
TextWrapping="Wrap" />
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Height="34"
VerticalContentAlignment="Center"
Text="{Binding PendingSetpointText, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="1"
MinWidth="76"
Margin="8,0,0,0"
Padding="8,2"
Command="{Binding DataContext.WritePumpSetpointCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="写入" />
</Grid>
<UniformGrid Margin="0,10,0,0" Columns="3">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="目标" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding PendingSetpointText, StringFormat={}{0} L/min}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="读回" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding SetpointReadbackDisplay}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="实时" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding FlowDisplay}" />
</StackPanel>
</UniformGrid>
<Border Margin="0,10,0,0"
Padding="10,6"
Background="{Binding SetpointStatusBackground}"
CornerRadius="8">
<TextBlock FontSize="12"
FontWeight="Bold"
Foreground="{Binding SetpointStatusForeground}"
Text="{Binding Rs485RunStateText}" />
</Border>
</StackPanel>
<StackPanel Grid.Column="1"
Margin="12,0,0,0"
VerticalAlignment="Stretch">
<Button Height="34"
MinWidth="84"
Padding="12,4"
Margin="0,44,0,0"
Command="{Binding DataContext.StartSingleRs485PumpCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="启动"
IsEnabled="{Binding CanStartRs485Action}"
Background="#FF2B8F6A"
ToolTipService.ShowOnDisabled="True"
ToolTip="{Binding StartActionHint}" />
<Button Height="34"
MinWidth="84"
Margin="0,8,0,0"
Padding="12,4"
Command="{Binding DataContext.StopSingleRs485PumpCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="停止"
IsEnabled="{Binding CanStopRs485Action}"
Background="#FFB85C38"
ToolTipService.ShowOnDisabled="True"
ToolTip="{Binding StopActionHint}" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
<DataTemplate x:Key="PressureDropValveControlCardTemplate" DataType="{x:Type models:ValveControlChannel}">
<Button MinWidth="132"
Height="34"
Margin="0,0,10,10"
Padding="12,4"
Command="{Binding DataContext.ToggleValveControlCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="{Binding ToggleButtonText}"
Background="#FF4D8C72"
IsEnabled="{Binding DataContext.CanModifySession, RelativeSource={RelativeSource AncestorType=Window}}"
ToolTip="{Binding StateText}" />
</DataTemplate>
</Border.Resources>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="12" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="0"
ItemsSource="{Binding PressureDropRs485FlowPumpControls}"
ItemTemplate="{StaticResource PressureDropRs485QuickPumpCardTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ItemsSource="{Binding PressureDropSecondaryValveControls}"
ItemTemplate="{StaticResource PressureDropValveControlCardTemplate}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasPressureDropSecondaryValveControls}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Border>
<Border>
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource CardBorderStyle}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasSelectedItem}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsRecirculationSelected}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding HasRecirculationRs485FlowPumpControls}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Border.Resources>
<DataTemplate x:Key="RecirculationRs485PumpCardTemplate" DataType="{x:Type models:PumpControlChannel}">
<Border MinWidth="360"
Margin="0,0,10,10"
Padding="12"
Background="#FFF8FBFC"
CornerRadius="12"
BorderBrush="#FFD8E4E8"
BorderThickness="1">
<StackPanel>
<TextBlock FontSize="14"
FontWeight="Bold"
Text="{Binding Name}"
TextWrapping="Wrap" />
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Height="34"
VerticalContentAlignment="Center"
Text="{Binding PendingSetpointText, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="1"
MinWidth="76"
Margin="8,0,0,0"
Padding="8,2"
Command="{Binding DataContext.WritePumpSetpointCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="写入" />
</Grid>
<UniformGrid Margin="0,10,0,0" Columns="3">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="目标" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding PendingSetpointText, StringFormat={}{0} L/min}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="读回" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding SetpointReadbackDisplay}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="实时" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding FlowDisplay}" />
</StackPanel>
</UniformGrid>
<Border Margin="0,10,0,0"
Padding="10,6"
Background="{Binding SetpointStatusBackground}"
CornerRadius="8">
<TextBlock FontSize="12"
FontWeight="Bold"
Foreground="{Binding SetpointStatusForeground}"
Text="{Binding Rs485RunStateText}" />
</Border>
</StackPanel>
</Border>
</DataTemplate>
</Border.Resources>
<StackPanel>
<DockPanel LastChildFill="False">
<StackPanel DockPanel.Dock="Left">
</StackPanel>
<WrapPanel DockPanel.Dock="Right" Margin="12,0,0,0">
<Button MinWidth="120"
Height="34"
Padding="12,4"
Margin="0,0,8,8"
Command="{Binding StartRecirculationRs485PumpsCommand}"
Content="统一启动三泵"
Background="#FF2B8F6A" />
<Button MinWidth="120"
Height="34"
Padding="12,4"
Margin="0,0,0,8"
Command="{Binding StopRecirculationRs485PumpsCommand}"
Content="统一停止三泵"
Background="#FFB85C38" />
</WrapPanel>
</DockPanel>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="12" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="0"
ItemsSource="{Binding RecirculationRs485FlowPumpControls}"
ItemTemplate="{StaticResource RecirculationRs485PumpCardTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</StackPanel>
</Border>
<Border>
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource CardBorderStyle}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasSelectedItem}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsAntiCollapseSelected}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding HasPressureDropRs485FlowPumpControls}" Value="False" />
<Condition Binding="{Binding HasAntiCollapsePumpControls}" Value="False" />
<Condition Binding="{Binding HasAntiCollapseSecondaryValveControls}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Collapsed" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Border.Resources>
<DataTemplate x:Key="AntiCollapseRs485QuickPumpCardTemplate" DataType="{x:Type models:PumpControlChannel}">
<Border MinWidth="420"
Margin="0,0,10,10"
Padding="12"
Background="#FFF8FBFC"
CornerRadius="12"
BorderBrush="#FFD8E4E8"
BorderThickness="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="96" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock FontSize="14"
FontWeight="Bold"
Text="{Binding Name}"
TextWrapping="Wrap" />
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Height="34"
VerticalContentAlignment="Center"
Text="{Binding PendingSetpointText, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="1"
MinWidth="76"
Margin="8,0,0,0"
Padding="8,2"
Command="{Binding DataContext.WritePumpSetpointCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="写入" />
</Grid>
<UniformGrid Margin="0,10,0,0" Columns="3">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="目标" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding PendingSetpointText, StringFormat={}{0} L/min}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="读回" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding SetpointReadbackDisplay}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="实时" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding FlowDisplay}" />
</StackPanel>
</UniformGrid>
<Border Margin="0,10,0,0"
Padding="10,6"
Background="{Binding SetpointStatusBackground}"
CornerRadius="8">
<TextBlock FontSize="12"
FontWeight="Bold"
Foreground="{Binding SetpointStatusForeground}"
Text="{Binding Rs485RunStateText}" />
</Border>
</StackPanel>
<StackPanel Grid.Column="1"
Margin="12,0,0,0"
VerticalAlignment="Stretch">
<Button Height="34"
MinWidth="84"
Padding="12,4"
Margin="0,44,0,0"
Command="{Binding DataContext.StartSingleRs485PumpCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="启动"
IsEnabled="{Binding CanStartRs485Action}"
Background="#FF2B8F6A"
ToolTipService.ShowOnDisabled="True"
ToolTip="{Binding StartActionHint}" />
<Button Height="34"
MinWidth="84"
Margin="0,8,0,0"
Padding="12,4"
Command="{Binding DataContext.StopSingleRs485PumpCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="停止"
IsEnabled="{Binding CanStopRs485Action}"
Background="#FFB85C38"
ToolTipService.ShowOnDisabled="True"
ToolTip="{Binding StopActionHint}" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
<DataTemplate x:Key="AntiCollapsePumpControlCardTemplate" DataType="{x:Type models:PumpControlChannel}">
<Button MinWidth="132"
Height="34"
Margin="0,0,10,10"
Padding="12,4"
Command="{Binding DataContext.TogglePumpControlCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="{Binding ToggleButtonText}"
Background="#FF4D8C72"
IsEnabled="{Binding DataContext.CanModifySession, RelativeSource={RelativeSource AncestorType=Window}}"
ToolTip="{Binding StateText}" />
</DataTemplate>
<DataTemplate x:Key="AntiCollapseValveControlCardTemplate" DataType="{x:Type models:ValveControlChannel}">
<Button MinWidth="132"
Height="34"
Margin="0,0,10,10"
Padding="12,4"
Command="{Binding DataContext.ToggleValveControlCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="{Binding ToggleButtonText}"
Background="#FF4D8C72"
IsEnabled="{Binding DataContext.CanModifySession, RelativeSource={RelativeSource AncestorType=Window}}"
ToolTip="{Binding StateText}" />
</DataTemplate>
</Border.Resources>
<StackPanel>
<StackPanel Margin="0,8,0,0" Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding PressureDropRs485FlowPumpControls}"
ItemTemplate="{StaticResource AntiCollapseRs485QuickPumpCardTemplate}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasPressureDropRs485FlowPumpControls}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl ItemsSource="{Binding NegativeAssistPumpControls}"
ItemTemplate="{StaticResource AntiCollapsePumpControlCardTemplate}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasAntiCollapsePumpControls}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl ItemsSource="{Binding AntiCollapseSecondaryValveControls}"
ItemTemplate="{StaticResource AntiCollapseValveControlCardTemplate}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasAntiCollapseSecondaryValveControls}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</StackPanel>
</Border>
<Border>
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource CardBorderStyle}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasSelectedItem}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsHemolysisPrimaryInputSelected}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding HasHemolysisRs485FlowPumpControls}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Border.Resources>
<DataTemplate x:Key="HemolysisRs485PumpCardTemplate" DataType="{x:Type models:PumpControlChannel}">
<Border MinWidth="420"
Margin="0,0,10,10"
Padding="12"
Background="#FFF8FBFC"
CornerRadius="12"
BorderBrush="#FFD8E4E8"
BorderThickness="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="96" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock FontSize="14"
FontWeight="Bold"
Text="{Binding Name}"
TextWrapping="Wrap" />
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Height="34"
VerticalContentAlignment="Center"
Text="{Binding PendingSetpointText, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="1"
MinWidth="76"
Margin="8,0,0,0"
Padding="8,2"
Command="{Binding DataContext.WritePumpSetpointCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="写入" />
</Grid>
<UniformGrid Margin="0,10,0,0" Columns="3">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="目标" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding PendingSetpointText, StringFormat={}{0} L/min}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="读回" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding SetpointReadbackDisplay}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="实时" />
<TextBlock FontSize="13" FontWeight="Bold" Text="{Binding FlowDisplay}" />
</StackPanel>
</UniformGrid>
<Border Margin="0,10,0,0"
Padding="10,6"
Background="{Binding SetpointStatusBackground}"
CornerRadius="8">
<TextBlock FontSize="12"
FontWeight="Bold"
Foreground="{Binding SetpointStatusForeground}"
Text="{Binding Rs485RunStateText}" />
</Border>
</StackPanel>
<StackPanel Grid.Column="1"
Margin="12,0,0,0"
VerticalAlignment="Stretch">
<Button Height="34"
MinWidth="84"
Padding="12,4"
Margin="0,44,0,0"
Command="{Binding DataContext.StartSingleRs485PumpCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="启动"
IsEnabled="{Binding CanStartRs485Action}"
Background="#FF2B8F6A"
ToolTipService.ShowOnDisabled="True"
ToolTip="{Binding StartActionHint}" />
<Button Height="34"
MinWidth="84"
Margin="0,8,0,0"
Padding="12,4"
Command="{Binding DataContext.StopSingleRs485PumpCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Content="停止"
IsEnabled="{Binding CanStopRs485Action}"
Background="#FFB85C38"
ToolTipService.ShowOnDisabled="True"
ToolTip="{Binding StopActionHint}" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</Border.Resources>
<StackPanel>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="12" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="0"
ItemsSource="{Binding HemolysisRs485FlowPumpControls}"
ItemTemplate="{StaticResource HemolysisRs485PumpCardTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</StackPanel>
</Border>
<Border>
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource CardBorderStyle}">

View File

@@ -198,6 +198,11 @@ public partial class PumpControlChannel : ObservableObject
: PendingRs485RunningState == true || IsRunning
? "停止"
: "启动";
public string ToggleButtonText => IsRs485Busy
? $"{Name}处理中"
: PendingRs485RunningState == true || IsRunning
? $"停止{Name}"
: $"启动{Name}";
public bool CanStartRs485Action => !IsRs485Busy
&& PendingRs485RunningState != true
&& !IsRunning
@@ -228,6 +233,7 @@ public partial class PumpControlChannel : ObservableObject
OnPropertyChanged(nameof(StateHint));
OnPropertyChanged(nameof(IndicatorColor));
OnPropertyChanged(nameof(ActionText));
OnPropertyChanged(nameof(ToggleButtonText));
OnPropertyChanged(nameof(CanStartRs485Action));
OnPropertyChanged(nameof(CanStopRs485Action));
OnPropertyChanged(nameof(StartActionHint));
@@ -252,6 +258,7 @@ public partial class PumpControlChannel : ObservableObject
OnPropertyChanged(nameof(StateText));
OnPropertyChanged(nameof(StateHint));
OnPropertyChanged(nameof(IndicatorColor));
OnPropertyChanged(nameof(ToggleButtonText));
OnPropertyChanged(nameof(CardPrimaryDisplay));
}
@@ -342,6 +349,7 @@ public partial class PumpControlChannel : ObservableObject
OnPropertyChanged(nameof(StateText));
OnPropertyChanged(nameof(StateHint));
OnPropertyChanged(nameof(ActionText));
OnPropertyChanged(nameof(ToggleButtonText));
OnPropertyChanged(nameof(CanStartRs485Action));
OnPropertyChanged(nameof(CanStopRs485Action));
OnPropertyChanged(nameof(StartActionHint));
@@ -354,6 +362,7 @@ public partial class PumpControlChannel : ObservableObject
{
OnPropertyChanged(nameof(StateHint));
OnPropertyChanged(nameof(ActionText));
OnPropertyChanged(nameof(ToggleButtonText));
OnPropertyChanged(nameof(CanStartRs485Action));
OnPropertyChanged(nameof(CanStopRs485Action));
OnPropertyChanged(nameof(StartActionHint));

View File

@@ -16,6 +16,12 @@ public partial class ValveControlChannel : ObservableObject
public string StateText => !StateAvailable ? "未知" : IsOpen ? "开启" : "关闭";
public string ActionText => IsOpen ? "关闭" : "开启";
public string ToggleButtonText => Key == "CirculatingWaterTemperature"
? (IsOpen ? "停止循环水温" : "启动循环水温")
: IsOpen
? $"停止{Name}"
: $"启动{Name}";
public string CirculatingWaterToggleText => IsOpen ? "停止循环水温" : "启动循环水温";
public string IndicatorColor => !StateAvailable ? "#FF94A6AE" : IsOpen ? "#FF32B06A" : "#FFC8D4DA";
public string StateHint => !StateAvailable ? "未取得 PLC 状态" : $"{Name}已{StateText}";
public bool HideRealtimeCardStateDescription => Key is "TestLoopValve1" or "TestLoopValve2" or "CirculatingWaterTemperature";
@@ -24,6 +30,8 @@ public partial class ValveControlChannel : ObservableObject
{
OnPropertyChanged(nameof(StateText));
OnPropertyChanged(nameof(ActionText));
OnPropertyChanged(nameof(ToggleButtonText));
OnPropertyChanged(nameof(CirculatingWaterToggleText));
OnPropertyChanged(nameof(IndicatorColor));
OnPropertyChanged(nameof(StateHint));
}
@@ -31,6 +39,8 @@ public partial class ValveControlChannel : ObservableObject
partial void OnStateAvailableChanged(bool value)
{
OnPropertyChanged(nameof(StateText));
OnPropertyChanged(nameof(ToggleButtonText));
OnPropertyChanged(nameof(CirculatingWaterToggleText));
OnPropertyChanged(nameof(IndicatorColor));
OnPropertyChanged(nameof(StateHint));
}

View File

@@ -41,6 +41,20 @@ public partial class MainViewModel
private const double DefaultRs485RawPerLitrePerMinute = 100d;
private const double DefaultRs485RawOffset = 0d;
private const int MaxRs485MotorCommand = short.MaxValue;
private const string PressureDropRs485PumpKey = "PressureDropPump";
private const string KinkResistanceRs485PumpKey = "KinkResistancePump";
private static readonly string[] HemolysisRs485PumpKeys =
[
"HemolysisDrainageSinglePump",
"HemolysisReturnSinglePump",
"HemolysisDualLumenPump"
];
private static readonly string[] RecirculationRs485PumpKeys =
[
"RecirculationMainPump",
"RecirculationReturnPump",
"RecirculationDrainagePump"
];
private static readonly TimeSpan Rs485RuntimeRefreshInterval = TimeSpan.FromSeconds(4);
private DateTime _lastRs485RuntimeRefreshUtc = DateTime.MinValue;
private string _lastRs485RuntimeRefreshFailureMessage = string.Empty;
@@ -78,14 +92,28 @@ public partial class MainViewModel
[ObservableProperty]
private bool rs485AdvancedSettingsVisible;
[ObservableProperty]
private bool realtimeActuatorControlsVisible = true;
[ObservableProperty]
private string rs485StatusText = "RS485 待确认";
public ObservableCollection<string> Rs485ParityOptions { get; } = new(["Even", "Odd", "None"]);
public ObservableCollection<PumpControlChannel> Rs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> ActiveRs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> RealtimeRs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> PressureDropRs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> KinkResistanceRs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> HemolysisRs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> RecirculationRs485FlowPumpControls { get; } = [];
public bool HasRealtimeRs485FlowPumpControls => RealtimeRs485FlowPumpControls.Count > 0;
public bool HasPressureDropRs485FlowPumpControls => PressureDropRs485FlowPumpControls.Count > 0;
public bool HasKinkResistanceRs485FlowPumpControls => KinkResistanceRs485FlowPumpControls.Count > 0;
public bool HasHemolysisRs485FlowPumpControls => HemolysisRs485FlowPumpControls.Count > 0;
public bool HasRecirculationRs485FlowPumpControls => RecirculationRs485FlowPumpControls.Count > 0;
public int SelectedRs485PumpCount => ActiveRs485FlowPumpControls.Count(item => item.IsBatchSelected);
public string Rs485AdvancedSettingsToggleText => Rs485AdvancedSettingsVisible ? "收起高级维护" : "展开高级维护";
public string RealtimeActuatorControlsToggleText => RealtimeActuatorControlsVisible ? "隐藏执行机构控制" : "显示执行机构控制";
public string Rs485ConnectionSummary =>
$"{Rs485PortName} / {Rs485BaudRate}bps / {Rs485Parity} / {Rs485DataBits}-{Rs485StopBits}";
public int Rs485EnabledPumpCount => Rs485FlowPumpControls.Count(item => item.Rs485Enabled);
@@ -121,6 +149,13 @@ public partial class MainViewModel
partial void OnRs485PersistPresetAfterWriteChanged(bool value) => UpdateAndPersistRs485Settings();
partial void OnRs485AutoStartPumpAfterWriteChanged(bool value) => UpdateAndPersistRs485Settings();
partial void OnRs485AdvancedSettingsVisibleChanged(bool value) => OnPropertyChanged(nameof(Rs485AdvancedSettingsToggleText));
partial void OnRealtimeActuatorControlsVisibleChanged(bool value) => OnPropertyChanged(nameof(RealtimeActuatorControlsToggleText));
[RelayCommand]
private void ToggleRealtimeActuatorControls()
{
RealtimeActuatorControlsVisible = !RealtimeActuatorControlsVisible;
}
private void InitializeRs485FlowControl()
{
@@ -290,14 +325,130 @@ public partial class MainViewModel
private void RefreshActiveRs485FlowPumpControls()
{
ActiveRs485FlowPumpControls.Clear();
RealtimeRs485FlowPumpControls.Clear();
PressureDropRs485FlowPumpControls.Clear();
KinkResistanceRs485FlowPumpControls.Clear();
HemolysisRs485FlowPumpControls.Clear();
RecirculationRs485FlowPumpControls.Clear();
foreach (var pump in Rs485FlowPumpControls.Where(item => item.Rs485Enabled))
{
ActiveRs485FlowPumpControls.Add(pump);
if (string.Equals(pump.Key, PressureDropRs485PumpKey, StringComparison.Ordinal))
{
PressureDropRs485FlowPumpControls.Add(pump);
}
else if (string.Equals(pump.Key, KinkResistanceRs485PumpKey, StringComparison.Ordinal))
{
KinkResistanceRs485FlowPumpControls.Add(pump);
}
else if (HemolysisRs485PumpKeys.Contains(pump.Key, StringComparer.Ordinal))
{
HemolysisRs485FlowPumpControls.Add(pump);
}
else if (RecirculationRs485PumpKeys.Contains(pump.Key, StringComparer.Ordinal))
{
RecirculationRs485FlowPumpControls.Add(pump);
}
else
{
RealtimeRs485FlowPumpControls.Add(pump);
}
}
OnPropertyChanged(nameof(HasRealtimeRs485FlowPumpControls));
OnPropertyChanged(nameof(HasPressureDropRs485FlowPumpControls));
OnPropertyChanged(nameof(HasKinkResistanceRs485FlowPumpControls));
OnPropertyChanged(nameof(HasHemolysisRs485FlowPumpControls));
OnPropertyChanged(nameof(HasRecirculationRs485FlowPumpControls));
RaiseRs485CalibrationSummaryChanges();
}
[RelayCommand]
private async Task StartRecirculationRs485Pumps()
{
if (!EnsureSessionEditable("再循环三泵统一启动"))
{
return;
}
var pumps = RecirculationRs485FlowPumpControls.ToList();
if (pumps.Count == 0)
{
Rs485StatusText = "当前未配置再循环 RS485 泵。";
LatestAction = Rs485StatusText;
return;
}
var issuedCount = 0;
var confirmedCount = 0;
var skippedCount = 0;
var failedCount = 0;
foreach (var pump in pumps)
{
var effectiveRunning = pump.PendingRs485RunningState ?? pump.IsRunning;
if (effectiveRunning)
{
skippedCount++;
continue;
}
var startResult = await TryWriteAndStartPumpCore(pump, "再循环三泵统一启动");
if (startResult is Rs485StartExecutionResult.PendingConfirmation or Rs485StartExecutionResult.Confirmed)
{
issuedCount++;
if (startResult == Rs485StartExecutionResult.Confirmed)
{
confirmedCount++;
}
}
else
{
failedCount++;
}
}
var summary = $"再循环三泵启动完成:已下发 {issuedCount} 台,已确认运行 {confirmedCount} 台,跳过 {skippedCount} 台,失败 {failedCount} 台。";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("再循环三泵启动", summary));
}
[RelayCommand]
private async Task StopRecirculationRs485Pumps()
{
if (!EnsureSessionEditable("再循环三泵统一停止"))
{
return;
}
var pumps = RecirculationRs485FlowPumpControls.ToList();
if (pumps.Count == 0)
{
Rs485StatusText = "当前未配置再循环 RS485 泵。";
LatestAction = Rs485StatusText;
return;
}
var stoppedCount = 0;
var failedCount = 0;
foreach (var pump in pumps)
{
if (await TryTogglePumpControlViaRs485(pump, nextState: false) == Rs485ToggleExecutionResult.Succeeded)
{
stoppedCount++;
}
else
{
failedCount++;
}
}
var summary = $"再循环三泵停止完成:已下发停止 {stoppedCount} 台,失败 {failedCount} 台。";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("再循环三泵停止", summary));
}
private async Task RefreshRs485RuntimeStateSilentlyAsync()
{
if (DateTime.UtcNow - _lastRs485RuntimeRefreshUtc < Rs485RuntimeRefreshInterval)

View File

@@ -353,6 +353,45 @@ public partial class MainViewModel : ObservableObject, IDisposable
public ObservableCollection<DeviceChannel> Channels { get; }
public ObservableCollection<PumpControlChannel> PumpControls { get; }
public ObservableCollection<ValveControlChannel> ValveControls { get; }
public IEnumerable<ValveControlChannel> RealtimeValveControls =>
ValveControls.Where(item =>
!string.Equals(item.Key, "CirculatingWaterTemperature", StringComparison.Ordinal)
&& !string.Equals(item.Key, "TestLoopValve1", StringComparison.Ordinal)
&& !string.Equals(item.Key, "TestLoopValve2", StringComparison.Ordinal));
public ValveControlChannel? CirculatingWaterControl =>
ValveControls.FirstOrDefault(item => string.Equals(item.Key, "CirculatingWaterTemperature", StringComparison.Ordinal));
public bool HasCirculatingWaterControl => CirculatingWaterControl is not null;
public IEnumerable<ValveControlChannel> PressureDropAuxiliaryValveControls =>
ValveControls.Where(item =>
string.Equals(item.Key, "CirculatingWaterTemperature", StringComparison.Ordinal)
|| string.Equals(item.Key, "TestLoopValve1", StringComparison.Ordinal))
.OrderBy(item => string.Equals(item.Key, "CirculatingWaterTemperature", StringComparison.Ordinal) ? 0 : 1);
public bool HasPressureDropAuxiliaryValveControls => PressureDropAuxiliaryValveControls.Any();
public IEnumerable<ValveControlChannel> PressureDropSecondaryValveControls =>
ValveControls.Where(item => string.Equals(item.Key, "TestLoopValve1", StringComparison.Ordinal));
public bool HasPressureDropSecondaryValveControls => PressureDropSecondaryValveControls.Any();
public ValveControlChannel? RecirculationCirculatingWaterControl =>
ValveControls.FirstOrDefault(item => string.Equals(item.Key, "CirculatingWaterTemperature", StringComparison.Ordinal));
public bool HasRecirculationCirculatingWaterControl => RecirculationCirculatingWaterControl is not null;
public PumpControlChannel? AntiCollapseNegativeAssistPumpControl =>
NegativeAssistPumpControls.FirstOrDefault();
public bool HasAntiCollapseNegativeAssistPumpControl => AntiCollapseNegativeAssistPumpControl is not null;
public ValveControlChannel? AntiCollapseCirculatingWaterControl =>
ValveControls.FirstOrDefault(item => string.Equals(item.Key, "CirculatingWaterTemperature", StringComparison.Ordinal));
public bool HasAntiCollapseCirculatingWaterControl => AntiCollapseCirculatingWaterControl is not null;
public ValveControlChannel? AntiCollapseTestLoopValve2Control =>
ValveControls.FirstOrDefault(item => string.Equals(item.Key, "TestLoopValve2", StringComparison.Ordinal));
public bool HasAntiCollapseTestLoopValve2Control => AntiCollapseTestLoopValve2Control is not null;
public IEnumerable<ValveControlChannel> AntiCollapseAuxiliaryValveControls =>
ValveControls.Where(item =>
string.Equals(item.Key, "CirculatingWaterTemperature", StringComparison.Ordinal)
|| string.Equals(item.Key, "TestLoopValve2", StringComparison.Ordinal))
.OrderBy(item => string.Equals(item.Key, "CirculatingWaterTemperature", StringComparison.Ordinal) ? 0 : 1);
public bool HasAntiCollapseAuxiliaryValveControls => AntiCollapseAuxiliaryValveControls.Any();
public IEnumerable<ValveControlChannel> AntiCollapseSecondaryValveControls =>
ValveControls.Where(item => string.Equals(item.Key, "TestLoopValve2", StringComparison.Ordinal));
public bool HasAntiCollapseSecondaryValveControls => AntiCollapseSecondaryValveControls.Any();
public bool HasAntiCollapsePumpControls => NegativeAssistPumpControls.Any();
public IEnumerable<PumpControlChannel> NegativeAssistPumpControls => PumpControlsFor("NegativeAssistPump");
public IEnumerable<PumpControlChannel> PressureDropPumpControls => PumpControlsFor("NegativeAssistPump", "PressureDropPump");
public IEnumerable<PumpControlChannel> RecirculationPumpControls => PumpControlsFor("RecirculationMainPump", "RecirculationReturnPump", "RecirculationDrainagePump");
@@ -559,7 +598,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
public string PressureTrendCurrentSummary => $"近端 {ProximalPressureDisplay} / 远端 {DistalPressureDisplay} / ΔP {DeltaPressureDisplay}";
public string FlowTrendCurrentSummary => BuildFlowTrendCurrentSummary();
public string HemolysisRecordTemplate =>
"3.2 试验血液准备\n" +
"1. 试验血液准备\n" +
$"- 依据 GB/T 16886.4 进行血液预处理。\n" +
$"- 血液来源:{HemolysisTestParameters.BloodSource}\n" +
$"- 采血日期:{FormatHemolysisDate(HemolysisTestParameters.CollectionDate)}\n" +
@@ -569,13 +608,13 @@ public partial class MainViewModel : ObservableObject, IDisposable
$"- 血中葡萄糖:{FormatHemolysisValue(HemolysisTestParameters.Glucose, "F1")} mmol/L\n" +
$"- 血红蛋白:{FormatHemolysisValue(HemolysisTestParameters.TotalHemoglobin, "F1")} g/dL\n" +
$"- 初始游离血红蛋白 fHb{FormatHemolysisValue(GetHemolysisInitialFreeHemoglobin(), "F1")} mg/dL\n\n" +
"3.3 测试回路说明\n" +
"2. 测试回路说明\n" +
$"- 回路总充盈量:{FormatHemolysisValue(HemolysisTestParameters.CircuitPrimingVolume, "F0")} mL\n" +
$"- 回路初始血液通道试验液容积差:{FormatHemolysisValue(HemolysisTestParameters.CircuitVolumeDifference, "F2")} %(应 ≤ 1%\n" +
$"- 设定流量:{FormatHemolysisValue(HemolysisTestParameters.SetFlow, "F2")} L/min\n" +
$"- 设定运行时间:{FormatHemolysisValue(HemolysisTestParameters.RunTimeMinutes, "F0")} min\n" +
$"- 温度控制:{FormatHemolysisValue(HemolysisTestParameters.TargetTemperature, "F1")} ℃ ± 2 ℃\n\n" +
"4. 试验运行与取样记录";
"3. 试验运行与取样记录";
public string HemolysisRecordNoteTemplate => BuildHemolysisRecordNoteText();
public bool HasAntiCollapseBaseline => _antiCollapseBaselinePressureDrop.HasValue && _antiCollapseBaselineFlow.HasValue;
public string AntiCollapseBaselineDisplay => HasAntiCollapseBaseline
@@ -778,11 +817,35 @@ public partial class MainViewModel : ObservableObject, IDisposable
}
var nextState = !valve.IsOpen;
ValveControlChannel? pairedValve = null;
if (nextState)
{
pairedValve = valve.Key switch
{
"TestLoopValve1" => ValveControls.FirstOrDefault(item => string.Equals(item.Key, "TestLoopValve2", StringComparison.Ordinal)),
"TestLoopValve2" => ValveControls.FirstOrDefault(item => string.Equals(item.Key, "TestLoopValve1", StringComparison.Ordinal)),
_ => null
};
if (pairedValve?.IsOpen == true)
{
_telemetryService.SetValveOpen(pairedValve.Key, false);
}
}
_telemetryService.SetValveOpen(valve.Key, nextState);
var traceDetail = $"{valve.Name} => {(nextState ? "" : "")}";
if (pairedValve?.IsOpen == true)
{
traceDetail += $" / {pairedValve.Name} => 关闭";
}
LatestAction = IsTelemetryOnline
? $"{valve.Name} 已发送{(nextState ? "" : "")}指令。"
? pairedValve?.IsOpen == true
? $"{valve.Name} 已发送{(nextState ? "" : "")}指令,并自动关闭 {pairedValve.Name}。"
: $"{valve.Name} 已发送{(nextState ? "" : "")}指令。"
: $"{valve.Name} 指令未下发PLC 当前离线。";
TraceEvents.Insert(0, NewTrace("阀控", $"{valve.Name} => {(nextState ? "" : "")}"));
TraceEvents.Insert(0, NewTrace("阀控", traceDetail));
_ = RefreshTelemetryAsync();
}
@@ -1349,7 +1412,6 @@ public partial class MainViewModel : ObservableObject, IDisposable
RefreshTelemetryPanel();
RefreshDeviceStatus();
RefreshComputedState();
RefreshFilteredItemsView();
}
catch (Exception ex)
{
@@ -1760,7 +1822,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
var calculationHematocrit = GetHemolysisCalculationHematocrit();
return
"5. 结果计算\n" +
"4. 结果计算\n" +
$"- 标准取样点完成情况:{BuildHemolysisSamplingCompletionSummary()}\n" +
$"- ΔfHb (T360 - T0){FormatHemolysisDisplay(GetHemolysisDeltaFreeHemoglobin(), "F1", "mg/dL")}\n" +
"- NIH = ΔfHb × V(L) × (1-Hct) / (Q×T)\n" +