fix(radio): apply LoRaConfig.sync_word to the chip on SET_CONFIG

`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<RK,DLY>::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.
This commit is contained in:
swaits committed 2026-05-11 11:10:47 -06:00
1 parent 04dc58c943
commit 0b9f9ee882
2 files changed
+21 -5

No files matched your search

Generated
+5 -5
View File
@@ -1812,7 +1812,7 @@ dependencies = [
[[package]] [[package]]
name = "lora-modulation" name = "lora-modulation"
version = "0.1.5" 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 = [ dependencies = [
"defmt 0.3.100", "defmt 0.3.100",
] ]
@@ -1820,7 +1820,7 @@ dependencies = [
[[package]] [[package]]
name = "lora-phy" name = "lora-phy"
version = "3.0.2-alpha" 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 = [ dependencies = [
"defmt 0.3.100", "defmt 0.3.100",
"document-features", "document-features",
@@ -1834,7 +1834,7 @@ dependencies = [
[[package]] [[package]]
name = "lorawan" name = "lorawan"
version = "0.9.1" 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 = [ dependencies = [
"aes", "aes",
"cmac", "cmac",
@@ -1846,7 +1846,7 @@ dependencies = [
[[package]] [[package]]
name = "lorawan-device" name = "lorawan-device"
version = "0.12.2" 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 = [ dependencies = [
"defmt 0.3.100", "defmt 0.3.100",
"document-features", "document-features",
@@ -1861,7 +1861,7 @@ dependencies = [
[[package]] [[package]]
name = "lorawan-macros" name = "lorawan-macros"
version = "0.1.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
+16
View File
@@ -2096,6 +2096,22 @@ fn to_lora_mod_params(
async fn reconfigure_radio(lora: &mut LoRaDriver, cfg: &LoRaConfig) -> Result<(), RadioError> { async fn reconfigure_radio(lora: &mut LoRaDriver, cfg: &LoRaConfig) -> Result<(), RadioError> {
lora_setup("reconfigure_radio.enter_standby", lora.enter_standby()).await?; 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 // Calibrate image etc. via a throwaway create_modulation_params call
// — inside the SET_CONFIG OK window so first TX/RX is fast. // — inside the SET_CONFIG OK window so first TX/RX is fast.
let _ = to_lora_mod_params(lora, cfg)?; let _ = to_lora_mod_params(lora, cfg)?;