From 0b9f9ee88225d9795b6455b476d5a9aac2d1ff7a Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Mon, 11 May 2026 10:49:48 -0600 Subject: [PATCH] fix(radio): apply LoRaConfig.sync_word to the chip on SET_CONFIG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `reconfigure_radio` previously consumed `cfg.sync_word` from the host in name only — it called `enter_standby` and constructed a fresh set of LoRa modulation params (which carry sf/bw/cr/freq but not the sync word), then returned. lora-phy v3 has no public sync-word setter for SX126x, and there's no codepath in this crate that touches the chip's LoRaSyncword registers (0x0740/0x0741) after boot. The chip therefore sat on whatever `LoRa::new(_, false, _)` set at cold init — `LORAWAN_PRIVATE_SYNCWORD` (byte 0x12, register pair 0x1424) — for the entire session, regardless of what the host requested. That happened to work for every existing user because everyone wants 0x1424 (MeshCore's sync byte). It manifested as soon as a host actually needed a different value: `anon_ping.py` survived because MeshCore matches, but any custom mesh / non-MeshCore network would get silent wrong-sync-word TXes that no peer could hear and no host-side diagnostic could pin down. This commit applies the host-supplied sync word inside `reconfigure_radio`, between the existing `enter_standby` and the mod-params construction: - `cfg.sync_word` (the protocol's u16, RadioLib-style; e.g. 0x1424 == byte 0x12, 0x3444 == byte 0x34) is decoded back to its underlying single byte: `byte = (hi & 0xF0) | (lo >> 4)` after `to_be_bytes()`, the inverse of lora-phy's `convert_sync_word` nibble-expansion. - That byte is handed to lora-phy's new `LoRa::set_lora_sync_word` (added in the matching lora-phy commit), which honours SX1261/2 datasheet §8.3.1 by driving `ensure_ready` + conditional `set_standby` before writing the register pair, and caches the new value in the `LoRa` struct so a later cold-reset NRESET (Recovering state) resumes with the host's sync word rather than reverting to the construction-time default. - Failure of the sync-word write lands the same way `enter_standby` failure already did: `apply_set_config` wipes `self.config`, sends `ERR(ERadio)`, and routes the chart to `Recovering` to NRESET + cold-init. No new error-handling plumbing required. Cargo.lock bumps lora-phy from `fixed#e8a52ebd` to `fixed#8d66d1c4`, which is the merge of three pre-existing `fix/*` branches plus a new `feat/set-sync-word` branch carrying the lora-phy API addition. Verified on hardware with a two-dongle bench test (`examples/sync_word_test.py`): one dongle pinned to sync 0x5464, the other rotating through six sync words. RX hears exactly the matching cycles with realistic RSSI / SNR; non-matching cycles are correctly filtered by the SX126x packet engine before reaching the host. --- Cargo.lock | 10 +++++----- src/radio.rs | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2121cdf..b5a2571 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1812,7 +1812,7 @@ dependencies = [ [[package]] name = "lora-modulation" version = "0.1.5" -source = "git+https://github.com/swaits/lora-rs.git?branch=fixed#e8a52ebd35352ce0267148fd392a0b1956d7735f" +source = "git+https://github.com/swaits/lora-rs.git?branch=fixed#043d7659dfe2148913f16b2f7ea3e9241642b770" dependencies = [ "defmt 0.3.100", ] @@ -1820,7 +1820,7 @@ dependencies = [ [[package]] name = "lora-phy" version = "3.0.2-alpha" -source = "git+https://github.com/swaits/lora-rs.git?branch=fixed#e8a52ebd35352ce0267148fd392a0b1956d7735f" +source = "git+https://github.com/swaits/lora-rs.git?branch=fixed#043d7659dfe2148913f16b2f7ea3e9241642b770" dependencies = [ "defmt 0.3.100", "document-features", @@ -1834,7 +1834,7 @@ dependencies = [ [[package]] name = "lorawan" version = "0.9.1" -source = "git+https://github.com/swaits/lora-rs.git?branch=fixed#e8a52ebd35352ce0267148fd392a0b1956d7735f" +source = "git+https://github.com/swaits/lora-rs.git?branch=fixed#043d7659dfe2148913f16b2f7ea3e9241642b770" dependencies = [ "aes", "cmac", @@ -1846,7 +1846,7 @@ dependencies = [ [[package]] name = "lorawan-device" version = "0.12.2" -source = "git+https://github.com/swaits/lora-rs.git?branch=fixed#e8a52ebd35352ce0267148fd392a0b1956d7735f" +source = "git+https://github.com/swaits/lora-rs.git?branch=fixed#043d7659dfe2148913f16b2f7ea3e9241642b770" dependencies = [ "defmt 0.3.100", "document-features", @@ -1861,7 +1861,7 @@ dependencies = [ [[package]] name = "lorawan-macros" version = "0.1.0" -source = "git+https://github.com/swaits/lora-rs.git?branch=fixed#e8a52ebd35352ce0267148fd392a0b1956d7735f" +source = "git+https://github.com/swaits/lora-rs.git?branch=fixed#043d7659dfe2148913f16b2f7ea3e9241642b770" dependencies = [ "proc-macro2", "quote", diff --git a/src/radio.rs b/src/radio.rs index cb39047..06ddf25 100644 --- a/src/radio.rs +++ b/src/radio.rs @@ -2096,6 +2096,22 @@ fn to_lora_mod_params( async fn reconfigure_radio(lora: &mut LoRaDriver, cfg: &LoRaConfig) -> Result<(), RadioError> { lora_setup("reconfigure_radio.enter_standby", lora.enter_standby()).await?; + // Apply the host-supplied LoRa sync word. The protocol field is a + // u16 holding the SX126x register pair in big-endian order + // (RadioLib convention: 0x1424 == byte 0x12 == LoRa "private" / + // MeshCore; 0x3444 == byte 0x34 == LoRaWAN "public"). lora-phy's + // set_lora_sync_word takes the underlying single-byte form, so + // undo the nibble encoding: convert_sync_word(byte) puts the + // chip's mandatory 0x4 in the low nibble of each register byte, + // and the high nibble of each register byte holds the original + // byte's high/low nibble respectively. + let [hi, lo] = cfg.sync_word.to_be_bytes(); + let sync_byte = (hi & 0xF0) | (lo >> 4); + lora_setup( + "reconfigure_radio.set_lora_sync_word", + lora.set_lora_sync_word(sync_byte), + ) + .await?; // Calibrate image etc. via a throwaway create_modulation_params call // — inside the SET_CONFIG OK window so first TX/RX is fast. let _ = to_lora_mod_params(lora, cfg)?;