feat: add LED indicators and headless mode support for V3/V4
- Detect missing OLED at boot, set headless_mode flag - LED solid: normal operation (radio online) - LED fast blink: button held >5s (entering WCC config mode) - LED slow breathe: WiFi Captive Configure portal active - Allow 1-3s button press in WCC mode to power off - Next boot after WCC power-off skips config portal (unless unconfigured) - LED indicators active on both V3 and V4, with or without display - Clean up LED PWM on deep sleep
This commit is contained in:
@@ -557,6 +557,10 @@ void config_portal_start() {
|
||||
display.display();
|
||||
}
|
||||
#endif
|
||||
// Headless: LED ramp will be driven from the WCC portal loop
|
||||
if (headless_mode) {
|
||||
Serial.println("[Config] Headless mode — LED will breathe during config portal");
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Stop Config Portal ──────────────────────────────────────────────────────
|
||||
|
||||
1
Config.h
1
Config.h
@@ -145,6 +145,7 @@
|
||||
bool hw_ready = false;
|
||||
bool radio_error = false;
|
||||
bool disp_ready = false;
|
||||
bool headless_mode = false;
|
||||
bool pmu_ready = false;
|
||||
bool promisc = false;
|
||||
bool implicit = false;
|
||||
|
||||
3
Input.h
3
Input.h
@@ -96,6 +96,9 @@
|
||||
display.display();
|
||||
}
|
||||
#endif
|
||||
headless_led_fast_blink();
|
||||
} else if (display_lock_white) {
|
||||
headless_led_fast_blink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +243,9 @@ TcpInterface* local_tcp_interface_ptr = nullptr;
|
||||
// RTC memory flag — survives software reset but not power cycle
|
||||
RTC_NOINIT_ATTR uint32_t boundary_config_request;
|
||||
#define BOUNDARY_CONFIG_MAGIC 0xC0F19A7E
|
||||
// RTC flag to skip config portal on next boot (set when user powers off from WCC)
|
||||
RTC_NOINIT_ATTR uint32_t boundary_skip_config;
|
||||
#define BOUNDARY_SKIP_MAGIC 0x5E1FC0F0
|
||||
|
||||
// Bootloop detection: count rapid reboots in RTC memory.
|
||||
// After BOOTLOOP_THRESHOLD consecutive reboots within BOOTLOOP_WINDOW_MS,
|
||||
@@ -473,7 +476,17 @@ void setup() {
|
||||
|
||||
display_unblank();
|
||||
disp_ready = display_init();
|
||||
if (disp_ready) {
|
||||
update_display();
|
||||
} else {
|
||||
headless_mode = true;
|
||||
Serial.println("[Headless] No display detected — running in headless mode");
|
||||
}
|
||||
#endif
|
||||
|
||||
// LED solid on at boot for V3/V4 boards (with or without display)
|
||||
#if BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_HELTEC32_V3
|
||||
headless_led_solid();
|
||||
#endif
|
||||
|
||||
// ── Boundary Mode: check if config portal is needed ──
|
||||
@@ -514,7 +527,16 @@ void setup() {
|
||||
// OR bootloop detected
|
||||
bool need_config = boundary_needs_config();
|
||||
bool config_requested = (boundary_config_request == BOUNDARY_CONFIG_MAGIC);
|
||||
bool skip_config = (boundary_skip_config == BOUNDARY_SKIP_MAGIC);
|
||||
boundary_config_request = 0; // Clear flag immediately
|
||||
boundary_skip_config = 0; // Clear skip flag immediately
|
||||
|
||||
// Skip flag only suppresses a button-triggered re-entry, not a genuinely
|
||||
// unconfigured device. If there's no config saved, always show the portal.
|
||||
if (skip_config && config_requested) {
|
||||
Serial.println("[Boundary] Skipping config portal — user requested normal boot");
|
||||
config_requested = false;
|
||||
}
|
||||
|
||||
if (need_config || config_requested || bootloop_detected) {
|
||||
if (bootloop_detected) {
|
||||
@@ -526,8 +548,39 @@ void setup() {
|
||||
}
|
||||
config_portal_start();
|
||||
// Block here: only run the config portal until user saves and device reboots
|
||||
// Track button state for "off" action (1-3s press = sleep)
|
||||
bool wcc_btn_down = false;
|
||||
uint32_t wcc_btn_down_at = 0;
|
||||
while (config_portal_is_active()) {
|
||||
config_portal_loop();
|
||||
|
||||
// Headless LED: slow ramp breathe effect during WCC mode
|
||||
headless_led_ramp();
|
||||
|
||||
// Button handling: allow 1-3s press to turn off (deep sleep)
|
||||
// Next power-on boots to normal mode since boundary_config_request is cleared
|
||||
#if HAS_INPUT
|
||||
{
|
||||
int btn = digitalRead(pin_btn_usr1);
|
||||
if (btn == LOW && !wcc_btn_down) {
|
||||
wcc_btn_down = true;
|
||||
wcc_btn_down_at = millis();
|
||||
} else if (btn == HIGH && wcc_btn_down) {
|
||||
uint32_t held = millis() - wcc_btn_down_at;
|
||||
wcc_btn_down = false;
|
||||
if (held >= 700 && held <= 5000) {
|
||||
Serial.println("[Boundary] Button press in WCC mode — powering off");
|
||||
boundary_skip_config = BOUNDARY_SKIP_MAGIC; // Skip config on next boot
|
||||
headless_led_off();
|
||||
config_portal_stop();
|
||||
#if HAS_SLEEP
|
||||
sleep_now();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MCU_VARIANT == MCU_ESP32
|
||||
esp_task_wdt_reset();
|
||||
#endif
|
||||
@@ -2511,6 +2564,13 @@ void loop() {
|
||||
if (disp_ready && !display_updating) update_display();
|
||||
#endif
|
||||
|
||||
// LED solid when operational on V3/V4 boards (yield to fast blink during white screen)
|
||||
#if BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_HELTEC32_V3
|
||||
if (radio_online && !display_lock_white) {
|
||||
headless_led_solid();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_PMU
|
||||
if (pmu_ready) update_pmu();
|
||||
#endif
|
||||
@@ -2558,6 +2618,8 @@ void sleep_now() {
|
||||
#endif
|
||||
#endif
|
||||
#if BOARD_MODEL == BOARD_HELTEC32_V4
|
||||
headless_led_off();
|
||||
headless_led_detach_pwm();
|
||||
digitalWrite(LORA_PA_CPS, LOW);
|
||||
digitalWrite(LORA_PA_CSD, LOW);
|
||||
digitalWrite(LORA_PA_PWR_EN, LOW);
|
||||
|
||||
77
Utilities.h
77
Utilities.h
@@ -72,6 +72,10 @@ uint8_t eeprom_read(uint32_t mapped_addr);
|
||||
#endif
|
||||
|
||||
#if HAS_INPUT == true
|
||||
// Forward declarations for headless LED functions (defined later in this file)
|
||||
void headless_led_fast_blink();
|
||||
void headless_led_ramp();
|
||||
void headless_led_off();
|
||||
#include "Input.h"
|
||||
#endif
|
||||
|
||||
@@ -382,6 +386,79 @@ extern RNS::Reticulum reticulum;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ── Headless LED indicators (for Heltec V4 without OLED) ─────────────────
|
||||
// Uses LEDC PWM for smooth ramp effects on pin_led_tx (GPIO 35)
|
||||
#if BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_HELTEC32_V3
|
||||
#define HEADLESS_LED_CHANNEL 0
|
||||
bool headless_led_pwm_attached = false;
|
||||
|
||||
void headless_led_ensure_pwm() {
|
||||
if (!headless_led_pwm_attached) {
|
||||
ledcSetup(HEADLESS_LED_CHANNEL, 5000, 8); // channel 0, 5kHz, 8-bit
|
||||
ledcAttachPin(pin_led_tx, HEADLESS_LED_CHANNEL);
|
||||
headless_led_pwm_attached = true;
|
||||
}
|
||||
}
|
||||
|
||||
void headless_led_detach_pwm() {
|
||||
if (headless_led_pwm_attached) {
|
||||
ledcDetachPin(pin_led_tx);
|
||||
headless_led_pwm_attached = false;
|
||||
pinMode(pin_led_tx, OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
// Solid ON — normal headless operation
|
||||
void headless_led_solid() {
|
||||
headless_led_ensure_pwm();
|
||||
ledcWrite(HEADLESS_LED_CHANNEL, 255);
|
||||
}
|
||||
|
||||
// Fast blink — replaces "white screen" indicator (non-blocking, call from loop)
|
||||
void headless_led_fast_blink() {
|
||||
headless_led_ensure_pwm();
|
||||
static uint32_t last_toggle = 0;
|
||||
static bool on = false;
|
||||
uint32_t now = millis();
|
||||
if (now - last_toggle >= 100) { // 5Hz blink
|
||||
last_toggle = now;
|
||||
on = !on;
|
||||
ledcWrite(HEADLESS_LED_CHANNEL, on ? 255 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Slow ramp in/out — breathe effect for WiFi Captive Configure mode
|
||||
void headless_led_ramp() {
|
||||
headless_led_ensure_pwm();
|
||||
static uint32_t last_step = 0;
|
||||
static uint8_t brightness = 0;
|
||||
static int8_t direction = 1;
|
||||
uint32_t now = millis();
|
||||
if (now - last_step >= 10) { // ~100 steps/sec, full cycle ~5 seconds
|
||||
last_step = now;
|
||||
brightness += direction;
|
||||
if (brightness >= 255) { brightness = 255; direction = -1; }
|
||||
if (brightness == 0) { direction = 1; }
|
||||
ledcWrite(HEADLESS_LED_CHANNEL, brightness);
|
||||
}
|
||||
}
|
||||
|
||||
void headless_led_off() {
|
||||
if (headless_led_pwm_attached) {
|
||||
ledcWrite(HEADLESS_LED_CHANNEL, 0);
|
||||
} else {
|
||||
digitalWrite(pin_led_tx, LOW);
|
||||
}
|
||||
}
|
||||
#else
|
||||
void headless_led_ensure_pwm() {}
|
||||
void headless_led_detach_pwm() {}
|
||||
void headless_led_solid() {}
|
||||
void headless_led_fast_blink() {}
|
||||
void headless_led_ramp() {}
|
||||
void headless_led_off() {}
|
||||
#endif
|
||||
|
||||
void hard_reset(void) {
|
||||
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560
|
||||
wdt_enable(WDTO_15MS);
|
||||
|
||||
Reference in New Issue
Block a user