From d4b8828c791c7e6e63b94b605f88786bec25a3cf Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sat, 17 Jan 2026 09:30:46 -0800 Subject: [PATCH 01/13] add noise floor --- src/mesh/RadioLibInterface.cpp | 112 +++++++++++++++++++++- src/mesh/RadioLibInterface.h | 51 +++++++++- src/mesh/SX126xInterface.cpp | 6 ++ src/mesh/SX126xInterface.h | 2 + src/modules/Telemetry/DeviceTelemetry.cpp | 8 +- 5 files changed, 171 insertions(+), 8 deletions(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 80e51b8bcbd..b9a5a2dca5c 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -15,6 +15,7 @@ #include "PortduinoGlue.h" #include "meshUtils.h" #endif + void LockingArduinoHal::spiBeginTransaction() { spiLock->lock(); @@ -28,6 +29,7 @@ void LockingArduinoHal::spiEndTransaction() spiLock->unlock(); } + #if ARCH_PORTDUINO void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in) { @@ -40,6 +42,12 @@ RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE c : NotifiedWorkerThread("RadioIf"), module(hal, cs, irq, rst, busy), iface(_iface) { instance = this; + + // Initialize noise floor samples array + for (uint8_t i = 0; i < NOISE_FLOOR_SAMPLES; i++) { + noiseFloorSamples[i] = 0; + } + #if defined(ARCH_STM32WL) && defined(USE_SX1262) module.setCb_digitalWrite(stm32wl_emulate_digitalWrite); module.setCb_digitalRead(stm32wl_emulate_digitalRead); @@ -246,6 +254,96 @@ bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id) return txQueue.find(from, id); } +// ============================================================================ +// NOISE FLOOR IMPLEMENTATION +// ============================================================================ + +void RadioLibInterface::updateNoiseFloor() +{ + // Only update if we're in receive mode and not actively receiving + if (!isReceiving) { + LOG_DEBUG("Skipping noise floor update - not in receive mode or actively receiving"); + return; + } + + // Don't try to read RSSI if we're not in a stable receive state + if (sendingPacket != NULL) { + return; + } + + // Rate limit updates + uint32_t now = millis(); + if (now - lastNoiseFloorUpdate < NOISE_FLOOR_UPDATE_INTERVAL_MS) { + return; + } + lastNoiseFloorUpdate = now; + + // Get current RSSI from the radio + int16_t rssi = getCurrentRSSI(); + + if (rssi == 0 || rssi < NOISE_FLOOR_MIN) { + LOG_DEBUG("Skipping invalid RSSI reading: %d", rssi); + return; + } + + // Check if we need to restart collection (hit max samples) + if (noiseFloorSampleCount >= NOISE_FLOOR_SAMPLES) { + LOG_INFO("Noise floor sample collection complete (%d samples, avg: %.1f dBm). Restarting.", NOISE_FLOOR_SAMPLES, + currentNoiseFloor); + noiseFloorSampleCount = 0; + // Clear the array + for (uint8_t i = 0; i < NOISE_FLOOR_SAMPLES; i++) { + noiseFloorSamples[i] = 0; + } + } + + // Store the sample + noiseFloorSamples[noiseFloorSampleCount] = (float)rssi; + noiseFloorSampleCount++; + + // Calculate the new average + currentNoiseFloor = getAverageNoiseFloor(); + + LOG_INFO("Noise floor: %.1f dBm (sample %d/%d: %d dBm)", currentNoiseFloor, noiseFloorSampleCount, NOISE_FLOOR_SAMPLES, rssi); +} + +float RadioLibInterface::getAverageNoiseFloor() +{ + if (noiseFloorSampleCount == 0) { + return NOISE_FLOOR_MIN; // Return minimum if no samples + } + + float sum = 0.0f; + + for (uint8_t i = 0; i < noiseFloorSampleCount; i++) { + sum += noiseFloorSamples[i]; + } + + float average = sum / noiseFloorSampleCount; + + // Clamp to minimum of -120 dBm + if (average < NOISE_FLOOR_MIN) { + average = NOISE_FLOOR_MIN; + } + + return average; +} + +void RadioLibInterface::resetNoiseFloor() +{ + noiseFloorSampleCount = 0; + currentNoiseFloor = NOISE_FLOOR_MIN; + for (uint8_t i = 0; i < NOISE_FLOOR_SAMPLES; i++) { + noiseFloorSamples[i] = 0; + } + lastNoiseFloorUpdate = 0; + LOG_INFO("Noise floor reset - collection will restart"); +} + +// ============================================================================ +// END NOISE FLOOR IMPLEMENTATION +// ============================================================================ + /** radio helper thread callback. We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of 'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision. @@ -255,6 +353,9 @@ currently active. */ void RadioLibInterface::onNotify(uint32_t notification) { + + updateNoiseFloor(); + switch (notification) { case ISR_TX: handleTransmitInterrupt(); @@ -386,11 +487,6 @@ bool RadioLibInterface::removePendingTXPacket(NodeNum from, PacketId id, uint32_ return false; } -/** - * Remove a packet that is eligible for replacement from the TX queue - */ -// void RadioLibInterface::removePending - void RadioLibInterface::handleTransmitInterrupt() { // This can be null if we forced the device to enter standby mode. In that case @@ -419,6 +515,9 @@ void RadioLibInterface::completeSending() // We are done sending that packet, release it packetPool.release(p); + + // Update noise floor after transmitting (radio is now in a good state to sample) + updateNoiseFloor(); } } @@ -516,6 +615,9 @@ void RadioLibInterface::startReceive() { isReceiving = true; powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn); + + // Opportunistically update noise floor when entering receive mode + updateNoiseFloor(); } void RadioLibInterface::configHardwareForSend() diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 833c887108e..a29877a40d2 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -97,11 +97,38 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified /// are _trying_ to receive a packet currently (note - we might just be waiting for one) bool isReceiving = false; + protected: + // Noise floor tracking - collects up to 20 samples then restarts + static const uint8_t NOISE_FLOOR_SAMPLES = 20; + static const int16_t NOISE_FLOOR_MIN = -120; // Minimum noise floor clamp in dBm + float noiseFloorSamples[NOISE_FLOOR_SAMPLES]; + uint8_t noiseFloorSampleCount = 0; + uint32_t lastNoiseFloorUpdate = 0; + static const uint32_t NOISE_FLOOR_UPDATE_INTERVAL_MS = 5000; + float currentNoiseFloor = NOISE_FLOOR_MIN; + + /** + * Update the noise floor measurement by sampling RSSI when not receiving + * Collects up to 20 samples then automatically restarts + */ + void updateNoiseFloor(); + + /** + * Override from NotifiedWorkerThread - called periodically by the thread + */ + virtual int16_t getCurrentRSSI() = 0; + public: /** Our ISR code currently needs this to find our active instance */ static RadioLibInterface *instance; + /** + * Calculate the average noise floor from collected samples + * Clamps result to minimum of -120 dBm + */ + float getAverageNoiseFloor(); + /** * Glue functions called from ISR land */ @@ -158,6 +185,28 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified /** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */ virtual bool findInTxQueue(NodeNum from, PacketId id) override; + /** + * Get the current calculated noise floor in dBm + * Returns -120 dBm if not yet calibrated + */ + float getNoiseFloor() { return currentNoiseFloor; } + + /** + * Check if we have collected any noise floor samples + */ + bool hasNoiseFloorSamples() { return noiseFloorSampleCount > 0; } + + /** + * Get the number of samples collected in current cycle (0-20) + */ + uint8_t getNoiseFloorSampleCount() { return noiseFloorSampleCount; } + + /** + * Reset the noise floor calibration + * Will automatically restart collection + */ + void resetNoiseFloor(); + private: /** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually * doing the transmit */ @@ -264,4 +313,4 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override; -}; +}; \ No newline at end of file diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 0e3069c14cf..52668fb0006 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -256,6 +256,12 @@ template bool SX126xInterface::reconfigure() return RADIOLIB_ERR_NONE; } +template int16_t SX126xInterface::getCurrentRSSI() +{ + float rssi = lora.getRSSI(false); + return (int16_t)round(rssi); +} + template void INTERRUPT_ATTR SX126xInterface::disableInterrupt() { lora.clearDio1Action(); diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index b8f16ac6da9..02649b4ebc8 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -39,6 +39,8 @@ template class SX126xInterface : public RadioLibInterface */ T lora; + int16_t getCurrentRSSI() override; + /** * Glue functions called from ISR land */ diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 066b9361d69..90ecccdee32 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -18,6 +18,8 @@ int32_t DeviceTelemetryModule::runOnce() { + sendLocalStatsToPhone(); + refreshUptime(); bool isImpoliteRole = IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_ROUTER); @@ -117,6 +119,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry() telemetry.variant.local_stats.air_util_tx = airTime->utilizationTXPercent(); telemetry.variant.local_stats.num_online_nodes = numOnlineNodes; telemetry.variant.local_stats.num_total_nodes = nodeDB->getNumMeshNodes(); + telemetry.variant.local_stats.noise_floor = RadioLibInterface::instance->getAverageNoiseFloor(); if (RadioLibInterface::instance) { telemetry.variant.local_stats.num_packets_tx = RadioLibInterface::instance->txGood; telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad; @@ -141,10 +144,11 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry() telemetry.variant.local_stats.num_tx_relay_canceled = router->txRelayCanceled; } - LOG_INFO("Sending local stats: uptime=%i, channel_utilization=%f, air_util_tx=%f, num_online_nodes=%i, num_total_nodes=%i", + LOG_INFO("Sending local stats: uptime=%i, channel_utilization=%f, air_util_tx=%f, num_online_nodes=%i, num_total_nodes=%i, " + "noise_floor=%f", telemetry.variant.local_stats.uptime_seconds, telemetry.variant.local_stats.channel_utilization, telemetry.variant.local_stats.air_util_tx, telemetry.variant.local_stats.num_online_nodes, - telemetry.variant.local_stats.num_total_nodes); + telemetry.variant.local_stats.num_total_nodes, telemetry.variant.local_stats.noise_floor); LOG_INFO("num_packets_tx=%i, num_packets_rx=%i, num_packets_rx_bad=%i", telemetry.variant.local_stats.num_packets_tx, telemetry.variant.local_stats.num_packets_rx, telemetry.variant.local_stats.num_packets_rx_bad); From 5fd527a7cf41e98f12c50d5400d44b4b141acb45 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sat, 17 Jan 2026 19:16:07 -0800 Subject: [PATCH 02/13] Sliding window noise floor --- src/mesh/LR11x0Interface.cpp | 6 ++++ src/mesh/LR11x0Interface.h | 2 ++ src/mesh/RF95Interface.cpp | 6 ++++ src/mesh/RF95Interface.h | 2 ++ src/mesh/RadioInterface.h | 6 ++++ src/mesh/RadioLibInterface.cpp | 64 +++++++++++++++------------------- src/mesh/RadioLibInterface.h | 13 +++---- src/mesh/SX128xInterface.cpp | 6 ++++ src/mesh/SX128xInterface.h | 2 ++ 9 files changed, 66 insertions(+), 41 deletions(-) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index af6dd92e962..63d37aa77d6 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -306,4 +306,10 @@ template bool LR11x0Interface::sleep() return true; } + +template int16_t LR11x0Interface::getCurrentRSSI() +{ + float rssi = lora.getRSSI(false); + return (int16_t)round(rssi); +} #endif diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h index 840184bbf60..8a9bec2e145 100644 --- a/src/mesh/LR11x0Interface.h +++ b/src/mesh/LR11x0Interface.h @@ -33,6 +33,8 @@ template class LR11x0Interface : public RadioLibInterface */ T lora; + int16_t getCurrentRSSI() override; + /** * Glue functions called from ISR land */ diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index da0039d3839..13f49e21f41 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -338,4 +338,10 @@ bool RF95Interface::sleep() return true; } + +int16_t RF95Interface::getCurrentRSSI() +{ + float rssi = lora->getRSSI(false); + return (int16_t)round(rssi); +} #endif \ No newline at end of file diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h index ffd8ae0082b..2226067646e 100644 --- a/src/mesh/RF95Interface.h +++ b/src/mesh/RF95Interface.h @@ -37,6 +37,8 @@ class RF95Interface : public RadioLibInterface */ virtual void disableInterrupt() override; + int16_t getCurrentRSSI() override; + /** * Enable a particular ISR callback glue function */ diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 6049a11cc27..42a09208aac 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -250,6 +250,12 @@ class RadioInterface */ virtual void saveChannelNum(uint32_t savedChannelNum); + /** + * Get current RSSI reading from the radio. + * Returns 0 if not available. + */ + virtual int16_t getCurrentRSSI() { return 0; } + private: /** * Convert our modemConfig enum into wf, sf, etc... diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index b9a5a2dca5c..6a1fb55a91f 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -254,13 +254,8 @@ bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id) return txQueue.find(from, id); } -// ============================================================================ -// NOISE FLOOR IMPLEMENTATION -// ============================================================================ - void RadioLibInterface::updateNoiseFloor() { - // Only update if we're in receive mode and not actively receiving if (!isReceiving) { LOG_DEBUG("Skipping noise floor update - not in receive mode or actively receiving"); return; @@ -286,40 +281,46 @@ void RadioLibInterface::updateNoiseFloor() return; } - // Check if we need to restart collection (hit max samples) - if (noiseFloorSampleCount >= NOISE_FLOOR_SAMPLES) { - LOG_INFO("Noise floor sample collection complete (%d samples, avg: %.1f dBm). Restarting.", NOISE_FLOOR_SAMPLES, - currentNoiseFloor); - noiseFloorSampleCount = 0; - // Clear the array - for (uint8_t i = 0; i < NOISE_FLOOR_SAMPLES; i++) { - noiseFloorSamples[i] = 0; - } - } + // Store the sample in the rolling window + noiseFloorSamples[currentSampleIndex] = (float)rssi; + currentSampleIndex++; - // Store the sample - noiseFloorSamples[noiseFloorSampleCount] = (float)rssi; - noiseFloorSampleCount++; + // Wrap around when we reach the buffer size - this creates the rolling window + if (currentSampleIndex >= NOISE_FLOOR_SAMPLES) { + currentSampleIndex = 0; + isNoiseFloorBufferFull = true; + } - // Calculate the new average + // Calculate the new average using the rolling window currentNoiseFloor = getAverageNoiseFloor(); - LOG_INFO("Noise floor: %.1f dBm (sample %d/%d: %d dBm)", currentNoiseFloor, noiseFloorSampleCount, NOISE_FLOOR_SAMPLES, rssi); + LOG_DEBUG("Noise floor: %.1f dBm (samples: %d, latest: %d dBm)", currentNoiseFloor, getNoiseFloorSampleCount(), rssi); } float RadioLibInterface::getAverageNoiseFloor() { - if (noiseFloorSampleCount == 0) { - return NOISE_FLOOR_MIN; // Return minimum if no samples + uint8_t sampleCount = getNoiseFloorSampleCount(); + + if (sampleCount == 0) { + return 0; // Return minimum if no samples } float sum = 0.0f; - for (uint8_t i = 0; i < noiseFloorSampleCount; i++) { - sum += noiseFloorSamples[i]; + // Calculate sum using the rolling window + if (isNoiseFloorBufferFull) { + // Buffer is full - sum all samples + for (uint8_t i = 0; i < NOISE_FLOOR_SAMPLES; i++) { + sum += noiseFloorSamples[i]; + } + } else { + // Buffer not yet full - sum only collected samples + for (uint8_t i = 0; i < currentSampleIndex; i++) { + sum += noiseFloorSamples[i]; + } } - float average = sum / noiseFloorSampleCount; + float average = sum / sampleCount; // Clamp to minimum of -120 dBm if (average < NOISE_FLOOR_MIN) { @@ -331,19 +332,12 @@ float RadioLibInterface::getAverageNoiseFloor() void RadioLibInterface::resetNoiseFloor() { - noiseFloorSampleCount = 0; + currentSampleIndex = 0; + isNoiseFloorBufferFull = false; currentNoiseFloor = NOISE_FLOOR_MIN; - for (uint8_t i = 0; i < NOISE_FLOOR_SAMPLES; i++) { - noiseFloorSamples[i] = 0; - } - lastNoiseFloorUpdate = 0; - LOG_INFO("Noise floor reset - collection will restart"); + LOG_INFO("Noise floor reset - rolling window collection will restart"); } -// ============================================================================ -// END NOISE FLOOR IMPLEMENTATION -// ============================================================================ - /** radio helper thread callback. We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of 'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision. diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index a29877a40d2..a1a8bbec33a 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -98,18 +98,19 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified bool isReceiving = false; protected: - // Noise floor tracking - collects up to 20 samples then restarts + // Noise floor tracking - rolling window of samples static const uint8_t NOISE_FLOOR_SAMPLES = 20; static const int16_t NOISE_FLOOR_MIN = -120; // Minimum noise floor clamp in dBm float noiseFloorSamples[NOISE_FLOOR_SAMPLES]; - uint8_t noiseFloorSampleCount = 0; + uint8_t currentSampleIndex = 0; + bool isNoiseFloorBufferFull = false; uint32_t lastNoiseFloorUpdate = 0; static const uint32_t NOISE_FLOOR_UPDATE_INTERVAL_MS = 5000; float currentNoiseFloor = NOISE_FLOOR_MIN; /** * Update the noise floor measurement by sampling RSSI when not receiving - * Collects up to 20 samples then automatically restarts + * Uses a rolling window approach to maintain recent samples */ void updateNoiseFloor(); @@ -194,12 +195,12 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified /** * Check if we have collected any noise floor samples */ - bool hasNoiseFloorSamples() { return noiseFloorSampleCount > 0; } + bool hasNoiseFloorSamples() { return isNoiseFloorBufferFull || currentSampleIndex > 0; } /** - * Get the number of samples collected in current cycle (0-20) + * Get the number of samples in the rolling window */ - uint8_t getNoiseFloorSampleCount() { return noiseFloorSampleCount; } + uint8_t getNoiseFloorSampleCount() { return isNoiseFloorBufferFull ? NOISE_FLOOR_SAMPLES : currentSampleIndex; } /** * Reset the noise floor calibration diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 80872af07dd..040219740e2 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -323,4 +323,10 @@ template bool SX128xInterface::sleep() return true; } + +template int16_t SX128xInterface::getCurrentRSSI() +{ + float rssi = lora.getRSSI(false); + return (int16_t)round(rssi); +} #endif \ No newline at end of file diff --git a/src/mesh/SX128xInterface.h b/src/mesh/SX128xInterface.h index acdcbbb27c8..cf44bcb89ce 100644 --- a/src/mesh/SX128xInterface.h +++ b/src/mesh/SX128xInterface.h @@ -35,6 +35,8 @@ template class SX128xInterface : public RadioLibInterface */ T lora; + int16_t getCurrentRSSI() override; + /** * Glue functions called from ISR land */ From e1b995fa2eb897c4b8fe729c22b3ecc68d5cb0fb Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sat, 17 Jan 2026 19:19:15 -0800 Subject: [PATCH 03/13] Add getCurrentRSSI() to SimRadio for noise floor support --- src/platform/portduino/SimRadio.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp index 6e7fe24cbaf..f9fabb6170d 100644 --- a/src/platform/portduino/SimRadio.cpp +++ b/src/platform/portduino/SimRadio.cpp @@ -362,4 +362,10 @@ uint32_t SimRadio::getPacketTime(uint32_t pl, bool received) uint32_t msecs = tPacket * 1000; return msecs; +} + +int16_t SimRadio::getCurrentRSSI() +{ + // Simulated radio - return a reasonable default noise floor + return -120; } \ No newline at end of file From c12de75f30fa5bcebdc85c741c7bc1b90cb0dec5 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sat, 17 Jan 2026 19:21:42 -0800 Subject: [PATCH 04/13] Remove sendLocalStatsToPhone call from runOnce --- src/modules/Telemetry/DeviceTelemetry.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 90ecccdee32..2e50db1c885 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -18,7 +18,6 @@ int32_t DeviceTelemetryModule::runOnce() { - sendLocalStatsToPhone(); refreshUptime(); bool isImpoliteRole = From 02c980b026e872b911f96acc986368de53124d38 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:08:58 -0800 Subject: [PATCH 05/13] Change noise floor to int32_t type --- src/mesh/RadioLibInterface.cpp | 8 ++++---- src/mesh/RadioLibInterface.h | 6 +++--- src/modules/Telemetry/DeviceTelemetry.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 6a1fb55a91f..ccc7c7c2c46 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -297,15 +297,15 @@ void RadioLibInterface::updateNoiseFloor() LOG_DEBUG("Noise floor: %.1f dBm (samples: %d, latest: %d dBm)", currentNoiseFloor, getNoiseFloorSampleCount(), rssi); } -float RadioLibInterface::getAverageNoiseFloor() +int32_t RadioLibInterface::getAverageNoiseFloor() { uint8_t sampleCount = getNoiseFloorSampleCount(); if (sampleCount == 0) { - return 0; // Return minimum if no samples + return NOISE_FLOOR_MIN; // Return minimum if no samples } - float sum = 0.0f; + int32_t sum = 0; // Calculate sum using the rolling window if (isNoiseFloorBufferFull) { @@ -320,7 +320,7 @@ float RadioLibInterface::getAverageNoiseFloor() } } - float average = sum / sampleCount; + int32_t average = sum / sampleCount; // Clamp to minimum of -120 dBm if (average < NOISE_FLOOR_MIN) { diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index a1a8bbec33a..7ca4f5517a6 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -100,13 +100,13 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified protected: // Noise floor tracking - rolling window of samples static const uint8_t NOISE_FLOOR_SAMPLES = 20; - static const int16_t NOISE_FLOOR_MIN = -120; // Minimum noise floor clamp in dBm - float noiseFloorSamples[NOISE_FLOOR_SAMPLES]; + static const int32_t NOISE_FLOOR_MIN = -120; // Minimum noise floor clamp in dBm + int32_t noiseFloorSamples[NOISE_FLOOR_SAMPLES]; uint8_t currentSampleIndex = 0; bool isNoiseFloorBufferFull = false; uint32_t lastNoiseFloorUpdate = 0; static const uint32_t NOISE_FLOOR_UPDATE_INTERVAL_MS = 5000; - float currentNoiseFloor = NOISE_FLOOR_MIN; + int32_t currentNoiseFloor = NOISE_FLOOR_MIN; /** * Update the noise floor measurement by sampling RSSI when not receiving diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 2e50db1c885..32e224b9f03 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -118,7 +118,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry() telemetry.variant.local_stats.air_util_tx = airTime->utilizationTXPercent(); telemetry.variant.local_stats.num_online_nodes = numOnlineNodes; telemetry.variant.local_stats.num_total_nodes = nodeDB->getNumMeshNodes(); - telemetry.variant.local_stats.noise_floor = RadioLibInterface::instance->getAverageNoiseFloor(); + telemetry.variant.local_stats.noise_floor = (float)RadioLibInterface::instance->getAverageNoiseFloor(); if (RadioLibInterface::instance) { telemetry.variant.local_stats.num_packets_tx = RadioLibInterface::instance->txGood; telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad; From 3ce3112609a2f95c37d2820847e59e7156008418 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:21:43 -0800 Subject: [PATCH 06/13] Use int32_t for RSSI sample storage in noise floor --- src/mesh/RadioLibInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index ccc7c7c2c46..8396c1be69d 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -282,7 +282,7 @@ void RadioLibInterface::updateNoiseFloor() } // Store the sample in the rolling window - noiseFloorSamples[currentSampleIndex] = (float)rssi; + noiseFloorSamples[currentSampleIndex] = (int32_t)rssi; currentSampleIndex++; // Wrap around when we reach the buffer size - this creates the rolling window From 3e52ca34593c79fbcd12fdcd5891b9b9d5a9a8b4 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:24:39 -0800 Subject: [PATCH 07/13] Remove float cast from noise floor assignment --- src/modules/Telemetry/DeviceTelemetry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 32e224b9f03..2e50db1c885 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -118,7 +118,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry() telemetry.variant.local_stats.air_util_tx = airTime->utilizationTXPercent(); telemetry.variant.local_stats.num_online_nodes = numOnlineNodes; telemetry.variant.local_stats.num_total_nodes = nodeDB->getNumMeshNodes(); - telemetry.variant.local_stats.noise_floor = (float)RadioLibInterface::instance->getAverageNoiseFloor(); + telemetry.variant.local_stats.noise_floor = RadioLibInterface::instance->getAverageNoiseFloor(); if (RadioLibInterface::instance) { telemetry.variant.local_stats.num_packets_tx = RadioLibInterface::instance->txGood; telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad; From d1b1c104ea4a791b38a30a7986b1042cb1afc4bf Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:42:32 -0800 Subject: [PATCH 08/13] 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 --- src/mesh/RadioLibInterface.cpp | 20 ++++++++++++-------- src/mesh/RadioLibInterface.h | 16 ++++++++-------- src/modules/Telemetry/DeviceTelemetry.cpp | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 8396c1be69d..be9c66e92ee 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -43,9 +43,9 @@ RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE c { instance = this; - // Initialize noise floor samples array + // Initialize noise floor samples array with a valid baseline for (uint8_t i = 0; i < NOISE_FLOOR_SAMPLES; i++) { - noiseFloorSamples[i] = 0; + noiseFloorSamples[i] = NOISE_FLOOR_MIN; } #if defined(ARCH_STM32WL) && defined(USE_SX1262) @@ -256,13 +256,17 @@ bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id) void RadioLibInterface::updateNoiseFloor() { - if (!isReceiving) { - LOG_DEBUG("Skipping noise floor update - not in receive mode or actively receiving"); + // Only sample when the radio is not actively transmitting or receiving + // This allows sampling both when truly idle and after transmitting (when isReceiving may be false) + bool busyTx = sendingPacket != NULL; + bool busyRx = isReceiving && isActivelyReceiving(); + + if (busyTx || busyRx) { return; } - // Don't try to read RSSI if we're not in a stable receive state - if (sendingPacket != NULL) { + // Also check for pending interrupts + if (isIRQPending()) { return; } @@ -276,7 +280,7 @@ void RadioLibInterface::updateNoiseFloor() // Get current RSSI from the radio int16_t rssi = getCurrentRSSI(); - if (rssi == 0 || rssi < NOISE_FLOOR_MIN) { + if (rssi > 0 || rssi < NOISE_FLOOR_MIN) { LOG_DEBUG("Skipping invalid RSSI reading: %d", rssi); return; } @@ -294,7 +298,7 @@ void RadioLibInterface::updateNoiseFloor() // Calculate the new average using the rolling window currentNoiseFloor = getAverageNoiseFloor(); - LOG_DEBUG("Noise floor: %.1f dBm (samples: %d, latest: %d dBm)", currentNoiseFloor, getNoiseFloorSampleCount(), rssi); + LOG_DEBUG("Noise floor: %d dBm (samples: %d, latest: %d dBm)", currentNoiseFloor, getNoiseFloorSampleCount(), rssi); } int32_t RadioLibInterface::getAverageNoiseFloor() diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 7ca4f5517a6..a3ef28edb6c 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -109,7 +109,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified int32_t currentNoiseFloor = NOISE_FLOOR_MIN; /** - * Update the noise floor measurement by sampling RSSI when not receiving + * Update the noise floor measurement by sampling RSSI when receiving * Uses a rolling window approach to maintain recent samples */ void updateNoiseFloor(); @@ -124,11 +124,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ static RadioLibInterface *instance; + /** + * Get the current calculated noise floor in dBm + * Returns -120 dBm if not yet calibrated + */ + int32_t getNoiseFloor() { return currentNoiseFloor; } + /** * Calculate the average noise floor from collected samples * Clamps result to minimum of -120 dBm */ - float getAverageNoiseFloor(); + int32_t getAverageNoiseFloor(); /** * Glue functions called from ISR land @@ -186,12 +192,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified /** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */ virtual bool findInTxQueue(NodeNum from, PacketId id) override; - /** - * Get the current calculated noise floor in dBm - * Returns -120 dBm if not yet calibrated - */ - float getNoiseFloor() { return currentNoiseFloor; } - /** * Check if we have collected any noise floor samples */ diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 2e50db1c885..52eab322157 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -118,8 +118,8 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry() telemetry.variant.local_stats.air_util_tx = airTime->utilizationTXPercent(); telemetry.variant.local_stats.num_online_nodes = numOnlineNodes; telemetry.variant.local_stats.num_total_nodes = nodeDB->getNumMeshNodes(); - telemetry.variant.local_stats.noise_floor = RadioLibInterface::instance->getAverageNoiseFloor(); if (RadioLibInterface::instance) { + telemetry.variant.local_stats.noise_floor = RadioLibInterface::instance->getAverageNoiseFloor(); telemetry.variant.local_stats.num_packets_tx = RadioLibInterface::instance->txGood; telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad; telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad; From 514b5dea1fa78f3df8464b785fe4f254314a4248 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Mon, 19 Jan 2026 21:52:37 -0800 Subject: [PATCH 09/13] Fix RSSI condition to include zero value --- src/mesh/RadioLibInterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index be9c66e92ee..614ff329f09 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -280,7 +280,7 @@ void RadioLibInterface::updateNoiseFloor() // Get current RSSI from the radio int16_t rssi = getCurrentRSSI(); - if (rssi > 0 || rssi < NOISE_FLOOR_MIN) { + if (rssi >= 0 || rssi < NOISE_FLOOR_MIN) { LOG_DEBUG("Skipping invalid RSSI reading: %d", rssi); return; } @@ -663,4 +663,4 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp) return res == RADIOLIB_ERR_NONE; } -} \ No newline at end of file +} From 866abf0dcf7ddd94f772fc47b179c6edd951faa4 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Mon, 19 Jan 2026 23:59:08 -0800 Subject: [PATCH 10/13] Change noise floor initialization to zero --- src/mesh/RadioLibInterface.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 614ff329f09..742cd04badf 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -43,9 +43,9 @@ RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE c { instance = this; - // Initialize noise floor samples array with a valid baseline + // Initialize noise floor samples array with 0 for (uint8_t i = 0; i < NOISE_FLOOR_SAMPLES; i++) { - noiseFloorSamples[i] = NOISE_FLOOR_MIN; + noiseFloorSamples[i] = 0; } #if defined(ARCH_STM32WL) && defined(USE_SX1262) @@ -306,7 +306,7 @@ int32_t RadioLibInterface::getAverageNoiseFloor() uint8_t sampleCount = getNoiseFloorSampleCount(); if (sampleCount == 0) { - return NOISE_FLOOR_MIN; // Return minimum if no samples + return 0; // Return 0 if no samples } int32_t sum = 0; From b830c8db12f3b00878cc297d55ad4fd91512cd45 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:51:17 -0800 Subject: [PATCH 11/13] Disable noise floor for LR11x0 chips: getRSSI(bool) unsupported --- src/mesh/LR11x0Interface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index f31a980b49d..8042038a308 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -318,9 +318,9 @@ template bool LR11x0Interface::sleep() return true; } +// LR11x0 chips do not support getRSSI(bool) for instantaneous RSSI - noise floor measurement is disabled for these chips template int16_t LR11x0Interface::getCurrentRSSI() { - float rssi = lora.getRSSI(false); - return (int16_t)round(rssi); + return 1; } #endif From da7419403e2c6ba7f1a2614557792c1c537b297e Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:56:38 -0800 Subject: [PATCH 12/13] 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. --- src/mesh/RadioLibInterface.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 742cd04badf..6ef3d0e2542 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -280,7 +280,7 @@ void RadioLibInterface::updateNoiseFloor() // Get current RSSI from the radio int16_t rssi = getCurrentRSSI(); - if (rssi >= 0 || rssi < NOISE_FLOOR_MIN) { + if (rssi >= 0 || rssi < NOISE_FLOOR_MIN) { LOG_DEBUG("Skipping invalid RSSI reading: %d", rssi); return; } @@ -352,8 +352,6 @@ currently active. void RadioLibInterface::onNotify(uint32_t notification) { - updateNoiseFloor(); - switch (notification) { case ISR_TX: handleTransmitInterrupt(); From 6dd2e2fbcae0cd2ddf69202f48d4d9dce0555d4c Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:53:43 -0800 Subject: [PATCH 13/13] fix lr11x0 current rssi --- platformio.ini | 1 + src/mesh/LR11x0Interface.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 77e9cf214a6..4c591821407 100644 --- a/platformio.ini +++ b/platformio.ini @@ -49,6 +49,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_FSK4=1 -DRADIOLIB_EXCLUDE_APRS=1 -DRADIOLIB_EXCLUDE_LORAWAN=1 + -DRADIOLIB_GODMODE=1 -DMESHTASTIC_EXCLUDE_DROPZONE=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 -DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1 diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 8042038a308..bbba8dac01b 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -318,9 +318,10 @@ template bool LR11x0Interface::sleep() return true; } -// LR11x0 chips do not support getRSSI(bool) for instantaneous RSSI - noise floor measurement is disabled for these chips template int16_t LR11x0Interface::getCurrentRSSI() { - return 1; + float rssi = -110; + lora.getRssiInst(&rssi); + return (int16_t)round(rssi); } #endif