From 5f2b465bfdeb8505f5b5090eeaf4aa8175d7d6a8 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 23:39:41 +0000 Subject: [PATCH] Add StatusLED class and integrate external status LED - Implement StatusLED class for PWM-driven monochrome LED. - Support customizable breathing patterns with a 4-phase utility function. - Integrate StatusLED into main.cpp to show system status (booting, WiFi, SD, playback). - Add STATUS_LED_PIN=GPIO_NUM_14 to platformio.ini. Co-authored-by: t0mg <1903597+t0mg@users.noreply.github.com> --- platformio.ini | 2 ++ src/StatusLED.cpp | 87 +++++++++++++++++++++++++++++++++++++++++++++++ src/StatusLED.h | 40 ++++++++++++++++++++++ src/main.cpp | 21 ++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 src/StatusLED.cpp create mode 100644 src/StatusLED.h diff --git a/platformio.ini b/platformio.ini index e1b1d43..3e45dc2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -84,3 +84,5 @@ build_flags = -DSYS_OUT=GPIO_NUM_40 ; Battery monitor -DBATTERY_VOLTAGE_PIN=GPIO_NUM_1 + ; Status LED + -DSTATUS_LED_PIN=GPIO_NUM_14 diff --git a/src/StatusLED.cpp b/src/StatusLED.cpp new file mode 100644 index 0000000..03cc0b5 --- /dev/null +++ b/src/StatusLED.cpp @@ -0,0 +1,87 @@ +#include "StatusLED.h" + +#define LEDC_TIMER_8_BIT 8 +#define LEDC_BASE_FREQ 5000 + +StatusLED::StatusLED(int pin, int channel) + : _pin(pin), _channel(channel), _off_ms(0), _fadeIn_ms(0), _on_ms(0), _fadeOut_ms(0), _totalCycle_ms(0), _maxBrightness(255) { +} + +void StatusLED::begin() { + if (_pin < 0) return; + + // Check if the pin is a valid GPIO + if (!digitalPinIsValid(_pin)) return; + + ledcSetup(_channel, LEDC_BASE_FREQ, LEDC_TIMER_8_BIT); + ledcAttachPin(_pin, _channel); + ledcWrite(_channel, 0); +} + +void StatusLED::setPattern(int off_ms, int fadeIn_ms, int on_ms, int fadeOut_ms) { + _off_ms = off_ms; + _fadeIn_ms = fadeIn_ms; + _on_ms = on_ms; + _fadeOut_ms = (fadeOut_ms < 0) ? fadeIn_ms : fadeOut_ms; + _totalCycle_ms = _off_ms + _fadeIn_ms + _on_ms + _fadeOut_ms; + _maxBrightness = 255; + + if (_totalCycle_ms == 0) { + // If all are zero, it's effectively off, but we need a cycle for update() + _totalCycle_ms = 1; + } +} + +void StatusLED::update() { + if (_pin < 0 || _totalCycle_ms <= 0) return; + + unsigned long now = millis(); + int timeInCycle = now % _totalCycle_ms; + int brightness = 0; + + if (timeInCycle < _off_ms) { + // Off phase + brightness = 0; + } else if (timeInCycle < _off_ms + _fadeIn_ms) { + // Fade in phase + int fadeTime = timeInCycle - _off_ms; + brightness = (fadeTime * _maxBrightness) / _fadeIn_ms; + } else if (timeInCycle < _off_ms + _fadeIn_ms + _on_ms) { + // On phase + brightness = _maxBrightness; + } else { + // Fade out phase + int fadeTime = timeInCycle - (_off_ms + _fadeIn_ms + _on_ms); + if (_fadeOut_ms > 0) { + brightness = _maxBrightness - (fadeTime * _maxBrightness) / _fadeOut_ms; + } else { + brightness = 0; + } + } + + ledcWrite(_channel, brightness); +} + +void StatusLED::off() { + setPattern(1000, 0, 0, 0); + ledcWrite(_channel, 0); +} + +void StatusLED::solid(uint8_t brightness) { + // A trick to make it solid: make it all "on" phase + setPattern(0, 0, 1000, 0); + _maxBrightness = brightness; +} + +void StatusLED::breathe(int duration) { + int fade = duration / 2; + setPattern(0, fade, 0, fade); +} + +void StatusLED::blink(int duration) { + setPattern(duration, 0, duration, 0); +} + +void StatusLED::fastBlink(int duration) { + setPattern(duration, 0, duration, 0); +} diff --git a/src/StatusLED.h b/src/StatusLED.h new file mode 100644 index 0000000..6581684 --- /dev/null +++ b/src/StatusLED.h @@ -0,0 +1,40 @@ +#ifndef STATUS_LED_H +#define STATUS_LED_H + +#include + +class StatusLED { +public: + StatusLED(int pin, int channel = 1); + void begin(); + void update(); + + /** + * Set a breathing/blinking pattern. + * @param off_ms Duration the LED is completely off. + * @param fadeIn_ms Duration of the fade-in (from 0 to 255). + * @param on_ms Duration the LED is completely on at full brightness. + * @param fadeOut_ms Duration of the fade-out (from 255 to 0). If -1, defaults to fadeIn_ms. + */ + void setPattern(int off_ms, int fadeIn_ms, int on_ms, int fadeOut_ms = -1); + + void off(); + void solid(uint8_t brightness = 255); + void breathe(int duration = 2000); + void blink(int duration = 500); + void fastBlink(int duration = 100); + +private: + int _pin; + int _channel; + + int _off_ms; + int _fadeIn_ms; + int _on_ms; + int _fadeOut_ms; + + int _totalCycle_ms; + uint8_t _maxBrightness; +}; + +#endif // STATUS_LED_H diff --git a/src/main.cpp b/src/main.cpp index 8cc50a5..d7e5637 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "VideoPlayer/StreamVideoSource.h" #include "VideoPlayer/VideoPlayer.h" #include "WifiManager.h" +#include "StatusLED.h" #include #include #include @@ -33,6 +34,7 @@ Display display(&prefs); Button button(SYS_OUT, SYS_EN); AsyncWebServer server(80); Battery battery(BATTERY_VOLTAGE_PIN, 3.3, 200000.0, 100000.0); +StatusLED statusLED(STATUS_LED_PIN); unsigned long shutdown_time = 0; WifiManager wifiManager(&server, &prefs, &battery); bool wifiManagerActive = false; @@ -61,6 +63,8 @@ void setShutdownTime(int minutes) void setup() { + statusLED.begin(); + statusLED.fastBlink(100); display.fillScreen(TFT_BLACK); Serial.begin(115200); delay(500); // Wait for serial to initialize @@ -95,6 +99,11 @@ void setup() display.flushSprite(); Serial.println("Failed to mount SD Card. Initializing WifiManager."); wifiManager.begin(); + if (wifiManager.isAPMode()) { + statusLED.breathe(2000); + } else { + statusLED.breathe(1000); + } wifiManagerActive = true; Serial.printf("Wifi Connected: %s\n", wifiManager.getIpAddress().toString().c_str()); @@ -120,6 +129,7 @@ void setup() Serial.println("SD Card mounted successfully."); display.drawOSD("SD Card found !", CENTER, STANDARD); display.flushSprite(); + statusLED.blink(500); VideoSource *videoCandidate = new SDCardVideoSource(card, "/"); if (videoCandidate->fetchVideoData()) @@ -201,6 +211,7 @@ unsigned long lastBatteryUpdate = 0; void loop() { + statusLED.update(); delay(5); unsigned long now = millis(); @@ -226,6 +237,16 @@ void loop() lastBatteryUpdate = now; } + static MediaPlayerState lastState = MediaPlayerState::STOPPED; + if (currentPlayer && currentPlayer->getState() != lastState) { + lastState = currentPlayer->getState(); + if (lastState == MediaPlayerState::PLAYING) { + statusLED.breathe(4000); + } else if (lastState == MediaPlayerState::PAUSED) { + statusLED.solid(64); + } + } + button.update(); if (wifiManagerActive) {