Skip to content

Noise floor#9347

Merged
thebentern merged 22 commits into
meshtastic:developfrom
RCGV1:noise-floor
May 29, 2026
Merged

Noise floor#9347
thebentern merged 22 commits into
meshtastic:developfrom
RCGV1:noise-floor

Conversation

@RCGV1

@RCGV1 RCGV1 commented Jan 18, 2026

Copy link
Copy Markdown
Member

I implemented a noise floor calculation by getting the radios current RSSI if its not transmitting nor receiving, this value is then added to a sliding window average which can hold up to 20 measurements. Noise Floor is sent via local stats.
This is extremely useful to determine if a cavity filter is needed for a particular location.

Depends on:
meshtastic/protobufs#846

iOS Interface:
Screenshot 2026-01-17 at 7 26 34 PM

🤝 Attestations

  • I have tested that my proposed changes behave as described.
  • I have tested that my proposed changes do not cause any obvious regressions on the following devices:
    • Heltec (Lora32) V3
    • LilyGo T-Deck
    • LilyGo T-Beam
    • RAK WisBlock 4631
    • Seeed Studio T-1000E tracker card
    • Other (please specify below)

@thebentern thebentern requested a review from Copilot January 19, 2026 18:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements noise floor calculation for Meshtastic firmware by sampling the radio's RSSI when idle, maintaining a sliding window average of up to 20 measurements, and exposing the value through local stats telemetry.

Changes:

  • Added noise floor tracking infrastructure to RadioLibInterface with rolling window averaging
  • Implemented getCurrentRSSI() for all radio chip variants (SX126x, SX128x, LR11x0, RF95, SimRadio)
  • Integrated noise floor measurement into telemetry module for reporting

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
src/mesh/RadioLibInterface.h Adds noise floor tracking member variables, methods declarations, and getCurrentRSSI() pure virtual method
src/mesh/RadioLibInterface.cpp Implements updateNoiseFloor(), getAverageNoiseFloor(), and resetNoiseFloor() with rolling window logic
src/mesh/SX126xInterface.h/cpp Implements getCurrentRSSI() for SX126x radios
src/mesh/SX128xInterface.h/cpp Implements getCurrentRSSI() for SX128x radios
src/mesh/LR11x0Interface.h/cpp Implements getCurrentRSSI() for LR11x0 radios
src/mesh/RF95Interface.h/cpp Implements getCurrentRSSI() for RF95 radios
src/mesh/RadioInterface.h Adds default getCurrentRSSI() implementation returning 0
src/platform/portduino/SimRadio.cpp Implements getCurrentRSSI() for simulated radio returning -120 dBm
src/modules/Telemetry/DeviceTelemetry.cpp Integrates noise floor value into local stats telemetry and logging

Comment thread src/mesh/RadioLibInterface.cpp Outdated
Comment thread src/mesh/RadioLibInterface.cpp Outdated
Comment thread src/modules/Telemetry/DeviceTelemetry.cpp Outdated
Comment thread src/mesh/RadioLibInterface.cpp Outdated
Comment thread src/mesh/RadioLibInterface.h Outdated
Comment thread src/mesh/RadioLibInterface.cpp Outdated
Comment thread src/mesh/RadioLibInterface.cpp Outdated
Comment on lines +350 to +351

updateNoiseFloor();

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance concern: updateNoiseFloor() is called at the beginning of every onNotify() callback (line 351), which is invoked for every ISR event (ISR_TX, ISR_RX, TRANSMIT_DELAY_COMPLETED). This could be inefficient. The function already has rate limiting (5 second interval), but it still performs multiple checks on every notification. Consider moving the call to only the ISR_RX case or after startReceive() calls where it's most relevant.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot has a valid comment here. We shouldn't call this here, because every delay added here can cause the LoRa radio queue to get full (we have to read it out immediately because it can hold only 256 bytes). Doing it after entering receive mode is fine, and a period call e.g. at the end of Router::runOnce() or something.

Comment thread src/mesh/RadioLibInterface.cpp Outdated
// Get current RSSI from the radio
int16_t rssi = getCurrentRSSI();

