Bench-reproduced 2026-08-08 on the RAK4631 test rig.
Symptom
RadioLibWrapper::_noise_floor walks down to the -120 clamp and never recovers. A wedged node then stops printing noise_floor entirely (block never completes) rather than reporting a wrong value.
Root cause
src/helpers/radiolib/RadioLibWrappers.cpp:90 admits a sample only if rssi < _noise_floor + SAMPLING_THRESHOLD (14). Each 64-sample block mean is therefore computed from a lower-truncated set and is biased down. At -120 the admission threshold is -106, ordinary ~-104 idle samples no longer qualify, _num_floor_samples stalls and the block never completes.
resetAGC() (which sets _noise_floor = 0) is the only escape, and it is gated on agc_reset_interval — verified 0 on our hardware, so there is none.
Reproduction (bench RAK4631, 869.618/62.5/SF8/CR5)
| stimulus |
result |
| quiet |
stable -103 +/-1, moves both directions |
| 90 zero-hop adverts @ 4s |
excursions to -90/-91, recovers, no descent |
| 250 zero-hop adverts @ 0.4s |
-120 at 18.9s, still -120 at 168s |
Cadence is decisive: wide spacing leaves quiet gaps that let the estimator recover; tight spacing keeps ambient elevated across whole blocks so the bias compounds.
Impact on this fork
Our repeater sets interference_threshold = 0 (MyMesh.cpp:1002), so isChannelActive() short-circuits and the wedged floor is telemetry-only, not TX-gating. But the reported noise_floor in repeater stats is then permanently garbage, and any non-zero interference threshold turns it into a hard TX blocker.
Fix
Adopt upstream PR meshcore-dev#2933 (median of the block instead of the ratcheted mean; accept every idle sample; write only on block completion; resetAGC no longer forces _noise_floor = 0).
Verification pending: flash the patched build and show it does not wedge under the identical 0.4s stimulus.
Bench-reproduced 2026-08-08 on the RAK4631 test rig.
Symptom
RadioLibWrapper::_noise_floorwalks down to the -120 clamp and never recovers. A wedged node then stops printingnoise_floorentirely (block never completes) rather than reporting a wrong value.Root cause
src/helpers/radiolib/RadioLibWrappers.cpp:90admits a sample only ifrssi < _noise_floor + SAMPLING_THRESHOLD(14). Each 64-sample block mean is therefore computed from a lower-truncated set and is biased down. At -120 the admission threshold is -106, ordinary ~-104 idle samples no longer qualify,_num_floor_samplesstalls and the block never completes.resetAGC()(which sets_noise_floor = 0) is the only escape, and it is gated onagc_reset_interval— verified0on our hardware, so there is none.Reproduction (bench RAK4631, 869.618/62.5/SF8/CR5)
Cadence is decisive: wide spacing leaves quiet gaps that let the estimator recover; tight spacing keeps ambient elevated across whole blocks so the bias compounds.
Impact on this fork
Our repeater sets
interference_threshold = 0(MyMesh.cpp:1002), soisChannelActive()short-circuits and the wedged floor is telemetry-only, not TX-gating. But the reported noise_floor in repeater stats is then permanently garbage, and any non-zero interference threshold turns it into a hard TX blocker.Fix
Adopt upstream PR meshcore-dev#2933 (median of the block instead of the ratcheted mean; accept every idle sample; write only on block completion;
resetAGCno longer forces_noise_floor = 0).Verification pending: flash the patched build and show it does not wedge under the identical 0.4s stimulus.