feat!: bridge on DongLoRa Protocol v2 and async client
The 1.0 bridge running on the async Dongle API from donglora-client 1.0.0 and the Protocol v2 wire format. * Radio loop is now a tokio task, not a dedicated std::thread. Everything is async end to end. * [radio] config schema: tx_power renamed to tx_power_dbm (plain i8, no "max" keyword), cad field removed (CAD is a per-TX flag in v1.0), default sync_word changed 0x3444 to 0x1424 to match the SX126x register encoding of MeshCore private sync byte. * TX retry with backoff (new [tx] config section). Every TX runs through Dongle::tx_with_retry, retrying TX_DONE(CHANNEL_BUSY) and ERR(EBUSY) per PROTOCOL.md §6.10 / §C.5.5. Defaults: 3 attempts, 20-100 ms randomized backoff, doubling to 500 ms cap, 5 s per-attempt timeout. Configurable via max_attempts, backoff_min_ms, backoff_max_ms, backoff_cap_ms, backoff_multiplier, per_attempt_timeout_secs. * TUI packet log shows retry state: in-flight retries render with a cycle marker in yellow and an N/M attempt counter, terminal TX failures render in red, post-retry success shows final attempt count. * Stats gain tx_retries and tx_failures counters surfaced in StatsSnapshot. * Measured airtime from TX_DONE replaces the theoretical estimate, feeding the rate limiter with the real on-air duration. * The explicit 2 s liveness ping loop is gone. The Dongle runs a 500 ms internal keepalive, matching spec §3.4 inactivity window with 2x margin.
This commit is contained in:
11 files changed
+1010
-669
No files matched your search
+4
-7
@@ -58,15 +58,12 @@ pub fn run_wizard(config_path: &Path, existing: Option<&Config>) -> Result<()> {
|
||||
let sync_word_str = prompt_default("Sync Word", &sw_default)?;
|
||||
let sync_word = parse_sync_word(&sync_word_str)?;
|
||||
|
||||
let tx_power = prompt_default("TX Power (dBm or 'max')", &radio.tx_power)?;
|
||||
let tx_power_default = format!("{}", radio.tx_power_dbm);
|
||||
let tx_power_dbm: i8 = prompt_parse("TX Power (dBm)", &tx_power_default)?;
|
||||
|
||||
let pre_default = format!("{}", radio.preamble);
|
||||
let preamble: u16 = prompt_parse("Preamble", &pre_default)?;
|
||||
|
||||
let cad_default = if radio.cad { "on" } else { "off" };
|
||||
let cad_str = prompt_default("CAD", cad_default)?;
|
||||
let cad = matches!(cad_str.to_lowercase().as_str(), "on" | "true" | "yes" | "1");
|
||||
|
||||
let port_default = radio.port.as_deref().unwrap_or("auto");
|
||||
let port_str = prompt_default("Serial Port", port_default)?;
|
||||
let port = if port_str == "auto" || port_str.is_empty() { None } else { Some(port_str) };
|
||||
@@ -81,9 +78,8 @@ pub fn run_wizard(config_path: &Path, existing: Option<&Config>) -> Result<()> {
|
||||
spreading_factor,
|
||||
coding_rate,
|
||||
sync_word,
|
||||
tx_power,
|
||||
tx_power_dbm,
|
||||
preamble,
|
||||
cad,
|
||||
},
|
||||
bridge: BridgeSection {
|
||||
passphrase,
|
||||
@@ -92,6 +88,7 @@ pub fn run_wizard(config_path: &Path, existing: Option<&Config>) -> Result<()> {
|
||||
rate_limit_pps: bridge.rate_limit_pps,
|
||||
log_file: bridge.log_file,
|
||||
},
|
||||
tx: existing.map(|c| c.tx.clone()).unwrap_or_default(),
|
||||
};
|
||||
|
||||
write_config(config_path, &cfg)?;
|
||||
|
||||
Reference in new issue
Block a user