set leds on/off

This commit is contained in:
liquidraver
2026-07-31 08:27:10 +02:00
parent aa14944a93
commit 4e880df81a
15 changed files with 259 additions and 23 deletions
+12 -1
View File
@@ -687,9 +687,18 @@ Repeaters and room servers default to `CONFIG_ZEPHCORE_REPEATER_GPS_INTERVAL_SEC
- Battery ADC with optional regulator-gated voltage divider, 8-sample average (boards with `zephyr,user` ADC node; MG24 has no battery divider, ADC disabled)
- UF2 bootloader entry via GPREGRET magic (0x57 = UF2, 0xA8 = BLE DFU)
- TX LED bracketing for LoRa transmissions
- TX LED bracketing for LoRa transmissions (gated by the LED master switch below)
- Bootloader version detection via flash memory scan
**LED master switch** (`helpers/led_gate.{c,h}`, `set leds on|off`, all roles): one process-wide
flag every LED driver consults — heartbeat and unread-message LEDs in `helpers/ui/ui_common.c`, the
`lora-tx-led` in `ZephyrBoard::onBeforeTransmit()`, and the message/shutdown flashes. It lives
outside the UI layer because `ui_common.c` is only compiled when a UI is enabled, while a headless
repeater still blinks on every transmit. `ui_common.c` overrides the weak `zephcore_leds_ui_sync()`
hook so a CLI change also stops a lit heartbeat and refreshes the UI's LEDs page. Persisted in
`NodePrefs.leds_disabled` (companion offset 93; repeater offset 120, magic-encoded — see §13).
Does not cover the display backlight, which has its own UI brightness setting (`display_brightness`).
### 7.6 WiFi / MQTT / TCP Transports
- **`adapters/wifi/ZephyrWiFiStation.c`**: WiFi STA client (ESP32) used by observer and repeater uplink
@@ -991,6 +1000,8 @@ meshtimesync(151).
**Repeater/room-server `/lfs/repeater/prefs` (297 bytes)**`app/RepeaterDataStore.cpp`
`loadPrefs()`/`savePrefs()` (same field order as `helpers/CommonCLI.cpp`; offset comments inline).
Key ranges: name(4-36), radio(72-119), adaptive-delay(80-111, ignored at runtime),
leds_disabled(120, magic-encoded `0xA0`/`0xA1` — the byte formerly held `agc_reset_interval`, which
stored seconds/4, so any other value is a legacy interval and decodes to "LEDs on"),
Arduino-bridge(127-151, read+discarded), GPS(156-161), owner_info(170-290), rx_boost/duty(290-291),
reserved(292-293, was APC), flood_max_unscoped/advert(294-295), meshtimesync(296). Older shorter files
load cleanly — reads past EOF are no-ops, so newer fields keep their defaults and a one-time
+3
View File
@@ -568,6 +568,9 @@ target_sources(app PRIVATE
helpers/MeshTimeSync.cpp
helpers/oled_power.c
helpers/fatal_reboot.c
# LED master gate ("set leds on|off"). Always compiled: helpers/ui/ui_common.c
# only exists in UI builds, but a headless repeater still drives lora-tx-led.
helpers/led_gate.c
)
# Boot-time hardware-RTC auto-discovery (compact raw-I2C). Always compiled so
+2
View File
@@ -208,6 +208,7 @@ All `set uplink.*` changes are saved immediately and only applied after reboot.
| `get guest.password` | Guest access password |
| `get owner.info` | Owner/contact info (pipes `\|` display as newlines) |
| `get int.thresh` | Interference threshold |
| `get leds` | LED master switch: `on` or `off` |
| `get agc.reset.interval` | Removed - replies `use rxduty instead` |
| `get multi.acks` | Extra ACK transmit count (`0` or `1`) |
| `get path.hash.mode` | Path hashing algorithm: `0`, `1`, or `2` |
@@ -254,6 +255,7 @@ Changes are persisted immediately unless noted. Some require a reboot.
| `set guest.password <pwd>` | | Set guest access password |
| `set owner.info <text>` | Use `\|` for newlines | Owner/contact information |
| `set int.thresh <value>` | | Interference detection threshold |
| `set leds <on\|off\|1\|0>` | default **on** | Master switch for every LED on the node, applied live and persisted: heartbeat, unread-message and LoRa TX-activity LEDs, plus the message and shutdown flashes. Works on every role, including headless repeaters where the TX LED is the only one that ever lights. Does **not** cover the display backlight, which is a separate UI brightness setting. |
| `set agc.reset.interval <ms>` | Accepted, ignored | Removed - replies `use rxduty instead` |
| `set multi.acks <0\|1>` | | Enable extra ACK transmits |
| `set path.hash.mode <mode>` | 0, 1, or 2 | Path hashing algorithm |
+8 -1
View File
@@ -4,6 +4,7 @@
#include "ZephyrBoard.h"
#include "battery_curve.h"
#include "led_gate.h"
#include <zephyr/kernel.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/drivers/sensor.h>
@@ -246,7 +247,13 @@ const char *ZephyrBoard::getManufacturerName() const
void ZephyrBoard::onBeforeTransmit()
{
#if HAS_TX_LED
gpio_pin_set_dt(&tx_led, 1);
/* Honour the LED master gate ("set leds off"). On a headless repeater this
* is the only LED that ever lights, so the gate has to be checked here and
* not just in the UI layer. onAfterTransmit() still clears the pin
* unconditionally, so a gate flipped mid-transmit can't strand it lit. */
if (!zephcore_leds_disabled()) {
gpio_pin_set_dt(&tx_led, 1);
}
#endif
}
+14 -2
View File
@@ -173,7 +173,11 @@ bool RepeaterDataStore::loadPrefs(NodePrefs& prefs) {
fs_read(&file, &prefs.allow_read_only, sizeof(prefs.allow_read_only));
fs_read(&file, &prefs.multi_acks, sizeof(prefs.multi_acks));
fs_read(&file, &prefs.bw, sizeof(prefs.bw));
fs_read(&file, &prefs.agc_reset_interval, sizeof(prefs.agc_reset_interval));
/* 120: leds_disabled, magic-encoded. Formerly agc_reset_interval — see the
* LEDS_PREF_* comment in NodePrefs.h for why this is not a bare 0/1.
* leds_byte stays 0 (→ LEDs on) if the file is short. */
uint8_t leds_byte = 0;
fs_read(&file, &leds_byte, sizeof(leds_byte));
fs_read(&file, &prefs.path_hash_mode, sizeof(prefs.path_hash_mode));
fs_read(&file, &prefs.loop_detect, sizeof(prefs.loop_detect));
fs_read(&file, pad, 1);
@@ -215,6 +219,10 @@ bool RepeaterDataStore::loadPrefs(NodePrefs& prefs) {
fs_close(&file);
/* Only the explicit "off" magic disables LEDs; a legacy AGC interval or an
* unwritten byte both mean "on". */
prefs.leds_disabled = (leds_byte == LEDS_PREF_OFF) ? 1 : 0;
/* Migrate uninitialized backoff_multiplier (0.0 or NaN) to default */
if (prefs.backoff_multiplier == 0.0f || prefs.backoff_multiplier != prefs.backoff_multiplier) {
prefs.backoff_multiplier = 0.2f;
@@ -318,7 +326,11 @@ bool RepeaterDataStore::savePrefs(const NodePrefs& prefs) {
fs_write(&file, &prefs.allow_read_only, sizeof(prefs.allow_read_only));
fs_write(&file, &prefs.multi_acks, sizeof(prefs.multi_acks));
fs_write(&file, &prefs.bw, sizeof(prefs.bw));
fs_write(&file, &prefs.agc_reset_interval, sizeof(prefs.agc_reset_interval));
/* 120: leds_disabled, magic-encoded (was agc_reset_interval). */
{
uint8_t leds_byte = prefs.leds_disabled ? LEDS_PREF_OFF : LEDS_PREF_ON;
fs_write(&file, &leds_byte, sizeof(leds_byte));
}
fs_write(&file, &prefs.path_hash_mode, sizeof(prefs.path_hash_mode));
fs_write(&file, &prefs.loop_detect, sizeof(prefs.loop_detect));
fs_write(&file, pad, 1);
+10
View File
@@ -38,6 +38,7 @@ LOG_MODULE_REGISTER(zephcore_observer_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#include <ZephyrWiFiStation.h>
#include <ZephyrMQTTPublisher.h>
#include "observer_creds.h"
#include <helpers/led_gate.h>
/* ========== LED (optional) ========== */
@@ -357,6 +358,15 @@ int main(void)
data_store.savePrefs(*prefs);
}
/* 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);
LOG_INF("LEDs: %s (from prefs)", leds_off ? "disabled" : "enabled");
}
/* Initialize USB serial for CLI */
#if ZEPHCORE_USB_STACK && DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart)
usb_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
+37 -2
View File
@@ -5,6 +5,7 @@
#include "CommonCLI.h"
#include "battery_curve.h"
#include "led_gate.h"
#include <helpers/MeshTimeSync.h>
#include <helpers/time_sync.h>
#include <adapters/clock/ZephyrRTCDiscover.h>
@@ -67,6 +68,7 @@ void CommonCLI::loadPrefs(const char* path) {
}
uint8_t pad[8];
uint8_t leds_byte = 0;
bool ok = true;
/* Read fields in Arduino-compatible binary order.
@@ -93,7 +95,9 @@ void CommonCLI::loadPrefs(const char* path) {
ok = ok && prefs_read(&file, &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
ok = ok && prefs_read(&file, &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
ok = ok && prefs_read(&file, &_prefs->bw, sizeof(_prefs->bw)); // 116
ok = ok && prefs_read(&file, &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
/* 120: leds_disabled, magic-encoded. Formerly agc_reset_interval — see the
* LEDS_PREF_* comment in NodePrefs.h for why this is not a bare 0/1. */
ok = ok && prefs_read(&file, &leds_byte, sizeof(leds_byte)); // 120
ok = ok && prefs_read(&file, &_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121
ok = ok && prefs_read(&file, &_prefs->loop_detect, sizeof(_prefs->loop_detect)); // 122
ok = ok && prefs_read(&file, pad, 1); // 123
@@ -135,6 +139,10 @@ void CommonCLI::loadPrefs(const char* path) {
fs_close(&file);
/* Only the explicit "off" magic disables LEDs; a legacy AGC interval, an
* unwritten byte, or a truncated file all mean "on". */
_prefs->leds_disabled = (leds_byte == LEDS_PREF_OFF) ? 1 : 0;
// Sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0.0f, 20.0f);
_prefs->tx_delay_factor = constrain(_prefs->tx_delay_factor, 0.0f, 2.0f);
@@ -218,7 +226,11 @@ void CommonCLI::savePrefs(const char* path) {
fs_write(&file, &_prefs->allow_read_only, sizeof(_prefs->allow_read_only));
fs_write(&file, &_prefs->multi_acks, sizeof(_prefs->multi_acks));
fs_write(&file, &_prefs->bw, sizeof(_prefs->bw));
fs_write(&file, &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval));
/* 120: leds_disabled, magic-encoded (was agc_reset_interval). */
{
uint8_t leds_byte = _prefs->leds_disabled ? LEDS_PREF_OFF : LEDS_PREF_ON;
fs_write(&file, &leds_byte, sizeof(leds_byte));
}
fs_write(&file, &_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode));
fs_write(&file, &_prefs->loop_detect, sizeof(_prefs->loop_detect));
fs_write(&file, pad, 1);
@@ -457,6 +469,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
snprintf(reply, CLI_REPLY_SIZE, "> %.2f", (double)_prefs->airtime_factor);
} else if (memcmp(config, "int.thresh", 10) == 0) {
snprintf(reply, CLI_REPLY_SIZE, "> %u", (uint32_t)_prefs->interference_threshold);
} else if (memcmp(config, "leds", 4) == 0) {
snprintf(reply, CLI_REPLY_SIZE, "> %s", _prefs->leds_disabled ? "off" : "on");
} else if (memcmp(config, "agc.reset.interval", 18) == 0) {
strcpy(reply, "Removed - use rxduty instead");
} else if (memcmp(config, "multi.acks", 10) == 0) {
@@ -629,6 +643,27 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
savePrefs();
strcpy(reply, "OK");
}
} else if (memcmp(config, "leds ", 5) == 0) {
/* Master switch for every LED on the node: heartbeat, unread-message
* and LoRa TX activity, plus the message and shutdown flashes. Not
* the display backlight that has its own UI brightness setting. */
const char* val = &config[5];
int on;
if (memcmp(val, "on", 2) == 0 || val[0] == '1') {
on = 1;
} else if (memcmp(val, "off", 3) == 0 || val[0] == '0') {
on = 0;
} else {
on = -1;
}
if (on < 0) {
strcpy(reply, "Error: must be on or off");
} else {
_prefs->leds_disabled = on ? 0 : 1;
zephcore_leds_set_disabled(_prefs->leds_disabled != 0);
savePrefs();
strcpy(reply, "OK");
}
} else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
/* Periodic AGC recalibration was removed: it reset the noise floor
* to its unseeded sentinel on every fire, forcing a fresh seed and
+18 -6
View File
@@ -36,6 +36,22 @@
#define CAD_OFFSET_MIN (-8)
#define CAD_OFFSET_MAX 12
/* leds_disabled, as stored in the repeater/room-server/observer prefs layout.
*
* It occupies the byte that used to hold agc_reset_interval (offset 120),
* retired when periodic AGC recalibration was removed. That byte is NOT
* reusable as a plain 0/1 boolean: the old command stored seconds/4, so a node
* upgrading from a build that had it configured has an arbitrary small integer
* sitting there, and a bare non-zero test would silently kill its LEDs. Hence
* a magic encoding anything that is not one of these two values is a legacy
* AGC interval and decodes to the default (LEDs on). The first savePrefs()
* claims the byte for good.
*
* The companion layout is unaffected: it has always stored leds_disabled as a
* plain 0/1 at its own offset 93. */
#define LEDS_PREF_ON 0xA0
#define LEDS_PREF_OFF 0xA1
struct NodePrefs {
/* ---- Common fields (both roles) ---- */
float airtime_factor;
@@ -62,10 +78,7 @@ struct NodePrefs {
uint8_t flood_max_unscoped; // hop limit for un-scoped (ROUTE_TYPE_FLOOD) floods
uint8_t flood_max_advert; // hop limit for ADVERT floods (curbs advert churn)
uint8_t interference_threshold;
uint8_t agc_reset_interval; // RETIRED: read/written for on-disk layout
// compatibility only, never acted on.
// "set agc.reset.interval" replies
// "use rxduty instead".
uint8_t leds_disabled; // 1 = all LEDs off (heartbeat, unread, LoRa TX)
// Power saving
uint8_t powersaving_enabled;
// GPS settings
@@ -107,7 +120,6 @@ struct NodePrefs {
uint8_t path_hash_mode; // path mode 0-2
uint8_t autoadd_max_hops; // 0 = no limit, N = up to N-1 hops
uint8_t loop_detect; // LOOP_DETECT_{OFF,MINIMAL,MODERATE,STRICT}
uint8_t leds_disabled; // 1 = LEDs off
char default_scope_name[31]; // companion: default flood scope region name ("" = null)
uint8_t default_scope_key[16]; // companion: default flood scope TransportKey
uint8_t ble_disabled; // 1 = BLE advertising off
@@ -155,7 +167,7 @@ static inline void initNodePrefs(NodePrefs* prefs) {
prefs->flood_max_unscoped = 64; // un-scoped flood hop limit (defaults to flood_max)
prefs->flood_max_advert = 8; // ADVERT flood hop limit (upstream default)
prefs->interference_threshold = 0;
prefs->agc_reset_interval = 0;
prefs->leds_disabled = 0; // LEDs on
prefs->powersaving_enabled = 0;
prefs->gps_enabled = 0;
prefs->gps_interval = 300; // 5 minutes
+36
View File
@@ -0,0 +1,36 @@
/*
* ZephCore - LED master gate
* Copyright (c) 2025 ZephCore
* SPDX-License-Identifier: MIT
*/
#include "led_gate.h"
#include <zephyr/kernel.h>
#include <zephyr/sys/atomic.h>
/* Atomic because the readers are not all on one thread: the heartbeat work
* handler runs on the system work queue, while the TX LED is driven from the
* dispatcher's transmit path. Writers are the main thread (boot / CLI) and the
* UI task. */
static atomic_t s_leds_disabled;
/*
* Weak: overridden by helpers/ui/ui_common.c in builds that have a UI, so a
* change made from the CLI also extinguishes a lit heartbeat LED immediately
* and syncs the UI's LED page. No-op in headless builds, where ui_common.c
* isn't compiled at all which is exactly why the gate lives here and not
* there.
*/
__weak void zephcore_leds_ui_sync(bool disabled) { ARG_UNUSED(disabled); }
bool zephcore_leds_disabled(void)
{
return atomic_get(&s_leds_disabled) != 0;
}
void zephcore_leds_set_disabled(bool disabled)
{
atomic_set(&s_leds_disabled, disabled ? 1 : 0);
zephcore_leds_ui_sync(disabled);
}
+45
View File
@@ -0,0 +1,45 @@
/*
* ZephCore - LED master gate
* Copyright (c) 2025 ZephCore
* SPDX-License-Identifier: MIT
*
* One process-wide "are LEDs allowed" flag, consulted by every LED driver in
* the firmware:
* - heartbeat / unread-message LEDs (helpers/ui/ui_common.c, UI builds only)
* - LoRa TX activity LED (adapters/board/ZephyrBoard.cpp, every role)
*
* It lives here rather than in ui_common.c because ui_common.c is only
* compiled when a UI is enabled, while a repeater with no display still has a
* blinking lora-tx-led that users want to be able to shut off.
*
* Set from persisted prefs at boot, and live via "set leds on|off" (all roles)
* or the UI LED toggle page (companions with buttons/joystick).
*/
#ifndef ZEPHCORE_LED_GATE_H
#define ZEPHCORE_LED_GATE_H
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* true = every LED stays dark, including message and shutdown flashes. */
bool zephcore_leds_disabled(void);
/* Set the gate. Any LED currently lit is dealt with by zephcore_leds_ui_sync()
* below; the momentary TX LED clears itself at the end of the transmit in
* progress. Safe to call from any role, with or without a UI. */
void zephcore_leds_set_disabled(bool disabled);
/* Called by zephcore_leds_set_disabled() after the flag changes. Weak no-op in
* led_gate.c; helpers/ui/ui_common.c overrides it to stop/restart the heartbeat
* cycle and refresh the UI's LED page. Not meant to be called directly. */
void zephcore_leds_ui_sync(bool disabled);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHCORE_LED_GATE_H */
+23 -8
View File
@@ -19,6 +19,7 @@
#include <ZephyrSensorManager.h> /* gps_power_off_for_shutdown */
#include "ui_mesh_actions.h" /* mesh_disable_power_regulators (weak) */
#include "led_gate.h" /* shared with the LoRa TX LED */
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
@@ -87,7 +88,6 @@ static const struct gpio_dt_spec s_msg_led =
#if HAS_HEARTBEAT_LED
static struct k_work_delayable s_led_on_work;
static struct k_work_delayable s_led_off_work;
static bool s_leds_disabled;
/*
* Weak: returns current unread message count for pulse-width adaptation.
@@ -114,7 +114,7 @@ static void led_on_work_handler(struct k_work *work)
uint16_t mc = ui_led_get_msg_count();
uint16_t on_ms = (mc > 0) ? LED_ON_MSG_MS : LED_ON_MS;
if (!s_leds_disabled) {
if (!zephcore_leds_disabled()) {
gpio_pin_set_dt(&s_heartbeat_led, 1);
#if HAS_MSG_LED
if (mc > 0) {
@@ -155,7 +155,7 @@ void ui_led_heartbeat_init(void)
void ui_set_heartbeat_led(bool enabled)
{
#if HAS_HEARTBEAT_LED
if (enabled && !s_leds_disabled) {
if (enabled && !zephcore_leds_disabled()) {
if (gpio_is_ready_dt(&s_heartbeat_led)) {
k_work_reschedule(&s_led_on_work, K_NO_WAIT);
}
@@ -172,10 +172,15 @@ void ui_set_heartbeat_led(bool enabled)
#endif
}
void ui_set_leds_disabled(bool disabled)
/*
* Strong override of the weak hook in led_gate.c: react to a gate change from
* anywhere (UI toggle, "set leds", boot). Stops or restarts the heartbeat cycle
* and refreshes the UI's LED page. The gate flag itself is already set by the
* time we get here do NOT call back into ui_set_leds_disabled() from here.
*/
void zephcore_leds_ui_sync(bool disabled)
{
#if HAS_HEARTBEAT_LED
s_leds_disabled = disabled;
if (disabled) {
k_work_cancel_delayable(&s_led_on_work);
k_work_cancel_delayable(&s_led_off_work);
@@ -196,6 +201,14 @@ void ui_set_leds_disabled(bool disabled)
ui_led_on_disabled_changed(disabled);
}
/* UI-facing spelling of the same thing. Kept because the UI toggle pages and
* the companion boot path call it by this name; the gate is what actually
* governs every LED. */
void ui_set_leds_disabled(bool disabled)
{
zephcore_leds_set_disabled(disabled);
}
/* Flash the heartbeat LED immediately on message receipt.
* Cancels the current cycle, pulses at LED_ON_MSG_MS width, then the
* work chain resumes the normal heartbeat automatically.
@@ -203,7 +216,7 @@ void ui_set_leds_disabled(bool disabled)
void ui_led_flash_msg(void)
{
#if HAS_HEARTBEAT_LED
if (!s_leds_disabled && gpio_is_ready_dt(&s_heartbeat_led)) {
if (!zephcore_leds_disabled() && gpio_is_ready_dt(&s_heartbeat_led)) {
k_work_cancel_delayable(&s_led_on_work);
k_work_cancel_delayable(&s_led_off_work);
gpio_pin_set_dt(&s_heartbeat_led, 1);
@@ -213,11 +226,13 @@ void ui_led_flash_msg(void)
}
/* Flash the heartbeat LED 3 times on shutdown.
* Used as a visual power-off indicator when the buzzer is muted. */
* Used as a visual power-off indicator when the buzzer is muted.
* Suppressed by "set leds off" a node the user asked to keep dark stays dark
* even at power-off. */
void ui_led_flash_shutdown(void)
{
#if HAS_HEARTBEAT_LED
if (gpio_is_ready_dt(&s_heartbeat_led)) {
if (!zephcore_leds_disabled() && gpio_is_ready_dt(&s_heartbeat_led)) {
for (int i = 0; i < 3; i++) {
gpio_pin_set_dt(&s_heartbeat_led, 1);
k_sleep(K_MSEC(100));
+4 -1
View File
@@ -19,6 +19,7 @@
#include <zephyr/kernel.h>
#include "ui_task.h"
#include "led_gate.h"
#define WEAK __attribute__((weak))
@@ -119,9 +120,11 @@ WEAK void ui_set_buzzer_quiet(bool quiet)
ARG_UNUSED(quiet);
}
/* Not a no-op: a headless build still has the LoRa TX LED, and the gate that
* governs it lives outside the UI layer precisely so this case works. */
WEAK void ui_set_leds_disabled(bool disabled)
{
ARG_UNUSED(disabled);
zephcore_leds_set_disabled(disabled);
}
WEAK void ui_set_heartbeat_led(bool enabled)
+5 -2
View File
@@ -30,6 +30,7 @@ LOG_MODULE_REGISTER(zephcore_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#include "ui_task.h"
#include "ui_mesh_actions.h"
#include "oled_power.h"
#include "led_gate.h"
#if IS_ENABLED(CONFIG_ZEPHCORE_UI_BUZZER)
#include "buzzer.h"
#endif
@@ -1518,9 +1519,11 @@ int main(void)
#endif
/* Restore LED enabled/disabled state from persisted prefs.
* If LEDs were disabled, stop the heartbeat LED cycle. */
* If LEDs were disabled, stop the heartbeat LED cycle. Straight to the gate
* rather than via ui_set_leds_disabled(): the gate also governs the LoRa TX
* LED and is linked into every build, UI or not. */
bool leds_off = companion_mesh.prefs.leds_disabled != 0;
ui_set_leds_disabled(leds_off);
zephcore_leds_set_disabled(leds_off);
ui_set_heartbeat_led(!leds_off);
LOG_INF("LEDs: %s (from prefs)", leds_off ? "disabled" : "enabled");
+21
View File
@@ -22,6 +22,7 @@ LOG_MODULE_REGISTER(zephcore_repeater_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#include <zephyr/drivers/hwinfo.h>
#include <zephyr/sys/reboot.h>
#include "oled_power.h"
#include "led_gate.h"
/* BLE controller assert handler — BT is compiled even for repeater (via zephcore_common.conf) */
#if IS_ENABLED(CONFIG_BT_CTLR_ASSERT_HANDLER)
@@ -438,6 +439,17 @@ static mesh::SimpleMeshTables mesh_tables;
/* RepeaterMesh requires: board, radio, ms_clock, rng, rtc, tables */
static RepeaterMesh repeater_mesh(zephyr_board, lora_radio, ms_clock, zephyr_rng, rtc_clock, mesh_tables);
/* Strong override of the weak stub in ui_mesh_actions_stubs.c: keep the prefs
* copy in step when the LEDs page toggles, so "get leds" reports what the node
* is actually doing. RAM only a repeater has no deferred-save path off the UI
* thread, so a UI toggle lasts until reboot; "set leds" is what persists. */
extern "C" void mesh_set_leds_disabled(bool disabled)
{
if (repeater_mesh_ptr) {
repeater_mesh_ptr->getNodePrefs()->leds_disabled = disabled ? 1 : 0;
}
}
static void refresh_repeater_ui_radio_state(void)
{
if (!repeater_mesh_ptr) {
@@ -659,6 +671,15 @@ int main(void)
oled_sleep();
#endif
/* Apply the persisted LED master switch ("set leds on|off"). After ui_init()
* so the heartbeat cycle exists to be stopped; before the radio starts so the
* first transmit already honours it. */
{
bool leds_off = repeater_mesh.getNodePrefs()->leds_disabled != 0;
zephcore_leds_set_disabled(leds_off);
LOG_INF("LEDs: %s (from prefs)", leds_off ? "disabled" : "enabled");
}
/* Log environment sensor availability */
if (env_sensors_available()) {
LOG_INF("Environment sensors available");
+21
View File
@@ -22,6 +22,7 @@ LOG_MODULE_REGISTER(zephcore_room_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#include <zephyr/drivers/hwinfo.h>
#include <zephyr/sys/reboot.h>
#include "oled_power.h"
#include "led_gate.h"
/* BLE controller assert handler — BT is compiled even for repeater (via zephcore_common.conf) */
#if IS_ENABLED(CONFIG_BT_CTLR_ASSERT_HANDLER)
@@ -373,6 +374,17 @@ static mesh::SimpleMeshTables mesh_tables;
static RoomServerMesh room_mesh(zephyr_board, lora_radio, ms_clock, zephyr_rng, rtc_clock, mesh_tables);
#endif
/* Strong override of the weak stub in ui_mesh_actions_stubs.c: keep the prefs
* copy in step when the LEDs page toggles, so "get leds" reports what the node
* is actually doing. RAM only there is no deferred-save path off the UI
* thread, so a UI toggle lasts until reboot; "set leds" is what persists. */
extern "C" void mesh_set_leds_disabled(bool disabled)
{
if (room_mesh_ptr) {
room_mesh_ptr->getNodePrefs()->leds_disabled = disabled ? 1 : 0;
}
}
/* Repeater event loop */
static void room_event_loop(void)
{
@@ -548,6 +560,15 @@ int main(void)
oled_sleep();
#endif
/* Apply the persisted LED master switch ("set leds on|off"). After ui_init()
* so the heartbeat cycle exists to be stopped; before the radio starts so the
* first transmit already honours it. */
{
bool leds_off = room_mesh.getNodePrefs()->leds_disabled != 0;
zephcore_leds_set_disabled(leds_off);
LOG_INF("LEDs: %s (from prefs)", leds_off ? "disabled" : "enabled");
}
/* Log environment sensor availability */
if (env_sensors_available()) {
LOG_INF("Environment sensors available");