Skip to content

Fix/noise floor ratchet, adapt noise to the real floor with recovery in both directions#2933

Open
usrflo wants to merge 3 commits into
meshcore-dev:devfrom
usrflo:fix/noise-floor-ratchet
Open

Fix/noise floor ratchet, adapt noise to the real floor with recovery in both directions#2933
usrflo wants to merge 3 commits into
meshcore-dev:devfrom
usrflo:fix/noise-floor-ratchet

Conversation

@usrflo

@usrflo usrflo commented Jul 12, 2026

Copy link
Copy Markdown

Symptom

On long-running nodes (I saw it with an ufo integration branch, which combines feature/repeated-sending-2 + feature/quiet-dwell), direct-packet resends and dwell-gated TX get deferred or suppressed even on a quiet channel.
The radio's reported noise_floor drifts to the -120 clamp and never recovers, so the RSSI-margin LBT checks (isResendChannelActive, isChannelNoisy, isChannelActive with interference_threshold) stay permanently over-sensitive — every send looks like it collides with noise.

Root cause

RadioLibWrapper::loop() calibrated _noise_floor from a 64-sample block, but only accepted samples that satisfied

rssi < _noise_floor + SAMPLING_THRESHOLD   // SAMPLING_THRESHOLD = 14

That filter is a one-way ratchet: it admits ever-lower samples but rejects anything above the current floor, so the block mean can only move down. Over time it walks to the -120 lower clamp and sticks there. The only thing that reset it was resetAGC setting _noise_floor = 0 — but resetAGC is gated on agc_reset_interval, which defaults to 0 (off), and forcing 0 would anyway open a brief permissive LBT window (margin = RSSI − 0) until the next block completes.

The fix

Replace the ratcheted block mean with the median of the 64-sample block:

  • Accept every idle sample (!isReceivingPacket()) — no downward bias.
  • Sort the 64 samples and take the median (mean of the two middle values). The median rejects transient interference spikes in both directions and recovers upward as well as down.
  • Write _noise_floor only after a full block is collected. The previous value stays valid while the next block is sampled — no reset-to-0, hence no permissive LBT window during reconvergence.
  • Clamp the result to -120 (lower bound of the radio's RSSI range).
  • resetAGC() no longer touches _noise_floor; it only discards the in-progress block (the analog frontend was just reset, so queued samples are stale). _noise_floor itself is left in place because the median estimator no longer needs the hard reset that the ratchet did.

SAMPLING_THRESHOLD is removed (it only fed the ratchet filter).
NUM_NOISE_FLOOR_SAMPLES (64) moves to the header so the sample buffer can be a member array.

chart-noise-floor

usrflo and others added 3 commits July 11, 2026 12:10
The noise-floor calibration sampled only RSSI values below the current
floor + threshold, a one-way ratchet: it accepted ever-lower samples but
never recovered upward, so _noise_floor drifted to the -120 clamp and
stayed there. That left the RSSI-margin LBT (isChannelActive with
interference_threshold, plus isResendChannelActive / isChannelNoisy on
the feature branches that consume _noise_floor) permanently over-sensitive
— resends and dwell-gated TX deferred even on a quiet channel.

Replace the ratcheted block mean with the median of the 64-sample block:
- accepts every idle (!isReceivingPacket) sample — no downward bias;
- median rejects transient interference spikes (high and low outliers) and
  recovers in BOTH directions;
- _noise_floor is written only after a full block, so the previous value
  stays valid while the next block is sampled — no reset-to-0 and thus no
  permissive LBT window (margin = RSSI - 0) during reconvergence.

resetAGC no longer forces _noise_floor = 0 (the stuck-ratchet workaround);
it only discards the in-progress block so a fresh one is measured after the
analog frontend reset.

Verified: Heltec_v3_repeater firmware build (compiles RadioLibWrappers.cpp
against real RadioLib).

Co-Authored-By: Claude <noreply@anthropic.com>
Documents the ratchet-to-median fix on fix/noise-floor-ratchet: symptom,
root cause (one-way ratchet drift to -120), the median-of-64 replacement,
files touched, build verification note (sim does not compile
RadioLibWrappers.cpp; verified via Heltec_v3_repeater), and merge intent.

Co-Authored-By: Claude <noreply@anthropic.com>
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.

1 participant