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
This commit is contained in:
jirogit 2026-03-26 12:29:35 -07:00
parent c5bacd7de9
commit 2d11d9fedb
2 changed files with 20 additions and 14 deletions

View file

@ -34,6 +34,7 @@ void RadioLibWrapper::begin() {
_noise_floor = 0; _noise_floor = 0;
_threshold = 0; _threshold = 0;
_busy_count = 0; // initialize exponential backoff counter
// start average out some samples // start average out some samples
_num_floor_samples = 0; _num_floor_samples = 0;
@ -183,39 +184,43 @@ bool RadioLibWrapper::isChannelActive() {
if (_threshold == 0) return false; // interference check is disabled if (_threshold == 0) return false; // interference check is disabled
#ifdef JP_STRICT #ifdef JP_STRICT
// ARIB STD-T108 compliant LBT: continuous RSSI sensing for >= 5ms // 5ms continuous RSSI sensing
// Energy-based sensing required; LoRa CAD alone is not sufficient
uint32_t sense_start = millis(); uint32_t sense_start = millis();
uint32_t sense_duration_ms = 5; while (millis() - sense_start < 5) {
while (millis() - sense_start < sense_duration_ms) {
if (getCurrentRSSI() > -80.0f) { if (getCurrentRSSI() > -80.0f) {
// Channel busy detected during 5ms sensing window // RSSI busy: backoff and return without CAD
uint32_t backoff_until = millis() + random(8000, 22000); _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) { while (millis() < backoff_until) {
vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE vTaskDelay(1);
} }
return true; return true;
} }
vTaskDelay(1); // yield CPU between RSSI samples vTaskDelay(1);
} }
#endif #endif
// CAD
int16_t result = performChannelScan(); 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; state = STATE_IDLE;
startRecv(); startRecv();
if (result != RADIOLIB_CHANNEL_FREE) { if (result != RADIOLIB_CHANNEL_FREE) {
// Random backoff to desynchronize retries between competing nodes // CAD busy: backoff
uint32_t backoff_until = millis() + random(8000, 22000); _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) { while (millis() < backoff_until) {
vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE vTaskDelay(1);
} }
return true; return true;
} }
_busy_count = 0;
// Small jitter even when channel is free to prevent simultaneous TX // Small jitter even when channel is free to prevent simultaneous TX
// from two nodes that both detect a free channel at the same time // from two nodes that both detect a free channel at the same time
uint32_t jitter_until = millis() + random(0, 500); uint32_t jitter_until = millis() + random(0, 500);

View file

@ -9,6 +9,7 @@ protected:
mesh::MainBoard* _board; mesh::MainBoard* _board;
uint32_t n_recv, n_sent, n_recv_errors, _tx_start_ms; uint32_t n_recv, n_sent, n_recv_errors, _tx_start_ms;
int16_t _noise_floor, _threshold; int16_t _noise_floor, _threshold;
uint8_t _busy_count; // consecutive busy detections for exponential backoff
uint16_t _num_floor_samples; uint16_t _num_floor_samples;
int32_t _floor_sample_sum; int32_t _floor_sample_sum;