This commit is contained in:
GukSang.Jin
2026-05-27 09:28:48 +08:00
parent 5477fdf210
commit dcf50ea870
6 changed files with 216 additions and 34 deletions

View File

@@ -14,12 +14,14 @@ public sealed class CValueCalibrationActionViewModel : ObservableObject
string label,
ICommand command,
string inactiveDisplayText,
string activeDisplayText)
string activeDisplayText,
bool showStateText = true)
{
Label = label;
Command = command;
InactiveDisplayText = inactiveDisplayText;
ActiveDisplayText = activeDisplayText;
ShowStateText = showStateText;
}
public string Label { get; }
@@ -28,6 +30,8 @@ public sealed class CValueCalibrationActionViewModel : ObservableObject
public string ActiveDisplayText { get; }
public bool ShowStateText { get; }
public ICommand Command { get; }
public bool IsActive
@@ -74,6 +78,8 @@ public sealed class CValueCalibrationActionViewModel : ObservableObject
public string DisplayText => IsBusy
? _busyDisplayText
: !ShowStateText
? Label
: IsStatusKnown
? IsActive ? ActiveDisplayText : InactiveDisplayText
: $"{Label}:未知";

View File

@@ -96,11 +96,11 @@ public sealed class CValueCalibrationViewModel : PageViewModel
TopActions =
[
CreateAction(CirculatingWaterAction, $"{CirculatingWaterAction}:关闭", $"{CirculatingWaterAction}:开启"),
CreateAction(MethaneValveAction, $"{MethaneValveAction}:关闭", $"{MethaneValveAction}:开启"),
CreateAction(FanAction, $"{FanAction}:关闭", $"{FanAction}:开启"),
CreateAction(IgniterAction, $"{IgniterAction}:关闭", $"{IgniterAction}:开启"),
CreateAction(SamplingPumpAction, $"{SamplingPumpAction}:关闭", $"{SamplingPumpAction}:开启")
CreateAction(CirculatingWaterAction, CirculatingWaterAction, CirculatingWaterAction, showStateText: false),
CreateAction(MethaneValveAction, MethaneValveAction, MethaneValveAction, showStateText: false),
CreateAction(FanAction, FanAction, FanAction, showStateText: false),
CreateAction(IgniterAction, IgniterAction, IgniterAction, showStateText: false),
CreateAction(SamplingPumpAction, SamplingPumpAction, SamplingPumpAction, showStateText: false)
];
BottomActions =
@@ -524,9 +524,15 @@ public sealed class CValueCalibrationViewModel : PageViewModel
private CValueCalibrationActionViewModel CreateAction(
string label,
string inactiveDisplayText,
string activeDisplayText)
string activeDisplayText,
bool showStateText = true)
{
var action = new CValueCalibrationActionViewModel(label, ActionCommand, inactiveDisplayText, activeDisplayText);
var action = new CValueCalibrationActionViewModel(
label,
ActionCommand,
inactiveDisplayText,
activeDisplayText,
showStateText);
_actionsByLabel[label] = action;
return action;
}
@@ -574,7 +580,7 @@ public sealed class CValueCalibrationViewModel : PageViewModel
var nextValue = !isActive;
if (_tcpDeviceConnectionService.TryWriteCoil(coilAddress, nextValue))
{
LastAction = $"{action}{(nextValue ? "" : "")}";
LastAction = $"{action}控制完成";
if (!UpdateActionStatus(action, coilAddress) && _actionsByLabel.TryGetValue(action, out var actionViewModel))
{
actionViewModel.UpdateStatus(nextValue);
@@ -583,7 +589,7 @@ public sealed class CValueCalibrationViewModel : PageViewModel
return;
}
LastAction = $"{action}{(nextValue ? "" : "")}失败";
LastAction = $"{action}控制失败";
Debug.WriteLine($"C value calibration action '{action}' write failed.");
}

View File

