Add opt-in history-anchored post-processing reconciliation for premature dial transitions (#4112)#4137
Open
Apfelstruhdl wants to merge 3 commits into
Open
Conversation
Pure, dependency-free C++ module that reconciles the per-frame recognition candidate against the last trusted value using the meter's physical priors (monotonic, slow vs. frame rate, absolute encoder, over-determined dial chain). A most-significant analog-dial carry is accepted only as a single forward step with wrap evidence from the dial below - its observed transition (kept valid for a short window, since the wrap and the valid carry can land on different frames) or inference from the value itself (a genuine carry passes the sub-position through zero; a premature dial flip leaves it unchanged). The gate applies on unambiguous frames too, since a misread dial need not land near an integer. Within-step motion is trusted on unambiguous frames; a stuck value recovers on a clean frame with a stable recognition after sustained holds; physically impossible single-frame jumps are never accepted. Includes a host unit test (g++, 53 checks) covering the carry gate on all evidence channels, premature-flip rejection on clean and ambiguous frames, backward/multi-step carry rejection, gross-misread rejection, scale invariance, floating-point boundary safety, and stability-bounded recovery.
…l#4112) Opt-in per-<NUMBER> parameters HistoryReconcile (default false) and HistoryMaxJump (default auto-derived from the dial scale). When enabled for a sequence with analog ROIs, doFlow() runs the reconciliation core in place of the fixed AllowNegativeRates/MaxRateValue guards: it derives the clean/wrap signals from the analog dial floats (plus the previous frame) and the msbStep / noiseTol / maxJump thresholds from the meter's decimal scaling (correct for digit+analog and analog-only layouts, extended resolution or not; a single-dial sequence disables the carry gate for lack of an evidence channel). The digit-chain CheckDigitIncreaseConsistency path is unchanged and behaviour is byte-identical while the feature is off. Adds NumberPost state, config parsing/handlers, parameter documentation, and a Changelog entry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an opt-in history-anchored reconciliation layer to post-processing that fixes a class of wrong readings on meters with "early transition" analog dials, addressing #4112.
On such meters a pointer (or digit) flips ahead of the real rollover, so a single frame can be recognised as a value that is off by one dial step (e.g. ±100 L). The existing single-frame logic and the
MaxRateValue/AllowNegativeRatesguards can't distinguish such a premature — and often stable — misread from real flow, so it gets accepted and poisonsPreValue; a tightened rate cap then rejects the legitimate catch-up and pins the reading. Config alone can't resolve this (the flip magnitude overlaps real flow;Analog_erroris a compile-time constant).Default-off, so existing installs are byte-for-byte unaffected.
How it works
A new pure, dependency-free module (
MeterReconcile,code/components/jomjol_flowcontroll/MeterReconcile.{h,cpp}) reconciles each frame's recognised value against the last trusted value using the meter's physical properties:All thresholds (
msbStep,noiseTol,maxJump) are derived per-frame from the meter's decimal scaling, so it works for digit+analog and analog-only layouts, extended resolution or not. It complementsCheckDigitIncreaseConsistency(which handles the digit chain) by adding the equivalent protection on the analog dial chain, and replaces the fixed rate guards only while enabled.Configuration
Two new per-
<NUMBER>parameters (docs underparam-docs/):HistoryReconcile(defaultfalse) — enable it.HistoryMaxJump(default: auto-derived from the dial scale) — optional gross-misread ceiling override.No effect on sequences without analog ROIs.
Testing
code/test/host/, 53 checks) cover the carry gate (all three evidence channels), premature-flip rejection on clean and ambiguous frames, backward/multi-step carry rejection, gross-misread rejection, scale invariance, floating-point boundary safety, and stability-bounded recovery. A CI job compiles and runs them on every push (can be dropped if you'd prefer not to touch the workflow).PreValue;ana1read as 9.x while just past 0) — rejected every frame while genuine flow between them is still tracked;ana19→0 + thousands digit +dig5=N) — crossed cleanly in real time, zero hold frames (the case that previously stalled for ~50 min);Notes
history-anchored guard (
CheckDigitIncreaseConsistency), and the reconciler's evidence signals(boundary-band cleanliness, wrap observation, sub-position inference) are properties of
continuously rotating pointers — a digit wheel parks at integers, so the ambiguity model does
not transfer. Extending it to digit-only meters with continuous digit models would be possible
future work, but needs its own ambiguity model and validation.
most-significant-dial step. With very long capture intervals, heavy flow can exceed that
per-cycle step; the value then holds during the burst and catches up shortly after flow pauses
(nothing lost, but readings lag). A time-aware accept path for such multi-step catch-up would be
possible future work.
AntiAliasingfor the alignment rotation) lowers how often the guard has to engage — measured ~50 % less analog scatter on the test meter — but that's independent of this change.