diff --git a/DentistryHandpieces/MainWindow.xaml b/DentistryHandpieces/MainWindow.xaml
index 5880810..c8795e6 100644
--- a/DentistryHandpieces/MainWindow.xaml
+++ b/DentistryHandpieces/MainWindow.xaml
@@ -872,7 +872,6 @@
-
-
-
-
diff --git a/DentistryHandpieces/MainWindowViewModel.cs b/DentistryHandpieces/MainWindowViewModel.cs
index a6a48e4..6f7b302 100644
--- a/DentistryHandpieces/MainWindowViewModel.cs
+++ b/DentistryHandpieces/MainWindowViewModel.cs
@@ -45,6 +45,7 @@ public sealed class MainWindowViewModel : ObservableObject
private const ushort AxialDoneCoil = 72;
private const ushort AxialStopCoil = 73;
private const ushort SpeedTorqueStartCoil = 80;
+ private const ushort SpeedTorqueEnabledCoil = 81;
private const ushort SpeedTorqueDoneCoil = 82;
private const ushort SpeedTorqueStopCoil = 83;
private const ushort SpeedTorqueResetCoil = 90;
@@ -56,6 +57,11 @@ public sealed class MainWindowViewModel : ObservableObject
private const ushort AxialResetCoil = 95;
private const ushort AxialResetEnabledCoil = 96;
private const ushort AxialResetDoneCoil = 97;
+ private const ushort AxialForceZeroCoil = 1100;
+ private const ushort TorqueZeroCoil = 1101;
+ private const ushort SpeedZeroCoil = 1300;
+ private const ushort PressureZeroCoil = 1301;
+ private const ushort SecondaryTorqueZeroCoil = 1302;
private const ushort SpeedTorquePositionRegister = 14;
private const ushort AxialPositionRegister = 16;
private const ushort AxialSampleStartRegister = 72;
@@ -208,7 +214,6 @@ public sealed class MainWindowViewModel : ObservableObject
private bool _isApplyingParameterConfigToInputs;
private bool _isDisplacementResetting;
private bool _isSpeedTorqueResetting;
- private bool _isVentValveOpen;
private bool _hasShownSpeedTorqueEndWarnings;
private DateTime _lastParameterReadFailureLogAt = DateTime.MinValue;
private string _dialIndicatorText = "0.000 mm";
@@ -247,7 +252,6 @@ public sealed class MainWindowViewModel : ObservableObject
private string _noLoadSpeedTestButtonText = "测试";
private string _displacementResetButtonText = "复位";
private string _speedTorqueResetButtonText = "复位";
- private string _ventValveButtonText = "开启通气阀";
public MainWindowViewModel(IPlcCoilService plcCoilService, IPlcRegisterService plcRegisterService, IFileDialogService fileDialogService)
{
@@ -271,7 +275,6 @@ public sealed class MainWindowViewModel : ObservableObject
SelectAxialJumpForceSetpointModeCommand = new AsyncRelayCommand(SelectAxialJumpForceSetpointModeAsync);
ForwardSpeedTorqueCommand = new AsyncRelayCommand(ForwardSpeedTorqueAsync);
BackwardSpeedTorqueCommand = new AsyncRelayCommand(BackwardSpeedTorqueAsync);
- VentValveCommand = new AsyncRelayCommand(ToggleVentValveAsync);
StartSpeedTorqueCommand = new AsyncRelayCommand(StartSpeedTorqueAsync);
StopSpeedTorqueCommand = new AsyncRelayCommand(StopSpeedTorqueAsync);
ResetSpeedTorqueCommand = new AsyncRelayCommand(ResetSpeedTorqueAsync);
@@ -328,8 +331,6 @@ public sealed class MainWindowViewModel : ObservableObject
public IAsyncRelayCommand BackwardSpeedTorqueCommand { get; }
- public IAsyncRelayCommand VentValveCommand { get; }
-
public IAsyncRelayCommand StartSpeedTorqueCommand { get; }
public IAsyncRelayCommand StopSpeedTorqueCommand { get; }
@@ -690,12 +691,6 @@ public sealed class MainWindowViewModel : ObservableObject
private set => SetProperty(ref _speedTorqueResetButtonText, value);
}
- public string VentValveButtonText
- {
- get => _ventValveButtonText;
- private set => SetProperty(ref _ventValveButtonText, value);
- }
-
private async void RealtimeTimer_Tick(object? sender, EventArgs e)
{
if (_isReadingRealtime)
@@ -732,7 +727,13 @@ public sealed class MainWindowViewModel : ObservableObject
_realtimeSpeed = realtimeSpeed;
AppendTorqueSample(GetScaledTorque(), DateTime.Now);
ApplyResetCoilValues(coilValues);
- UpdateVentValveState(ReadCoilValue(coilValues, VentValveCoil));
+ bool isVentValveOpen = ReadCoilValue(coilValues, VentValveCoil);
+ if (_isSpeedTorqueRunning && !isVentValveOpen)
+ {
+ Log.Error("转速/扭矩测试运行中检测到通气阀关闭,M{VentValveCoil}=0,立即停止测试", VentValveCoil);
+ await AutoStopSpeedTorqueAsync("状态:通气阀关闭保护,已停止");
+ }
+
CaptureRealtimeSample(dialIndicator, coilValues);
FinalizeNoLoadSpeedRunIfDue();
QueueSnapshotIfDue();
@@ -2097,28 +2098,49 @@ public sealed class MainWindowViewModel : ObservableObject
await MoveSpeedTorqueDisplacementAsync();
}
- private async Task ToggleVentValveAsync()
+ private async Task WriteAndVerifyVentValveStateAsync(bool targetState, string reason)
{
try
{
+ PlcConnectionConfig config = _parameterConfig.ToPlcConnectionConfig();
+ await _plcCoilService.WriteCoilAsync(config, VentValveCoil, targetState);
+ await Task.Delay(100);
+
IReadOnlyDictionary values = await _plcCoilService.ReadCoilValuesAsync(
- _parameterConfig.ToPlcConnectionConfig(),
- [VentValveCoil]);
- bool targetState = !ReadCoilValue(values, VentValveCoil);
+ config,
+ [VentValveCoil, SpeedTorqueEnabledCoil]);
+ bool actualState = ReadCoilValue(values, VentValveCoil);
+ bool speedTorqueEnabled = ReadCoilValue(values, SpeedTorqueEnabledCoil);
+ if (actualState != targetState)
+ {
+ StatusText = targetState
+ ? "通气阀未能保持开启,已阻止测试启动。"
+ : "通气阀未能关闭,请检查 PLC 状态。";
+ Log.Error(
+ "PLC通气阀状态校验失败:原因 {Reason},要求 M{VentValveCoil}={Expected},实际 {Actual},扭矩使能 M{EnabledCoil}={Enabled}",
+ reason,
+ VentValveCoil,
+ targetState ? 1 : 0,
+ actualState ? 1 : 0,
+ SpeedTorqueEnabledCoil,
+ speedTorqueEnabled ? 1 : 0);
+ return false;
+ }
- await _plcCoilService.WriteCoilAsync(
- _parameterConfig.ToPlcConnectionConfig(),
+ Log.Information(
+ "PLC通气阀状态校验成功:原因 {Reason},M{VentValveCoil}={Value},扭矩使能 M{EnabledCoil}={Enabled}",
+ reason,
VentValveCoil,
- targetState);
-
- UpdateVentValveState(targetState);
- StatusText = targetState ? "通气阀已开启。" : "通气阀已关闭。";
- Log.Information("PLC常开触点开关成功:通气阀,M{CoilAddress}={Value}", VentValveCoil, targetState ? 1 : 0);
+ actualState ? 1 : 0,
+ SpeedTorqueEnabledCoil,
+ speedTorqueEnabled ? 1 : 0);
+ return true;
}
catch (Exception ex)
{
- StatusText = $"PLC 通气阀开关失败:{OperatorMessageFormatter.FromException(ex)}";
- Log.Error(ex, "PLC常开触点开关失败:通气阀,M{CoilAddress}", VentValveCoil);
+ StatusText = $"PLC 通气阀状态确认失败:{OperatorMessageFormatter.FromException(ex)}";
+ Log.Error(ex, "PLC通气阀状态确认失败:原因 {Reason},M{VentValveCoil}", reason, VentValveCoil);
+ return false;
}
}
@@ -2338,13 +2360,46 @@ public sealed class MainWindowViewModel : ObservableObject
return;
}
+ SpeedTorqueTestButtonText = "归零中";
+ StatusText = "转速/扭矩测试启动前归零中。";
+ if (!await ExecuteZeroingSequenceAsync(
+ "转速/扭矩测试",
+ (TorqueZeroCoil, "扭矩归零 M1101"),
+ (SpeedZeroCoil, "转速归零 M1300"),
+ (PressureZeroCoil, "压力归零 M1301"),
+ (SecondaryTorqueZeroCoil, "扭矩归零 M1302")))
+ {
+ SpeedTorqueTestButtonText = "测试";
+ return;
+ }
+
+ if (!await WriteAndVerifyVentValveStateAsync(true, "转速/扭矩测试启动前"))
+ {
+ SpeedTorqueTestButtonText = "测试";
+ return;
+ }
+
if (!await PulsePlcAsync(SpeedTorqueStartCoil, "转速/扭矩测试"))
{
+ await WriteAndVerifyVentValveStateAsync(false, "转速/扭矩测试启动失败");
+ SpeedTorqueTestButtonText = "测试";
+ return;
+ }
+
+ if (!await WriteAndVerifyVentValveStateAsync(true, "转速/扭矩测试启动后"))
+ {
+ await PulsePlcAsync(SpeedTorqueStopCoil, "通气阀未保持开启,停止转速/扭矩测试");
+ await WriteAndVerifyVentValveStateAsync(false, "转速/扭矩测试启动后校验失败");
+ StatusText = "测试启动后通气阀未保持开启,已发送停止指令。";
+ SpeedTorqueTestButtonText = "测试";
return;
}
if (!TryGetRealtimeSpeed(out _) || !TryGetRealtimeTorque(out _))
{
+ await PulsePlcAsync(SpeedTorqueStopCoil, "实时数据无效,停止转速/扭矩测试");
+ await WriteAndVerifyVentValveStateAsync(false, "转速/扭矩测试启动数据无效");
+ SpeedTorqueTestButtonText = "测试";
UpdateSpeedTorqueDisplay();
return;
}
@@ -2362,7 +2417,13 @@ public sealed class MainWindowViewModel : ObservableObject
AppendTorqueSample(GetScaledTorque(), _speedTorqueStartedAt.Value);
_maxSpeedTorqueDisplacement = Math.Max(_maxSpeedTorqueDisplacement, Math.Abs(_speedTorqueDisplacement));
UpdateSpeedTorqueDisplay();
- Log.Information("转速/扭矩测试已启动,起始转速 {Speed},起始扭矩 {Torque},起始位移 {Displacement}", _realtimeSpeed, GetScaledTorque(), _speedTorqueDisplacement);
+ Log.Information(
+ "转速/扭矩测试已启动,通气阀 M{VentValveCoil}=1,启动 M{StartCoil}已触发,起始转速 {Speed},起始扭矩 {Torque},起始位移 {Displacement}",
+ VentValveCoil,
+ SpeedTorqueStartCoil,
+ _realtimeSpeed,
+ GetScaledTorque(),
+ _speedTorqueDisplacement);
await AutoStopIfSpeedTorqueProtectionReachedAsync();
}
@@ -2415,6 +2476,7 @@ public sealed class MainWindowViewModel : ObservableObject
_hasShownSpeedTorqueEndWarnings = false;
ClearTorqueSamples();
UpdateSpeedTorqueDisplay();
+ await WriteAndVerifyVentValveStateAsync(false, "转速/扭矩复位");
Log.Information("转速/扭矩复位完成,零点 {ZeroPosition}", _speedTorqueZero);
}
finally
@@ -2452,13 +2514,26 @@ public sealed class MainWindowViewModel : ObservableObject
return;
}
+ DisplacementTestButtonText = "归零中";
+ StatusText = "轴向测试启动前归零中。";
+ if (!await ExecuteZeroingSequenceAsync(
+ "轴向测试",
+ (AxialForceZeroCoil, "轴向力归零 M1100")))
+ {
+ DisplacementTestButtonText = "测试";
+ return;
+ }
+
if (!await PulsePlcAsync(AxialStartCoil, "轴向测试"))
{
+ DisplacementTestButtonText = "测试";
return;
}
if (!TryGetDialValue(out _) || !TryGetAxialForceValue(out _))
{
+ await PulsePlcAsync(AxialStopCoil, "实时数据无效,停止轴向测试");
+ DisplacementTestButtonText = "测试";
UpdateDisplacementDisplay();
return;
}
@@ -2589,6 +2664,32 @@ public sealed class MainWindowViewModel : ObservableObject
}
}
+ private async Task ExecuteZeroingSequenceAsync(
+ string testName,
+ params (ushort CoilAddress, string ActionName)[] zeroingActions)
+ {
+ foreach ((ushort coilAddress, string actionName) in zeroingActions)
+ {
+ if (!await PulsePlcAsync(coilAddress, actionName))
+ {
+ StatusText = $"{testName}启动已阻止:{actionName}失败。";
+ Log.Error(
+ "{TestName}启动前归零失败,停止启动序列,失败点位 M{CoilAddress},动作 {ActionName}",
+ testName,
+ coilAddress,
+ actionName);
+ return false;
+ }
+ }
+
+ StatusText = $"{testName}归零完成,正在启动测试。";
+ Log.Information(
+ "{TestName}启动前归零序列完成,点位 {ZeroingCoils}",
+ testName,
+ string.Join(", ", zeroingActions.Select(static action => $"M{action.CoilAddress}")));
+ return true;
+ }
+
private async Task BeginAxialManualMotionAsync(ushort coilAddress, string actionName)
{
if (!await WriteManualMotionCoilAsync(coilAddress, true, $"{actionName}按下"))
@@ -2822,6 +2923,9 @@ public sealed class MainWindowViewModel : ObservableObject
{
if (!TryGetRealtimeSpeed(out double speed) || !TryGetRealtimeTorque(out double torque))
{
+ _isSpeedTorqueRunning = false;
+ SpeedTorqueTestButtonText = "测试";
+ await WriteAndVerifyVentValveStateAsync(false, "转速/扭矩测试结束数据无效");
UpdateSpeedTorqueDisplay();
return;
}
@@ -2837,6 +2941,11 @@ public sealed class MainWindowViewModel : ObservableObject
PersistCurrentPayloadSnapshot("转速/扭矩测试停止");
UpdateSpeedTorqueDisplay();
Log.Information("转速/扭矩测试停止:{Status},最终位移 {FinalDisplacement},最终转速 {FinalSpeed},最终扭矩 {FinalTorque}", status, _finalSpeedTorqueDisplacement, _finalSpeed, _finalTorque);
+ if (!await WriteAndVerifyVentValveStateAsync(false, "转速/扭矩测试结束"))
+ {
+ StatusText = "测试已停止,但通气阀关闭状态未确认,请检查设备。";
+ }
+
await ShowSpeedTorqueEndWarningsAsync();
}
@@ -2926,12 +3035,6 @@ public sealed class MainWindowViewModel : ObservableObject
SpeedTorqueResetButtonText = _isSpeedTorqueResetting || enabled ? "复位中" : "复位";
}
- private void UpdateVentValveState(bool isOpen)
- {
- _isVentValveOpen = isOpen;
- VentValveButtonText = _isVentValveOpen ? "关闭通气阀" : "开启通气阀";
- }
-
private static bool ReadCoilValue(IReadOnlyDictionary coilValues, ushort address)
{
return coilValues.TryGetValue(address, out bool value) && value;