@@ -6,6 +6,8 @@ namespace ConeCalorimeter.ViewModels;
public sealed class DeviceActionViewModel : ObservableObject
{
private string _displayText;
private bool _isActive;
private bool _isStatusKnown = true;
public DeviceActionViewModel(string label, ICommand command)
{
@@ -24,8 +26,26 @@ public sealed class DeviceActionViewModel : ObservableObject
public ICommand Command { get; }
public bool IsActive
{
get => _isActive;
private set => SetProperty(ref _isActive, value);
}
public bool IsStatusKnown
{
get => _isStatusKnown;
private set => SetProperty(ref _isStatusKnown, value);
}
public void SetDisplayText(string displayText)
{
DisplayText = displayText;
}
public void UpdateStatus(bool isActive, bool isStatusKnown = true)
{
IsStatusKnown = isStatusKnown;
IsActive = isActive;
}
}

View File

@@ -15,8 +15,12 @@ namespace ConeCalorimeter.ViewModels;
public sealed class TestPageViewModel : PageViewModel
{
private const string ResetAction = "复位";
private const string FanAction = "风机";
private const string IgniterAction = "点火器";
private const ushort ResetCoil = 88;
private const ushort ResetCompleteCoil = 89;
private const ushort FanCoil = 54;
private const ushort IgniterCoil = 53;
private const double MinimumHeatReleaseAxisMaximum = 150;
private const double MinimumTotalAxisMaximum = 150;
private const double PlotAxisPaddingFactor = 1.1;
@@ -30,6 +34,7 @@ public sealed class TestPageViewModel : PageViewModel
private readonly LineSeries _totalHeatSeries;
private readonly LineSeries _totalSmokeSeries;
private readonly DispatcherTimer _resetStatusTimer;
private readonly Dictionary<string, DeviceActionViewModel> _deviceActionsByLabel = [];
private DeviceActionViewModel _resetAction = null!;
private bool _flameDetected;
private bool _resetInProgress;
@@ -87,12 +92,14 @@ public sealed class TestPageViewModel : PageViewModel
new DeviceActionViewModel("称重台降", ExecuteDeviceActionCommand),
new DeviceActionViewModel("测试开始", ExecuteDeviceActionCommand),
new DeviceActionViewModel("测试结束", ExecuteDeviceActionCommand),
new DeviceActionViewModel("风机开", ExecuteDeviceActionCommand),
new DeviceActionViewModel("风机关", ExecuteDeviceActionCommand),
new DeviceActionViewModel("点火开", ExecuteDeviceActionCommand),
new DeviceActionViewModel("点火关", ExecuteDeviceActionCommand),
new DeviceActionViewModel(FanAction, ExecuteDeviceActionCommand),
new DeviceActionViewModel(IgniterAction, ExecuteDeviceActionCommand),
_resetAction
];
foreach (var action in DeviceActions)
{
_deviceActionsByLabel[action.Label] = action;
}
HeatReleasePlot = CreatePlotModel(out _heatReleaseSeries, out _totalHeatSeries, out _totalSmokeSeries);
UpdateSnapshot(_experimentDataService.CurrentSnapshot);
@@ -102,7 +109,12 @@ public sealed class TestPageViewModel : PageViewModel
{
Interval = TimeSpan.FromSeconds(1)
};
_resetStatusTimer.Tick += (_, _) => RefreshResetStatus();
_resetStatusTimer.Tick += (_, _) =>
{
RefreshResetStatus();
RefreshToggleActionStates();
};
RefreshToggleActionStates();
_resetStatusTimer.Start();
}
@@ -357,14 +369,21 @@ public sealed class TestPageViewModel : PageViewModel
return;
}
LastAction = action;
if (action == ResetAction)
{
LastAction = action;
ExecuteResetAction();
return;
}
if (IsToggleDeviceAction(action))
{
ToggleDeviceAction(action);
return;
}
LastAction = action;
if (action == "测试开始")
{
_experimentDataService.StartTest();
@@ -455,6 +474,60 @@ public sealed class TestPageViewModel : PageViewModel
HeatReleasePlot.InvalidatePlot(true);
}
private void RefreshToggleActionStates()
{
UpdateToggleActionStatus(FanAction, FanCoil);
UpdateToggleActionStatus(IgniterAction, IgniterCoil);
}
private bool UpdateToggleActionStatus(string action, ushort coilAddress)
{
if (!_deviceActionsByLabel.TryGetValue(action, out var actionViewModel))
{
return false;
}
if (_tcpDeviceConnectionService.TryReadCoil(coilAddress, out var isActive))
{
actionViewModel.UpdateStatus(isActive);
return true;
}
actionViewModel.UpdateStatus(actionViewModel.IsActive, false);
return false;
}
private void ToggleDeviceAction(string action)
{
if (!TryGetToggleDeviceActionCoil(action, out var coilAddress))
{
return;
}
if (!_tcpDeviceConnectionService.TryReadCoil(coilAddress, out var isActive))
{
LastAction = $"{action}状态读取失败";
Debug.WriteLine($"Device action '{action}' state read failed.");
return;
}
var nextValue = !isActive;
if (_tcpDeviceConnectionService.TryWriteCoil(coilAddress, nextValue))
{
LastAction = $"{action}控制完成";
if (!UpdateToggleActionStatus(action, coilAddress)
&& _deviceActionsByLabel.TryGetValue(action, out var actionViewModel))
{
actionViewModel.UpdateStatus(nextValue);
}
return;
}
LastAction = $"{action}控制失败";
Debug.WriteLine($"Device action '{action}' write failed.");
}
private static bool TryGetDeviceActionCoil(string action, out ushort coilAddress, out bool value)
{
value = true;
@@ -467,19 +540,26 @@ public sealed class TestPageViewModel : PageViewModel
case "测试结束":
coilAddress = 67;
return true;
case "点火开":
coilAddress = 53;
default:
coilAddress = 0;
return false;
}
}
private static bool IsToggleDeviceAction(string action)
{
return TryGetToggleDeviceActionCoil(action, out _);
}
private static bool TryGetToggleDeviceActionCoil(string action, out ushort coilAddress)
{
switch (action)
{
case FanAction:
coilAddress = FanCoil;
return true;
case "点火关":
coilAddress = 53;
value = false;
return true;
case "风机开":
coilAddress = 54;
return true;
case "风机关":
coilAddress = 54;
value = false;
case IgniterAction:
coilAddress = IgniterCoil;
return true;
default:
coilAddress = 0;

View File

@@ -23,11 +23,36 @@
<Setter Property="Background" Value="#EEF1EF" />
<Setter Property="BorderBrush" Value="#A9B1AD" />
<Setter Property="Foreground" Value="#172320" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="ButtonBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="6"
SnapsToDevicePixels="True">
<ContentPresenter Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="ButtonBorder" Property="Opacity" Value="0.86" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="ButtonBorder" Property="Opacity" Value="0.62" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Background" Value="#DDEFE6" />
<Setter Property="BorderBrush" Value="#3F8B63" />
<Setter Property="Foreground" Value="#0D3B24" />
<Setter Property="Background" Value="#2F8F5B" />
<Setter Property="BorderBrush" Value="#236A45" />
<Setter Property="Foreground" Value="#FFFFFF" />
</DataTrigger>
<DataTrigger Binding="{Binding IsStatusKnown}" Value="False">
<Setter Property="Background" Value="#E5E7E6" />

View File

@@ -18,6 +18,51 @@
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<Style x:Key="StatusDeviceActionButtonStyle"
TargetType="Button"
BasedOn="{StaticResource InstrumentButtonStyle}">
<Setter Property="Background" Value="#EEF1EF" />
<Setter Property="BorderBrush" Value="#A9B1AD" />
<Setter Property="Foreground" Value="#172320" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="ButtonBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="6"
SnapsToDevicePixels="True">
<ContentPresenter Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="ButtonBorder" Property="Opacity" Value="0.86" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="ButtonBorder" Property="Opacity" Value="0.62" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Background" Value="#2F8F5B" />
<Setter Property="BorderBrush" Value="#236A45" />
<Setter Property="Foreground" Value="#FFFFFF" />
</DataTrigger>
<DataTrigger Binding="{Binding IsStatusKnown}" Value="False">
<Setter Property="Background" Value="#E5E7E6" />
<Setter Property="BorderBrush" Value="#B8BFBB" />
<Setter Property="Foreground" Value="#68736E" />
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="TopMetricTemplate">
<Border Margin="8,10"
Padding="10,8"
@@ -206,7 +251,7 @@
<ItemsControl ItemsSource="{Binding DeviceActions}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" Rows="4" />
<UniformGrid Columns="3" Rows="3" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
@@ -225,7 +270,7 @@
TouchUp="DeviceActionButton_TouchUp"
TouchLeave="DeviceActionButton_TouchLeave"
LostTouchCapture="DeviceActionButton_LostTouchCapture"
Style="{StaticResource InstrumentButtonStyle}" />
Style="{StaticResource StatusDeviceActionButtonStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>