This commit is contained in:
GukSang.Jin
2026-05-11 10:37:14 +08:00
parent f3d3289d51
commit 808ef94b91
2 changed files with 52 additions and 50 deletions

View File

@@ -21,8 +21,8 @@ public sealed class ModbusProcessDataReader
private static readonly PlcTelemetryRegisterMap DefaultTelemetryRegisterMap = new( private static readonly PlcTelemetryRegisterMap DefaultTelemetryRegisterMap = new(
SlaveAddress: 1, SlaveAddress: 1,
ForceAddress: 1314, ForceAddress: 1314,
HorizontalSpeedAddress: 350, HorizontalSpeedAddress: 370,
HorizontalDisplacementAddress: 360, HorizontalDisplacementAddress: 380,
LiftSpeedAddress: 310, LiftSpeedAddress: 310,
LiftDisplacementAddress: 320, LiftDisplacementAddress: 320,
LiftPositionAddress: 12, LiftPositionAddress: 12,
@@ -111,10 +111,10 @@ public sealed class ModbusProcessDataReader
var liftDisplacement = ReadFloatAt(motionRegisters, motionStartAddress, _telemetryRegisterMap.LiftDisplacementAddress); var liftDisplacement = ReadFloatAt(motionRegisters, motionStartAddress, _telemetryRegisterMap.LiftDisplacementAddress);
var liftPosition = ReadFloatAt(positionRegisters, positionStartAddress, _telemetryRegisterMap.LiftPositionAddress); var liftPosition = ReadFloatAt(positionRegisters, positionStartAddress, _telemetryRegisterMap.LiftPositionAddress);
var horizontalPosition = ReadFloatAt(positionRegisters, positionStartAddress, _telemetryRegisterMap.HorizontalPositionAddress); var horizontalPosition = ReadFloatAt(positionRegisters, positionStartAddress, _telemetryRegisterMap.HorizontalPositionAddress);
var completed = horizontalDisplacement >= _recipe.TravelMm; var completed = false;
return new ProcessFrame( return new ProcessFrame(
horizontalDisplacement, horizontalPosition,
Math.Max(force, 0.001), Math.Max(force, 0.001),
horizontalSpeed, horizontalSpeed,
completed, completed,

View File

@@ -47,12 +47,13 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private const ushort CoilReset = 90; private const ushort CoilReset = 90;
private const ushort RegisterSledMassGrams = 400; private const ushort RegisterSledMassGrams = 400;
private const ushort RegisterReplicateCount = 406; private const ushort RegisterReplicateCount = 406;
private const ushort RegisterHorizontalSpeed = 350; private const ushort RegisterHorizontalSpeedSetpoint = 370;
private const ushort RegisterHorizontalDisplacement = 360; private const ushort RegisterHorizontalTravelSetpoint = 380;
private const ushort RegisterLiftSpeed = 310; private const ushort RegisterLiftSpeed = 310;
private const ushort RegisterLiftDisplacement = 320; private const ushort RegisterLiftDisplacement = 320;
private static readonly TimeSpan RealtimeChartRefreshInterval = TimeSpan.FromMilliseconds(250); private static readonly TimeSpan RealtimeChartRefreshInterval = TimeSpan.FromMilliseconds(250);
private const double RealtimeChartDisplacementStepMm = 0.5; private const double RealtimeChartDisplacementStepMm = 0.5;
private const double RealtimeChartXAxisPaddingMm = 5;
private readonly DispatcherTimer _timer; private readonly DispatcherTimer _timer;
private readonly DispatcherTimer _deviceReconnectTimer; private readonly DispatcherTimer _deviceReconnectTimer;
@@ -132,6 +133,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private int _kineticSampleCount; private int _kineticSampleCount;
private double _runStartHorizontalPositionMm = double.NaN; private double _runStartHorizontalPositionMm = double.NaN;
private double _lastRealtimeChartDisplacementMm = double.NaN; private double _lastRealtimeChartDisplacementMm = double.NaN;
private double _realtimeChartMaxDisplacementMm;
private DateTime _lastRealtimeChartRefreshAt = DateTime.MinValue; private DateTime _lastRealtimeChartRefreshAt = DateTime.MinValue;
private double _lastForceXAxisMaxLimit; private double _lastForceXAxisMaxLimit;
private double _lastForceYAxisMaxLimit; private double _lastForceYAxisMaxLimit;
@@ -943,7 +945,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private void UpdateLiveProcessSnapshot(ProcessFrame frame) private void UpdateLiveProcessSnapshot(ProcessFrame frame)
{ {
CurrentForceN = Math.Max(frame.ForceN, 0.001); CurrentForceN = Math.Max(frame.ForceN, 0.001);
CurrentDisplacementMm = frame.HorizontalPosition; CurrentDisplacementMm = frame.DisplacementMm;
CurrentSpeedMmPerMin = frame.SpeedMmPerMin; CurrentSpeedMmPerMin = frame.SpeedMmPerMin;
CurrentLiftSpeed = frame.LiftSpeed; CurrentLiftSpeed = frame.LiftSpeed;
CurrentLiftDisplacement = frame.LiftDisplacement; CurrentLiftDisplacement = frame.LiftDisplacement;
@@ -995,6 +997,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private void AddRealtimeChartSample(double displacementMm, double forceN) private void AddRealtimeChartSample(double displacementMm, double forceN)
{ {
TrackRealtimeChartBounds(displacementMm);
var sample = new ObservablePoint(displacementMm, forceN); var sample = new ObservablePoint(displacementMm, forceN);
if (_forceSamples.Count == 0 || !IsFinite(_lastRealtimeChartDisplacementMm)) if (_forceSamples.Count == 0 || !IsFinite(_lastRealtimeChartDisplacementMm))
{ {
@@ -1013,37 +1017,46 @@ public sealed class MainViewModel : ObservableObject, IDisposable
_forceSamples[^1] = sample; _forceSamples[^1] = sample;
} }
private void TrackRealtimeChartBounds(double displacementMm)
{
if (IsFinite(displacementMm))
{
_realtimeChartMaxDisplacementMm = Math.Max(_realtimeChartMaxDisplacementMm, displacementMm);
}
}
private bool AppendRunningSample(ProcessFrame frame, out bool isCompleted) private bool AppendRunningSample(ProcessFrame frame, out bool isCompleted)
{ {
isCompleted = false; isCompleted = false;
if (!TryResolveRunningPosition(frame, out var horizontalPositionMm, out var travelDeltaMm)) if (!TryResolveRunningDisplacement(frame, out var displacementMm))
{ {
AddWarningEvent("实时采样包含无效水平位,已跳过本次曲线点。"); AddWarningEvent("实时采样包含无效水平位,已跳过本次曲线点。");
return false; return false;
} }
var forceN = CurrentForceN; var forceN = Math.Max(frame.ForceN, 0.001);
if (!IsFinite(forceN)) if (!IsFinite(forceN))
{ {
AddWarningEvent("实时采样包含无效力值,已跳过本次曲线点。"); AddWarningEvent("实时采样包含无效力值,已跳过本次曲线点。");
return false; return false;
} }
CurrentDisplacementMm = horizontalPositionMm; CurrentDisplacementMm = displacementMm;
CurrentForceN = forceN;
var isFirstChartSample = _forceSamples.Count == 0; var isFirstChartSample = _forceSamples.Count == 0;
_currentRunSamples.Add(new RawSampleRecord _currentRunSamples.Add(new RawSampleRecord
{ {
RunId = _activeRunId, RunId = _activeRunId,
SampleIndex = _currentRunSamples.Count + 1, SampleIndex = _currentRunSamples.Count + 1,
CapturedAt = DateTime.Now, CapturedAt = DateTime.Now,
DisplacementMm = horizontalPositionMm, DisplacementMm = displacementMm,
ForceN = forceN, ForceN = forceN,
SpeedMmPerMin = frame.SpeedMmPerMin SpeedMmPerMin = frame.SpeedMmPerMin
}); });
AddRealtimeChartSample(horizontalPositionMm, forceN); AddRealtimeChartSample(displacementMm, forceN);
UpdateCurrentPoint(horizontalPositionMm, forceN); UpdateCurrentPoint(displacementMm, forceN);
UpdatePreviewResult(travelDeltaMm, forceN); UpdatePreviewResult(displacementMm, forceN);
StaticCoefficient1 = frame.StaticCoefficient1; StaticCoefficient1 = frame.StaticCoefficient1;
KineticCoefficient1 = frame.KineticCoefficient1; KineticCoefficient1 = frame.KineticCoefficient1;
StandardDeviation1 = frame.StandardDeviation1; StandardDeviation1 = frame.StandardDeviation1;
@@ -1053,8 +1066,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
var activeReplicateCount = GetActiveReplicateCount(); var activeReplicateCount = GetActiveReplicateCount();
CurrentStaticCoefficient = ResolveRepresentativeValue(activeReplicateCount, StaticCoefficient1, StaticCoefficient2); CurrentStaticCoefficient = ResolveRepresentativeValue(activeReplicateCount, StaticCoefficient1, StaticCoefficient2);
CurrentKineticCoefficient = ResolveRepresentativeValue(activeReplicateCount, KineticCoefficient1, KineticCoefficient2); CurrentKineticCoefficient = ResolveRepresentativeValue(activeReplicateCount, KineticCoefficient1, KineticCoefficient2);
TrialProgressPercent = Math.Min(100, travelDeltaMm / Math.Max(Recipe.TravelMm, 1) * 100); TrialProgressPercent = Math.Min(100, displacementMm / Math.Max(GetActiveTravelMm(), 1) * 100);
isCompleted = travelDeltaMm >= GetActiveTravelMm(); isCompleted = displacementMm >= GetActiveTravelMm();
RefreshRealtimeChartPresentation(force: isFirstChartSample || isCompleted); RefreshRealtimeChartPresentation(force: isFirstChartSample || isCompleted);
if (isFirstChartSample) if (isFirstChartSample)
{ {
@@ -1064,23 +1077,21 @@ public sealed class MainViewModel : ObservableObject, IDisposable
return true; return true;
} }
private bool TryResolveRunningPosition(ProcessFrame frame, out double horizontalPositionMm, out double travelDeltaMm) private bool TryResolveRunningDisplacement(ProcessFrame frame, out double displacementMm)
{ {
horizontalPositionMm = 0; displacementMm = 0;
travelDeltaMm = 0;
if (!IsFinite(frame.HorizontalPosition)) if (!IsFinite(frame.HorizontalPosition))
{ {
return false; return false;
} }
horizontalPositionMm = frame.HorizontalPosition;
if (!IsFinite(_runStartHorizontalPositionMm)) if (!IsFinite(_runStartHorizontalPositionMm))
{ {
_runStartHorizontalPositionMm = horizontalPositionMm; _runStartHorizontalPositionMm = frame.HorizontalPosition;
} }
travelDeltaMm = Math.Abs(horizontalPositionMm - _runStartHorizontalPositionMm); displacementMm = Math.Abs(frame.HorizontalPosition - _runStartHorizontalPositionMm);
return IsFinite(horizontalPositionMm) && IsFinite(travelDeltaMm); return IsFinite(displacementMm);
} }
private void UpdatePreviewResult(double displacementMm, double forceN) private void UpdatePreviewResult(double displacementMm, double forceN)
@@ -1115,8 +1126,15 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private void UpdateCurrentPoint(double x, double y) private void UpdateCurrentPoint(double x, double y)
{ {
var sample = new ObservablePoint(x, y);
if (_currentPointSample.Count == 1)
{
_currentPointSample[0] = sample;
return;
}
_currentPointSample.Clear(); _currentPointSample.Clear();
_currentPointSample.Add(new ObservablePoint(x, y)); _currentPointSample.Add(sample);
} }
private void UpdateReferenceLines() private void UpdateReferenceLines()
@@ -1148,12 +1166,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private double CalculateRealtimeXAxisMaxLimit() private double CalculateRealtimeXAxisMaxLimit()
{ {
var sampleMax = _forceSamples.Count == 0 var visibleMax = Math.Max(_realtimeChartMaxDisplacementMm, CurrentDisplacementMm);
? 0 return Math.Max(visibleMax + RealtimeChartXAxisPaddingMm, 1);
: _forceSamples.Max(sample => sample.X ?? 0);
var currentMax = Math.Max(CurrentDisplacementMm, CurrentHorizontalPosition);
var visibleMax = Math.Max(sampleMax, currentMax);
return Math.Max(visibleMax + 5, 1);
} }
private void UpdateAxisLimits(double xMax, double yMax) private void UpdateAxisLimits(double xMax, double yMax)
@@ -1203,21 +1217,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private bool UpdateKineticBand(double yMax) private bool UpdateKineticBand(double yMax)
{ {
var travelMm = GetActiveTravelMm(); var travelMm = GetActiveTravelMm();
double xi; var xi = travelMm * 0.35;
double xj; var xj = travelMm;
if (IsFinite(_runStartHorizontalPositionMm))
{
var direction = CurrentHorizontalPosition >= _runStartHorizontalPositionMm ? 1 : -1;
var kineticStart = _runStartHorizontalPositionMm + direction * travelMm * 0.35;
var kineticEnd = _runStartHorizontalPositionMm + direction * travelMm;
xi = Math.Min(kineticStart, kineticEnd);
xj = Math.Max(kineticStart, kineticEnd);
}
else
{
xi = travelMm * 0.35;
xj = travelMm;
}
var changed = HasMeaningfulChange(_kineticBand.Xi ?? double.NaN, xi) var changed = HasMeaningfulChange(_kineticBand.Xi ?? double.NaN, xi)
|| HasMeaningfulChange(_kineticBand.Xj ?? double.NaN, xj) || HasMeaningfulChange(_kineticBand.Xj ?? double.NaN, xj)
@@ -1315,6 +1316,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
_kineticSampleCount = 0; _kineticSampleCount = 0;
_runStartHorizontalPositionMm = double.NaN; _runStartHorizontalPositionMm = double.NaN;
_lastRealtimeChartDisplacementMm = double.NaN; _lastRealtimeChartDisplacementMm = double.NaN;
_realtimeChartMaxDisplacementMm = 0;
_lastRealtimeChartRefreshAt = DateTime.MinValue; _lastRealtimeChartRefreshAt = DateTime.MinValue;
} }
@@ -1813,8 +1815,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
var sledMassGrams = await ReadFloatHoldingRegisterAsync(master, RegisterSledMassGrams); var sledMassGrams = await ReadFloatHoldingRegisterAsync(master, RegisterSledMassGrams);
var liftSpeedMmPerMin = await ReadFloatHoldingRegisterAsync(master, RegisterLiftSpeed); var liftSpeedMmPerMin = await ReadFloatHoldingRegisterAsync(master, RegisterLiftSpeed);
var liftDisplacementMm = await ReadFloatHoldingRegisterAsync(master, RegisterLiftDisplacement); var liftDisplacementMm = await ReadFloatHoldingRegisterAsync(master, RegisterLiftDisplacement);
var speedMmPerMin = await ReadFloatHoldingRegisterAsync(master, RegisterHorizontalSpeed); var speedMmPerMin = await ReadFloatHoldingRegisterAsync(master, RegisterHorizontalSpeedSetpoint);
var travelMm = await ReadFloatHoldingRegisterAsync(master, RegisterHorizontalDisplacement); var travelMm = await ReadFloatHoldingRegisterAsync(master, RegisterHorizontalTravelSetpoint);
var replicateCount = Math.Clamp((int)Math.Round(await ReadFloatHoldingRegisterAsync(master, RegisterReplicateCount)), 0, 1); var replicateCount = Math.Clamp((int)Math.Round(await ReadFloatHoldingRegisterAsync(master, RegisterReplicateCount)), 0, 1);
Recipe.SledMassGrams = sledMassGrams; Recipe.SledMassGrams = sledMassGrams;
@@ -1849,10 +1851,10 @@ public sealed class MainViewModel : ObservableObject, IDisposable
await _deviceConnectionService.WriteFloatRegisterAsync(ModbusSlaveAddress, RegisterLiftDisplacement, Recipe.LiftDisplacementMm); await _deviceConnectionService.WriteFloatRegisterAsync(ModbusSlaveAddress, RegisterLiftDisplacement, Recipe.LiftDisplacementMm);
break; break;
case nameof(TestRecipe.SpeedMmPerMin): case nameof(TestRecipe.SpeedMmPerMin):
await _deviceConnectionService.WriteFloatRegisterAsync(ModbusSlaveAddress, RegisterHorizontalSpeed, Recipe.SpeedMmPerMin); await _deviceConnectionService.WriteFloatRegisterAsync(ModbusSlaveAddress, RegisterHorizontalSpeedSetpoint, Recipe.SpeedMmPerMin);
break; break;
case nameof(TestRecipe.TravelMm): case nameof(TestRecipe.TravelMm):
await _deviceConnectionService.WriteFloatRegisterAsync(ModbusSlaveAddress, RegisterHorizontalDisplacement, Recipe.TravelMm); await _deviceConnectionService.WriteFloatRegisterAsync(ModbusSlaveAddress, RegisterHorizontalTravelSetpoint, Recipe.TravelMm);
break; break;
case nameof(TestRecipe.ReplicateCount): case nameof(TestRecipe.ReplicateCount):
await _deviceConnectionService.WriteFloatRegisterAsync(ModbusSlaveAddress, RegisterReplicateCount, Math.Clamp(Recipe.ReplicateCount, 0, 1)); await _deviceConnectionService.WriteFloatRegisterAsync(ModbusSlaveAddress, RegisterReplicateCount, Math.Clamp(Recipe.ReplicateCount, 0, 1));