feat(radio): adaptive LBT — scale CAD retries to current LoRa config

The CAD pre-TX loop previously ran up to 10 retries with a fixed 10 ms
sleep — roughly one max-length packet only at SF7/BW500. At SF10/BW125
the 100 ms total was ~4% of one packet; at SF12/BW125 LBT was a no-op.

Now the per-retry sleep is sized to one time-on-air of an assumed-peer
80-byte packet under the active config, cached on RadioContext and
recomputed on every SET_CONFIG. Retries scale as
clamp(floor(BUDGET/T)+1, 1, 10) with BUDGET=2.5 s, so fast configs
still get up to 10 retries while SF11/12 degrade to single-CAD-and-bail
(logged at config-apply). Sleeps are jittered ±20% per iteration to
decorrelate simultaneously-backing-off contenders.

The ToA math, CR→{1..4} mapping, retry-count derivation, and jitter
helper live in a pure src/lbt.rs module so they're host-testable via
DONGLORA_HOST_TEST=1 cargo test (13 tests).

Validated on-device: 20 iterations of the AI Bot test flow that
previously saw ~2 ChannelBusy errors now see zero at the same SF/BW.
This commit is contained in:
swaits committed 2026-04-25 20:15:44 -06:00
1 parent 23ddc15557
commit d262abe853
6 files changed
+370 -21

No files matched your search

+4 -9
View File
@@ -32,7 +32,10 @@ ucobs = { version = "0.3", features = ["legacy-msrv"] }
heapless = "0.9"
donglora-protocol = "1"
hsmc = { version = "0.2", default-features = false, features = ["embassy"] }
lora-phy = { version = "3.0.2-alpha", features = ["defmt-03"] } # matches the in-tree [patch.crates-io] override; drop the pre-release selector once upstream releases the fix
# Pulled from swaits/lora-rs fork until lora-rs/lora-rs#428 (pre-command
# BUSY check) is merged + released on crates.io. Switch back to a normal
# crates.io version then.
lora-phy = { git = "https://github.com/swaits/lora-rs.git", branch = "fix/sx126x-busy-race", features = ["defmt-03"] }
portable-atomic = { version = "1", optional = true, default-features = false, features = ["critical-section"] }
static_cell = "2.1"
@@ -212,14 +215,6 @@ esp-rtos = { git = "https://github.com/esp-rs/esp-hal.git", branch = "main"
esp-backtrace = { git = "https://github.com/esp-rs/esp-hal.git", branch = "main" }
esp-println = { git = "https://github.com/esp-rs/esp-hal.git", branch = "main" }
# Local fix for a BUSY-assertion race: lora-phy currently polls BUSY
# *after* each SPI op, but SX1261-2 §8.1.4 specifies a *pre*-command
# check (same pattern as RadioLib's SPIwriteStream). This override pulls
# in the corrected ordering and renames `wait_on_busy` to
# `wait_until_ready`. See TODO.md for the PR body. Remove this line once
# the fix is merged upstream + released on crates.io.
lora-phy = { path = "../../lora-rs/lora-phy" }
[lints.clippy]
undocumented_unsafe_blocks = "deny"