From dc83cc8800f1a59d6d9fd539977275bc3b26a3da Mon Sep 17 00:00:00 2001 From: "GukSang.Jin" Date: Mon, 15 Jun 2026 18:45:15 +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 --- DentistryHandpieces/MainWindow.xaml.cs | 53 +++ DentistryHandpieces/MainWindowViewModel.cs | 428 ++++++++++++++++----- 2 files changed, 391 insertions(+), 90 deletions(-) diff --git a/DentistryHandpieces/MainWindow.xaml.cs b/DentistryHandpieces/MainWindow.xaml.cs index 4c66884..fc70166 100644 --- a/DentistryHandpieces/MainWindow.xaml.cs +++ b/DentistryHandpieces/MainWindow.xaml.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -13,6 +14,8 @@ public partial class MainWindow : Window private readonly DispatcherTimer _hiddenSettingsPressTimer; private bool _isHiddenSettingsPressActive; private bool _hasOpenedHiddenSettingsForCurrentPress; + private bool _isCloseStopInProgress; + private bool _allowConfirmedClose; public MainWindow() { @@ -25,9 +28,59 @@ public partial class MainWindow : Window }; _hiddenSettingsPressTimer.Tick += HiddenSettingsPressTimer_Tick; Deactivated += (_, _) => ReleaseAllManualMotionTargetsAsync(); + Closing += MainWindow_Closing; Log.Information("主窗口创建完成"); } + private async void MainWindow_Closing(object? sender, CancelEventArgs e) + { + if (_allowConfirmedClose + || DataContext is not MainWindowViewModel viewModel + || !viewModel.HasActiveControlRun()) + { + return; + } + + e.Cancel = true; + if (_isCloseStopInProgress) + { + return; + } + + _isCloseStopInProgress = true; + try + { + Log.Warning("检测到活动测试,关闭窗口前开始发送并确认停止指令"); + if (!await viewModel.TryStopActiveTestsForCloseAsync()) + { + viewModel.CancelCloseStopRequest(); + MessageBox.Show( + "测试停止状态未确认,软件将保持开启。请检查设备状态并再次点击停止。", + "无法安全关闭", + MessageBoxButton.OK, + MessageBoxImage.Warning); + return; + } + + _allowConfirmedClose = true; + Close(); + } + catch (Exception ex) + { + viewModel.CancelCloseStopRequest(); + Log.Error(ex, "关闭软件前停止活动测试失败,窗口保持开启"); + MessageBox.Show( + $"关闭软件前停止测试失败,软件将保持开启:{OperatorMessageFormatter.FromException(ex)}", + "无法安全关闭", + MessageBoxButton.OK, + MessageBoxImage.Error); + } + finally + { + _isCloseStopInProgress = false; + } + } + private void HiddenSettingsHotspot_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { e.Handled = true; diff --git a/DentistryHandpieces/MainWindowViewModel.cs b/DentistryHandpieces/MainWindowViewModel.cs index 1de119e..3b73a24 100644 --- a/DentistryHandpieces/MainWindowViewModel.cs +++ b/DentistryHandpieces/MainWindowViewModel.cs @@ -43,6 +43,7 @@ public sealed class MainWindowViewModel : ObservableObject private const ushort VentValveCoil = 6; private const ushort AxialForceModeCoil = 30; private const ushort AxialStartCoil = 70; + private const ushort AxialEnabledCoil = 71; private const ushort AxialDoneCoil = 72; private const ushort AxialStopCoil = 73; private const ushort SpeedTorqueStartCoil = 80; @@ -107,7 +108,7 @@ public sealed class MainWindowViewModel : ObservableObject private static readonly TimeSpan RealtimeDataFreshnessTimeout = TimeSpan.FromSeconds(3); private static readonly TimeSpan SnapshotInterval = TimeSpan.FromSeconds(5); private static readonly TimeSpan NoLoadCaptureDuration = TimeSpan.FromSeconds(3); - private static readonly TimeSpan SpeedTorqueStartConfirmationTimeout = TimeSpan.FromSeconds(5); + private static readonly TimeSpan RunStateConfirmationTimeout = TimeSpan.FromSeconds(5); private static readonly ushort[] RealtimeRegisterAddresses = [ @@ -154,7 +155,9 @@ public sealed class MainWindowViewModel : ObservableObject [ VentValveCoil, AxialForceModeCoil, + AxialEnabledCoil, AxialDoneCoil, + SpeedTorqueEnabledCoil, SpeedTorqueDoneCoil, SpeedTorqueStableCoil, SpeedTorqueResetEnabledCoil, @@ -200,7 +203,10 @@ public sealed class MainWindowViewModel : ObservableObject private double? _finalDisplacement; private double _axialForce; private double? _finalAxialForce; + private bool _isDisplacementStarting; private bool _isDisplacementRunning; + private bool _isDisplacementStartupUncertain; + private int _axialDisabledPollCount; private double _realtimeSpeed; private double _realtimeTorque; private double _realtimePressure; @@ -215,23 +221,28 @@ public sealed class MainWindowViewModel : ObservableObject private double? _finalSpeed; private double? _finalTorque; private DateTime? _speedTorqueStartedAt; + private bool _isSpeedTorqueStarting; private bool _isSpeedTorqueRunning; - private bool _isSpeedTorqueCompletionArmed; - private bool _hasLoggedStaleSpeedTorqueDone; + private bool _isSpeedTorqueStartupUncertain; + private int _speedTorqueDisabledPollCount; private bool _wasSpeedTorqueStable; private bool _isReadingRealtime; private bool _isWritingAxialForceMode; private bool _isReadingParameterConfig; private bool _hasLoadedParameterConfigFromPlc; private bool _lastRealtimeReadFailed; + private bool _hasReportedRunStateCommunicationLoss; private DateTime _lastSuccessfulRealtimeReadAt = DateTime.MinValue; private bool _isAutoStoppingDisplacement; private bool _isAutoStoppingSpeedTorque; + private bool _isConfirmingDisplacementStop; + private bool _isConfirmingSpeedTorqueStop; private bool _isApplyingParameterConfigToInputs; private bool _isDisplacementResetting; private bool _isSpeedTorqueResetting; private bool _isSpeedTorqueZeroing; private bool _hasShownSpeedTorqueEndWarnings; + private bool _isCloseStopRequested; private DateTime _lastParameterReadFailureLogAt = DateTime.MinValue; private string _dialIndicatorText = "0.000 mm"; private string _axialAxisPositionText = "0.000 mm"; @@ -382,6 +393,39 @@ public sealed class MainWindowViewModel : ObservableObject return _parameterConfig.ToPlcConnectionConfig(); } + internal bool HasActiveControlRun() + { + return _isDisplacementStarting + || _isDisplacementRunning + || _isSpeedTorqueStarting + || _isSpeedTorqueRunning; + } + + internal async Task TryStopActiveTestsForCloseAsync() + { + _isCloseStopRequested = true; + if (_isDisplacementStarting) + { + await AbortDisplacementStartupAsync("关闭软件前已中止轴向启动。", "关闭软件"); + _isDisplacementStarting = false; + } + + if (_isSpeedTorqueStarting) + { + await AbortSpeedTorqueStartupAsync("关闭软件前已中止转速/扭矩启动。", "关闭软件"); + _isSpeedTorqueStarting = false; + } + + bool axialStopped = await StopDisplacementControlAsync("状态:关闭软件前已停止", "关闭软件前轴向停止"); + bool speedTorqueStopped = await StopSpeedTorqueControlAsync("状态:关闭软件前已停止", "关闭软件前转速/扭矩停止"); + return axialStopped && speedTorqueStopped; + } + + internal void CancelCloseStopRequest() + { + _isCloseStopRequested = false; + } + internal async Task BeginManualMotionAsync(ManualMotionTarget target) { return target switch @@ -814,38 +858,30 @@ public sealed class MainWindowViewModel : ObservableObject FinalizeNoLoadSpeedRunIfDue(); QueueSnapshotIfDue(); - if (_isDisplacementRunning && ReadCoilValue(coilValues, AxialDoneCoil)) - { - StopDisplacementTest("状态:已完成"); - } - - bool isSpeedTorqueDone = ReadCoilValue(coilValues, SpeedTorqueDoneCoil); - if (_isSpeedTorqueRunning && !_isSpeedTorqueCompletionArmed) - { - if (!isSpeedTorqueDone) - { - _isSpeedTorqueCompletionArmed = true; - _hasLoggedStaleSpeedTorqueDone = false; - Log.Information("转速/扭矩完成判定已就绪:新测试启动后已确认 M{DoneCoil}=0", SpeedTorqueDoneCoil); - } - else if (!_hasLoggedStaleSpeedTorqueDone) - { - _hasLoggedStaleSpeedTorqueDone = true; - Log.Warning("忽略转速/扭矩测试启动后的残留完成状态:M{DoneCoil}=1;等待先恢复为0后再接受完成信号", SpeedTorqueDoneCoil); - } - } - else if (_isSpeedTorqueRunning && isSpeedTorqueDone) - { - await StopSpeedTorqueTestAsync("状态:已完成"); - } + bool hadActiveControlRun = HasActiveControlRun(); + SynchronizeAxialRunState( + ReadCoilValue(coilValues, AxialEnabledCoil), + ReadCoilValue(coilValues, AxialDoneCoil)); + await SynchronizeSpeedTorqueRunStateAsync( + ReadCoilValue(coilValues, SpeedTorqueEnabledCoil), + ReadCoilValue(coilValues, SpeedTorqueDoneCoil)); await AutoStopIfSetpointReachedAsync(); await AutoStopIfSpeedTorqueProtectionReachedAsync(); if (_lastRealtimeReadFailed) { - StatusText = "实时数据已恢复"; + if (HasActiveControlRun()) + { + StatusText = "PLC 实时通信已恢复,测试运行状态已按实际点位同步。"; + } + else if (!hadActiveControlRun) + { + StatusText = "实时数据已恢复"; + } + _lastRealtimeReadFailed = false; + _hasReportedRunStateCommunicationLoss = false; Log.Information("PLC实时数据读取已恢复"); } } @@ -857,6 +893,15 @@ public sealed class MainWindowViewModel : ObservableObject _lastRealtimeReadFailed = true; Log.Warning(ex, "PLC实时数据读取失败,后续相同故障将等待恢复后再记录"); } + + if (HasActiveControlRun() + && !_hasReportedRunStateCommunicationLoss + && DateTime.Now - _lastSuccessfulRealtimeReadAt > RealtimeDataFreshnessTimeout) + { + _hasReportedRunStateCommunicationLoss = true; + StatusText = "PLC 通讯异常超过 3 秒,测试运行状态无法确认;保持测试中并持续重连。"; + Log.Error("PLC通讯异常超过 {TimeoutSeconds} 秒,活动测试运行状态无法确认,软件保持运行状态", RealtimeDataFreshnessTimeout.TotalSeconds); + } } finally { @@ -864,6 +909,48 @@ public sealed class MainWindowViewModel : ObservableObject } } + private void SynchronizeAxialRunState(bool enabled, bool done) + { + if (!_isDisplacementRunning || _isConfirmingDisplacementStop) + { + _axialDisabledPollCount = 0; + return; + } + + if (done) + { + StopDisplacementTest("状态:已完成"); + return; + } + + _axialDisabledPollCount = enabled ? 0 : _axialDisabledPollCount + 1; + if (_axialDisabledPollCount >= 3) + { + StopDisplacementTest("状态:PLC 轴向运行使能已断开"); + } + } + + private async Task SynchronizeSpeedTorqueRunStateAsync(bool enabled, bool done) + { + if (!_isSpeedTorqueRunning || _isConfirmingSpeedTorqueStop) + { + _speedTorqueDisabledPollCount = 0; + return; + } + + if (done) + { + await StopSpeedTorqueTestAsync("状态:已完成"); + return; + } + + _speedTorqueDisabledPollCount = enabled ? 0 : _speedTorqueDisabledPollCount + 1; + if (_speedTorqueDisabledPollCount >= 3) + { + await StopSpeedTorqueTestAsync("状态:PLC 转速/扭矩运行使能已断开"); + } + } + private async void ParameterRetryTimer_Tick(object? sender, EventArgs e) { if (_hasLoadedParameterConfigFromPlc) @@ -2597,18 +2684,20 @@ public sealed class MainWindowViewModel : ObservableObject return; } - if (_isSpeedTorqueRunning) + if (_isSpeedTorqueStarting || _isSpeedTorqueRunning) { UpdateSpeedTorqueDisplay(); return; } + _isSpeedTorqueStarting = true; SpeedTorqueTestButtonText = "启动中"; StatusText = "转速/扭矩测试启动中..."; if (!await PulsePlcAsync(SpeedTorqueStartCoil, "转速/扭矩测试")) { await AbortSpeedTorqueStartupAsync("M80 启动指令失败,已停止测试。", "M80启动指令失败"); + _isSpeedTorqueStarting = false; return; } @@ -2617,20 +2706,30 @@ public sealed class MainWindowViewModel : ObservableObject if (!await WaitForSpeedTorqueStartAsync()) { await AbortSpeedTorqueStartupAsync("PLC 未进入扭矩测试运行状态,已停止测试。", "启动状态确认失败"); + _isSpeedTorqueStarting = false; + return; + } + + if (_isCloseStopRequested) + { + await AbortSpeedTorqueStartupAsync("关闭软件请求已中止转速/扭矩启动。", "关闭软件"); + _isSpeedTorqueStarting = false; return; } if (!TryGetRealtimeSpeed(out _) || !TryGetRealtimeTorque(out _)) { await AbortSpeedTorqueStartupAsync("实时数据无效,已停止测试。", "启动数据无效"); + _isSpeedTorqueStarting = false; UpdateSpeedTorqueDisplay(); return; } + _isSpeedTorqueStarting = false; PrepareSessionForNewRun(); _isSpeedTorqueRunning = true; - _isSpeedTorqueCompletionArmed = true; - _hasLoggedStaleSpeedTorqueDone = false; + _isSpeedTorqueStartupUncertain = false; + _speedTorqueDisabledPollCount = 0; _wasSpeedTorqueStable = false; SpeedTorqueTestButtonText = "测试中"; _hasShownSpeedTorqueEndWarnings = false; @@ -2655,7 +2754,15 @@ public sealed class MainWindowViewModel : ObservableObject private async Task WaitForSpeedTorqueStartAsync() { - DateTime deadline = DateTime.Now.Add(SpeedTorqueStartConfirmationTimeout); + return await WaitForRunStartAsync( + SpeedTorqueEnabledCoil, + SpeedTorqueDoneCoil, + "转速/扭矩"); + } + + private async Task WaitForRunStartAsync(ushort enabledCoil, ushort doneCoil, string testName) + { + DateTime deadline = DateTime.Now.Add(RunStateConfirmationTimeout); bool lastEnabled = false; bool lastDone = false; bool hasReadState = false; @@ -2667,17 +2774,18 @@ public sealed class MainWindowViewModel : ObservableObject { IReadOnlyDictionary values = await _plcCoilService.ReadCoilValuesAsync( _parameterConfig.ToPlcConnectionConfig(), - [SpeedTorqueEnabledCoil, SpeedTorqueDoneCoil]); - lastEnabled = ReadCoilValue(values, SpeedTorqueEnabledCoil); - lastDone = ReadCoilValue(values, SpeedTorqueDoneCoil); + [enabledCoil, doneCoil]); + lastEnabled = ReadCoilValue(values, enabledCoil); + lastDone = ReadCoilValue(values, doneCoil); hasReadState = true; if (lastEnabled && !lastDone) { Log.Information( - "转速/扭矩启动状态确认成功:M{EnabledCoil}=1,M{DoneCoil}=0,通气阀保持手动控制", - SpeedTorqueEnabledCoil, - SpeedTorqueDoneCoil); + "{TestName}启动状态确认成功:M{EnabledCoil}=1,M{DoneCoil}=0", + testName, + enabledCoil, + doneCoil); return true; } } @@ -2688,50 +2796,47 @@ public sealed class MainWindowViewModel : ObservableObject hasLoggedReadFailure = true; Log.Warning( ex, - "转速/扭矩启动状态读取失败,继续等待 M{EnabledCoil}/M{DoneCoil}", - SpeedTorqueEnabledCoil, - SpeedTorqueDoneCoil); + "{TestName}启动状态读取失败,继续等待 M{EnabledCoil}/M{DoneCoil}", + testName, + enabledCoil, + doneCoil); } } await Task.Delay(200); } - StatusText = "M80 启动后 PLC 未确认扭矩使能,已阻止测试运行。"; + StatusText = $"{testName}启动后 PLC 未确认运行使能,已阻止测试运行。"; Log.Error( - "转速/扭矩启动状态确认超时:等待 {TimeoutSeconds} 秒,已读取状态 {HasReadState},最后 M{EnabledCoil}={Enabled},M{DoneCoil}={Done}", - SpeedTorqueStartConfirmationTimeout.TotalSeconds, + "{TestName}启动状态确认超时:等待 {TimeoutSeconds} 秒,已读取状态 {HasReadState},最后 M{EnabledCoil}={Enabled},M{DoneCoil}={Done}", + testName, + RunStateConfirmationTimeout.TotalSeconds, hasReadState, - SpeedTorqueEnabledCoil, + enabledCoil, lastEnabled ? 1 : 0, - SpeedTorqueDoneCoil, + doneCoil, lastDone ? 1 : 0); return false; } private async Task AbortSpeedTorqueStartupAsync(string status, string reason) { - bool stopSent = await PulsePlcAsync(SpeedTorqueStopCoil, $"转速/扭矩{reason}停止"); - _isSpeedTorqueRunning = false; - _isSpeedTorqueCompletionArmed = false; - _hasLoggedStaleSpeedTorqueDone = false; - SpeedTorqueTestButtonText = "测试"; - StatusText = status; + bool stopped = await SendStopAndConfirmAsync(SpeedTorqueStopCoil, SpeedTorqueEnabledCoil, $"转速/扭矩{reason}停止"); + _isSpeedTorqueRunning = !stopped; + _isSpeedTorqueStartupUncertain = !stopped; + _speedTorqueDisabledPollCount = 0; + SpeedTorqueTestButtonText = stopped ? "测试" : "停止未确认"; + StatusText = stopped ? status : $"{status} M83 停止未确认,请再次点击停止。"; Log.Error( - "转速/扭矩启动已中止:原因 {Reason},M{StopCoil}停止指令成功 {StopSent},通气阀保持手动控制", + "转速/扭矩启动已中止:原因 {Reason},M{StopCoil}停止状态确认 {Stopped},通气阀保持手动控制", reason, SpeedTorqueStopCoil, - stopSent); + stopped); } private async Task StopSpeedTorqueAsync() { - if (!await PulsePlcAsync(SpeedTorqueStopCoil, "转速/扭矩停止")) - { - return; - } - - await StopSpeedTorqueTestAsync("状态:已停止"); + await StopSpeedTorqueControlAsync("状态:已停止", "转速/扭矩停止"); } private async Task ResetSpeedTorqueAsync() @@ -2767,8 +2872,8 @@ public sealed class MainWindowViewModel : ObservableObject FinalizeSpeedTorqueRun("复位前中止"); PersistCurrentPayloadSnapshot("转速/扭矩复位前"); _isSpeedTorqueRunning = false; - _isSpeedTorqueCompletionArmed = false; - _hasLoggedStaleSpeedTorqueDone = false; + _isSpeedTorqueStartupUncertain = false; + _speedTorqueDisabledPollCount = 0; SpeedTorqueTestButtonText = "测试"; _realtimeSpeed = 0; _realtimeTorque = 0; @@ -2857,31 +2962,52 @@ public sealed class MainWindowViewModel : ObservableObject private async Task StartDisplacementAsync() { - if (_isDisplacementRunning) + if (_isDisplacementStarting || _isDisplacementRunning) { UpdateDisplacementDisplay(); return; } + _isDisplacementStarting = true; DisplacementTestButtonText = "启动中"; StatusText = "轴向测试启动中..."; if (!await PulsePlcAsync(AxialStartCoil, "轴向测试")) { + _isDisplacementStarting = false; DisplacementTestButtonText = "测试"; return; } + DisplacementTestButtonText = "启动确认中"; + StatusText = "轴向测试启动指令已发送,正在确认 PLC 轴向使能。"; + if (!await WaitForRunStartAsync(AxialEnabledCoil, AxialDoneCoil, "轴向测试")) + { + await AbortDisplacementStartupAsync("PLC 未进入轴向测试运行状态,已停止测试。", "启动状态确认失败"); + _isDisplacementStarting = false; + return; + } + + if (_isCloseStopRequested) + { + await AbortDisplacementStartupAsync("关闭软件请求已中止轴向启动。", "关闭软件"); + _isDisplacementStarting = false; + return; + } + if (!TryGetDialValue(out _) || !TryGetAxialForceValue(out _)) { - await PulsePlcAsync(AxialStopCoil, "实时数据无效,停止轴向测试"); - DisplacementTestButtonText = "测试"; + await AbortDisplacementStartupAsync("实时数据无效,已停止轴向测试。", "启动数据无效"); + _isDisplacementStarting = false; UpdateDisplacementDisplay(); return; } + _isDisplacementStarting = false; PrepareSessionForNewRun(); _isDisplacementRunning = true; + _isDisplacementStartupUncertain = false; + _axialDisabledPollCount = 0; DisplacementTestButtonText = "测试中"; _activeDisplacementRun = CreateTestRun("轴向位移动量测试"); _finalDisplacement = null; @@ -2894,12 +3020,22 @@ public sealed class MainWindowViewModel : ObservableObject private async Task StopDisplacementAsync() { - if (!await PulsePlcAsync(AxialStopCoil, "轴向停止")) - { - return; - } + await StopDisplacementControlAsync("状态:已停止", "轴向停止"); + } - StopDisplacementTest("状态:已停止"); + private async Task AbortDisplacementStartupAsync(string status, string reason) + { + bool stopped = await SendStopAndConfirmAsync(AxialStopCoil, AxialEnabledCoil, $"轴向{reason}停止"); + _isDisplacementRunning = !stopped; + _isDisplacementStartupUncertain = !stopped; + _axialDisabledPollCount = 0; + DisplacementTestButtonText = stopped ? "测试" : "停止未确认"; + StatusText = stopped ? status : $"{status} M73 停止未确认,请再次点击停止。"; + Log.Error( + "轴向测试启动已中止:原因 {Reason},M{StopCoil}停止状态确认 {Stopped}", + reason, + AxialStopCoil, + stopped); } private async Task ResetDisplacementAsync() @@ -2940,6 +3076,8 @@ public sealed class MainWindowViewModel : ObservableObject _finalAxialForce = null; await UpdateAxialForceFromInputAsync(); _isDisplacementRunning = false; + _isDisplacementStartupUncertain = false; + _axialDisabledPollCount = 0; DisplacementTestButtonText = "测试"; UpdateDisplacementDisplay(); Log.Information("轴向复位完成,千分表零点 {DialZero}", _dialZero); @@ -3006,6 +3144,53 @@ public sealed class MainWindowViewModel : ObservableObject } } + private async Task SendStopAndConfirmAsync(ushort stopCoil, ushort enabledCoil, string actionName) + { + if (!await PulsePlcAsync(stopCoil, actionName)) + { + return false; + } + + DateTime deadline = DateTime.Now.Add(RunStateConfirmationTimeout); + bool hasLoggedReadFailure = false; + while (DateTime.Now < deadline) + { + try + { + IReadOnlyDictionary values = await _plcCoilService.ReadCoilValuesAsync( + _parameterConfig.ToPlcConnectionConfig(), + [enabledCoil]); + if (!ReadCoilValue(values, enabledCoil)) + { + Log.Information( + "PLC停止状态确认成功:{ActionName},M{StopCoil}已触发,M{EnabledCoil}=0", + actionName, + stopCoil, + enabledCoil); + return true; + } + } + catch (Exception ex) + { + if (!hasLoggedReadFailure) + { + hasLoggedReadFailure = true; + Log.Warning(ex, "PLC停止状态读取失败:{ActionName},继续等待 M{EnabledCoil}=0", actionName, enabledCoil); + } + } + + await Task.Delay(200); + } + + StatusText = $"{actionName}未确认:PLC M{enabledCoil}仍未确认断开。"; + Log.Error( + "PLC停止状态确认超时:{ActionName},等待 {TimeoutSeconds} 秒后 M{EnabledCoil}仍未确认断开", + actionName, + RunStateConfirmationTimeout.TotalSeconds, + enabledCoil); + return false; + } + private async Task ExecuteZeroingSequenceAsync( string testName, params (ushort CoilAddress, string ActionName)[] zeroingActions) @@ -3249,10 +3434,7 @@ public sealed class MainWindowViewModel : ObservableObject try { Log.Warning("轴向测试触发自动停止:{Status},位移 {Displacement},轴向力 {AxialForce}", status, _relativeDisplacement, GetScaledAxialForce()); - if (await PulsePlcAsync(AxialStopCoil, "轴向保护停止")) - { - StopDisplacementTest(status); - } + await StopDisplacementControlAsync(status, "轴向保护停止"); } finally { @@ -3260,21 +3442,59 @@ public sealed class MainWindowViewModel : ObservableObject } } + private async Task StopDisplacementControlAsync(string status, string actionName) + { + if (!_isDisplacementRunning) + { + return true; + } + + _isConfirmingDisplacementStop = true; + try + { + StatusText = $"{actionName}指令已发送,正在确认 PLC 轴向使能断开。"; + if (!await SendStopAndConfirmAsync(AxialStopCoil, AxialEnabledCoil, actionName)) + { + DisplacementTestButtonText = "停止未确认"; + StatusText = $"{actionName}未确认,现场运行状态未知;请检查设备并再次点击停止。"; + return false; + } + + StopDisplacementTest(status); + return true; + } + finally + { + _isConfirmingDisplacementStop = false; + } + } + private void StopDisplacementTest(string status) { - if (!_isDisplacementRunning && _finalDisplacement.HasValue) + if (!_isDisplacementRunning) { UpdateDisplacementDisplay(); return; } _isDisplacementRunning = false; + _axialDisabledPollCount = 0; DisplacementTestButtonText = "测试"; + if (_isDisplacementStartupUncertain) + { + _isDisplacementStartupUncertain = false; + StatusText = status; + UpdateDisplacementDisplay(); + Log.Warning("轴向启动未确认状态已解除:{Status}", status); + return; + } + _finalDisplacement = Math.Abs(_axialSampleDifference) > 0.000001 ? _axialSampleDifference : _relativeDisplacement; _finalAxialForce = GetScaledAxialForce(); FinalizeDisplacementRun(status); PersistCurrentPayloadSnapshot("轴向测试停止"); UpdateDisplacementDisplay(); + StatusText = status; Log.Information("轴向测试停止:{Status},最终位移 {FinalDisplacement},最终轴向力 {FinalAxialForce}", status, _finalDisplacement, _finalAxialForce); } @@ -3345,10 +3565,7 @@ public sealed class MainWindowViewModel : ObservableObject try { Log.Warning("转速/扭矩测试触发自动停止:{Status},位移 {Displacement},转速 {Speed},扭矩 {Torque}", status, _speedTorqueDisplacement, _realtimeSpeed, GetScaledTorque()); - if (await PulsePlcAsync(SpeedTorqueStopCoil, "转速/扭矩保护停止")) - { - await StopSpeedTorqueTestAsync(status); - } + await StopSpeedTorqueControlAsync(status, "转速/扭矩保护停止"); } finally { @@ -3356,30 +3573,60 @@ public sealed class MainWindowViewModel : ObservableObject } } + private async Task StopSpeedTorqueControlAsync(string status, string actionName) + { + if (!_isSpeedTorqueRunning) + { + return true; + } + + _isConfirmingSpeedTorqueStop = true; + try + { + StatusText = $"{actionName}指令已发送,正在确认 PLC 扭矩使能断开。"; + if (!await SendStopAndConfirmAsync(SpeedTorqueStopCoil, SpeedTorqueEnabledCoil, actionName)) + { + SpeedTorqueTestButtonText = "停止未确认"; + StatusText = $"{actionName}未确认,现场运行状态未知;请检查设备并再次点击停止。"; + return false; + } + + await StopSpeedTorqueTestAsync(status); + return true; + } + finally + { + _isConfirmingSpeedTorqueStop = false; + } + } + private async Task StopSpeedTorqueTestAsync(string status) { - if (!TryGetRealtimeSpeed(out double speed) || !TryGetRealtimeTorque(out double torque)) + if (!_isSpeedTorqueRunning) { - _isSpeedTorqueRunning = false; - _isSpeedTorqueCompletionArmed = false; - _hasLoggedStaleSpeedTorqueDone = false; - SpeedTorqueTestButtonText = "测试"; UpdateSpeedTorqueDisplay(); return; } _isSpeedTorqueRunning = false; - _isSpeedTorqueCompletionArmed = false; - _hasLoggedStaleSpeedTorqueDone = false; + _speedTorqueDisabledPollCount = 0; SpeedTorqueTestButtonText = "测试"; - _realtimeSpeed = speed; - _realtimeTorque = torque; - _finalSpeed = speed; + if (_isSpeedTorqueStartupUncertain) + { + _isSpeedTorqueStartupUncertain = false; + StatusText = status; + UpdateSpeedTorqueDisplay(); + Log.Warning("转速/扭矩启动未确认状态已解除:{Status}", status); + return; + } + + _finalSpeed = _realtimeSpeed; _finalTorque = GetScaledTorque(); _finalSpeedTorqueDisplacement = _speedTorqueDisplacement; FinalizeSpeedTorqueRun(status); PersistCurrentPayloadSnapshot("转速/扭矩测试停止"); UpdateSpeedTorqueDisplay(); + StatusText = status; Log.Information("转速/扭矩测试停止:{Status},最终位移 {FinalDisplacement},最终转速 {FinalSpeed},最终扭矩 {FinalTorque},通气阀保持手动控制", status, _finalSpeedTorqueDisplacement, _finalSpeed, _finalTorque); await ShowSpeedTorqueEndWarningsAsync(); @@ -3983,7 +4230,8 @@ public sealed class MainWindowViewModel : ObservableObject private bool HasActiveRuns() { - return _activeDisplacementRun is not null + return HasActiveControlRun() + || _activeDisplacementRun is not null || _activeSpeedTorqueRun is not null || _activeNoLoadSpeedRun is not null; }