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

View File

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