From 6965d395608c9f9789933092ba02742e20035928 Mon Sep 17 00:00:00 2001 From: "GukSang.Jin" Date: Mon, 25 May 2026 18:41:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0121?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CValueCalibrationActionViewModel.cs | 43 ++++- .../ViewModels/CValueCalibrationViewModel.cs | 177 +++++++++++++++--- .../Views/CValueCalibrationView.xaml | 2 + ConeCalorimeter/Views/HelpDialogWindow.xaml | 20 ++ .../Views/HelpDialogWindow.xaml.cs | 6 + 5 files changed, 218 insertions(+), 30 deletions(-) diff --git a/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs b/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs index 0d51510..da839e0 100644 --- a/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs +++ b/ConeCalorimeter/ViewModels/CValueCalibrationActionViewModel.cs @@ -7,6 +7,8 @@ public sealed class CValueCalibrationActionViewModel : ObservableObject { private bool _isActive; private bool _isStatusKnown; + private bool _isBusy; + private string _busyDisplayText = string.Empty; public CValueCalibrationActionViewModel( string label, @@ -54,11 +56,31 @@ public sealed class CValueCalibrationActionViewModel : ObservableObject } } - public string DisplayText => IsStatusKnown + public bool IsBusy + { + get => _isBusy; + private set + { + if (SetProperty(ref _isBusy, value)) + { + OnPropertyChanged(nameof(DisplayText)); + OnPropertyChanged(nameof(StatusText)); + OnPropertyChanged(nameof(IsEnabled)); + } + } + } + + public bool IsEnabled => !IsBusy; + + public string DisplayText => IsBusy + ? _busyDisplayText + : IsStatusKnown ? IsActive ? ActiveDisplayText : InactiveDisplayText : $"{Label}:未知"; - public string StatusText => IsStatusKnown + public string StatusText => IsBusy + ? "进行中" + : IsStatusKnown ? IsActive ? "开启" : "关闭" : "未知"; @@ -67,4 +89,21 @@ public sealed class CValueCalibrationActionViewModel : ObservableObject IsStatusKnown = isStatusKnown; IsActive = isActive; } + + public void BeginWaiting(string busyDisplayText) + { + _busyDisplayText = busyDisplayText; + IsStatusKnown = true; + IsActive = true; + IsBusy = true; + OnPropertyChanged(nameof(DisplayText)); + OnPropertyChanged(nameof(StatusText)); + } + + public void FinishWaiting() + { + IsStatusKnown = true; + IsActive = false; + IsBusy = false; + } } diff --git a/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs b/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs index caaf2e8..0e3594e 100644 --- a/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs +++ b/ConeCalorimeter/ViewModels/CValueCalibrationViewModel.cs @@ -48,7 +48,9 @@ public sealed class CValueCalibrationViewModel : PageViewModel private readonly Action _calibrationCompletedAction; private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService; private readonly DispatcherTimer _refreshTimer; + private readonly DispatcherTimer _pairedActionCompletionTimer; private readonly Dictionary _actionsByLabel = []; + private readonly Dictionary _pairedActionWaitStates = []; private readonly HashSet _loggedFloatDiagnostics = []; private readonly HashSet _loggedInvalidFloatDiagnostics = []; private string _baselineOxygenText = ""; @@ -90,6 +92,22 @@ public sealed class CValueCalibrationViewModel : PageViewModel ]; _actionsByLabel[CalibrationStartAction].UpdateStatus(false); _actionsByLabel[BaselineCollectionAction].UpdateStatus(false); + _pairedActionWaitStates[CalibrationStartAction] = new PairedActionWaitState( + CalibrationEndCoil, + "标定中", + "标定完成", + _calibrationCompletedAction); + _pairedActionWaitStates[BaselineCollectionAction] = new PairedActionWaitState( + BaselineEndCoil, + "基线采集中", + "基线采集完成", + _baselineCollectionCompletedAction); + + _pairedActionCompletionTimer = new DispatcherTimer + { + Interval = TimeSpan.FromMilliseconds(250) + }; + _pairedActionCompletionTimer.Tick += (_, _) => RefreshPairedActionCompletions(); RefreshDeviceValues(); _refreshTimer = new DispatcherTimer @@ -155,14 +173,13 @@ public sealed class CValueCalibrationViewModel : PageViewModel return; } - LastAction = action; - if (IsPairedAction(action)) { - TogglePairedAction(action); + StartPairedAction(action); return; } + LastAction = action; ToggleHoldAction(action); } @@ -324,7 +341,7 @@ public sealed class CValueCalibrationViewModel : PageViewModel Debug.WriteLine($"C value calibration action '{action}' write failed."); } - private void TogglePairedAction(string action) + private void StartPairedAction(string action) { if (!TryGetPairedActionCoils(action, out var startCoilAddress, out var endCoilAddress, out var endActionText)) { @@ -336,34 +353,98 @@ public sealed class CValueCalibrationViewModel : PageViewModel return; } - var isActive = actionViewModel.IsActive; - var coilAddress = isActive ? endCoilAddress : startCoilAddress; - var actionText = isActive ? endActionText : action; - - if (TryStartCoilPulse(coilAddress, actionText)) + if (actionViewModel.IsBusy) { - var completedBaselineCollection = action == BaselineCollectionAction && isActive; - var completedCalibration = action == CalibrationStartAction && isActive; - LastAction = completedBaselineCollection - ? "基线采集完成" - : completedCalibration ? "标定完成" : actionText; - actionViewModel.UpdateStatus(!isActive); - - if (completedBaselineCollection) - { - _baselineCollectionCompletedAction(); - } - - if (completedCalibration) - { - _calibrationCompletedAction(); - } - return; } - LastAction = $"{actionText}失败"; - Debug.WriteLine($"C value calibration action '{actionText}' write failed."); + if (!_pairedActionWaitStates.TryGetValue(action, out var waitState)) + { + return; + } + + if (!TryStartCoilPulse(startCoilAddress, action)) + { + LastAction = $"{action}失败"; + Debug.WriteLine($"C value calibration action '{action}' write failed."); + return; + } + + var completionIsAlreadyActive = _tcpDeviceConnectionService.TryReadCoil(endCoilAddress, out var isComplete) && isComplete; + waitState.Begin(completionIsAlreadyActive); + actionViewModel.BeginWaiting(waitState.WaitingText); + _pairedActionCompletionTimer.Start(); + LastAction = waitState.WaitingText; + + if (completionIsAlreadyActive) + { + Debug.WriteLine( + $"C value calibration completion coil {endCoilAddress} was already active when '{action}' started; " + + "waiting for it to reset before accepting completion."); + } + } + + private void RefreshPairedActionCompletions() + { + var hasWaitingAction = false; + + foreach (var (action, waitState) in _pairedActionWaitStates) + { + if (!waitState.IsWaiting) + { + continue; + } + + hasWaitingAction = true; + + if (!_actionsByLabel.TryGetValue(action, out var actionViewModel)) + { + continue; + } + + if (!_tcpDeviceConnectionService.TryReadCoil(waitState.CompletionCoilAddress, out var isComplete)) + { + LastAction = $"{waitState.WaitingText},完成状态读取失败"; + Debug.WriteLine( + $"C value calibration completion coil {waitState.CompletionCoilAddress} read failed for '{action}'."); + continue; + } + + if (!isComplete) + { + waitState.MarkCompletionInactive(); + continue; + } + + if (waitState.RequiresInactiveBeforeCompletion && !waitState.HasSeenInactiveCompletionState) + { + continue; + } + + CompletePairedAction(waitState, actionViewModel); + } + + if (!hasWaitingAction) + { + _pairedActionCompletionTimer.Stop(); + } + } + + private void CompletePairedAction( + PairedActionWaitState waitState, + CValueCalibrationActionViewModel actionViewModel) + { + LastAction = waitState.CompletedText; + waitState.End(); + + try + { + waitState.CompletedAction(); + } + finally + { + actionViewModel.FinishWaiting(); + } } private bool TryStartCoilPulse(ushort coilAddress, string actionText) @@ -441,4 +522,44 @@ public sealed class CValueCalibrationViewModel : PageViewModel { return action is BaselineCollectionAction or CalibrationStartAction; } + + private sealed class PairedActionWaitState( + ushort completionCoilAddress, + string waitingText, + string completedText, + Action completedAction) + { + public ushort CompletionCoilAddress { get; } = completionCoilAddress; + + public string WaitingText { get; } = waitingText; + + public string CompletedText { get; } = completedText; + + public Action CompletedAction { get; } = completedAction; + + public bool IsWaiting { get; private set; } + + public bool RequiresInactiveBeforeCompletion { get; private set; } + + public bool HasSeenInactiveCompletionState { get; private set; } + + public void Begin(bool completionIsAlreadyActive) + { + IsWaiting = true; + RequiresInactiveBeforeCompletion = completionIsAlreadyActive; + HasSeenInactiveCompletionState = !completionIsAlreadyActive; + } + + public void MarkCompletionInactive() + { + HasSeenInactiveCompletionState = true; + } + + public void End() + { + IsWaiting = false; + RequiresInactiveBeforeCompletion = false; + HasSeenInactiveCompletionState = false; + } + } } diff --git a/ConeCalorimeter/Views/CValueCalibrationView.xaml b/ConeCalorimeter/Views/CValueCalibrationView.xaml index 1d8bffb..c38fd18 100644 --- a/ConeCalorimeter/Views/CValueCalibrationView.xaml +++ b/ConeCalorimeter/Views/CValueCalibrationView.xaml @@ -231,6 +231,7 @@