Noise floor (#9347)
* 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>
This commit is contained in:
co-authored by
GitHub
Ben Meadors
Jonathan Bennett
parent
f9fea562aa
commit
982440d21d
@@ -368,4 +368,10 @@ template <typename T> bool LR11x0Interface<T>::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T> int16_t LR11x0Interface<T>::getCurrentRSSI()
|
||||
{
|
||||
float rssi = lora.getRSSI(false, true);
|
||||
return (int16_t)round(rssi);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -37,6 +37,8 @@ template <class T> class LR11x0Interface : public RadioLibInterface
|
||||
*/
|
||||
T lora;
|
||||
|
||||
int16_t getCurrentRSSI() override;
|
||||
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
*/
|
||||
|
||||
@@ -136,17 +136,6 @@ template <typename T> bool LR20x0Interface<T>::init()
|
||||
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
|
||||
return false;
|
||||
|
||||
// Some basic info about the module's explicit firmware version - no other info available
|
||||
// Currently requires radiolib godmode
|
||||
|
||||
#if RADIOLIB_GODMODE
|
||||
uint8_t fwMajor = 0;
|
||||
uint8_t fwMinor = 0;
|
||||
int versionRes = lora.getVersion(&fwMajor, &fwMinor);
|
||||
if (versionRes == RADIOLIB_ERR_NONE)
|
||||
LOG_DEBUG("LR20x0 FW %d.%d", fwMajor, fwMinor);
|
||||
#endif
|
||||
|
||||
LOG_INFO("Frequency set to %f", getFreq());
|
||||
LOG_INFO("Bandwidth set to %f", bw);
|
||||
LOG_INFO("Power output set to %d", power);
|
||||
@@ -154,17 +143,6 @@ template <typename T> bool LR20x0Interface<T>::init()
|
||||
if (res == RADIOLIB_ERR_NONE)
|
||||
res = lora.setCRC(2);
|
||||
|
||||
// Standard DCDC ramp timing from RadioLib workarounds (register 0x00F20024)
|
||||
// Currently requires radiolib godmode
|
||||
#if RADIOLIB_GODMODE
|
||||
if (res == RADIOLIB_ERR_NONE) {
|
||||
uint8_t rampTimes[4] = {15, 15, 15, 15}; // Standard case for all conditions
|
||||
res = lora.setRegMode(RADIOLIB_LR2021_REG_MODE_SIMO_NORMAL, rampTimes);
|
||||
if (res != RADIOLIB_ERR_NONE)
|
||||
LOG_WARN("LR2021 setRegMode failed: %d", res);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef LR2021_DIO_AS_RF_SWITCH
|
||||
bool dioAsRfSwitch = true;
|
||||
#elif defined(ARCH_PORTDUINO)
|
||||
@@ -396,4 +374,10 @@ template <typename T> bool LR20x0Interface<T>::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T> int16_t LR20x0Interface<T>::getCurrentRSSI()
|
||||
{
|
||||
float rssi = lora.getRSSI(false, true);
|
||||
return (int16_t)round(rssi);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -37,6 +37,8 @@ template <class T> class LR20x0Interface : public RadioLibInterface
|
||||
*/
|
||||
T lora;
|
||||
|
||||
int16_t getCurrentRSSI() override;
|
||||
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
*/
|
||||
|
||||
@@ -341,4 +341,10 @@ bool RF95Interface::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int16_t RF95Interface::getCurrentRSSI()
|
||||
{
|
||||
float rssi = lora->getRSSI(false);
|
||||
return (int16_t)round(rssi);
|
||||
}
|
||||
#endif
|
||||
@@ -37,6 +37,8 @@ class RF95Interface : public RadioLibInterface
|
||||
*/
|
||||
virtual void disableInterrupt() override;
|
||||
|
||||
int16_t getCurrentRSSI() override;
|
||||
|
||||
/**
|
||||
* Enable a particular ISR callback glue function
|
||||
*/
|
||||
|
||||
@@ -283,6 +283,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...
|
||||
|
||||
@@ -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 unused sample slots to a sane default; sample count controls averaging.
|
||||
for (uint8_t i = 0; i < NOISE_FLOOR_SAMPLES; i++) {
|
||||
noiseFloorSamples[i] = NOISE_FLOOR_DEFAULT;
|
||||
}
|
||||
|
||||
#if defined(ARCH_STM32WL) && defined(USE_SX1262)
|
||||
module.setCb_digitalWrite(stm32wl_emulate_digitalWrite);
|
||||
module.setCb_digitalRead(stm32wl_emulate_digitalRead);
|
||||
@@ -246,6 +254,87 @@ bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id)
|
||||
return txQueue.find(from, id);
|
||||
}
|
||||
|
||||
void RadioLibInterface::updateNoiseFloor()
|
||||
{
|
||||
// Only sample from idle receive mode. TX/RX-critical paths must return to radio work quickly.
|
||||
if (!isReceiving || sendingPacket != NULL || isActivelyReceiving() || isIRQPending()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
if (now - lastNoiseFloorUpdate < NOISE_FLOOR_UPDATE_INTERVAL_MS) {
|
||||
return;
|
||||
}
|
||||
lastNoiseFloorUpdate = now;
|
||||
|
||||
int16_t rssi = getCurrentRSSI();
|
||||
if (rssi == NOISE_FLOOR_INVALID || rssi >= 0 || rssi < NOISE_FLOOR_VALID_MIN) {
|
||||
LOG_DEBUG("Skipping invalid RSSI reading: %d", rssi);
|
||||
return;
|
||||
}
|
||||
|
||||
noiseFloorSamples[currentSampleIndex] = (int32_t)rssi;
|
||||
currentSampleIndex++;
|
||||
|
||||
if (currentSampleIndex >= NOISE_FLOOR_SAMPLES) {
|
||||
currentSampleIndex = 0;
|
||||
isNoiseFloorBufferFull = true;
|
||||
}
|
||||
|
||||
currentNoiseFloor = getAverageNoiseFloorInternal();
|
||||
|
||||
LOG_DEBUG("Noise floor: %d dBm (samples: %d, latest: %d dBm)", currentNoiseFloor, getNoiseFloorSampleCountInternal(), rssi);
|
||||
}
|
||||
|
||||
uint8_t RadioLibInterface::getNoiseFloorSampleCountInternal() const
|
||||
{
|
||||
return isNoiseFloorBufferFull ? NOISE_FLOOR_SAMPLES : currentSampleIndex;
|
||||
}
|
||||
|
||||
int32_t RadioLibInterface::getAverageNoiseFloorInternal() const
|
||||
{
|
||||
uint8_t sampleCount = getNoiseFloorSampleCountInternal();
|
||||
|
||||
if (sampleCount == 0) {
|
||||
return NOISE_FLOOR_DEFAULT;
|
||||
}
|
||||
|
||||
int32_t sum = 0;
|
||||
for (uint8_t i = 0; i < sampleCount; i++) {
|
||||
sum += noiseFloorSamples[i];
|
||||
}
|
||||
|
||||
return sum / sampleCount;
|
||||
}
|
||||
|
||||
int32_t RadioLibInterface::getAverageNoiseFloor()
|
||||
{
|
||||
return getAverageNoiseFloorInternal();
|
||||
}
|
||||
|
||||
int32_t RadioLibInterface::getNoiseFloor()
|
||||
{
|
||||
return currentNoiseFloor;
|
||||
}
|
||||
|
||||
bool RadioLibInterface::hasNoiseFloorSamples()
|
||||
{
|
||||
return getNoiseFloorSampleCountInternal() > 0;
|
||||
}
|
||||
|
||||
uint8_t RadioLibInterface::getNoiseFloorSampleCount()
|
||||
{
|
||||
return getNoiseFloorSampleCountInternal();
|
||||
}
|
||||
|
||||
void RadioLibInterface::resetNoiseFloor()
|
||||
{
|
||||
currentSampleIndex = 0;
|
||||
isNoiseFloorBufferFull = false;
|
||||
currentNoiseFloor = NOISE_FLOOR_DEFAULT;
|
||||
LOG_INFO("Noise floor reset - rolling window collection will restart");
|
||||
}
|
||||
|
||||
bool RadioLibInterface::randomBytes(uint8_t *buffer, size_t length)
|
||||
{
|
||||
if (!buffer || length == 0 || !iface) {
|
||||
@@ -273,6 +362,7 @@ currently active.
|
||||
*/
|
||||
void RadioLibInterface::onNotify(uint32_t notification)
|
||||
{
|
||||
|
||||
switch (notification) {
|
||||
case ISR_TX:
|
||||
handleTransmitInterrupt();
|
||||
@@ -404,11 +494,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
|
||||
|
||||
@@ -99,6 +99,26 @@ 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 - rolling window of samples.
|
||||
static const uint8_t NOISE_FLOOR_SAMPLES = 20;
|
||||
static const int32_t NOISE_FLOOR_DEFAULT = -120;
|
||||
static const int32_t NOISE_FLOOR_VALID_MIN = -127;
|
||||
static const int32_t NOISE_FLOOR_INVALID = -128;
|
||||
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;
|
||||
int32_t currentNoiseFloor = NOISE_FLOOR_DEFAULT;
|
||||
|
||||
/**
|
||||
* Pure virtual hook for derived radio interfaces to provide instantaneous RSSI.
|
||||
* Implementations should return dBm, or an invalid value that updateNoiseFloor()
|
||||
* can reject.
|
||||
*/
|
||||
virtual int16_t getCurrentRSSI() = 0;
|
||||
|
||||
public:
|
||||
/** Our ISR code currently needs this to find our active instance
|
||||
*/
|
||||
@@ -111,6 +131,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
instance = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current calculated noise floor in dBm
|
||||
* Returns -120 dBm if not yet calibrated
|
||||
*/
|
||||
int32_t getNoiseFloor();
|
||||
|
||||
/**
|
||||
* Calculate the average noise floor from collected samples
|
||||
*/
|
||||
int32_t getAverageNoiseFloor();
|
||||
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
*/
|
||||
@@ -179,6 +210,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;
|
||||
|
||||
/**
|
||||
* Update the noise floor measurement by sampling RSSI from a slow path.
|
||||
* This should not be called from radio interrupt or TX/RX critical paths.
|
||||
*/
|
||||
void updateNoiseFloor();
|
||||
|
||||
/**
|
||||
* Check if we have collected any noise floor samples
|
||||
*/
|
||||
bool hasNoiseFloorSamples();
|
||||
|
||||
/**
|
||||
* Get the number of samples in the rolling window
|
||||
*/
|
||||
uint8_t getNoiseFloorSampleCount();
|
||||
|
||||
/**
|
||||
* Reset the noise floor calibration
|
||||
* Will automatically restart collection
|
||||
*/
|
||||
void resetNoiseFloor();
|
||||
|
||||
/**
|
||||
* Request randomness sourced from the LoRa modem, if supported by the active RadioLib interface.
|
||||
* @return true if len bytes were produced, false otherwise.
|
||||
@@ -186,6 +239,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
bool randomBytes(uint8_t *buffer, size_t length);
|
||||
|
||||
private:
|
||||
uint8_t getNoiseFloorSampleCountInternal() const;
|
||||
int32_t getAverageNoiseFloorInternal() const;
|
||||
|
||||
/** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually
|
||||
* doing the transmit */
|
||||
void setTransmitDelay();
|
||||
|
||||
@@ -265,6 +265,12 @@ template <typename T> bool SX126xInterface<T>::reconfigure()
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T> int16_t SX126xInterface<T>::getCurrentRSSI()
|
||||
{
|
||||
float rssi = lora.getRSSI(false);
|
||||
return (int16_t)round(rssi);
|
||||
}
|
||||
|
||||
template <typename T> void SX126xInterface<T>::disableInterrupt()
|
||||
{
|
||||
lora.clearDio1Action();
|
||||
|
||||
@@ -41,6 +41,8 @@ template <class T> class SX126xInterface : public RadioLibInterface
|
||||
*/
|
||||
T lora;
|
||||
|
||||
int16_t getCurrentRSSI() override;
|
||||
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
*/
|
||||
|
||||
@@ -325,4 +325,10 @@ template <typename T> bool SX128xInterface<T>::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T> int16_t SX128xInterface<T>::getCurrentRSSI()
|
||||
{
|
||||
float rssi = lora.getRSSI(false);
|
||||
return (int16_t)round(rssi);
|
||||
}
|
||||
#endif
|
||||
@@ -35,6 +35,8 @@ template <class T> class SX128xInterface : public RadioLibInterface
|
||||
*/
|
||||
T lora;
|
||||
|
||||
int16_t getCurrentRSSI() override;
|
||||
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ static constexpr uint16_t TX_HISTORY_KEY_DEVICE_TELEMETRY = 0x8001;
|
||||
|
||||
int32_t DeviceTelemetryModule::runOnce()
|
||||
{
|
||||
|
||||
refreshUptime();
|
||||
uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_DEVICE_TELEMETRY) : 0;
|
||||
bool isImpoliteRole = isSensorOrRouterRole();
|
||||
@@ -129,6 +130,8 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
|
||||
telemetry.variant.local_stats.num_online_nodes = numOnlineNodes;
|
||||
telemetry.variant.local_stats.num_total_nodes = nodeDB->getNumMeshNodes();
|
||||
if (RadioLibInterface::instance) {
|
||||
RadioLibInterface::instance->updateNoiseFloor();
|
||||
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;
|
||||
@@ -137,6 +140,8 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
|
||||
}
|
||||
#ifdef ARCH_PORTDUINO
|
||||
if (SimRadio::instance) {
|
||||
if (!RadioLibInterface::instance)
|
||||
telemetry.variant.local_stats.noise_floor = SimRadio::instance->getCurrentRSSI();
|
||||
telemetry.variant.local_stats.num_packets_tx = SimRadio::instance->txGood;
|
||||
telemetry.variant.local_stats.num_packets_rx = SimRadio::instance->rxGood + SimRadio::instance->rxBad;
|
||||
telemetry.variant.local_stats.num_packets_rx_bad = SimRadio::instance->rxBad;
|
||||
@@ -152,10 +157,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=%d",
|
||||
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);
|
||||
@@ -198,4 +204,4 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -48,6 +48,8 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
|
||||
// Convert Compressed_msg to normal msg and receive it
|
||||
void unpackAndReceive(meshtastic_MeshPacket &p);
|
||||
|
||||
int16_t getCurrentRSSI() override;
|
||||
|
||||
/**
|
||||
* Debugging counts
|
||||
*/
|
||||
@@ -93,4 +95,4 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
|
||||
virtual uint32_t getPacketTime(uint32_t pl, bool received = false) override;
|
||||
};
|
||||
|
||||
extern SimRadio *simRadio;
|
||||
extern SimRadio *simRadio;
|
||||
|
||||
@@ -15,7 +15,6 @@ board = promicro-nrf52840
|
||||
build_flags = ${nrf52840_base.build_flags}
|
||||
-I variants/nrf52840/diy/nrf52_promicro_diy_tcxo
|
||||
-D NRF52_PROMICRO_DIY
|
||||
; -D RADIOLIB_GODMODE=1 ; needed for some LR2021 items, but not enabled by default.
|
||||
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/nrf52_promicro_diy_tcxo>
|
||||
debug_tool = jlink
|
||||
|
||||
|
||||
Reference in New Issue
Block a user