fix(radio): cache mod params on SET_CONFIG so TX works without prior RX
The TX state machine's `prep_for_cad_act` (TxCadPrep entry) and `prep_for_tx_act` (TxPrep entry) both read `ctx.mdltn` and emit `TransmitFailed` → `ERR(ERadio)` if it's None. But `ctx.mdltn` was only ever assigned on the RX path — `try_start_rx_hw` (RxArming entry) and `cache_rx_pkt` (RxSession entry). `reconfigure_radio` already called `to_lora_mod_params(lora, cfg)` during SET_CONFIG but discarded the result via `let _ = …?;` (comment claimed image calibration; the function is pure compute on SX126x). Result: a freshly-booted dongle that issues TX before ever calling RX_START gets `ERR(ERadio)` on the first CmdTx because the chart's TX-entry actions find `ctx.mdltn = None`. The bug was hidden in practice by any prior RX_START (ai-bot, ping_pong --role rx, etc.) leaving `ctx.mdltn` populated across sessions until the next reflash. Fix: thread the `ModulationParams` out of `reconfigure_radio` as its return value, and have `apply_set_config` cache it in `self.mdltn` alongside `self.config`. The failure path clears `self.mdltn` along with `self.config` so a SET_CONFIG that fails leaves a clean Unconfigured state. Surfaced while testing the matching sync-word fix on freshly-flashed dongles via `examples/sync_word_test.py` — the first TX failed with `ERR(ERadio)` until this path was wired up.
This commit is contained in:
1 file changed
+10
-6
+10
-6
@@ -1252,7 +1252,9 @@ impl RadioActions for RadioActionContext<'_> {
|
||||
Some(lora) => reconfigure_radio(lora, &cfg).await,
|
||||
None => Err(RadioError::Busy),
|
||||
};
|
||||
if recfg_result.is_err() {
|
||||
let mdltn = match recfg_result {
|
||||
Ok(m) => m,
|
||||
Err(_) => {
|
||||
// Hardware failure: drop to Unconfigured (spec §6.3).
|
||||
// Clear all per-session state — including any in-flight
|
||||
// RxStart tag — so the next CmdRxStart in Unconfigured
|
||||
@@ -1274,7 +1276,9 @@ impl RadioActions for RadioActionContext<'_> {
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.config = Some(cfg);
|
||||
self.mdltn = Some(mdltn);
|
||||
info!("config set (apply_set_config success tag={=u16})", tag);
|
||||
self.cad_retry_delay_us = toa_us(&cfg, CAD_ASSUMED_PEER_PAYLOAD_B);
|
||||
if self.cad_retry_delay_us > CAD_BUDGET_US {
|
||||
@@ -2094,7 +2098,10 @@ fn to_lora_mod_params(
|
||||
lora.create_modulation_params(sf, bw, cr, cfg.freq_hz)
|
||||
}
|
||||
|
||||
async fn reconfigure_radio(lora: &mut LoRaDriver, cfg: &LoRaConfig) -> Result<(), RadioError> {
|
||||
async fn reconfigure_radio(
|
||||
lora: &mut LoRaDriver,
|
||||
cfg: &LoRaConfig,
|
||||
) -> Result<lora_phy::mod_params::ModulationParams, 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
|
||||
@@ -2112,10 +2119,7 @@ async fn reconfigure_radio(lora: &mut LoRaDriver, cfg: &LoRaConfig) -> Result<()
|
||||
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)?;
|
||||
Ok(())
|
||||
to_lora_mod_params(lora, cfg)
|
||||
}
|
||||
|
||||
fn validate_lora(cfg: &LoRaConfig, info: &Info) -> Result<(), ErrorCode> {
|
||||
|
||||
Reference in new issue
Block a user