if (rssi == 0 || rssi < NOISE_FLOOR_MIN) {

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validation logic issue: The condition checks rssi == 0 || rssi < NOISE_FLOOR_MIN to skip invalid readings. However, RSSI values are typically negative (e.g., -120 to -30 dBm), so checking for rssi == 0 might be too strict. A value of 0 dBm is technically valid though extremely unlikely. Consider whether this check is necessary, or if it should instead check for positive values which would truly indicate an error.

Suggested change
if (rssi == 0 || rssi < NOISE_FLOOR_MIN) {
if (rssi > 0 || rssi < NOISE_FLOOR_MIN) {

Copilot uses AI. Check for mistakes.
Comment thread src/mesh/RadioLibInterface.h Outdated
Comment thread src/mesh/RadioLibInterface.h Outdated
…nter

- Use robust busyTx/busyRx checks instead of simple isReceiving check
- Initialize noiseFloorSamples to NOISE_FLOOR_MIN instead of 0
- Move noise_floor assignment inside null check to prevent potential crash
- Change getNoiseFloor() and getAverageNoiseFloor() to return int32_t
- Fix RSSI validation to check for positive values (rssi > 0)
- Fix format specifier from %.1f to %d for int32_t
- Update comments to accurately reflect the sampling logic
@compumike

Copy link
Copy Markdown
Contributor

Very interesting!

We're not measuring Noise Floor (NF) in the sense of just random (stationary random process) background: thermal, amplifier, etc.

We're really interested in interference (potentially non-LoRa as well as LoRa, though excluding samples where a receive preamble is already detected by the radio).

It might be interesting to extract other statistics of these NF samples, not just the average.

For example: if we also extract the 10th-percentile and 90th-percentile values, this could tell us whether the interference is present all the time (10th and 90th close together), versus occasional (10th and 90th far apart). If they're far apart, it might actually inform us to delay a packet transmit if we measure a getRSSI(false) above a certain percentile, so we can attempt to avoid even non-LoRa interference! (Or maybe delay if it's more than X dB above the 10th percentile NF.) That would be pretty cool. 😄

@compumike

Copy link
Copy Markdown
Contributor

Another thought: about how long does the getRSSI(false) call take to return?

Assuming it's quick, have you looked at taking a burst of consecutive samples (say, 10) and averaging them before storing in the array? (Maybe not just averaging the 10 samples as dBm values, but instead converting them to linear power mW values, averaging in mW, and then converting back to dBm.) This might give a cleaner short-term NF measurement which then goes into the longer-term array.

@jp-bennett jp-bennett added the enhancement New feature or request label Jan 31, 2026
@kamcio2603

Copy link
Copy Markdown

any update?

@jock78

jock78 commented Feb 8, 2026

Copy link
Copy Markdown

Hi can provide Seeed Xiao Esp32, LILYGO T3v1.6.1, Heltec v4 and Heltec Pocket for testing.

Comment thread src/mesh/LR11x0Interface.cpp Outdated
@michalpan

Copy link
Copy Markdown

Hi,

this is a super cool feature. It would help a lot with installations in locations with high noise. But it would be possible to know this ahead of time, instead of hitting it during debugging why a node is deaf…

it would be super cool to see in the app.

cheers,
Michal

Per PR review feedback, calling updateNoiseFloor() in onNotify() for every
ISR event (ISR_TX, ISR_RX, TRANSMIT_DELAY_COMPLETED) can cause the LoRa
radio queue to get full. The noise floor sampling still happens in
startReceive() and after transmitting.
@RCGV1 RCGV1 requested a review from GUVWAF February 25, 2026 01:29
@RCGV1

RCGV1 commented Feb 25, 2026

Copy link
Copy Markdown
Member Author

@GUVWAF I had to enable "GodMode" to access that fucntion for lr11

@GUVWAF

GUVWAF commented Feb 26, 2026

Copy link
Copy Markdown
Member

@RCGV1 Ah, I see. We shouldn't do that, I'll see if I can make that a public method in RadioLib.

@GUVWAF

GUVWAF commented Mar 1, 2026

Copy link
Copy Markdown
Member

@RCGV1 For LR11x0, when we update RadioLib to include jgromes/RadioLib#1714, we can use lora.getRSSI(false, true).

For the rest, please see my comment on clamping.

Furthermore, we should still remove updating the noise floor from here, as we need the radio to go the receive mode as fast as possible in order not to lose packets. Moreover, at least for the LR11x0, it is necessary to have it in receive mode before doing the RSSI measurement.

Lastly, in startReceive() it is also done only before enabling the Rx interrupt, which is also not ideal.
I would say maybe we could call updateNoiseFloor() from within DeviceTelemetry, just before we send our telemetry to the phone, which happens every minute. Then every 15 min. when the LocalStats are sent, we have a fairly good average.

Comment thread src/modules/Telemetry/DeviceTelemetry.cpp Outdated
Comment thread src/mesh/RadioLibInterface.cpp Outdated
Comment thread src/mesh/RF95Interface.cpp
Comment thread src/mesh/RadioLibInterface.cpp Outdated
Comment thread src/mesh/LR11x0Interface.cpp Outdated
@Leoguy77

Leoguy77 commented Apr 3, 2026

Copy link
Copy Markdown

any updates? This feature would be great🙏

@michalpan

michalpan commented Apr 3, 2026 via email

Copy link
Copy Markdown

@RCGV1

RCGV1 commented Apr 3, 2026

Copy link
Copy Markdown
Member Author

I am waiting for the next RadioLib update because one of the chops doesn't support instant rssi and we need the next RadioLib update to support it (already got merged there)

Address noise-floor PR review feedback: move RSSI sampling out of radio hot paths, use the public LR11x0 instant RSSI API, reject invalid -128 readings without clamping, and synchronize noise-floor telemetry state.
@RCGV1

RCGV1 commented May 12, 2026

Copy link
Copy Markdown
Member Author

LR11x0 now uses lora.getRSSI(false, true).
Removed noise-floor updates from radio hot paths; sampling now happens from DeviceTelemetry.
Invalid LR11x0 RSSI -128 is rejected without clamping to -120.
Added locking around noise-floor sample state and fixed telemetry logging/default value handling.

@GUVWAF GUVWAF left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me now, left two comments. At least RadioLib's GODMODE should now be disabled.

Comment thread platformio.ini Outdated
Comment thread src/mesh/RadioLibInterface.cpp Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment thread src/modules/Telemetry/DeviceTelemetry.cpp
Comment thread src/platform/portduino/SimRadio.cpp
@RCGV1 RCGV1 requested a review from GUVWAF May 26, 2026 15:38
@thebentern thebentern merged commit 982440d into meshtastic:develop May 29, 2026
80 checks passed
thebentern added a commit that referenced this pull request May 29, 2026
Evil8it pushed a commit to Evil8it/ME4TACTNK that referenced this pull request Jun 10, 2026
raghumad pushed a commit to raghumad/mezulla-firmware that referenced this pull request Jun 25, 2026
* add noise floor

* Sliding window noise floor

* Add getCurrentRSSI() to SimRadio for noise floor support

* Remove sendLocalStatsToPhone call from runOnce

* Change noise floor to int32_t type

* Use int32_t for RSSI sample storage in noise floor

* Remove float cast from noise floor assignment

* Fix Copilot review issues: fix noise floor logic, types, and null pointer

- Use robust busyTx/busyRx checks instead of simple isReceiving check
- Initialize noiseFloorSamples to NOISE_FLOOR_MIN instead of 0
- Move noise_floor assignment inside null check to prevent potential crash
- Change getNoiseFloor() and getAverageNoiseFloor() to return int32_t
- Fix RSSI validation to check for positive values (rssi > 0)
- Fix format specifier from %.1f to %d for int32_t
- Update comments to accurately reflect the sampling logic

* Fix RSSI condition to include zero value

* Change noise floor initialization to zero

* Disable noise floor for LR11x0 chips: getRSSI(bool) unsupported

* Remove updateNoiseFloor call from onNotify to avoid radio queue overflow

Per PR review feedback, calling updateNoiseFloor() in onNotify() for every
ISR event (ISR_TX, ISR_RX, TRANSMIT_DELAY_COMPLETED) can cause the LoRa
radio queue to get full. The noise floor sampling still happens in
startReceive() and after transmitting.

* fix lr11x0 current rssi

* Address noise floor review comments

* Address Copilot SimRadio noise floor comments

* Fix RadioLibInterface formatting

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.