diff --git a/zephcore/adapters/datastore/ZephyrDataStore.cpp b/zephcore/adapters/datastore/ZephyrDataStore.cpp index 77e9c81..e03269c 100644 --- a/zephcore/adapters/datastore/ZephyrDataStore.cpp +++ b/zephcore/adapters/datastore/ZephyrDataStore.cpp @@ -368,7 +368,11 @@ void ZephyrDataStore::loadPrefs(NodePrefs &prefs) { bool prefs_exists = exists(PREFS_FILE); if (!prefs_exists) { - LOG_DBG("loadPrefs: no prefs file found"); + LOG_DBG("loadPrefs: no prefs file found, persisting defaults"); + /* Persist defaults so flash always has a prefs file from boot 1. + * Lets later code (e.g. tempradio revert) trust that flash is + * authoritative without a "first run" special case. */ + savePrefs(prefs); return; } diff --git a/zephcore/adapters/radio/LoRaRadioBase.cpp b/zephcore/adapters/radio/LoRaRadioBase.cpp index d03a750..1915c22 100644 --- a/zephcore/adapters/radio/LoRaRadioBase.cpp +++ b/zephcore/adapters/radio/LoRaRadioBase.cpp @@ -31,6 +31,9 @@ LoRaRadioBase::LoRaRadioBase(const struct device *lora_dev, MainBoard &board, _rx_boost_enabled(true), _tx_power_reduction_db(0), _config_cached(false), + _has_radio_override(false), + _override_freq(0), _override_bw(0), + _override_sf(0), _override_cr(0), _rx_cb(nullptr), _rx_cb_user_data(nullptr), _tx_done_cb(nullptr), _tx_done_cb_user_data(nullptr), _tx_thread_running(false), @@ -186,14 +189,20 @@ void LoRaRadioBase::rxCallbackStatic(const struct device *dev, uint8_t *data, void LoRaRadioBase::buildModemConfig(struct lora_modem_config &cfg, bool tx) { memset(&cfg, 0, sizeof(cfg)); - cfg.frequency = _prefs ? (uint32_t)(_prefs->freq * 1000000.0f) - : LoRaConfig::FREQ_HZ; - cfg.bandwidth = bw_khz_to_enum( - _prefs ? (uint16_t)(_prefs->bw) : (uint16_t)LoRaConfig::BANDWIDTH); - cfg.datarate = (enum lora_datarate)( - _prefs ? _prefs->sf : LoRaConfig::SPREADING_FACTOR); - cfg.coding_rate = cr_to_enum( - _prefs ? _prefs->cr : LoRaConfig::CODING_RATE); + /* Override wins for freq/bw/sf/cr (tempradio). Power, preamble, and + * other fields still come from _prefs. */ + float freq_mhz = _has_radio_override ? _override_freq + : (_prefs ? _prefs->freq : (LoRaConfig::FREQ_HZ / 1000000.0f)); + float bw_khz = _has_radio_override ? _override_bw + : (_prefs ? _prefs->bw : (float)LoRaConfig::BANDWIDTH); + uint8_t sf = _has_radio_override ? _override_sf + : (_prefs ? _prefs->sf : LoRaConfig::SPREADING_FACTOR); + uint8_t cr = _has_radio_override ? _override_cr + : (_prefs ? _prefs->cr : LoRaConfig::CODING_RATE); + cfg.frequency = (uint32_t)(freq_mhz * 1000000.0f); + cfg.bandwidth = bw_khz_to_enum((uint16_t)bw_khz); + cfg.datarate = (enum lora_datarate)sf; + cfg.coding_rate = cr_to_enum(cr); cfg.preamble_len = LoRaConfig::PREAMBLE_LEN; cfg.tx_power = _prefs ? (int8_t)_prefs->tx_power_dbm : LoRaConfig::TX_POWER_DBM; @@ -372,12 +381,30 @@ void LoRaRadioBase::reconfigure() void LoRaRadioBase::reconfigureWithParams(float freq, float bw, uint8_t sf, uint8_t cr) { - if (_prefs) { - _prefs->freq = freq; - _prefs->bw = bw; - _prefs->sf = sf; - _prefs->cr = cr; + /* Callers (ObserverMesh CLI handlers) write to _prefs and call + * savePrefs() before invoking us — the radio just needs to pick up + * the new params. Tempradio uses setRadioOverride() instead so it + * never touches _prefs. */ + (void)freq; (void)bw; (void)sf; (void)cr; + reconfigure(); +} + +void LoRaRadioBase::setRadioOverride(float freq, float bw, uint8_t sf, uint8_t cr) +{ + _override_freq = freq; + _override_bw = bw; + _override_sf = sf; + _override_cr = cr; + _has_radio_override = true; + reconfigure(); +} + +void LoRaRadioBase::clearRadioOverride() +{ + if (!_has_radio_override) { + return; } + _has_radio_override = false; reconfigure(); } diff --git a/zephcore/adapters/radio/LoRaRadioBase.h b/zephcore/adapters/radio/LoRaRadioBase.h index e17974b..398ed9b 100644 --- a/zephcore/adapters/radio/LoRaRadioBase.h +++ b/zephcore/adapters/radio/LoRaRadioBase.h @@ -36,6 +36,14 @@ public: void begin() override; void reconfigure(); void reconfigureWithParams(float freq, float bw, uint8_t sf, uint8_t cr); + + /* Temporary radio override — applies freq/bw/sf/cr without mutating + * _prefs. Used by tempradio so the saved prefs survive intact and + * concurrent savePrefs() calls don't poison flash. clearRadioOverride() + * reverts to whatever _prefs holds at that moment. */ + void setRadioOverride(float freq, float bw, uint8_t sf, uint8_t cr); + void clearRadioOverride(); + bool hasRadioOverride() const { return _has_radio_override; } int recvRaw(uint8_t *bytes, int sz) override; uint32_t getEstAirtimeFor(int len_bytes) override; float packetScore(float snr, int packet_len) override; @@ -153,6 +161,15 @@ protected: struct lora_modem_config _last_cfg; bool _config_cached; + /* Radio param override — when set, buildModemConfig() uses these + * for freq/bw/sf/cr instead of _prefs. Everything else (tx_power, + * preamble, APC reduction) still comes from _prefs. */ + bool _has_radio_override; + float _override_freq; + float _override_bw; + uint8_t _override_sf; + uint8_t _override_cr; + /* ISR RX callback — passed to lora_recv_async() / lora_recv_duty_cycle() */ static void rxCallbackStatic(const struct device *dev, uint8_t *data, uint16_t size, int16_t rssi, int8_t snr, diff --git a/zephcore/app/RepeaterDataStore.cpp b/zephcore/app/RepeaterDataStore.cpp index 187d1f3..a1988f6 100644 --- a/zephcore/app/RepeaterDataStore.cpp +++ b/zephcore/app/RepeaterDataStore.cpp @@ -137,6 +137,10 @@ bool RepeaterDataStore::loadPrefs(NodePrefs& prefs) { prefs.flood_advert_interval = 25; prefs.loop_detect = LOOP_DETECT_MINIMAL; prefs.path_hash_mode = 1; + /* Persist defaults so flash always has a prefs file from boot 1. + * Lets later code (e.g. tempradio revert) trust that flash is + * authoritative without a "first run" special case. */ + savePrefs(prefs); return true; } diff --git a/zephcore/app/RepeaterMesh.cpp b/zephcore/app/RepeaterMesh.cpp index 71ff33e..6426093 100644 --- a/zephcore/app/RepeaterMesh.cpp +++ b/zephcore/app/RepeaterMesh.cpp @@ -1638,13 +1638,13 @@ void RepeaterMesh::loop() { if (set_radio_at && millisHasNowPassed(set_radio_at)) { set_radio_at = 0; - getRadioDriver(_radio).reconfigureWithParams(pending_freq, pending_bw, pending_sf, pending_cr); + getRadioDriver(_radio).setRadioOverride(pending_freq, pending_bw, pending_sf, pending_cr); LOG_INF("Temp radio params applied"); } if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { revert_radio_at = 0; - getRadioDriver(_radio).reconfigureWithParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); + getRadioDriver(_radio).clearRadioOverride(); LOG_INF("Radio params restored"); } diff --git a/zephcore/src/main_repeater.cpp b/zephcore/src/main_repeater.cpp index a9a874f..5225b3d 100644 --- a/zephcore/src/main_repeater.cpp +++ b/zephcore/src/main_repeater.cpp @@ -341,6 +341,11 @@ static void repeater_event_loop(void) * on packet-driven events. */ if (repeater_mesh_ptr) { repeater_mesh_ptr->maintenanceLoop(); + /* Also drive loop() so time-based actions (advert + * timers, tempradio set/revert, contacts flush, + * uplink status) still fire when no LoRa/CLI + * traffic wakes the event loop. */ + repeater_mesh_ptr->loop(); } #endif