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;
_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);

View file

@ -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;