diff --git a/src/ProcessBus.App.Wpf/Controls/WaveformPanelControl.cs b/src/ProcessBus.App.Wpf/Controls/WaveformPanelControl.cs index d3870ec8..a9f1800b 100644 --- a/src/ProcessBus.App.Wpf/Controls/WaveformPanelControl.cs +++ b/src/ProcessBus.App.Wpf/Controls/WaveformPanelControl.cs @@ -41,6 +41,8 @@ public sealed class WaveformPanelControl : FrameworkElement private double _currentAutoPeak; private DateTime _voltagePeakUpdatedUtc = DateTime.MinValue; private DateTime _currentPeakUpdatedUtc = DateTime.MinValue; + private string? _voltageUnit; + private string? _currentUnit; private const double AutoScaleHeadroom = 1.28; private const double AutoScaleSmoothing = 0.18; // Steady-state deadband: the observed window peak jitters a little every cycle, and an @@ -64,6 +66,15 @@ public WaveformPanelControl() typeof(WaveformPanelControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)); + // A reused WPF control must never carry a raw-count peak range into a + // different SV publisher. Stream identity is cheap state, not packet work. + public static readonly DependencyProperty StreamKeyProperty = + DependencyProperty.Register( + nameof(StreamKey), typeof(string), typeof(WaveformPanelControl), + new FrameworkPropertyMetadata(string.Empty, + FrameworkPropertyMetadataOptions.AffectsRender, + static (d, _) => ((WaveformPanelControl)d).ResetScaleState())); + public static readonly DependencyProperty ShowModeProperty = DependencyProperty.Register( nameof(ShowMode), @@ -112,6 +123,42 @@ public WaveformSnapshot? Snapshot set => SetValue(SnapshotProperty, value); } + public string StreamKey + { + get => (string)GetValue(StreamKeyProperty); + set => SetValue(StreamKeyProperty, value); + } + + private void ResetScaleState() + { + _voltageAutoPeak = 0; + _currentAutoPeak = 0; + _voltagePeakUpdatedUtc = DateTime.MinValue; + _currentPeakUpdatedUtc = DateTime.MinValue; + _voltageUnit = null; + _currentUnit = null; + } + + private void ResetLaneScaleWhenUnitsChange(bool isVoltage, string unit) + { + if (isVoltage) + { + if (!WaveformAutoRangePolicy.UnitsChanged(_voltageUnit, unit)) + return; + _voltageUnit = unit; + _voltageAutoPeak = 0; + _voltagePeakUpdatedUtc = DateTime.MinValue; + } + else + { + if (!WaveformAutoRangePolicy.UnitsChanged(_currentUnit, unit)) + return; + _currentUnit = unit; + _currentAutoPeak = 0; + _currentPeakUpdatedUtc = DateTime.MinValue; + } + } + public string ShowMode { get => (string)GetValue(ShowModeProperty); @@ -355,6 +402,7 @@ private double ResolveLaneAmplitudeMax(LaneDefinition lane, IReadOnlyList= _voltageAutoPeak || (now - _voltagePeakUpdatedUtc).TotalSeconds > PeakHoldSeconds) + // A 1000× counts-to-V conversion (or sudden real magnitude change) + // must not be hidden as a flat line for the entire hold timeout. + if (WaveformAutoRangePolicy.ShouldReleasePeakHold(_voltageAutoPeak, targetPeak) || + (now - _voltagePeakUpdatedUtc).TotalSeconds > PeakHoldSeconds) { _voltageAutoPeak = targetPeak; _voltagePeakUpdatedUtc = now; @@ -459,7 +510,8 @@ private double ResolvePeakHoldScale(bool isVoltage, double targetPeak) return Math.Max(_voltageAutoPeak, 1.0); } - if (targetPeak >= _currentAutoPeak || (now - _currentPeakUpdatedUtc).TotalSeconds > PeakHoldSeconds) + if (WaveformAutoRangePolicy.ShouldReleasePeakHold(_currentAutoPeak, targetPeak) || + (now - _currentPeakUpdatedUtc).TotalSeconds > PeakHoldSeconds) { _currentAutoPeak = targetPeak; _currentPeakUpdatedUtc = now; diff --git a/src/ProcessBus.App.Wpf/MainWindow.xaml b/src/ProcessBus.App.Wpf/MainWindow.xaml index e5ff7c6a..95e4d51d 100644 --- a/src/ProcessBus.App.Wpf/MainWindow.xaml +++ b/src/ProcessBus.App.Wpf/MainWindow.xaml @@ -922,6 +922,7 @@ - - + + - - - - - - - - - - - - - - - - - - - +