This commit is contained in:
GukSang.Jin
2026-06-15 18:45:15 +08:00
parent 57b423fef1
commit dc83cc8800
2 changed files with 391 additions and 90 deletions

View File

@@ -1,3 +1,4 @@
using System.ComponentModel;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
@@ -13,6 +14,8 @@ public partial class MainWindow : Window
private readonly DispatcherTimer _hiddenSettingsPressTimer; private readonly DispatcherTimer _hiddenSettingsPressTimer;
private bool _isHiddenSettingsPressActive; private bool _isHiddenSettingsPressActive;
private bool _hasOpenedHiddenSettingsForCurrentPress; private bool _hasOpenedHiddenSettingsForCurrentPress;
private bool _isCloseStopInProgress;
private bool _allowConfirmedClose;
public MainWindow() public MainWindow()
{ {
@@ -25,9 +28,59 @@ public partial class MainWindow : Window
}; };
_hiddenSettingsPressTimer.Tick += HiddenSettingsPressTimer_Tick; _hiddenSettingsPressTimer.Tick += HiddenSettingsPressTimer_Tick;
Deactivated += (_, _) => ReleaseAllManualMotionTargetsAsync(); Deactivated += (_, _) => ReleaseAllManualMotionTargetsAsync();
Closing += MainWindow_Closing;
Log.Information("主窗口创建完成"); 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) private void HiddenSettingsHotspot_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{ {
e.Handled = true; e.Handled = true;

View File

@@ -43,6 +43,7 @@ public sealed class MainWindowViewModel : ObservableObject
private const ushort VentValveCoil = 6; private const ushort VentValveCoil = 6;
private const ushort AxialForceModeCoil = 30; private const ushort AxialForceModeCoil = 30;
private const ushort AxialStartCoil = 70; private const ushort AxialStartCoil = 70;
private const ushort AxialEnabledCoil = 71;
private const ushort AxialDoneCoil = 72; private const ushort AxialDoneCoil = 72;
private const ushort AxialStopCoil = 73; private const ushort AxialStopCoil = 73;
private const ushort SpeedTorqueStartCoil = 80; 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 RealtimeDataFreshnessTimeout = TimeSpan.FromSeconds(3);
private static readonly TimeSpan SnapshotInterval = TimeSpan.FromSeconds(5); private static readonly TimeSpan SnapshotInterval = TimeSpan.FromSeconds(5);
private static readonly TimeSpan NoLoadCaptureDuration = TimeSpan.FromSeconds(3); 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 = private static readonly ushort[] RealtimeRegisterAddresses =
[ [
@@ -154,7 +155,9 @@ public sealed class MainWindowViewModel : ObservableObject
[ [
VentValveCoil, VentValveCoil,
AxialForceModeCoil, AxialForceModeCoil,
AxialEnabledCoil,
AxialDoneCoil, AxialDoneCoil,
SpeedTorqueEnabledCoil,
SpeedTorqueDoneCoil, SpeedTorqueDoneCoil,
SpeedTorqueStableCoil, SpeedTorqueStableCoil,
SpeedTorqueResetEnabledCoil, SpeedTorqueResetEnabledCoil,
@@ -200,7 +203,10 @@ public sealed class MainWindowViewModel : ObservableObject
private double? _finalDisplacement; private double? _finalDisplacement;
private double _axialForce; private double _axialForce;
private double? _finalAxialForce; private double? _finalAxialForce;
private bool _isDisplacementStarting;
private bool _isDisplacementRunning; private bool _isDisplacementRunning;
private bool _isDisplacementStartupUncertain;
private int _axialDisabledPollCount;
private double _realtimeSpeed; private double _realtimeSpeed;
private double _realtimeTorque; private double _realtimeTorque;
private double _realtimePressure; private double _realtimePressure;
@@ -215,23 +221,28 @@ public sealed class MainWindowViewModel : ObservableObject
private double? _finalSpeed; private double? _finalSpeed;
private double? _finalTorque; private double? _finalTorque;
private DateTime? _speedTorqueStartedAt; private DateTime? _speedTorqueStartedAt;
private bool _isSpeedTorqueStarting;
private bool _isSpeedTorqueRunning; private bool _isSpeedTorqueRunning;
private bool _isSpeedTorqueCompletionArmed; private bool _isSpeedTorqueStartupUncertain;
private bool _hasLoggedStaleSpeedTorqueDone; private int _speedTorqueDisabledPollCount;
private bool _wasSpeedTorqueStable; private bool _wasSpeedTorqueStable;
private bool _isReadingRealtime; private bool _isReadingRealtime;
private bool _isWritingAxialForceMode; private bool _isWritingAxialForceMode;
private bool _isReadingParameterConfig; private bool _isReadingParameterConfig;
private bool _hasLoadedParameterConfigFromPlc; private bool _hasLoadedParameterConfigFromPlc;
private bool _lastRealtimeReadFailed; private bool _lastRealtimeReadFailed;
private bool _hasReportedRunStateCommunicationLoss;
private DateTime _lastSuccessfulRealtimeReadAt = DateTime.MinValue; private DateTime _lastSuccessfulRealtimeReadAt = DateTime.MinValue;
private bool _isAutoStoppingDisplacement; private bool _isAutoStoppingDisplacement;
private bool _isAutoStoppingSpeedTorque; private bool _isAutoStoppingSpeedTorque;
private bool _isConfirmingDisplacementStop;
private bool _isConfirmingSpeedTorqueStop;
private bool _isApplyingParameterConfigToInputs; private bool _isApplyingParameterConfigToInputs;
private bool _isDisplacementResetting; private bool _isDisplacementResetting;
private bool _isSpeedTorqueResetting; private bool _isSpeedTorqueResetting;
private bool _isSpeedTorqueZeroing; private bool _isSpeedTorqueZeroing;
private bool _hasShownSpeedTorqueEndWarnings; private bool _hasShownSpeedTorqueEndWarnings;
private bool _isCloseStopRequested;
private DateTime _lastParameterReadFailureLogAt = DateTime.MinValue; private DateTime _lastParameterReadFailureLogAt = DateTime.MinValue;
private string _dialIndicatorText = "0.000 mm"; private string _dialIndicatorText = "0.000 mm";
private string _axialAxisPositionText = "0.000 mm"; private string _axialAxisPositionText = "0.000 mm";
@@ -382,6 +393,39 @@ public sealed class MainWindowViewModel : ObservableObject
return _parameterConfig.ToPlcConnectionConfig(); return _parameterConfig.ToPlcConnectionConfig();
} }
internal bool HasActiveControlRun()
{
return _isDisplacementStarting
|| _isDisplacementRunning
|| _isSpeedTorqueStarting
|| _isSpeedTorqueRunning;
}
internal async Task<bool> 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<bool> BeginManualMotionAsync(ManualMotionTarget target) internal async Task<bool> BeginManualMotionAsync(ManualMotionTarget target)
{ {
return target switch return target switch
@@ -814,38 +858,30 @@ public sealed class MainWindowViewModel : ObservableObject
FinalizeNoLoadSpeedRunIfDue(); FinalizeNoLoadSpeedRunIfDue();
QueueSnapshotIfDue(); QueueSnapshotIfDue();
if (_isDisplacementRunning && ReadCoilValue(coilValues, AxialDoneCoil)) bool hadActiveControlRun = HasActiveControlRun();
{ SynchronizeAxialRunState(
StopDisplacementTest("状态:已完成"); ReadCoilValue(coilValues, AxialEnabledCoil),
} ReadCoilValue(coilValues, AxialDoneCoil));
await SynchronizeSpeedTorqueRunStateAsync(
bool isSpeedTorqueDone = ReadCoilValue(coilValues, SpeedTorqueDoneCoil); ReadCoilValue(coilValues, SpeedTorqueEnabledCoil),
if (_isSpeedTorqueRunning && !_isSpeedTorqueCompletionArmed) ReadCoilValue(coilValues, SpeedTorqueDoneCoil));
{
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("状态:已完成");
}
await AutoStopIfSetpointReachedAsync(); await AutoStopIfSetpointReachedAsync();
await AutoStopIfSpeedTorqueProtectionReachedAsync(); await AutoStopIfSpeedTorqueProtectionReachedAsync();
if (_lastRealtimeReadFailed) if (_lastRealtimeReadFailed)
{ {
StatusText = "实时数据已恢复"; if (HasActiveControlRun())
{
StatusText = "PLC 实时通信已恢复,测试运行状态已按实际点位同步。";
}
else if (!hadActiveControlRun)
{
StatusText = "实时数据已恢复";
}
_lastRealtimeReadFailed = false; _lastRealtimeReadFailed = false;
_hasReportedRunStateCommunicationLoss = false;
Log.Information("PLC实时数据读取已恢复"); Log.Information("PLC实时数据读取已恢复");
} }
} }
@@ -857,6 +893,15 @@ public sealed class MainWindowViewModel : ObservableObject
_lastRealtimeReadFailed = true; _lastRealtimeReadFailed = true;
Log.Warning(ex, "PLC实时数据读取失败后续相同故障将等待恢复后再记录"); Log.Warning(ex, "PLC实时数据读取失败后续相同故障将等待恢复后再记录");
} }
if (HasActiveControlRun()
&& !_hasReportedRunStateCommunicationLoss
&& DateTime.Now - _lastSuccessfulRealtimeReadAt > RealtimeDataFreshnessTimeout)
{
_hasReportedRunStateCommunicationLoss = true;
StatusText = "PLC 通讯异常超过 3 秒,测试运行状态无法确认;保持测试中并持续重连。";
Log.Error("PLC通讯异常超过 {TimeoutSeconds} 秒,活动测试运行状态无法确认,软件保持运行状态", RealtimeDataFreshnessTimeout.TotalSeconds);
}
} }
finally 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) private async void ParameterRetryTimer_Tick(object? sender, EventArgs e)
{ {
if (_hasLoadedParameterConfigFromPlc) if (_hasLoadedParameterConfigFromPlc)
@@ -2597,18 +2684,20 @@ public sealed class MainWindowViewModel : ObservableObject
return; return;
} }
if (_isSpeedTorqueRunning) if (_isSpeedTorqueStarting || _isSpeedTorqueRunning)
{ {
UpdateSpeedTorqueDisplay(); UpdateSpeedTorqueDisplay();
return; return;
} }
_isSpeedTorqueStarting = true;
SpeedTorqueTestButtonText = "启动中"; SpeedTorqueTestButtonText = "启动中";
StatusText = "转速/扭矩测试启动中..."; StatusText = "转速/扭矩测试启动中...";
if (!await PulsePlcAsync(SpeedTorqueStartCoil, "转速/扭矩测试")) if (!await PulsePlcAsync(SpeedTorqueStartCoil, "转速/扭矩测试"))
{ {
await AbortSpeedTorqueStartupAsync("M80 启动指令失败,已停止测试。", "M80启动指令失败"); await AbortSpeedTorqueStartupAsync("M80 启动指令失败,已停止测试。", "M80启动指令失败");
_isSpeedTorqueStarting = false;
return; return;
} }
@@ -2617,20 +2706,30 @@ public sealed class MainWindowViewModel : ObservableObject
if (!await WaitForSpeedTorqueStartAsync()) if (!await WaitForSpeedTorqueStartAsync())
{ {
await AbortSpeedTorqueStartupAsync("PLC 未进入扭矩测试运行状态,已停止测试。", "启动状态确认失败"); await AbortSpeedTorqueStartupAsync("PLC 未进入扭矩测试运行状态,已停止测试。", "启动状态确认失败");
_isSpeedTorqueStarting = false;
return;
}
if (_isCloseStopRequested)
{
await AbortSpeedTorqueStartupAsync("关闭软件请求已中止转速/扭矩启动。", "关闭软件");
_isSpeedTorqueStarting = false;
return; return;
} }
if (!TryGetRealtimeSpeed(out _) || !TryGetRealtimeTorque(out _)) if (!TryGetRealtimeSpeed(out _) || !TryGetRealtimeTorque(out _))
{ {
await AbortSpeedTorqueStartupAsync("实时数据无效,已停止测试。", "启动数据无效"); await AbortSpeedTorqueStartupAsync("实时数据无效,已停止测试。", "启动数据无效");
_isSpeedTorqueStarting = false;
UpdateSpeedTorqueDisplay(); UpdateSpeedTorqueDisplay();
return; return;
} }
_isSpeedTorqueStarting = false;
PrepareSessionForNewRun(); PrepareSessionForNewRun();
_isSpeedTorqueRunning = true; _isSpeedTorqueRunning = true;
_isSpeedTorqueCompletionArmed = true; _isSpeedTorqueStartupUncertain = false;
_hasLoggedStaleSpeedTorqueDone = false; _speedTorqueDisabledPollCount = 0;
_wasSpeedTorqueStable = false; _wasSpeedTorqueStable = false;
SpeedTorqueTestButtonText = "测试中"; SpeedTorqueTestButtonText = "测试中";
_hasShownSpeedTorqueEndWarnings = false; _hasShownSpeedTorqueEndWarnings = false;
@@ -2655,7 +2754,15 @@ public sealed class MainWindowViewModel : ObservableObject
private async Task<bool> WaitForSpeedTorqueStartAsync() private async Task<bool> WaitForSpeedTorqueStartAsync()
{ {
DateTime deadline = DateTime.Now.Add(SpeedTorqueStartConfirmationTimeout); return await WaitForRunStartAsync(
SpeedTorqueEnabledCoil,
SpeedTorqueDoneCoil,
"转速/扭矩");
}
private async Task<bool> WaitForRunStartAsync(ushort enabledCoil, ushort doneCoil, string testName)
{
DateTime deadline = DateTime.Now.Add(RunStateConfirmationTimeout);
bool lastEnabled = false; bool lastEnabled = false;
bool lastDone = false; bool lastDone = false;
bool hasReadState = false; bool hasReadState = false;
@@ -2667,17 +2774,18 @@ public sealed class MainWindowViewModel : ObservableObject
{ {
IReadOnlyDictionary<ushort, bool> values = await _plcCoilService.ReadCoilValuesAsync( IReadOnlyDictionary<ushort, bool> values = await _plcCoilService.ReadCoilValuesAsync(
_parameterConfig.ToPlcConnectionConfig(), _parameterConfig.ToPlcConnectionConfig(),
[SpeedTorqueEnabledCoil, SpeedTorqueDoneCoil]); [enabledCoil, doneCoil]);
lastEnabled = ReadCoilValue(values, SpeedTorqueEnabledCoil); lastEnabled = ReadCoilValue(values, enabledCoil);
lastDone = ReadCoilValue(values, SpeedTorqueDoneCoil); lastDone = ReadCoilValue(values, doneCoil);
hasReadState = true; hasReadState = true;
if (lastEnabled && !lastDone) if (lastEnabled && !lastDone)
{ {
Log.Information( Log.Information(
"转速/扭矩启动状态确认成功M{EnabledCoil}=1M{DoneCoil}=0,通气阀保持手动控制", "{TestName}启动状态确认成功M{EnabledCoil}=1M{DoneCoil}=0",
SpeedTorqueEnabledCoil, testName,
SpeedTorqueDoneCoil); enabledCoil,
doneCoil);
return true; return true;
} }
} }
@@ -2688,50 +2796,47 @@ public sealed class MainWindowViewModel : ObservableObject
hasLoggedReadFailure = true; hasLoggedReadFailure = true;
Log.Warning( Log.Warning(
ex, ex,
"转速/扭矩启动状态读取失败,继续等待 M{EnabledCoil}/M{DoneCoil}", "{TestName}启动状态读取失败,继续等待 M{EnabledCoil}/M{DoneCoil}",
SpeedTorqueEnabledCoil, testName,
SpeedTorqueDoneCoil); enabledCoil,
doneCoil);
} }
} }
await Task.Delay(200); await Task.Delay(200);
} }
StatusText = "M80 启动后 PLC 未确认扭矩使能,已阻止测试运行。"; StatusText = $"{testName}启动后 PLC 未确认运行使能,已阻止测试运行。";
Log.Error( Log.Error(
"转速/扭矩启动状态确认超时:等待 {TimeoutSeconds} 秒,已读取状态 {HasReadState},最后 M{EnabledCoil}={Enabled}M{DoneCoil}={Done}", "{TestName}启动状态确认超时:等待 {TimeoutSeconds} 秒,已读取状态 {HasReadState},最后 M{EnabledCoil}={Enabled}M{DoneCoil}={Done}",
SpeedTorqueStartConfirmationTimeout.TotalSeconds, testName,
RunStateConfirmationTimeout.TotalSeconds,
hasReadState, hasReadState,
SpeedTorqueEnabledCoil, enabledCoil,
lastEnabled ? 1 : 0, lastEnabled ? 1 : 0,
SpeedTorqueDoneCoil, doneCoil,
lastDone ? 1 : 0); lastDone ? 1 : 0);
return false; return false;
} }
private async Task AbortSpeedTorqueStartupAsync(string status, string reason) private async Task AbortSpeedTorqueStartupAsync(string status, string reason)
{ {
bool stopSent = await PulsePlcAsync(SpeedTorqueStopCoil, $"转速/扭矩{reason}停止"); bool stopped = await SendStopAndConfirmAsync(SpeedTorqueStopCoil, SpeedTorqueEnabledCoil, $"转速/扭矩{reason}停止");
_isSpeedTorqueRunning = false; _isSpeedTorqueRunning = !stopped;
_isSpeedTorqueCompletionArmed = false; _isSpeedTorqueStartupUncertain = !stopped;
_hasLoggedStaleSpeedTorqueDone = false; _speedTorqueDisabledPollCount = 0;
SpeedTorqueTestButtonText = "测试"; SpeedTorqueTestButtonText = stopped ? "测试" : "停止未确认";
StatusText = status; StatusText = stopped ? status : $"{status} M83 停止未确认,请再次点击停止。";
Log.Error( Log.Error(
"转速/扭矩启动已中止:原因 {Reason}M{StopCoil}停止指令成功 {StopSent},通气阀保持手动控制", "转速/扭矩启动已中止:原因 {Reason}M{StopCoil}停止状态确认 {Stopped},通气阀保持手动控制",
reason, reason,
SpeedTorqueStopCoil, SpeedTorqueStopCoil,
stopSent); stopped);
} }
private async Task StopSpeedTorqueAsync() private async Task StopSpeedTorqueAsync()
{ {
if (!await PulsePlcAsync(SpeedTorqueStopCoil, "转速/扭矩停止")) await StopSpeedTorqueControlAsync("状态:已停止", "转速/扭矩停止");
{
return;
}
await StopSpeedTorqueTestAsync("状态:已停止");
} }
private async Task ResetSpeedTorqueAsync() private async Task ResetSpeedTorqueAsync()
@@ -2767,8 +2872,8 @@ public sealed class MainWindowViewModel : ObservableObject
FinalizeSpeedTorqueRun("复位前中止"); FinalizeSpeedTorqueRun("复位前中止");
PersistCurrentPayloadSnapshot("转速/扭矩复位前"); PersistCurrentPayloadSnapshot("转速/扭矩复位前");
_isSpeedTorqueRunning = false; _isSpeedTorqueRunning = false;
_isSpeedTorqueCompletionArmed = false; _isSpeedTorqueStartupUncertain = false;
_hasLoggedStaleSpeedTorqueDone = false; _speedTorqueDisabledPollCount = 0;
SpeedTorqueTestButtonText = "测试"; SpeedTorqueTestButtonText = "测试";
_realtimeSpeed = 0; _realtimeSpeed = 0;
_realtimeTorque = 0; _realtimeTorque = 0;
@@ -2857,31 +2962,52 @@ public sealed class MainWindowViewModel : ObservableObject
private async Task StartDisplacementAsync() private async Task StartDisplacementAsync()
{ {
if (_isDisplacementRunning) if (_isDisplacementStarting || _isDisplacementRunning)
{ {
UpdateDisplacementDisplay(); UpdateDisplacementDisplay();
return; return;
} }
_isDisplacementStarting = true;
DisplacementTestButtonText = "启动中"; DisplacementTestButtonText = "启动中";
StatusText = "轴向测试启动中..."; StatusText = "轴向测试启动中...";
if (!await PulsePlcAsync(AxialStartCoil, "轴向测试")) if (!await PulsePlcAsync(AxialStartCoil, "轴向测试"))
{ {
_isDisplacementStarting = false;
DisplacementTestButtonText = "测试"; DisplacementTestButtonText = "测试";
return; 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 _)) if (!TryGetDialValue(out _) || !TryGetAxialForceValue(out _))
{ {
await PulsePlcAsync(AxialStopCoil, "实时数据无效,停止轴向测试"); await AbortDisplacementStartupAsync("实时数据无效,停止轴向测试。", "启动数据无效");
DisplacementTestButtonText = "测试"; _isDisplacementStarting = false;
UpdateDisplacementDisplay(); UpdateDisplacementDisplay();
return; return;
} }
_isDisplacementStarting = false;
PrepareSessionForNewRun(); PrepareSessionForNewRun();
_isDisplacementRunning = true; _isDisplacementRunning = true;
_isDisplacementStartupUncertain = false;
_axialDisabledPollCount = 0;
DisplacementTestButtonText = "测试中"; DisplacementTestButtonText = "测试中";
_activeDisplacementRun = CreateTestRun("轴向位移动量测试"); _activeDisplacementRun = CreateTestRun("轴向位移动量测试");
_finalDisplacement = null; _finalDisplacement = null;
@@ -2894,12 +3020,22 @@ public sealed class MainWindowViewModel : ObservableObject
private async Task StopDisplacementAsync() private async Task StopDisplacementAsync()
{ {
if (!await PulsePlcAsync(AxialStopCoil, "轴向停止")) await StopDisplacementControlAsync("状态:已停止", "轴向停止");
{ }
return;
}
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() private async Task ResetDisplacementAsync()
@@ -2940,6 +3076,8 @@ public sealed class MainWindowViewModel : ObservableObject
_finalAxialForce = null; _finalAxialForce = null;
await UpdateAxialForceFromInputAsync(); await UpdateAxialForceFromInputAsync();
_isDisplacementRunning = false; _isDisplacementRunning = false;
_isDisplacementStartupUncertain = false;
_axialDisabledPollCount = 0;
DisplacementTestButtonText = "测试"; DisplacementTestButtonText = "测试";
UpdateDisplacementDisplay(); UpdateDisplacementDisplay();
Log.Information("轴向复位完成,千分表零点 {DialZero}", _dialZero); Log.Information("轴向复位完成,千分表零点 {DialZero}", _dialZero);
@@ -3006,6 +3144,53 @@ public sealed class MainWindowViewModel : ObservableObject
} }
} }
private async Task<bool> 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<ushort, bool> 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<bool> ExecuteZeroingSequenceAsync( private async Task<bool> ExecuteZeroingSequenceAsync(
string testName, string testName,
params (ushort CoilAddress, string ActionName)[] zeroingActions) params (ushort CoilAddress, string ActionName)[] zeroingActions)
@@ -3249,10 +3434,7 @@ public sealed class MainWindowViewModel : ObservableObject
try try
{ {
Log.Warning("轴向测试触发自动停止:{Status},位移 {Displacement},轴向力 {AxialForce}", status, _relativeDisplacement, GetScaledAxialForce()); Log.Warning("轴向测试触发自动停止:{Status},位移 {Displacement},轴向力 {AxialForce}", status, _relativeDisplacement, GetScaledAxialForce());
if (await PulsePlcAsync(AxialStopCoil, "轴向保护停止")) await StopDisplacementControlAsync(status, "轴向保护停止");
{
StopDisplacementTest(status);
}
} }
finally finally
{ {
@@ -3260,21 +3442,59 @@ public sealed class MainWindowViewModel : ObservableObject
} }
} }
private async Task<bool> 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) private void StopDisplacementTest(string status)
{ {
if (!_isDisplacementRunning && _finalDisplacement.HasValue) if (!_isDisplacementRunning)
{ {
UpdateDisplacementDisplay(); UpdateDisplacementDisplay();
return; return;
} }
_isDisplacementRunning = false; _isDisplacementRunning = false;
_axialDisabledPollCount = 0;
DisplacementTestButtonText = "测试"; DisplacementTestButtonText = "测试";
if (_isDisplacementStartupUncertain)
{
_isDisplacementStartupUncertain = false;
StatusText = status;
UpdateDisplacementDisplay();
Log.Warning("轴向启动未确认状态已解除:{Status}", status);
return;
}
_finalDisplacement = Math.Abs(_axialSampleDifference) > 0.000001 ? _axialSampleDifference : _relativeDisplacement; _finalDisplacement = Math.Abs(_axialSampleDifference) > 0.000001 ? _axialSampleDifference : _relativeDisplacement;
_finalAxialForce = GetScaledAxialForce(); _finalAxialForce = GetScaledAxialForce();
FinalizeDisplacementRun(status); FinalizeDisplacementRun(status);
PersistCurrentPayloadSnapshot("轴向测试停止"); PersistCurrentPayloadSnapshot("轴向测试停止");
UpdateDisplacementDisplay(); UpdateDisplacementDisplay();
StatusText = status;
Log.Information("轴向测试停止:{Status},最终位移 {FinalDisplacement},最终轴向力 {FinalAxialForce}", status, _finalDisplacement, _finalAxialForce); Log.Information("轴向测试停止:{Status},最终位移 {FinalDisplacement},最终轴向力 {FinalAxialForce}", status, _finalDisplacement, _finalAxialForce);
} }
@@ -3345,10 +3565,7 @@ public sealed class MainWindowViewModel : ObservableObject
try try
{ {
Log.Warning("转速/扭矩测试触发自动停止:{Status},位移 {Displacement},转速 {Speed},扭矩 {Torque}", status, _speedTorqueDisplacement, _realtimeSpeed, GetScaledTorque()); Log.Warning("转速/扭矩测试触发自动停止:{Status},位移 {Displacement},转速 {Speed},扭矩 {Torque}", status, _speedTorqueDisplacement, _realtimeSpeed, GetScaledTorque());
if (await PulsePlcAsync(SpeedTorqueStopCoil, "转速/扭矩保护停止")) await StopSpeedTorqueControlAsync(status, "转速/扭矩保护停止");
{
await StopSpeedTorqueTestAsync(status);
}
} }
finally finally
{ {
@@ -3356,30 +3573,60 @@ public sealed class MainWindowViewModel : ObservableObject
} }
} }
private async Task<bool> 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) 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(); UpdateSpeedTorqueDisplay();
return; return;
} }
_isSpeedTorqueRunning = false; _isSpeedTorqueRunning = false;
_isSpeedTorqueCompletionArmed = false; _speedTorqueDisabledPollCount = 0;
_hasLoggedStaleSpeedTorqueDone = false;
SpeedTorqueTestButtonText = "测试"; SpeedTorqueTestButtonText = "测试";
_realtimeSpeed = speed; if (_isSpeedTorqueStartupUncertain)
_realtimeTorque = torque; {
_finalSpeed = speed; _isSpeedTorqueStartupUncertain = false;
StatusText = status;
UpdateSpeedTorqueDisplay();
Log.Warning("转速/扭矩启动未确认状态已解除:{Status}", status);
return;
}
_finalSpeed = _realtimeSpeed;
_finalTorque = GetScaledTorque(); _finalTorque = GetScaledTorque();
_finalSpeedTorqueDisplacement = _speedTorqueDisplacement; _finalSpeedTorqueDisplacement = _speedTorqueDisplacement;
FinalizeSpeedTorqueRun(status); FinalizeSpeedTorqueRun(status);
PersistCurrentPayloadSnapshot("转速/扭矩测试停止"); PersistCurrentPayloadSnapshot("转速/扭矩测试停止");
UpdateSpeedTorqueDisplay(); UpdateSpeedTorqueDisplay();
StatusText = status;
Log.Information("转速/扭矩测试停止:{Status},最终位移 {FinalDisplacement},最终转速 {FinalSpeed},最终扭矩 {FinalTorque},通气阀保持手动控制", status, _finalSpeedTorqueDisplacement, _finalSpeed, _finalTorque); Log.Information("转速/扭矩测试停止:{Status},最终位移 {FinalDisplacement},最终转速 {FinalSpeed},最终扭矩 {FinalTorque},通气阀保持手动控制", status, _finalSpeedTorqueDisplacement, _finalSpeed, _finalTorque);
await ShowSpeedTorqueEndWarningsAsync(); await ShowSpeedTorqueEndWarningsAsync();
@@ -3983,7 +4230,8 @@ public sealed class MainWindowViewModel : ObservableObject
private bool HasActiveRuns() private bool HasActiveRuns()
{ {
return _activeDisplacementRun is not null return HasActiveControlRun()
|| _activeDisplacementRun is not null
|| _activeSpeedTorqueRun is not null || _activeSpeedTorqueRun is not null
|| _activeNoLoadSpeedRun is not null; || _activeNoLoadSpeedRun is not null;
} }