From dcf50ea8705600e5c0cadf5334f0fae3b6b3e142 Mon Sep 17 00:00:00 2001 From: "GukSang.Jin" Date: Wed, 27 May 2026 09:28:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CValueCalibrationActionViewModel.cs | 8 +- .../ViewModels/CValueCalibrationViewModel.cs | 24 ++-- .../ViewModels/DeviceActionViewModel.cs | 20 +++ .../ViewModels/TestPageViewModel.cs | 118 +++++++++++++++--- .../Views/CValueCalibrationView.xaml | 31 ++++- ConeCalorimeter/Views/TestPageView.xaml | 49 +++++++- 6 files changed, 216 insertions(+), 34 deletions(-) diff --git a/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs b/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs index da839e0..f70383e 100644 --- a/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs +++ b/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs @@ -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}:未知"; diff --git a/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs b/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs index af57a61..feda720 100644 --- a/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs +++ b/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs @@ -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."); } diff --git a/ConeCalorimeter/ViewModels/DeviceActionViewModel.cs b/ConeCalorimeter/ViewModels/DeviceActionViewModel.cs index 8c561e1..fc7822e 100644 --- a/ConeCalorimeter/ViewModels/DeviceActionViewModel.cs +++ b/ConeCalorimeter/ViewModels/DeviceActionViewModel.cs @@ -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; + } } diff --git a/ConeCalorimeter/ViewModels/TestPageViewModel.cs b/ConeCalorimeter/ViewModels/TestPageViewModel.cs index b9e5d43..be9761a 100644 --- a/ConeCalorimeter/ViewModels/TestPageViewModel.cs +++ b/ConeCalorimeter/ViewModels/TestPageViewModel.cs @@ -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 _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; diff --git a/ConeCalorimeter/Views/CValueCalibrationView.xaml b/ConeCalorimeter/Views/CValueCalibrationView.xaml index c38fd18..7a0fb7e 100644 --- a/ConeCalorimeter/Views/CValueCalibrationView.xaml +++ b/ConeCalorimeter/Views/CValueCalibrationView.xaml @@ -23,11 +23,36 @@ + + + + + + + + + + + + + + + + + - - - + + + diff --git a/ConeCalorimeter/Views/TestPageView.xaml b/ConeCalorimeter/Views/TestPageView.xaml index 5c40155..5d5e857 100644 --- a/ConeCalorimeter/Views/TestPageView.xaml +++ b/ConeCalorimeter/Views/TestPageView.xaml @@ -18,6 +18,51 @@ + + - + @@ -225,7 +270,7 @@ TouchUp="DeviceActionButton_TouchUp" TouchLeave="DeviceActionButton_TouchLeave" LostTouchCapture="DeviceActionButton_LostTouchCapture" - Style="{StaticResource InstrumentButtonStyle}" /> + Style="{StaticResource StatusDeviceActionButtonStyle}" />