diff --git a/src/AR.Iec61850.Transports.Npcap/SvTransmitIntervalEstimator.cs b/src/AR.Iec61850.Transports.Npcap/SvTransmitIntervalEstimator.cs index 8394687..2b9ef30 100644 --- a/src/AR.Iec61850.Transports.Npcap/SvTransmitIntervalEstimator.cs +++ b/src/AR.Iec61850.Transports.Npcap/SvTransmitIntervalEstimator.cs @@ -1,9 +1,10 @@ namespace AR.Iec61850.Transports.Npcap; /// -/// Learns a stable SV transmit interval without allowing one scheduler-late observation +/// Learns a stable SV transmit interval without allowing scheduler-late observations /// to become the permanent wire rate. The estimator activates only after several -/// mutually consistent intervals have been observed. +/// mutually consistent unpaced intervals have been observed, then remains immutable +/// for the lifetime of that stream session. /// internal sealed class SvTransmitIntervalEstimator { @@ -35,23 +36,16 @@ public SvTransmitIntervalEstimator( public void Observe(long intervalTicks) { - if (intervalTicks < _minimumIntervalTicks || intervalTicks > _maximumIntervalTicks) - { - if (NominalIntervalTicks == 0) - ResetCandidate(); + // Once pacing is active, every subsequent interval is influenced by the pacer + // itself and by injection/scheduler lateness. Feeding those observations back + // into the nominal would create a one-way ratchet toward a slower wire rate. + // A deliberate sample-rate change therefore starts a new transport/session. + if (NominalIntervalTicks > 0) return; - } - if (NominalIntervalTicks > 0) + if (intervalTicks < _minimumIntervalTicks || intervalTicks > _maximumIntervalTicks) { - var minimumAccepted = NominalIntervalTicks * 3 / 4; - var maximumAccepted = NominalIntervalTicks * 3 / 2; - if (intervalTicks >= minimumAccepted && intervalTicks <= maximumAccepted) - { - NominalIntervalTicks = (long)Math.Round( - (NominalIntervalTicks * 0.9) + (intervalTicks * 0.1)); - } - + ResetCandidate(); return; } diff --git a/tests/AR.Iec61850.Tests/Transports/SvTransmitIntervalEstimatorTests.cs b/tests/AR.Iec61850.Tests/Transports/SvTransmitIntervalEstimatorTests.cs index 75b88da..a027fbe 100644 --- a/tests/AR.Iec61850.Tests/Transports/SvTransmitIntervalEstimatorTests.cs +++ b/tests/AR.Iec61850.Tests/Transports/SvTransmitIntervalEstimatorTests.cs @@ -37,25 +37,45 @@ public void InconsistentIntervals_DoNotActivatePacing() [Fact] public void ActiveNominalRate_IgnoresLongSchedulerStall() { - var estimator = new SvTransmitIntervalEstimator(20, 5_000, requiredConsistentIntervals: 4); - foreach (var interval in new long[] { 250, 249, 251, 250 }) - estimator.Observe(interval); - + var estimator = CreateActiveEstimator(); var nominalBeforeStall = estimator.NominalIntervalTicks; + estimator.Observe(4_000); Assert.Equal(nominalBeforeStall, estimator.NominalIntervalTicks); } [Fact] - public void ActiveNominalRate_TracksOnlyNearbySteadyIntervals() + public void ActiveNominalRate_DoesNotRatchetOnModerateLateness() { - var estimator = new SvTransmitIntervalEstimator(20, 5_000, requiredConsistentIntervals: 4); - foreach (var interval in new long[] { 250, 250, 250, 250 }) - estimator.Observe(interval); + var estimator = CreateActiveEstimator(); + var nominal = estimator.NominalIntervalTicks; + + foreach (var delayedInterval in new long[] { 300, 310, 290, 320, 300, 305, 295, 315 }) + estimator.Observe(delayedInterval); + Assert.Equal(nominal, estimator.NominalIntervalTicks); + } + + [Fact] + public void ActiveNominalRate_DoesNotFeedPacedIntervalsBackIntoEstimator() + { + var estimator = CreateActiveEstimator(); + var nominal = estimator.NominalIntervalTicks; + + estimator.Observe(248); + estimator.Observe(252); estimator.Observe(260); - Assert.InRange(estimator.NominalIntervalTicks, 250, 252); + Assert.Equal(nominal, estimator.NominalIntervalTicks); + } + + private static SvTransmitIntervalEstimator CreateActiveEstimator() + { + var estimator = new SvTransmitIntervalEstimator(20, 5_000, requiredConsistentIntervals: 4); + foreach (var interval in new long[] { 250, 249, 251, 250 }) + estimator.Observe(interval); + Assert.InRange(estimator.NominalIntervalTicks, 249, 251); + return estimator; } }