-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Noise floor #9347
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Noise floor #9347
Changes from all commits
d4b8828
5fd527a
e1b995f
c12de75
02c980b
1f7cd76
3ce3112
3e52ca3
d1b1c10
514b5de
866abf0
dbb0a96
b830c8d
da74194
6dd2e2f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 with 0 | ||
| 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,94 @@ bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id) | |
| return txQueue.find(from, id); | ||
| } | ||
|
|
||
| void RadioLibInterface::updateNoiseFloor() | ||
| { | ||
| // 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; | ||
| } | ||
|
|
||
| // Also check for pending interrupts | ||
| if (isIRQPending()) { | ||
| 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; | ||
| } | ||
|
|
||
| // Store the sample in the rolling window | ||
| noiseFloorSamples[currentSampleIndex] = (int32_t)rssi; | ||
| currentSampleIndex++; | ||
|
|
||
| // 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 using the rolling window | ||
| currentNoiseFloor = getAverageNoiseFloor(); | ||
|
|
||
| LOG_DEBUG("Noise floor: %d dBm (samples: %d, latest: %d dBm)", currentNoiseFloor, getNoiseFloorSampleCount(), rssi); | ||
| } | ||
|
|
||
| int32_t RadioLibInterface::getAverageNoiseFloor() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noise-floor buffer/index state is read and written without synchronization (getAverageNoiseFloor() vs updateNoiseFloor()), and telemetry calls into this from a different module thread (src/modules/Telemetry/DeviceTelemetry.cpp:122). |
||
| { | ||
| uint8_t sampleCount = getNoiseFloorSampleCount(); | ||
|
|
||
| if (sampleCount == 0) { | ||
| return 0; // Return 0 if no samples | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API/docs state uncalibrated should be -120 dBm (src/mesh/RadioLibInterface.h:129). This leaks impossible telemetry (0 dBm) via src/modules/Telemetry/DeviceTelemetry.cpp:122 and hurts data accuracy for fresh boot / low-traffic nodes. |
||
| } | ||
|
|
||
| int32_t sum = 0; | ||
|
|
||
| // 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]; | ||
| } | ||
| } | ||
|
|
||
| int32_t average = sum / sampleCount; | ||
|
|
||
| // Clamp to minimum of -120 dBm | ||
| if (average < NOISE_FLOOR_MIN) { | ||
| average = NOISE_FLOOR_MIN; | ||
| } | ||
|
|
||
| return average; | ||
thebentern marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| void RadioLibInterface::resetNoiseFloor() | ||
| { | ||
| currentSampleIndex = 0; | ||
| isNoiseFloorBufferFull = false; | ||
| currentNoiseFloor = NOISE_FLOOR_MIN; | ||
| LOG_INFO("Noise floor reset - rolling window collection will restart"); | ||
| } | ||
|
|
||
| /** 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 +351,7 @@ currently active. | |
| */ | ||
| void RadioLibInterface::onNotify(uint32_t notification) | ||
| { | ||
|
|
||
| switch (notification) { | ||
| case ISR_TX: | ||
| handleTransmitInterrupt(); | ||
|
|
@@ -386,11 +483,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 +511,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(); | ||
|
Comment on lines
+515
to
+516
|
||
| } | ||
| } | ||
|
|
||
|
|
@@ -516,6 +611,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() | ||
|
|
@@ -563,4 +661,4 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp) | |
|
|
||
| return res == RADIOLIB_ERR_NONE; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -97,11 +97,45 @@ 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_MIN = -120; // Minimum noise floor clamp in dBm | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why clamping to -120dBm? If there is only thermal noise, at 0 degrees Celsius and with a small bandwidth (62.5kHz), you can go down to -126dBm.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When testing with the LR1110, I noticed that a value of -128 is returned when it is invalid (i.e. not in receive mode while calling the function). Hence I would say let's check for this specific value, and not clamp it to -120. |
||
| 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_MIN; | ||
thebentern marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Update the noise floor measurement by sampling RSSI when receiving | ||
| * Uses a rolling window approach to maintain recent samples | ||
| */ | ||
| void updateNoiseFloor(); | ||
|
|
||
| /** | ||
| * Override from NotifiedWorkerThread - called periodically by the thread | ||
thebentern marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| */ | ||
| virtual int16_t getCurrentRSSI() = 0; | ||
|
|
||
| public: | ||
| /** Our ISR code currently needs this to find our active instance | ||
| */ | ||
| 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 | ||
| */ | ||
| int32_t getAverageNoiseFloor(); | ||
|
|
||
| /** | ||
| * Glue functions called from ISR land | ||
| */ | ||
|
|
@@ -158,6 +192,22 @@ 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; | ||
|
|
||
| /** | ||
| * Check if we have collected any noise floor samples | ||
| */ | ||
| bool hasNoiseFloorSamples() { return isNoiseFloorBufferFull || currentSampleIndex > 0; } | ||
|
|
||
| /** | ||
| * Get the number of samples in the rolling window | ||
| */ | ||
| uint8_t getNoiseFloorSampleCount() { return isNoiseFloorBufferFull ? NOISE_FLOOR_SAMPLES : currentSampleIndex; } | ||
|
|
||
| /** | ||
| * 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 +314,4 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified | |
| */ | ||
|
|
||
| bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override; | ||
| }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
|
|
||
| int32_t DeviceTelemetryModule::runOnce() | ||
| { | ||
|
|
||
| refreshUptime(); | ||
| bool isImpoliteRole = | ||
| IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_ROUTER); | ||
|
|
@@ -118,6 +119,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry() | |
| telemetry.variant.local_stats.num_online_nodes = numOnlineNodes; | ||
| telemetry.variant.local_stats.num_total_nodes = nodeDB->getNumMeshNodes(); | ||
| 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; | ||
|
|
@@ -141,10 +143,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", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return value should be checked instead of always assuming success