From 2d11d9fedba04db2d411527bf5df656d1b41c4dc Mon Sep 17 00:00:00 2001 From: jirogit Date: Thu, 26 Mar 2026 12:29:35 -0700 Subject: [PATCH] Add exponential backoff for channel busy detection Replace fixed random(8000-22000ms) backoff with exponential backoff: - 1st busy: 3-6s - 2nd busy: 6-12s - 3rd+ busy: 12-20s (capped) - Reset counter on channel free Results (48-byte simultaneous DM, JP SF12/BW125/CR4-5): - 3/3 success, delivered within 0:23-0:45 - Previous fixed backoff: 1:03-3:55 --- src/helpers/radiolib/RadioLibWrappers.cpp | 33 +++++++++++++---------- src/helpers/radiolib/RadioLibWrappers.h | 1 + 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 5ef5a076..915c0dad 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -34,6 +34,7 @@ void RadioLibWrapper::begin() { _noise_floor = 0; _threshold = 0; + _busy_count = 0; // initialize exponential backoff counter // start average out some samples _num_floor_samples = 0; @@ -183,39 +184,43 @@ bool RadioLibWrapper::isChannelActive() { if (_threshold == 0) return false; // interference check is disabled #ifdef JP_STRICT - // ARIB STD-T108 compliant LBT: continuous RSSI sensing for >= 5ms - // Energy-based sensing required; LoRa CAD alone is not sufficient + // 5ms continuous RSSI sensing uint32_t sense_start = millis(); - uint32_t sense_duration_ms = 5; - while (millis() - sense_start < sense_duration_ms) { + while (millis() - sense_start < 5) { if (getCurrentRSSI() > -80.0f) { - // Channel busy detected during 5ms sensing window - uint32_t backoff_until = millis() + random(8000, 22000); + // RSSI busy: backoff and return without CAD + _busy_count++; + uint32_t base_ms = 3000; + uint32_t max_backoff = min(base_ms * (1u << _busy_count), (uint32_t)20000); + uint32_t backoff_until = millis() + random(max_backoff / 2, max_backoff); while (millis() < backoff_until) { - vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE + vTaskDelay(1); } return true; } - vTaskDelay(1); // yield CPU between RSSI samples + vTaskDelay(1); } #endif + // CAD int16_t result = performChannelScan(); - // scanChannel() triggers DIO interrupt (CAD done) which sets STATE_INT_READY - // via setFlag() ISR. Clear it before restarting RX so recvRaw() doesn't - // try to read a non-existent packet and count a spurious recv error. state = STATE_IDLE; startRecv(); if (result != RADIOLIB_CHANNEL_FREE) { - // Random backoff to desynchronize retries between competing nodes - uint32_t backoff_until = millis() + random(8000, 22000); + // CAD busy: backoff + _busy_count++; + uint32_t base_ms = 3000; + uint32_t max_backoff = min(base_ms * (1u << _busy_count), (uint32_t)20000); + uint32_t backoff_until = millis() + random(max_backoff / 2, max_backoff); while (millis() < backoff_until) { - vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE + vTaskDelay(1); } return true; } + _busy_count = 0; + // Small jitter even when channel is free to prevent simultaneous TX // from two nodes that both detect a free channel at the same time uint32_t jitter_until = millis() + random(0, 500); diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 51d928ca..66ad2525 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -9,6 +9,7 @@ protected: mesh::MainBoard* _board; uint32_t n_recv, n_sent, n_recv_errors, _tx_start_ms; int16_t _noise_floor, _threshold; + uint8_t _busy_count; // consecutive busy detections for exponential backoff uint16_t _num_floor_samples; int32_t _floor_sample_sum;