diff --git a/src/helpers/radiolib/CadTiming.h b/src/helpers/radiolib/CadTiming.h new file mode 100644 index 00000000..c219e3cf --- /dev/null +++ b/src/helpers/radiolib/CadTiming.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace mesh { + +static constexpr uint32_t CAD_SCAN_MIN_TIMEOUT_MS = 100UL; +static constexpr uint32_t CAD_SCAN_MAX_TIMEOUT_MS = 3500UL; +static constexpr uint32_t CAD_SCAN_FALLBACK_TIMEOUT_MS = 500UL; + +// RadioLib's SX126x CAD default examines four LoRa symbols. Allow six symbols +// plus fixed command/TCXO overhead, then clamp the result so normal profiles +// fail quickly without rejecting deliberately slow custom radio settings. +inline uint32_t calculateCadScanTimeoutMillis(uint8_t sf, float bandwidth_khz) { + if (sf < 5 || sf > 12 || bandwidth_khz <= 0.0f) { + return CAD_SCAN_FALLBACK_TIMEOUT_MS; + } + + const float symbol_ms = static_cast(1UL << sf) / bandwidth_khz; + const float padded_ms = symbol_ms * 6.0f + 20.0f; + uint32_t timeout_ms = static_cast(padded_ms + 0.999f); + + if (timeout_ms < CAD_SCAN_MIN_TIMEOUT_MS) return CAD_SCAN_MIN_TIMEOUT_MS; + if (timeout_ms > CAD_SCAN_MAX_TIMEOUT_MS) return CAD_SCAN_MAX_TIMEOUT_MS; + return timeout_ms; +} + +} // namespace mesh diff --git a/src/helpers/radiolib/CustomSX1276Wrapper.h b/src/helpers/radiolib/CustomSX1276Wrapper.h index 3d04210c..b964cc1a 100644 --- a/src/helpers/radiolib/CustomSX1276Wrapper.h +++ b/src/helpers/radiolib/CustomSX1276Wrapper.h @@ -42,7 +42,7 @@ public: protected: int16_t performChannelScan() override { - return ((CustomSX1276 *)_radio)->tryScanChannel(CAD_SCAN_TIMEOUT_MS, *_board); + return ((CustomSX1276 *)_radio)->tryScanChannel(cadScanTimeoutMillis(), *_board); } bool radioDeepInit() override { diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index efcc03e3..a0e70859 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -625,9 +625,10 @@ int16_t RadioLibWrapper::performChannelScan() { int16_t result = _radio->startChannelScan(); if (result != RADIOLIB_ERR_NONE) return result; + const unsigned long timeout_ms = cadScanTimeoutMillis(); const unsigned long started = millis(); unsigned long last_watchdog_service = started; - while (millis() - started < CAD_SCAN_TIMEOUT_MS) { + while (millis() - started < timeout_ms) { result = _radio->getChannelScanResult(); if (result != RADIOLIB_ERR_UNKNOWN) return result; @@ -639,7 +640,7 @@ int16_t RadioLibWrapper::performChannelScan() { yield(); } - MESH_DEBUG_PRINTLN("RadioLibWrapper: CAD scan timed out"); + MESH_DEBUG_PRINTLN("RadioLibWrapper: CAD scan timed out after %lu ms", timeout_ms); return RADIOLIB_ERR_RX_TIMEOUT; } diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index b1e029b6..3493a921 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -2,6 +2,7 @@ #include #include +#include "CadTiming.h" // Fallback RX powersaving timings, only used until setRxPowerSaving() is called // (begin() always applies the persisted values). The authoritative defaults live @@ -12,7 +13,6 @@ class RadioLibWrapper : public mesh::Radio { protected: - static constexpr unsigned long CAD_SCAN_TIMEOUT_MS = 10000UL; PhysicalLayer* _radio; mesh::MainBoard* _board; uint32_t n_recv, n_sent, n_recv_errors; @@ -72,6 +72,11 @@ protected: void cacheParams(float freq, float bw, uint8_t sf, uint8_t cr) { _cur_freq = freq; _cur_bw = bw; _cur_sf = sf; _cur_cr = cr; _params_valid = true; } + unsigned long cadScanTimeoutMillis() const { + const uint8_t sf = _params_valid ? _cur_sf : getSpreadingFactor(); + const float bw = _params_valid ? _cur_bw : static_cast(LORA_BW); + return mesh::calculateCadScanTimeoutMillis(sf, bw); + } virtual int startReceiveMode(); virtual void stopReceiveDutyCycle(); virtual bool isPacketReady(); diff --git a/test/test_cad_timing/test_cad_timing.cpp b/test/test_cad_timing/test_cad_timing.cpp new file mode 100644 index 00000000..e38ab598 --- /dev/null +++ b/test/test_cad_timing/test_cad_timing.cpp @@ -0,0 +1,23 @@ +#include + +#include + +TEST(CadTiming, UsesShortDeadlineForCascadeProfile) { + EXPECT_EQ(mesh::calculateCadScanTimeoutMillis(7, 62.5f), 100UL); +} + +TEST(CadTiming, ScalesForSlowRadioSettings) { + EXPECT_EQ(mesh::calculateCadScanTimeoutMillis(12, 62.5f), 414UL); + EXPECT_EQ(mesh::calculateCadScanTimeoutMillis(12, 7.8f), 3171UL); +} + +TEST(CadTiming, BoundsInvalidAndExtremeInputs) { + EXPECT_EQ(mesh::calculateCadScanTimeoutMillis(0, 62.5f), 500UL); + EXPECT_EQ(mesh::calculateCadScanTimeoutMillis(7, 0.0f), 500UL); + EXPECT_EQ(mesh::calculateCadScanTimeoutMillis(12, 1.0f), 3500UL); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}