From 9604fe5f5107a863534fcb34771f42f180328201 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Mon, 22 Jun 2026 14:15:56 -0700 Subject: [PATCH] Reset noise floor on frequency changes --- src/mesh/RadioInterface.h | 4 +- src/mesh/RadioLibInterface.cpp | 17 ++++++ src/mesh/RadioLibInterface.h | 2 + test/test_radio/test_main.cpp | 101 ++++++++++++++++++++++++++++++++- 4 files changed, 120 insertions(+), 4 deletions(-) diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index f0b005e3e30..68f59ae2e0c 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -240,8 +240,8 @@ class RadioInterface protected: int8_t power = 17; // Set by applyModemConfig() - float savedFreq; - uint32_t savedChannelNum; + float savedFreq = 0.0f; + uint32_t savedChannelNum = 0; /*** * given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of bytes to send (including the diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 335e76a3bc7..35a4087a406 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -54,6 +54,12 @@ RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE c #endif } +static bool radioFrequencyChanged(float previousFreq, float currentFreq) +{ + const float delta = currentFreq - previousFreq; + return delta > 0.000001f || delta < -0.000001f; +} + #ifdef ARCH_ESP32 // ESP32 doesn't use that flag #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR() @@ -228,6 +234,16 @@ bool RadioLibInterface::canSleep() return res; } +bool RadioLibInterface::reconfigure() +{ + const float previousFreq = getFreq(); + bool result = RadioInterface::reconfigure(); + if (result && radioFrequencyChanged(previousFreq, getFreq())) { + resetNoiseFloor(); + } + return result; +} + /** Allow other firmware components to ask whether we are currently sending a packet Initially implemented to protect T-Echo's capacitive touch button from spurious presses during tx */ @@ -331,6 +347,7 @@ void RadioLibInterface::resetNoiseFloor() { currentSampleIndex = 0; isNoiseFloorBufferFull = false; + lastNoiseFloorUpdate = 0; currentNoiseFloor = NOISE_FLOOR_DEFAULT; LOG_INFO("Noise floor reset - rolling window collection will restart"); } diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index cfbfbe1b585..65740324f8a 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -167,6 +167,8 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, PhysicalLayer *iface = NULL); + virtual bool reconfigure() override; + virtual ErrorCode send(meshtastic_MeshPacket *p) override; /** diff --git a/test/test_radio/test_main.cpp b/test/test_radio/test_main.cpp index fbe2b1b1304..4acd783a477 100644 --- a/test/test_radio/test_main.cpp +++ b/test/test_radio/test_main.cpp @@ -1,10 +1,54 @@ #include "MeshRadio.h" +#include "MeshService.h" +#include "NodeDB.h" #include "RadioInterface.h" +#include "RadioLibInterface.h" #include "TestUtil.h" +#include #include #include "meshtastic/config.pb.h" +class MockMeshService : public MeshService +{ + public: + void sendClientNotification(meshtastic_ClientNotification *n) override { releaseClientNotificationToPool(n); } +}; + +static MockMeshService *mockMeshService; + +static LockingArduinoHal *getTestHal() +{ + static LockingArduinoHal hal(SPI, SPISettings(1000000, MSBFIRST, SPI_MODE0)); + return &hal; +} + +class TestableRadioLibInterface : public RadioLibInterface +{ + public: + TestableRadioLibInterface() : RadioLibInterface(getTestHal(), RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, nullptr) {} + + void seedNoiseFloorForTest() + { + noiseFloorSamples[0] = -110; + currentSampleIndex = 1; + isNoiseFloorBufferFull = false; + lastNoiseFloorUpdate = 1234; + currentNoiseFloor = -110; + } + + uint32_t getLastNoiseFloorUpdateForTest() const { return lastNoiseFloorUpdate; } + + protected: + void disableInterrupt() override {} + void enableInterrupt(void (*)()) override {} + bool isChannelActive() override { return false; } + bool isActivelyReceiving() override { return false; } + void addReceiveMetadata(meshtastic_MeshPacket *) override {} + uint32_t getPacketTime(uint32_t, bool) override { return 0; } + int16_t getCurrentRSSI() override { return NOISE_FLOOR_DEFAULT; } +}; + static void test_bwCodeToKHz_specialMappings() { TEST_ASSERT_FLOAT_WITHIN(0.0001f, 31.25f, bwCodeToKHz(31)); @@ -77,8 +121,59 @@ static void test_bootstrapLoRaConfigFromPreset_fallsBackIfBandwidthExceedsRegion TEST_ASSERT_EQUAL_UINT32(11, cfg.spread_factor); } -void setUp(void) {} -void tearDown(void) {} +static void configureLongFastUs(float frequencyOffset = 0.0f) +{ + config.lora = meshtastic_Config_LoRaConfig_init_zero; + config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US; + config.lora.use_preset = true; + config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; + config.lora.frequency_offset = frequencyOffset; +} + +static void test_radioLibReconfigureResetsNoiseFloorWhenFrequencyChanges() +{ + TestableRadioLibInterface testRadioLib; + configureLongFastUs(); + testRadioLib.reconfigure(); + testRadioLib.seedNoiseFloorForTest(); + + config.lora.frequency_offset = 0.125f; + testRadioLib.reconfigure(); + + TEST_ASSERT_FALSE(testRadioLib.hasNoiseFloorSamples()); + TEST_ASSERT_EQUAL_INT32(-120, testRadioLib.getNoiseFloor()); + TEST_ASSERT_EQUAL_UINT32(0, testRadioLib.getLastNoiseFloorUpdateForTest()); +} + +static void test_radioLibReconfigureKeepsNoiseFloorWhenFrequencyUnchanged() +{ + TestableRadioLibInterface testRadioLib; + configureLongFastUs(); + testRadioLib.reconfigure(); + testRadioLib.seedNoiseFloorForTest(); + + testRadioLib.reconfigure(); + + TEST_ASSERT_TRUE(testRadioLib.hasNoiseFloorSamples()); + TEST_ASSERT_EQUAL_INT32(-110, testRadioLib.getNoiseFloor()); + TEST_ASSERT_EQUAL_UINT32(1234, testRadioLib.getLastNoiseFloorUpdateForTest()); +} + +void setUp(void) +{ + mockMeshService = new MockMeshService(); + service = mockMeshService; + + config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US; + initRegion(); +} + +void tearDown(void) +{ + service = nullptr; + delete mockMeshService; + mockMeshService = nullptr; +} void setup() { @@ -94,6 +189,8 @@ void setup() RUN_TEST(test_bootstrapLoRaConfigFromPreset_setsDerivedFields_nonWideRegion); RUN_TEST(test_bootstrapLoRaConfigFromPreset_setsDerivedFields_wideRegion); RUN_TEST(test_bootstrapLoRaConfigFromPreset_fallsBackIfBandwidthExceedsRegionSpan); + RUN_TEST(test_radioLibReconfigureResetsNoiseFloorWhenFrequencyChanges); + RUN_TEST(test_radioLibReconfigureKeepsNoiseFloorWhenFrequencyUnchanged); exit(UNITY_END()); }