Skip to content

Rssi and noise floor improvements - #2842

Open
yg-ht wants to merge 13 commits into
meshcore-dev:devfrom
yg-ht:rssi_and_noise_floor_improvements
Open

Rssi and noise floor improvements#2842
yg-ht wants to merge 13 commits into
meshcore-dev:devfrom
yg-ht:rssi_and_noise_floor_improvements

Conversation

@yg-ht

@yg-ht yg-ht commented Jun 25, 2026

Copy link
Copy Markdown

This PR improves RSSI and noise-floor instrumentation. This should then re-enable the use of the interference threshold configuration, but I think this PR needs wider testing before that is done. So at the moment, it doesn't change the int.thresh / _prefs.interference_threshold from 0.

It enables stats-radio over Mesh as I have been diagnosing the RSSI behaviour from a number of sites concurrently. This might not be desirable, for example owing to concerns over bandwidth usage, but I am not sure I see any real harm in this. This particular stats call now has last_rssi and last_snr come from cached last-packet metrics rather than later instantaneous RSSI sampling. It also exposes stats-noise over the Mesh which goes into more detail about the noise floor sampling results. This reports the current approved noise floor, along with details about the current sampling for the next noise floor calibration. Details include: accepted sample count, sample min/median/max values, and rejected sample counts as the sample was erroneously low/high.

Noise floor calibration is performed after a small number of event triggers, as well as on a periodic basis. The rough execution flow is:

flowchart TD
    A[Periodic trigger<br/>or scheduled refresh] -- Start batch --> B0{batch loop}
    B0 -- Start sample --> B1{Batch maximum window size elapsed?}
    B1 -- No --> C{Radio idle in RX mode?}
    B1 -- Yes --> A

    C -- No --> B0
    C -- Yes --> D{Sample rate limit interval elapsed?}

    D -- No --> B0
    D -- Yes --> E[Read RSSI sample]

    E --> F{Sample acceptable?}
    F -- No --> G[Reject sample<br/>Update reject counters]
    G --> B0

    F -- Yes --> H[Accept sample<br/>Update min/median/max]
    H --> I{64 accepted samples?}

    I -- No --> B0
    I -- Yes --> J{Completed batch sane?}

    J -- No --> A
    J -- Yes --> K[Publish new noise floor]
    K --> A
Loading

So that people can tweak and understand the noise floor sampling process, there are now a few additional configuration options: sampling interval, calibration window, and clamp settings:

  • noise.sample.ms - in a calibration batch, how long do we sleep between samples (default: 50ms)
  • noise.window.secs - maximum time of a calibration batch (default: 60s)
  • noise.clamp.low - the "ludicrous" low RSSI value that can't be true (default: -125dBm)
  • noise.clamp.high - the "ludicrous" high RSSI value that can't be true (default: -80dBm)

I have been testing this on a few different repeaters in different locations and using different boards. I haven't seen any anomalous behaviour anywhere and wherever possible I have checked the noisefloor with a calibrated VNA. The values that are being generated by the VNA agree with those established by this codebase.

@yg-ht
yg-ht force-pushed the rssi_and_noise_floor_improvements branch from ba66877 to bafa673 Compare July 5, 2026 15:02
@yg-ht
yg-ht changed the base branch from main to dev July 5, 2026 15:27
@usrflo

usrflo commented Aug 5, 2026

Copy link
Copy Markdown

I checked your implementation in comparison to my #2933 that goes into the same direction.
In #2933, I accept every idle measurement and reduce the block to the median - outliers on both sides are eliminated without the need for additional filtering. This step depends on some more parameters in your implementation: is there a reason to prefer this logic (trusted_published_floor) over the simplified median approach?

ACETyr added a commit to ACETyr/MeshCore that referenced this pull request Aug 9, 2026
The fork carries meshcore-dev#2933 (usrflo's median noise-floor
estimator) ahead of mainline merging it. Nothing in the repo said so, and the
commit that applied it (a028adc) is authored by this fork with the provenance
only in its message body -- easy to lose track of, and easy to mistake for
fork-original work.

Records for each carried patch: who wrote it upstream, which PR, why we are not
waiting, and what has to happen to drop it again. Also notes meshcore-dev#2797 as resolved,
since 1.17 landed it and the replant deduplicated the fork copy automatically.

Calls out that meshcore-dev#2842 is a competing fix for the same root cause, roughly thirty
times the size, and that if it is the one that lands this is not a clean revert
-- it rewrites the estimator our patch touches, so the bench stimulus has to be
re-run against its clamp defaults rather than assumed equivalent.

Explicitly: do not re-submit meshcore-dev#2933 upstream under fork authorship.
@ACETyr

ACETyr commented Aug 10, 2026

Copy link
Copy Markdown

@yg-ht Bench comparison of this PR against #2933 and their common base, requested on #2933 — full data
and method there: #2933 (comment). Summary for this PR:

  • RAK4631 / SX1262, 869.618 MHz / 62.5 kHz / SF8 / CR5, int.thresh = 0, agc.reset.interval = 0,
    built from the PR head bafa673a with MESH_DEBUG=1 — no rebase, no merge. Stimulus: 250 zero-hop
    adverts at 0.4 s from a node ~1 m away, ~100 s at roughly 75 % channel occupancy.
  • Held −105/−106 throughout, idle and loaded. No descent, no clamp, no wedge. It stops publishing
    for the duration (one 126.5 s gap) and resumes ~26 s after the channel clears — suppressed, not
    stuck. The unpatched base under the same stimulus pins at −120 within 17 s.
  • stats-noise is what made that readable: accepted froze at 47/64, rejected_high climbed
    31 → 61 → 70, then the 60 s window expired and the batch restarted. Worth keeping.
  • Idle block cadence is 4.1 s vs 2.0 s on the base, which is just the 50 ms rate limit × 64 samples.
  • Unit tests: all 26 cases in test_rssi_packet_metrics pass, 39 across the native env, with
    g++ 15.2. As a check that the suite is actually wired to the code under test, forcing
    HIGH_BOUND_REJECT_JUMP_DB to 200 fails LowFloorStillRejectsLargeUpwardJumps and
    StrongRssiSamplesDoNotContaminateHealthyBatch; reverting turns them green again.

Two merge notes, neither about behaviour:

  • The PR no longer merges cleanly onto current dev (f6c25e6a). RadioLibWrappers.cpp/.h still
    auto-merge; the conflicts are confined to CommonCLI.cpp/.h.
  • The cause is that the four new prefs are appended to the positional blob at offsets 295–302, and dev
    has since replaced that blob with the /prefs.json ConfigSerializer (a0b52be4).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants