更新
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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<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)
|
||||
{
|
||||
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<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 lastDone = false;
|
||||
bool hasReadState = false;
|
||||
@@ -2667,17 +2774,18 @@ public sealed class MainWindowViewModel : ObservableObject
|
||||
{
|
||||
IReadOnlyDictionary<ushort, bool> 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<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(
|
||||
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<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)
|
||||
{
|
||||
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<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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user