更新20260612

This commit is contained in:
GukSang.Jin
2026-06-12 09:45:15 +08:00
parent e54d92edc2
commit 8943b6bf75
2 changed files with 53 additions and 151 deletions

View File

@@ -99,9 +99,10 @@ public sealed class MainWindowViewModel : ObservableObject
private const ushort PressureCoefficientRegister = 1378;
private const ushort PressureDisplayRegister = 1388;
private const double TenthsRegisterScale = 10.0;
private const int MaxTorqueSampleCount = 120;
private const int MaxTorqueSampleCount = 300;
private const double MinimumTorqueChangeThreshold = 0.05;
private const string TorqueUnit = "mN.m";
private static readonly TimeSpan RealtimeRefreshInterval = TimeSpan.FromMilliseconds(100);
private static readonly TimeSpan SnapshotInterval = TimeSpan.FromSeconds(5);
private static readonly TimeSpan NoLoadCaptureDuration = TimeSpan.FromSeconds(3);
private static readonly TimeSpan SpeedTorqueStartConfirmationTimeout = TimeSpan.FromSeconds(5);
@@ -305,7 +306,7 @@ public sealed class MainWindowViewModel : ObservableObject
_realtimeTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(500)
Interval = RealtimeRefreshInterval
};
_realtimeTimer.Tick += RealtimeTimer_Tick;
_realtimeTimer.Start();
@@ -747,8 +748,11 @@ public sealed class MainWindowViewModel : ObservableObject
try
{
PlcConnectionConfig config = _parameterConfig.ToPlcConnectionConfig();
IReadOnlyDictionary<ushort, float> values = await _plcRegisterService.ReadFloatValuesAsync(config, RealtimeRegisterAddresses);
IReadOnlyDictionary<ushort, bool> coilValues = await _plcCoilService.ReadCoilValuesAsync(config, RealtimeCoilAddresses);
Task<IReadOnlyDictionary<ushort, float>> registerReadTask =
_plcRegisterService.ReadFloatValuesAsync(config, RealtimeRegisterAddresses);
Task<IReadOnlyDictionary<ushort, bool>> coilReadTask =
_plcCoilService.ReadCoilValuesAsync(config, RealtimeCoilAddresses);
IReadOnlyDictionary<ushort, float> values = await registerReadTask;
double dialIndicator = ReadFloatValue(values, DialIndicatorRegister, "千分表显示");
double axialForce = ReadFloatValue(values, AxialForceDisplayRegister, "轴向力显示");
@@ -771,24 +775,24 @@ public sealed class MainWindowViewModel : ObservableObject
_realtimeTorque = realtimeTorque;
_realtimeSpeed = realtimeSpeed;
AppendTorqueSample(GetScaledTorque(), _realtimeSpeed, DateTime.Now);
ApplyResetCoilValues(coilValues);
bool isVentValveOpen = ReadCoilValue(coilValues, VentValveCoil);
VentValveButtonText = isVentValveOpen ? "关闭气阀" : "通气阀";
if (_isSpeedTorqueRunning && !isVentValveOpen)
{
Log.Error("转速/扭矩测试运行中检测到通气阀关闭M{VentValveCoil}=0立即停止测试", VentValveCoil);
await AutoStopSpeedTorqueAsync("状态:通气阀关闭保护,已停止");
}
CaptureRealtimeSample(dialIndicator, coilValues);
FinalizeNoLoadSpeedRunIfDue();
QueueSnapshotIfDue();
if (_isDisplacementRunning)
{
_maxDisplacement = Math.Max(_maxDisplacement, Math.Abs(_relativeDisplacement));
}
UpdateDisplacementDisplay();
UpdateSpeedTorqueDisplay();
UpdateNoLoadSpeedDisplay();
IReadOnlyDictionary<ushort, bool> coilValues = await coilReadTask;
ApplyResetCoilValues(coilValues);
bool isVentValveOpen = ReadCoilValue(coilValues, VentValveCoil);
VentValveButtonText = isVentValveOpen ? "关闭气阀" : "通气阀";
CaptureRealtimeSample(dialIndicator, coilValues);
FinalizeNoLoadSpeedRunIfDue();
QueueSnapshotIfDue();
if (_isDisplacementRunning && ReadCoilValue(coilValues, AxialDoneCoil))
{
StopDisplacementTest("状态:已完成");
@@ -814,9 +818,6 @@ public sealed class MainWindowViewModel : ObservableObject
await StopSpeedTorqueTestAsync("状态:已完成");
}
UpdateDisplacementDisplay();
UpdateSpeedTorqueDisplay();
UpdateNoLoadSpeedDisplay();
await AutoStopIfSetpointReachedAsync();
await AutoStopIfSpeedTorqueProtectionReachedAsync();
@@ -2188,52 +2189,6 @@ public sealed class MainWindowViewModel : ObservableObject
await MoveSpeedTorqueDisplacementAsync();
}
private async Task<bool> WriteAndVerifyVentValveStateAsync(bool targetState, string reason)
{
try
{
PlcConnectionConfig config = _parameterConfig.ToPlcConnectionConfig();
await _plcCoilService.WriteCoilAsync(config, VentValveCoil, targetState);
await Task.Delay(100);
IReadOnlyDictionary<ushort, bool> values = await _plcCoilService.ReadCoilValuesAsync(
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;
}
Log.Information(
"PLC通气阀状态校验成功原因 {Reason}M{VentValveCoil}={Value},扭矩使能 M{EnabledCoil}={Enabled}",
reason,
VentValveCoil,
actualState ? 1 : 0,
SpeedTorqueEnabledCoil,
speedTorqueEnabled ? 1 : 0);
return true;
}
catch (Exception ex)
{
StatusText = $"PLC 通气阀状态确认失败:{OperatorMessageFormatter.FromException(ex)}";
Log.Error(ex, "PLC通气阀状态确认失败原因 {Reason}M{VentValveCoil}", reason, VentValveCoil);
return false;
}
}
private void QueueFloatTestPageInputWrite(TestPageInputParameter parameter, ushort registerAddress, string fieldName, string input)
{
if (string.IsNullOrWhiteSpace(input))
@@ -2473,15 +2428,9 @@ public sealed class MainWindowViewModel : ObservableObject
SpeedTorqueTestButtonText = "启动中";
StatusText = "转速/扭矩测试启动中...";
if (!await WriteAndVerifyVentValveStateAsync(true, "转速/扭矩测试启动前"))
{
SpeedTorqueTestButtonText = "测试";
return;
}
if (!await PulsePlcAsync(SpeedTorqueStartCoil, "转速/扭矩测试"))
{
await AbortSpeedTorqueStartupAsync("M80 启动指令失败,已停止并关闭通气阀。", "M80启动指令失败");
await AbortSpeedTorqueStartupAsync("M80 启动指令失败,已停止测试。", "M80启动指令失败");
return;
}
@@ -2489,13 +2438,13 @@ public sealed class MainWindowViewModel : ObservableObject
StatusText = "转速/扭矩测试启动指令已发送,正在确认 PLC 扭矩使能。";
if (!await WaitForSpeedTorqueStartAsync())
{
await AbortSpeedTorqueStartupAsync("PLC 未进入扭矩测试运行状态,已停止并关闭通气阀。", "启动状态确认失败");
await AbortSpeedTorqueStartupAsync("PLC 未进入扭矩测试运行状态,已停止测试。", "启动状态确认失败");
return;
}
if (!TryGetRealtimeSpeed(out _) || !TryGetRealtimeTorque(out _))
{
await AbortSpeedTorqueStartupAsync("实时数据无效,已停止测试并关闭通气阀。", "启动数据无效");
await AbortSpeedTorqueStartupAsync("实时数据无效,已停止测试。", "启动数据无效");
UpdateSpeedTorqueDisplay();
return;
}
@@ -2515,11 +2464,11 @@ public sealed class MainWindowViewModel : ObservableObject
AppendTorqueSample(GetScaledTorque(), _realtimeSpeed, _speedTorqueStartedAt.Value);
_maxSpeedTorqueDisplacement = Math.Max(_maxSpeedTorqueDisplacement, Math.Abs(_speedTorqueDisplacement));
UpdateSpeedTorqueDisplay();
StatusText = "转速/扭矩测试已启动,通气阀与扭矩使能状态正常。";
StatusText = "转速/扭矩测试已启动,通气阀由手动按钮独立控制。";
Log.Information(
"转速/扭矩测试已启动,通气阀 M{VentValveCoil}=1启动 M{StartCoil}已触发,起始转速 {Speed},起始扭矩 {Torque},起始位移 {Displacement}",
VentValveCoil,
"转速/扭矩测试已启动,启动 M{StartCoil}已触发,通气阀 M{VentValveCoil}保持手动控制,起始转速 {Speed},起始扭矩 {Torque},起始位移 {Displacement}",
SpeedTorqueStartCoil,
VentValveCoil,
_realtimeSpeed,
GetScaledTorque(),
_speedTorqueDisplacement);
@@ -2529,7 +2478,6 @@ public sealed class MainWindowViewModel : ObservableObject
private async Task<bool> WaitForSpeedTorqueStartAsync()
{
DateTime deadline = DateTime.Now.Add(SpeedTorqueStartConfirmationTimeout);
bool lastVentValveOpen = false;
bool lastEnabled = false;
bool lastDone = false;
bool hasReadState = false;
@@ -2541,30 +2489,15 @@ public sealed class MainWindowViewModel : ObservableObject
{
IReadOnlyDictionary<ushort, bool> values = await _plcCoilService.ReadCoilValuesAsync(
_parameterConfig.ToPlcConnectionConfig(),
[VentValveCoil, SpeedTorqueEnabledCoil, SpeedTorqueDoneCoil]);
lastVentValveOpen = ReadCoilValue(values, VentValveCoil);
[SpeedTorqueEnabledCoil, SpeedTorqueDoneCoil]);
lastEnabled = ReadCoilValue(values, SpeedTorqueEnabledCoil);
lastDone = ReadCoilValue(values, SpeedTorqueDoneCoil);
hasReadState = true;
if (!lastVentValveOpen)
{
StatusText = "M80 启动后通气阀 M6 未保持开启,已阻止测试运行。";
Log.Error(
"转速/扭矩启动状态确认失败M80后通气阀关闭M{VentValveCoil}=0M{EnabledCoil}={Enabled}M{DoneCoil}={Done}",
VentValveCoil,
SpeedTorqueEnabledCoil,
lastEnabled ? 1 : 0,
SpeedTorqueDoneCoil,
lastDone ? 1 : 0);
return false;
}
if (lastEnabled && !lastDone)
{
Log.Information(
"转速/扭矩启动状态确认成功M{VentValveCoil}=1M{EnabledCoil}=1M{DoneCoil}=0",
VentValveCoil,
"转速/扭矩启动状态确认成功M{EnabledCoil}=1M{DoneCoil}=0,通气阀保持手动控制",
SpeedTorqueEnabledCoil,
SpeedTorqueDoneCoil);
return true;
@@ -2577,8 +2510,7 @@ public sealed class MainWindowViewModel : ObservableObject
hasLoggedReadFailure = true;
Log.Warning(
ex,
"转速/扭矩启动状态读取失败,继续等待 M{VentValveCoil}/M{EnabledCoil}/M{DoneCoil}",
VentValveCoil,
"转速/扭矩启动状态读取失败,继续等待 M{EnabledCoil}/M{DoneCoil}",
SpeedTorqueEnabledCoil,
SpeedTorqueDoneCoil);
}
@@ -2589,11 +2521,9 @@ public sealed class MainWindowViewModel : ObservableObject
StatusText = "M80 启动后 PLC 未确认扭矩使能,已阻止测试运行。";
Log.Error(
"转速/扭矩启动状态确认超时:等待 {TimeoutSeconds} 秒,已读取状态 {HasReadState},最后 M{VentValveCoil}={VentValveOpen}M{EnabledCoil}={Enabled}M{DoneCoil}={Done}",
"转速/扭矩启动状态确认超时:等待 {TimeoutSeconds} 秒,已读取状态 {HasReadState},最后 M{EnabledCoil}={Enabled}M{DoneCoil}={Done}",
SpeedTorqueStartConfirmationTimeout.TotalSeconds,
hasReadState,
VentValveCoil,
lastVentValveOpen ? 1 : 0,
SpeedTorqueEnabledCoil,
lastEnabled ? 1 : 0,
SpeedTorqueDoneCoil,
@@ -2604,19 +2534,16 @@ public sealed class MainWindowViewModel : ObservableObject
private async Task AbortSpeedTorqueStartupAsync(string status, string reason)
{
bool stopSent = await PulsePlcAsync(SpeedTorqueStopCoil, $"转速/扭矩{reason}停止");
bool ventClosed = await WriteAndVerifyVentValveStateAsync(false, $"转速/扭矩{reason}");
_isSpeedTorqueRunning = false;
_isSpeedTorqueCompletionArmed = false;
_hasLoggedStaleSpeedTorqueDone = false;
SpeedTorqueTestButtonText = "测试";
StatusText = status;
Log.Error(
"转速/扭矩启动已中止:原因 {Reason}M{StopCoil}停止指令成功 {StopSent}M{VentValveCoil}关闭确认成功 {VentClosed}",
"转速/扭矩启动已中止:原因 {Reason}M{StopCoil}停止指令成功 {StopSent}通气阀保持手动控制",
reason,
SpeedTorqueStopCoil,
stopSent,
VentValveCoil,
ventClosed);
stopSent);
}
private async Task StopSpeedTorqueAsync()
@@ -2670,7 +2597,6 @@ public sealed class MainWindowViewModel : ObservableObject
_hasShownSpeedTorqueEndWarnings = false;
ClearTorqueSamples();
UpdateSpeedTorqueDisplay();
await WriteAndVerifyVentValveStateAsync(false, "转速/扭矩复位");
Log.Information("转速/扭矩复位完成,零点 {ZeroPosition}", _speedTorqueZero);
}
finally
@@ -2691,7 +2617,7 @@ public sealed class MainWindowViewModel : ObservableObject
IReadOnlyDictionary<ushort, bool> values = await _plcCoilService.ReadCoilValuesAsync(
config,
[VentValveCoil, SpeedTorqueEnabledCoil]);
[VentValveCoil]);
bool actualState = ReadCoilValue(values, VentValveCoil);
if (actualState != targetState)
{
@@ -2700,25 +2626,21 @@ public sealed class MainWindowViewModel : ObservableObject
: "通气阀未能关闭,请检查 PLC 状态。";
StatusText = error;
Log.Warning(
"通气阀切换失败:目标 {Target},实际 {Actual}M{VentValveCoil}={ActualState},扭矩使能 M{EnabledCoil}={Enabled}",
"通气阀手动切换失败:目标 {Target},实际 {Actual}M{VentValveCoil}={ActualState}",
targetState ? "开启" : "关闭",
actualState ? "开启" : "关闭",
VentValveCoil,
actualState ? 1 : 0,
SpeedTorqueEnabledCoil,
ReadCoilValue(values, SpeedTorqueEnabledCoil) ? 1 : 0);
actualState ? 1 : 0);
return;
}
VentValveButtonText = actualState ? "关闭气阀" : "通气阀";
StatusText = actualState ? "通气阀已开启。" : "通气阀已关闭。";
Log.Information(
"通气阀切换成功:目标 {Target}M{VentValveCoil}={State},扭矩使能 M{EnabledCoil}={Enabled}",
"通气阀手动切换成功:目标 {Target}M{VentValveCoil}={State}",
targetState ? "开启" : "关闭",
VentValveCoil,
actualState ? 1 : 0,
SpeedTorqueEnabledCoil,
ReadCoilValue(values, SpeedTorqueEnabledCoil) ? 1 : 0);
actualState ? 1 : 0);
}
catch (Exception ex)
{
@@ -3201,7 +3123,6 @@ public sealed class MainWindowViewModel : ObservableObject
_isSpeedTorqueCompletionArmed = false;
_hasLoggedStaleSpeedTorqueDone = false;
SpeedTorqueTestButtonText = "测试";
await WriteAndVerifyVentValveStateAsync(false, "转速/扭矩测试结束数据无效");
UpdateSpeedTorqueDisplay();
return;
}
@@ -3218,11 +3139,7 @@ public sealed class MainWindowViewModel : ObservableObject
FinalizeSpeedTorqueRun(status);
PersistCurrentPayloadSnapshot("转速/扭矩测试停止");
UpdateSpeedTorqueDisplay();
Log.Information("转速/扭矩测试停止:{Status},最终位移 {FinalDisplacement},最终转速 {FinalSpeed},最终扭矩 {FinalTorque}", status, _finalSpeedTorqueDisplacement, _finalSpeed, _finalTorque);
if (!await WriteAndVerifyVentValveStateAsync(false, "转速/扭矩测试结束"))
{
StatusText = "测试已停止,但通气阀关闭状态未确认,请检查设备。";
}
Log.Information("转速/扭矩测试停止:{Status},最终位移 {FinalDisplacement},最终转速 {FinalSpeed},最终扭矩 {FinalTorque},通气阀保持手动控制", status, _finalSpeedTorqueDisplacement, _finalSpeed, _finalTorque);
await ShowSpeedTorqueEndWarningsAsync();
}
@@ -3367,20 +3284,7 @@ public sealed class MainWindowViewModel : ObservableObject
return;
}
double holdTimeSeconds = _parameterConfig.TorqueHoldTime;
if (holdTimeSeconds <= 0)
{
UpdateTorqueCurveStatus();
return;
}
double elapsedSeconds = Math.Max(0, (sampledAt - _speedTorqueStartedAt.Value).TotalSeconds);
if (elapsedSeconds > holdTimeSeconds)
{
UpdateTorqueCurveStatus();
return;
}
TorqueSamples.Add(new TorqueSamplePayload
{
ElapsedSeconds = elapsedSeconds,
@@ -3394,8 +3298,6 @@ public sealed class MainWindowViewModel : ObservableObject
{
TorqueSamples.RemoveAt(0);
}
UpdateTorqueCurveStatus();
}
private void ClearTorqueSamples()
@@ -3407,14 +3309,11 @@ public sealed class MainWindowViewModel : ObservableObject
private void UpdateTorqueCurveStatus()
{
if (_isSpeedTorqueRunning && _parameterConfig.TorqueHoldTime > 0 && TorqueSamples.Count > 0)
if (_isSpeedTorqueRunning && TorqueSamples.Count > 0)
{
double elapsed = TorqueSamples[^1].ElapsedSeconds;
if (elapsed < _parameterConfig.TorqueHoldTime)
{
TorqueCurveStatusText = $"保持时间曲线:采集中 {FormatConfigNumber(elapsed)} / {FormatConfigNumber(_parameterConfig.TorqueHoldTime)} s";
return;
}
TorqueCurveStatusText = $"实时曲线:采集中 {FormatConfigNumber(elapsed)} sX轴转速 / Y轴扭矩";
return;
}
TorqueCurvePayload curve = CreateTorqueCurvePayload();
@@ -3540,12 +3439,7 @@ public sealed class MainWindowViewModel : ObservableObject
private int GetTorqueSampleLimit()
{
if (_parameterConfig.TorqueHoldTime <= 0)
{
return MaxTorqueSampleCount;
}
return Math.Max(MaxTorqueSampleCount, (int)Math.Ceiling(_parameterConfig.TorqueHoldTime * 2.5) + 4);
return MaxTorqueSampleCount;
}
private void CaptureRealtimeSample(double dialIndicator, IReadOnlyDictionary<ushort, bool> coilValues)
@@ -3637,6 +3531,7 @@ public sealed class MainWindowViewModel : ObservableObject
}
AppendLatestSample(_activeSpeedTorqueRun);
_cachedTorqueCurve = CreateTorqueCurvePayload(_activeSpeedTorqueRun);
_activeSpeedTorqueRun.CompletedAt = DateTime.Now;
_activeSpeedTorqueRun.CompletionStatus = status;
_activeSpeedTorqueRun.FinalDisplacementMm = _finalSpeedTorqueDisplacement ?? _speedTorqueDisplacement;

View File

@@ -93,10 +93,17 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
return new Dictionary<ushort, float>();
}
IReadOnlyList<RegisterReadGroup> groups = CreateRegisterReadGroups(registerAddresses);
Task<ushort[]>[] readTasks = groups
.Select(group => ReadHoldingRegistersAsync(config, group.StartAddress, group.RegisterCount, cancellationToken))
.ToArray();
ushort[][] groupRegisters = await Task.WhenAll(readTasks);
var values = new Dictionary<ushort, float>();
foreach (RegisterReadGroup group in CreateRegisterReadGroups(registerAddresses))
for (int groupIndex = 0; groupIndex < groups.Count; groupIndex++)
{
ushort[] registers = await ReadHoldingRegistersAsync(config, group.StartAddress, group.RegisterCount, cancellationToken);
RegisterReadGroup group = groups[groupIndex];
ushort[] registers = groupRegisters[groupIndex];
foreach (ushort address in group.FloatAddresses)
{
int offset = address - group.StartAddress;