diff --git a/ConeCalorimeter/Services/ExperimentDataService.cs b/ConeCalorimeter/Services/ExperimentDataService.cs index 940d8fa..c999371 100644 --- a/ConeCalorimeter/Services/ExperimentDataService.cs +++ b/ConeCalorimeter/Services/ExperimentDataService.cs @@ -25,8 +25,6 @@ public sealed class ExperimentDataService : IExperimentDataService Records = []; CurrentSnapshot = _realtimeDataService.GetCurrentSnapshot(TimeSpan.Zero); - RecordSnapshot(CurrentSnapshot); - _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) @@ -71,11 +69,15 @@ public sealed class ExperimentDataService : IExperimentDataService var accumulatedSnapshot = ApplyAccumulatedTotals(massSnapshot); CurrentSnapshot = accumulatedSnapshot; - Records.Add(RealtimeDataRecord.FromSnapshot(accumulatedSnapshot)); - while (Records.Count > MaximumRows) + if (_isTestRunning) { - Records.RemoveAt(0); + Records.Add(RealtimeDataRecord.FromSnapshot(accumulatedSnapshot)); + + while (Records.Count > MaximumRows) + { + Records.RemoveAt(0); + } } SnapshotUpdated?.Invoke(this, accumulatedSnapshot); diff --git a/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs b/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs index a9104cb..0d51510 100644 --- a/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs +++ b/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs @@ -1,16 +1,70 @@ using System.Windows.Input; +using CommunityToolkit.Mvvm.ComponentModel; namespace ConeCalorimeter.ViewModels; -public sealed class CValueCalibrationActionViewModel +public sealed class CValueCalibrationActionViewModel : ObservableObject { - public CValueCalibrationActionViewModel(string label, ICommand command) + private bool _isActive; + private bool _isStatusKnown; + + public CValueCalibrationActionViewModel( + string label, + ICommand command, + string inactiveDisplayText, + string activeDisplayText) { Label = label; Command = command; + InactiveDisplayText = inactiveDisplayText; + ActiveDisplayText = activeDisplayText; } public string Label { get; } + public string InactiveDisplayText { get; } + + public string ActiveDisplayText { get; } + public ICommand Command { get; } + + public bool IsActive + { + get => _isActive; + private set + { + if (SetProperty(ref _isActive, value)) + { + OnPropertyChanged(nameof(DisplayText)); + OnPropertyChanged(nameof(StatusText)); + } + } + } + + public bool IsStatusKnown + { + get => _isStatusKnown; + private set + { + if (SetProperty(ref _isStatusKnown, value)) + { + OnPropertyChanged(nameof(DisplayText)); + OnPropertyChanged(nameof(StatusText)); + } + } + } + + public string DisplayText => IsStatusKnown + ? IsActive ? ActiveDisplayText : InactiveDisplayText + : $"{Label}:未知"; + + public string StatusText => IsStatusKnown + ? IsActive ? "开启" : "关闭" + : "未知"; + + public void UpdateStatus(bool isActive, bool isStatusKnown = true) + { + IsStatusKnown = isStatusKnown; + IsActive = isActive; + } } diff --git a/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs b/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs index ee81d78..f7af567 100644 --- a/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs +++ b/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs @@ -14,14 +14,28 @@ public sealed class CValueCalibrationViewModel : PageViewModel private const ushort PressureDifferenceRegister = 284; private const ushort OxygenRegister = 286; private const ushort CValueRegister = 308; + private const ushort CirculatingWaterCoil = 49; + private const ushort SamplingPumpCoil = 50; + private const ushort IgniterCoil = 53; + private const ushort FanCoil = 54; + private const ushort MethaneValveCoil = 55; + private const ushort BaselineCollectionCoil = 60; + private const ushort BaselineEndCoil = 62; + private const ushort CalibrationStartCoil = 70; + private const ushort CalibrationEndCoil = 72; + private const string CirculatingWaterAction = "循环水"; + private const string SamplingPumpAction = "取样泵"; + private const string IgniterAction = "点火器"; + private const string FanAction = "风机"; + private const string MethaneValveAction = "甲烷阀"; private const string BaselineCollectionAction = "基线采集"; private const string CalibrationStartAction = "标定开始"; - private static readonly TimeSpan PulseDelay = TimeSpan.FromMilliseconds(300); private readonly Action _closeAction; private readonly Action _helpAction; private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService; private readonly DispatcherTimer _refreshTimer; + private readonly Dictionary _actionsByLabel = []; private string _baselineOxygenText = ""; private string _temperatureText = ""; private string _pressureDifferenceText = ""; @@ -43,16 +57,17 @@ public sealed class CValueCalibrationViewModel : PageViewModel TopActions = [ - new CValueCalibrationActionViewModel("甲烷阀", ActionCommand), - new CValueCalibrationActionViewModel("风机", ActionCommand), - new CValueCalibrationActionViewModel("点火器", ActionCommand), - new CValueCalibrationActionViewModel("取样泵", ActionCommand) + CreateAction(CirculatingWaterAction, $"{CirculatingWaterAction}:关闭", $"{CirculatingWaterAction}:开启"), + CreateAction(MethaneValveAction, $"{MethaneValveAction}:关闭", $"{MethaneValveAction}:开启"), + CreateAction(FanAction, $"{FanAction}:关闭", $"{FanAction}:开启"), + CreateAction(IgniterAction, $"{IgniterAction}:关闭", $"{IgniterAction}:开启"), + CreateAction(SamplingPumpAction, $"{SamplingPumpAction}:关闭", $"{SamplingPumpAction}:开启") ]; BottomActions = [ - new CValueCalibrationActionViewModel("标定开始", ActionCommand), - new CValueCalibrationActionViewModel("基线采集", ActionCommand) + CreateAction(CalibrationStartAction, CalibrationStartAction, "标定结束"), + CreateAction(BaselineCollectionAction, BaselineCollectionAction, "基线结束") ]; RefreshDeviceValues(); @@ -121,13 +136,13 @@ public sealed class CValueCalibrationViewModel : PageViewModel LastAction = action; - if (IsPulseAction(action)) + if (IsPairedAction(action)) { - _ = PulseActionCoilAsync(action); + TogglePairedAction(action); return; } - WriteActionCoil(action); + ToggleHoldAction(action); } private void RefreshDeviceValues() @@ -139,6 +154,7 @@ public sealed class CValueCalibrationViewModel : PageViewModel PressureDifferenceText = ReadFloatText(PressureDifferenceRegister); CalibrationOxygenText = oxygenText; CValueText = ReadFloatText(CValueRegister); + RefreshActionStates(); } private string ReadFloatText(ushort registerAddress) @@ -148,61 +164,124 @@ public sealed class CValueCalibrationViewModel : PageViewModel : string.Empty; } - private void WriteActionCoil(string action) + private CValueCalibrationActionViewModel CreateAction( + string label, + string inactiveDisplayText, + string activeDisplayText) { - if (!TryGetActionCoil(action, out var coilAddress)) - { - return; - } - - if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true)) - { - Debug.WriteLine($"C value calibration action '{action}' write failed."); - } + var action = new CValueCalibrationActionViewModel(label, ActionCommand, inactiveDisplayText, activeDisplayText); + _actionsByLabel[label] = action; + return action; } - private async Task PulseActionCoilAsync(string action) + private void RefreshActionStates() { - if (!TryGetActionCoil(action, out var coilAddress)) - { - return; - } - - if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true)) - { - Debug.WriteLine($"C value calibration action '{action}' pulse start failed."); - return; - } - - await Task.Delay(PulseDelay); - - if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, false)) - { - Debug.WriteLine($"C value calibration action '{action}' pulse reset failed."); - } + UpdateActionStatus(CirculatingWaterAction, CirculatingWaterCoil); + UpdateActionStatus(MethaneValveAction, MethaneValveCoil); + UpdateActionStatus(FanAction, FanCoil); + UpdateActionStatus(IgniterAction, IgniterCoil); + UpdateActionStatus(SamplingPumpAction, SamplingPumpCoil); + UpdateActionStatus(BaselineCollectionAction, BaselineCollectionCoil); + UpdateActionStatus(CalibrationStartAction, CalibrationStartCoil); } - private static bool TryGetActionCoil(string action, out ushort coilAddress) + private bool UpdateActionStatus(string action, ushort coilAddress) + { + if (!_actionsByLabel.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 ToggleHoldAction(string action) + { + if (!TryGetHoldActionCoil(action, out var coilAddress)) + { + return; + } + + if (!_tcpDeviceConnectionService.TryReadCoil(coilAddress, out var isActive)) + { + LastAction = $"{action}状态读取失败"; + Debug.WriteLine($"C value calibration action '{action}' state read failed."); + return; + } + + var nextValue = !isActive; + if (_tcpDeviceConnectionService.TryWriteCoil(coilAddress, nextValue)) + { + LastAction = $"{action}{(nextValue ? "开启" : "关闭")}"; + if (!UpdateActionStatus(action, coilAddress) && _actionsByLabel.TryGetValue(action, out var actionViewModel)) + { + actionViewModel.UpdateStatus(nextValue); + } + + return; + } + + LastAction = $"{action}{(nextValue ? "开启" : "关闭")}失败"; + Debug.WriteLine($"C value calibration action '{action}' write failed."); + } + + private void TogglePairedAction(string action) + { + if (!TryGetPairedActionCoils(action, out var startCoilAddress, out var endCoilAddress, out var endActionText)) + { + return; + } + + if (!_tcpDeviceConnectionService.TryReadCoil(startCoilAddress, out var isActive)) + { + LastAction = $"{action}状态读取失败"; + Debug.WriteLine($"C value calibration action '{action}' state read failed."); + return; + } + + var coilAddress = isActive ? endCoilAddress : startCoilAddress; + var actionText = isActive ? endActionText : action; + + if (_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true)) + { + LastAction = actionText; + if (_actionsByLabel.TryGetValue(action, out var actionViewModel)) + { + actionViewModel.UpdateStatus(!isActive); + } + + return; + } + + LastAction = $"{actionText}失败"; + Debug.WriteLine($"C value calibration action '{actionText}' write failed."); + } + + private static bool TryGetHoldActionCoil(string action, out ushort coilAddress) { switch (action) { - case "甲烷阀": - coilAddress = 55; + case CirculatingWaterAction: + coilAddress = CirculatingWaterCoil; return true; - case "风机": - coilAddress = 54; + case MethaneValveAction: + coilAddress = MethaneValveCoil; return true; - case "点火器": - coilAddress = 53; + case FanAction: + coilAddress = FanCoil; return true; - case "取样泵": - coilAddress = 50; + case IgniterAction: + coilAddress = IgniterCoil; return true; - case BaselineCollectionAction: - coilAddress = 60; - return true; - case CalibrationStartAction: - coilAddress = 70; + case SamplingPumpAction: + coilAddress = SamplingPumpCoil; return true; default: coilAddress = 0; @@ -210,7 +289,33 @@ public sealed class CValueCalibrationViewModel : PageViewModel } } - private static bool IsPulseAction(string action) + private static bool TryGetPairedActionCoils( + string action, + out ushort startCoilAddress, + out ushort endCoilAddress, + out string endActionText) + { + switch (action) + { + case BaselineCollectionAction: + startCoilAddress = BaselineCollectionCoil; + endCoilAddress = BaselineEndCoil; + endActionText = "基线结束"; + return true; + case CalibrationStartAction: + startCoilAddress = CalibrationStartCoil; + endCoilAddress = CalibrationEndCoil; + endActionText = "标定结束"; + return true; + default: + startCoilAddress = 0; + endCoilAddress = 0; + endActionText = string.Empty; + return false; + } + } + + private static bool IsPairedAction(string action) { return action is BaselineCollectionAction or CalibrationStartAction; } diff --git a/ConeCalorimeter/ViewModels/ConeRadiationSettingsViewModel.cs b/ConeCalorimeter/ViewModels/ConeRadiationSettingsViewModel.cs index 1e8e360..d0fb753 100644 --- a/ConeCalorimeter/ViewModels/ConeRadiationSettingsViewModel.cs +++ b/ConeCalorimeter/ViewModels/ConeRadiationSettingsViewModel.cs @@ -14,6 +14,8 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel private const ushort SlopeRegister = 420; private const ushort InterceptRegister = 422; private const ushort AlarmCoil = 91; + private const ushort CirculatingWaterCoil = 49; + private const string CirculatingWaterAction = "循环水"; private readonly Action _closeAction; private readonly Action _helpAction; @@ -27,6 +29,7 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel private string _interceptText = ""; private string _lastAction = "待机"; private bool _alarmActive; + private bool _circulatingWaterActive; private bool _isEditingConeParameters; public ConeRadiationSettingsViewModel( @@ -107,6 +110,20 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel private set => SetProperty(ref _alarmActive, value); } + public bool CirculatingWaterActive + { + get => _circulatingWaterActive; + private set + { + if (SetProperty(ref _circulatingWaterActive, value)) + { + OnPropertyChanged(nameof(CirculatingWaterButtonText)); + } + } + } + + public string CirculatingWaterButtonText => CirculatingWaterActive ? "循环水:开启" : "循环水:关闭"; + public void BeginConeParameterEdit() { _isEditingConeParameters = true; @@ -126,6 +143,12 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel LastAction = action; + if (action == CirculatingWaterAction) + { + ToggleCirculatingWater(); + return; + } + if (action == "开始升温") { WriteTargetTemperature(); @@ -147,6 +170,10 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel } AlarmActive = _tcpDeviceConnectionService.TryReadCoil(AlarmCoil, out var alarmActive) && alarmActive; + if (_tcpDeviceConnectionService.TryReadCoil(CirculatingWaterCoil, out var circulatingWaterActive)) + { + CirculatingWaterActive = circulatingWaterActive; + } } private string ReadFloatText(ushort registerAddress) @@ -200,6 +227,27 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel Debug.WriteLine($"Cone radiation parameters write failed. Slope: {slopeWritten}, intercept: {interceptWritten}."); } + private void ToggleCirculatingWater() + { + if (!_tcpDeviceConnectionService.TryReadCoil(CirculatingWaterCoil, out var isActive)) + { + LastAction = "循环水状态读取失败"; + Debug.WriteLine("Cone radiation circulating water state read failed."); + return; + } + + var nextValue = !isActive; + if (_tcpDeviceConnectionService.TryWriteCoil(CirculatingWaterCoil, nextValue)) + { + CirculatingWaterActive = nextValue; + LastAction = $"循环水{(nextValue ? "开启" : "关闭")}"; + return; + } + + LastAction = $"循环水{(nextValue ? "开启" : "关闭")}失败"; + Debug.WriteLine("Cone radiation circulating water write failed."); + } + private void WriteActionCoil(string action) { if (!TryGetActionCoil(action, out var coilAddress)) @@ -218,9 +266,6 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel { switch (action) { - case "循环水": - coilAddress = 49; - return true; case "开始升温": coilAddress = 100; return true; diff --git a/ConeCalorimeter/Views/CValueCalibrationView.xaml b/ConeCalorimeter/Views/CValueCalibrationView.xaml index d3ac45a..c45c11d 100644 --- a/ConeCalorimeter/Views/CValueCalibrationView.xaml +++ b/ConeCalorimeter/Views/CValueCalibrationView.xaml @@ -16,6 +16,26 @@ + + @@ -209,13 +229,13 @@ -