diff --git a/zephcore/app/main_observer.cpp b/zephcore/app/main_observer.cpp index d8ac4f3..52755fa 100644 --- a/zephcore/app/main_observer.cpp +++ b/zephcore/app/main_observer.cpp @@ -39,7 +39,7 @@ LOG_MODULE_REGISTER(zephcore_observer_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL); #include #include #include "observer_creds.h" -#include +#include /* ========== LED (optional) ========== */ @@ -387,13 +387,7 @@ int main(void) /* Apply the persisted LED master switch. The observer has no CLI of its own * to change it, but a unit reflashed from a repeater build keeps the setting * — and it still drives lora-tx-led on TX-capable boards. */ - { - bool leds_off = prefs->leds_disabled != 0; - zephcore_leds_set_disabled(leds_off); - zephcore_leds_set_radio_mode(prefs->leds_radio_mode); - zephcore_leds_set_hb_mode(prefs->leds_hb_mode); - LOG_INF("LEDs: %s (from prefs)", leds_off ? "disabled" : "enabled"); - } + apply_boot_prefs(prefs, false); /* Initialize USB serial for CLI */ #if ZEPHCORE_USB_STACK && DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart) diff --git a/zephcore/helpers/boot_prefs.h b/zephcore/helpers/boot_prefs.h new file mode 100644 index 0000000..7c062e0 --- /dev/null +++ b/zephcore/helpers/boot_prefs.h @@ -0,0 +1,45 @@ +/* + * ZephCore - apply persisted prefs to hardware at boot + * Copyright (c) 2025 ZephCore + * SPDX-License-Identifier: MIT + * + * Shared by the repeater, room server and observer mains. Header-only so it + * logs under the including file's log module. + */ + +#ifndef ZEPHCORE_BOOT_PREFS_H +#define ZEPHCORE_BOOT_PREFS_H + +#include +#include +#include "led_gate.h" +#include "ZephyrGPSManager.h" + +/* Push the prefs that drive hardware directly (LED gate, GPS duty) into their + * managers. Ordering is load-bearing: + * - after loadPrefs(): before it the struct still holds the initNodePrefs() + * defaults (leds on, gps_interval=300, the companion value). On + * repeater/room server loadPrefs() is also where the 48 h GPS default is + * set and persisted. Calling this too early is the bug class where CLI + * readback is correct and the hardware disagrees. + * - after ui_init(), so the heartbeat cycle exists to be stopped. + * - after the GPS fix/event callbacks are registered, because + * gps_set_repeater_mode() starts acquisition immediately. + * gps_time_sync: put GPS in repeater (time-sync-only) mode. Only roles that + * own the GPS pass true. */ +static inline void apply_boot_prefs(const NodePrefs *p, bool gps_time_sync) +{ + bool leds_off = p->leds_disabled != 0; + + zephcore_leds_set_disabled(leds_off); + zephcore_leds_set_radio_mode(p->leds_radio_mode); + zephcore_leds_set_hb_mode(p->leds_hb_mode); + LOG_INF("LEDs: %s (from prefs)", leds_off ? "disabled" : "enabled"); + + if (gps_time_sync && gps_is_available()) { + gps_set_poll_interval_sec(p->gps_interval); + gps_set_repeater_mode(true); + } +} + +#endif /* ZEPHCORE_BOOT_PREFS_H */ diff --git a/zephcore/src/main_repeater.cpp b/zephcore/src/main_repeater.cpp index 135904e..3202d7e 100644 --- a/zephcore/src/main_repeater.cpp +++ b/zephcore/src/main_repeater.cpp @@ -22,7 +22,7 @@ LOG_MODULE_REGISTER(zephcore_repeater_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL); #include #include #include "oled_power.h" -#include "led_gate.h" +#include "boot_prefs.h" /* BLE controller assert handler — BT is compiled even for repeater (via zephcore_common.conf) */ #if IS_ENABLED(CONFIG_BT_CTLR_ASSERT_HANDLER) @@ -700,8 +700,8 @@ int main(void) } } - /* GPS callbacks only. The duty interval and the mode switch that starts - * acquisition are applied after loadPrefs() below — see the block there. */ + /* GPS callbacks only. Duty interval and repeater mode are applied by + * apply_boot_prefs() after loadPrefs() below. */ if (gps_is_available()) { gps_set_fix_callback(gps_fix_callback); gps_set_event_callback(gps_event_callback); @@ -760,48 +760,9 @@ int main(void) data_store.loadPrefs(*repeater_mesh.getNodePrefs()); lora_radio.setPrefs(repeater_mesh.getNodePrefs()); - /* Set GPS to repeater mode: power off now, wake every duty interval for - * time sync only. This prevents GPS from draining power on boards that - * have it (e.g., Wio Tracker). - * - * MUST come after loadPrefs(), for the same reason as the LED switch - * below -- but note loadPrefs() is not a pure read here. It is also the - * only place this role's GPS default is established: initNodePrefs() - * sets gps_interval=300 (the companion value), and the 48 h default is - * applied and PERSISTED by loadPrefs()'s first-boot path. - * - * This block used to sit ~55 lines earlier, where getNodePrefs() still - * held that 300. So the GPS manager was armed from the companion default - * on every boot and nothing re-applied the real value afterwards: - * - a stock repeater woke its GPS every 5 min instead of every 48 h; - * - any operator value, notably "set gps duty 0" (always-on), was - * stored and read back correctly but never reached the hardware. - * Same failure shape as the radio-frequency and LED ordering bugs in this - * file. main_companion.cpp has always had this order right. */ - if (gps_is_available()) { - gps_set_poll_interval_sec(repeater_mesh.getNodePrefs()->gps_interval); - gps_set_repeater_mode(true); - } - - /* Apply the persisted LED master switch ("set leds on|off"). MUST come - * after loadPrefs(): this used to sit just after ui_init(), ~45 lines - * earlier, where getNodePrefs() still held the initNodePrefs() default of - * leds_disabled=0. A node with "off" persisted therefore opened the gate on - * every boot and never closed it again -- `get leds` read the (correct) RAM - * prefs and said "off" while the LEDs kept blinking, until the user issued - * `set leds off` a second time to drive the gate directly. The other two - * ordering constraints still hold here: ui_init() has already run, so the - * heartbeat cycle exists to be stopped, and repeater_mesh.begin() -> - * Dispatcher::begin() -> Radio::begin() is still below, so the first - * transmit honours it. */ - { - const NodePrefs *lp = repeater_mesh.getNodePrefs(); - bool leds_off = lp->leds_disabled != 0; - zephcore_leds_set_disabled(leds_off); - zephcore_leds_set_radio_mode(lp->leds_radio_mode); - zephcore_leds_set_hb_mode(lp->leds_hb_mode); - LOG_INF("LEDs: %s (from prefs)", leds_off ? "disabled" : "enabled"); - } + /* LED gate and GPS duty from prefs. After loadPrefs() and ui_init(), + * before begin() -- see boot_prefs.h. */ + apply_boot_prefs(repeater_mesh.getNodePrefs(), true); /* Start mesh with data store - loads ACL, regions */ repeater_mesh.begin(&data_store); diff --git a/zephcore/src/main_room_server.cpp b/zephcore/src/main_room_server.cpp index e92ec3a..dd2483f 100644 --- a/zephcore/src/main_room_server.cpp +++ b/zephcore/src/main_room_server.cpp @@ -22,7 +22,7 @@ LOG_MODULE_REGISTER(zephcore_room_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL); #include #include #include "oled_power.h" -#include "led_gate.h" +#include "boot_prefs.h" /* BLE controller assert handler — BT is compiled even for repeater (via zephcore_common.conf) */ #if IS_ENABLED(CONFIG_BT_CTLR_ASSERT_HANDLER) @@ -586,8 +586,8 @@ int main(void) } } - /* GPS callbacks only. The duty interval and the mode switch that starts - * acquisition are applied after loadPrefs() below — see the block there. */ + /* GPS callbacks only. Duty interval and repeater mode are applied by + * apply_boot_prefs() after loadPrefs() below. */ if (gps_is_available()) { gps_set_fix_callback(gps_fix_callback); gps_set_event_callback(gps_event_callback); @@ -645,48 +645,9 @@ int main(void) data_store.loadPrefs(*room_mesh.getNodePrefs()); lora_radio.setPrefs(room_mesh.getNodePrefs()); - /* Set GPS to repeater mode: power off now, wake every duty interval for - * time sync only. This prevents GPS from draining power on boards that - * have it (e.g., Wio Tracker). - * - * MUST come after loadPrefs(), for the same reason as the LED switch - * below -- but note loadPrefs() is not a pure read here. It is also the - * only place this role's GPS default is established: initNodePrefs() - * sets gps_interval=300 (the companion value), and the 48 h default is - * applied and PERSISTED by loadPrefs()'s first-boot path. - * - * This block used to sit ~55 lines earlier, where getNodePrefs() still - * held that 300. So the GPS manager was armed from the companion default - * on every boot and nothing re-applied the real value afterwards: - * - a stock repeater woke its GPS every 5 min instead of every 48 h; - * - any operator value, notably "set gps duty 0" (always-on), was - * stored and read back correctly but never reached the hardware. - * Same failure shape as the radio-frequency and LED ordering bugs in this - * file. main_companion.cpp has always had this order right. */ - if (gps_is_available()) { - gps_set_poll_interval_sec(room_mesh.getNodePrefs()->gps_interval); - gps_set_repeater_mode(true); - } - - /* Apply the persisted LED master switch ("set leds on|off"). MUST come - * after loadPrefs(): this used to sit just after ui_init(), ~45 lines - * earlier, where getNodePrefs() still held the initNodePrefs() default of - * leds_disabled=0. A node with "off" persisted therefore opened the gate on - * every boot and never closed it again -- `get leds` read the (correct) RAM - * prefs and said "off" while the LEDs kept blinking, until the user issued - * `set leds off` a second time to drive the gate directly. The other two - * ordering constraints still hold here: ui_init() has already run, so the - * heartbeat cycle exists to be stopped, and room_mesh.begin() -> - * Dispatcher::begin() -> Radio::begin() is still below, so the first - * transmit honours it. */ - { - const NodePrefs *lp = room_mesh.getNodePrefs(); - bool leds_off = lp->leds_disabled != 0; - zephcore_leds_set_disabled(leds_off); - zephcore_leds_set_radio_mode(lp->leds_radio_mode); - zephcore_leds_set_hb_mode(lp->leds_hb_mode); - LOG_INF("LEDs: %s (from prefs)", leds_off ? "disabled" : "enabled"); - } + /* LED gate and GPS duty from prefs. After loadPrefs() and ui_init(), + * before begin() -- see boot_prefs.h. */ + apply_boot_prefs(room_mesh.getNodePrefs(), true); /* Start mesh with data store - loads ACL, regions */ room_mesh.begin(&data_store); diff --git a/zephcore/tools/formatter/boards/thinknode_m6/board.overlay b/zephcore/tools/formatter/boards/thinknode_m6/board.overlay index 9503be2..1a39716 100644 --- a/zephcore/tools/formatter/boards/thinknode_m6/board.overlay +++ b/zephcore/tools/formatter/boards/thinknode_m6/board.overlay @@ -1,22 +1,20 @@ /* - * ThinkNode M6 formatter overlay — minimal drivers, single LittleFS instance. + * ThinkNode M6 formatter overlay — minimal drivers. * - * The M6 main DTS includes both filesystem.dtsi (/lfs) and qspi-ext.dtsi - * (/ext), giving two LittleFS instances. Each needs ~8-10KB RAM for block - * caches; two instances overflow the formatter's RAM budget. Drop /ext here - * — the QSPI storage partition node remains so main.c can still erase it via - * FIXED_PARTITION_EXISTS(qspi_storage_partition). + * The M6 board DTS no longer carries the QSPI flash: no shipped unit has the + * chip fitted, so the board dropped &qspi, its partition, the P0.21 enable + * regulator and the qspi-ext.dtsi (/ext) include together (see the note in + * thinknode_m6.dts). There is therefore no /ext LittleFS instance to drop + * and no QSPI enable to hog here; main.c's FIXED_PARTITION_EXISTS() checks + * simply skip the QSPI erase. If the board ever restores QSPI, restore the + * "/delete-node/ &qspi_lfs;" and the P0.21 hog that used to live here. * - * Also replace regulator-fixed power-enable nodes with gpio-hogs so + * Replace the regulator-fixed power-enable node with a gpio-hog so * CONFIG_REGULATOR is not forced on by regulator-boot-on. */ -/* Remove /ext LittleFS fstab — formatter only needs one LittleFS instance */ -/delete-node/ &qspi_lfs; - -/* Remove regulator-fixed nodes and replace with gpio-hogs */ +/* Remove regulator-fixed node and replace with a gpio-hog */ /delete-node/ &pwr_enable; -/delete-node/ &qspi_flash_enable; &gpio0 { /* Board power enable (PIN_PWR_EN = P0.27) */ @@ -26,12 +24,4 @@ output-high; line-name = "PWR Enable"; }; - - /* QSPI flash enable (QSPI_FLASH_EN = P0.21) — must be HIGH before flash responds */ - qspi-flash-en-hog { - gpio-hog; - gpios = <21 GPIO_ACTIVE_HIGH>; - output-high; - line-name = "QSPI Flash Enable"; - }; };