fix LED flashing

This commit is contained in:
proddy
2026-08-05 15:43:40 +02:00
parent 3a81c70018
commit 1956ff8386
+314 -298
View File
@@ -16,301 +16,317 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "led.h" #include "led.h"
#include "emsesp.h" #include "emsesp.h"
namespace emsesp { namespace emsesp {
uuid::log::Logger LED::logger_{F_(led), uuid::log::Facility::KERN}; uuid::log::Logger LED::logger_{F_(led), uuid::log::Facility::KERN};
// initialise the LED, fetching the settings from the WebSettingsService // initialise the LED, fetching the settings from the WebSettingsService
// set the LED to on or off when in normal operating mode // set the LED to on or off when in normal operating mode
void LED::init() { void LED::init() {
// copy the application settings // copy the application settings
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings & settings) {
led_gpio_ = settings.led_gpio; led_gpio_ = settings.led_gpio;
led_type_ = settings.led_type; led_type_ = settings.led_type;
hide_led_ = settings.hide_led; hide_led_ = settings.hide_led;
}); });
if (!led_gpio_) { // 0 means disabled if (!led_gpio_) { // 0 means disabled
LOG_INFO("LED disabled"); LOG_INFO("LED disabled");
return; return;
} }
// for safety // for safety
if (!led_type_) { if (!led_type_) {
pinMode(led_gpio_, OUTPUT); pinMode(led_gpio_, OUTPUT);
} }
reset_led(); // start with LED in default state, depending on if it's hidden or not reset_led(); // start with LED in default state, depending on if it's hidden or not
} }
// handle LED routine // handle LED routine
// called from the System::loop() // called from the System::loop()
// returns true if the LED flash is active, i.e its a lock down state // returns true if the LED flash is active, i.e its a lock down state
bool LED::loop(uint8_t healthcheck, bool button_busy) { bool LED::loop(uint8_t healthcheck, bool button_busy) {
// if LED flashing is active it means its about to perform a factory reset, so don't do anything else and keep it flashing // if LED flashing is active it means its about to perform a factory reset, so don't do anything else and keep it flashing
if (led_fast_flash_timer_) { if (led_fast_flash_timer_) {
led_fast_flash(); led_fast_flash();
return true; return true;
} }
// the user-requested LED blink always has preference // the user-requested LED blink always has preference
if (!is_user_led_blink_) { if (!is_user_led_blink_) {
// check for button press. // check for button press.
// if button is pressed, show LED (yellow on RGB LED, on/off on standard LED) // while the button is held show the LED (yellow on RGB LED, on/off on standard LED),
// it will turn off on the next loop cycle // and on release go back to the default state and re-check the health on the next cycle
if (last_button_busy_ != button_busy) { if (last_button_busy_ != button_busy) {
last_button_busy_ = button_busy; last_button_busy_ = button_busy;
set_led(button_busy ? Color::OFF : Color::YELLOW); // Yellow if (button_busy) {
return false; set_led(Color::YELLOW); // Yellow
} } else {
reset_led();
// check the system health. previous_healthcheck_ = System::HEALTHCHECK_RESET;
// Set the sequence accordingly, only if the healthcheck is not 0 and has changed }
if (healthcheck != previous_healthcheck_) { return false;
previous_healthcheck_ = healthcheck; }
color_steps_[0] = Color::OFF;
color_steps_[1] = Color::OFF; // check the system health.
color_steps_[2] = Color::OFF; // Set the sequence accordingly, only if the healthcheck is not 0 and has changed
if (healthcheck != previous_healthcheck_) {
// if the healthcheck is 0, i.e. system is healthy, reset the LED previous_healthcheck_ = healthcheck;
if (healthcheck == 0) { color_steps_[0] = Color::OFF;
reset_led(); color_steps_[1] = Color::OFF;
return false; color_steps_[2] = Color::OFF;
}
// if the healthcheck is 0, i.e. system is healthy, reset the LED
// 1 flash (blue) is the EMS bus is not connected if (healthcheck == 0) {
// 2 flashes (red, red) if the network (wifi or ethernet) is not connected reset_led();
// 3 flashes (red, red, blue) is both the bus and the network are not connected return false;
bool no_network = (healthcheck & System::HEALTHCHECK_NO_NETWORK) == System::HEALTHCHECK_NO_NETWORK; }
bool no_bus = (healthcheck & System::HEALTHCHECK_NO_BUS) == System::HEALTHCHECK_NO_BUS;
// 1 flash (blue) is the EMS bus is not connected
// set step 1 // 2 flashes (red, red) if the network (wifi or ethernet) is not connected
if (no_network) { // 3 flashes (red, red, blue) is both the bus and the network are not connected
color_steps_[0] = Color::RED; // red, no network bool no_network = (healthcheck & System::HEALTHCHECK_NO_NETWORK) == System::HEALTHCHECK_NO_NETWORK;
} else if (no_bus) { bool no_bus = (healthcheck & System::HEALTHCHECK_NO_BUS) == System::HEALTHCHECK_NO_BUS;
color_steps_[0] = Color::BLUE; // blue, no bus
} // set step 1
if (no_network) {
// set step 2 color_steps_[0] = Color::RED; // red, no network
if (no_network) { } else if (no_bus) {
color_steps_[1] = Color::RED; // red, no network color_steps_[0] = Color::BLUE; // blue, no bus
} }
// set step 3 // set step 2
if (no_network && no_bus) { if (no_network) {
color_steps_[2] = Color::BLUE; // blue, no network and no bus color_steps_[1] = Color::RED; // red, no network
} }
}
} // set step 3
if (no_network && no_bus) {
// show the LED status based on the healthcheck and button busy status color_steps_[2] = Color::BLUE; // blue, no network and no bus
sequence_led(); }
}
return false;
} // there's nothing to flash, so leave the LED in the state reset_led() gave it.
// Running the sequence here would keep writing Color::OFF and a healthy system would never show green.
// turn the LED back it's default state depending on if it's hidden or not if (healthcheck == 0 || button_busy) {
void LED::reset_led() { return false;
is_user_led_blink_ = false; }
set_led(hide_led_ ? Color::OFF : Color::GREEN); // Green }
color_steps_[0] = Color::OFF;
color_steps_[1] = Color::OFF; // show the LED status based on the healthcheck and button busy status
color_steps_[2] = Color::OFF; sequence_led();
}
return false;
// LED flash every few ms and then perform a factory reset }
void LED::led_fast_flash() {
uint32_t current_time = uuid::get_uptime(); // turn the LED back it's default state depending on if it's hidden or not
void LED::reset_led() {
if (current_time - last_toggle_time_ >= LED_FLASH_INTERVAL_MS) { is_user_led_blink_ = false;
led_flash_state_ = !led_flash_state_; set_led(hide_led_ ? Color::OFF : Color::GREEN); // Green
last_toggle_time_ = current_time; color_steps_[0] = Color::OFF;
set_led(led_flash_state_ ? Color::YELLOW : Color::OFF); // Yellow color_steps_[1] = Color::OFF;
} color_steps_[2] = Color::OFF;
// after duration, turn off the LED, and call the format command // rewind the sequence so it starts from the top if the health degrades again
if (current_time - led_flash_start_time_ >= led_flash_duration_) { led_long_timer_ = 1; // 1 will kick it off immediately
set_led(Color::OFF); led_short_timer_ = 0;
led_fast_flash_timer_ = false; led_flash_step_ = 0;
#ifndef EMSESP_DEBUG }
System::command_format(nullptr, 0); // Execute format operation, unless in debug mode
#endif // LED flash every few ms and then perform a factory reset
} void LED::led_fast_flash() {
} uint32_t current_time = uuid::get_uptime();
// set LED on/off or RGB color if (current_time - last_toggle_time_ >= LED_FLASH_INTERVAL_MS) {
// ignores whether the LED is hidden or not (if hide_led_ is set) led_flash_state_ = !led_flash_state_;
void LED::set_led(Color color) { last_toggle_time_ = current_time;
if (!led_gpio_) { set_led(led_flash_state_ ? Color::YELLOW : Color::OFF); // Yellow
return; }
}
// after duration, turn off the LED, and call the format command
// RGB lookup table indexed by Color enum (must match enum order in led.h) if (current_time - led_flash_start_time_ >= led_flash_duration_) {
static constexpr uint8_t B = RGB_LED_BRIGHTNESS; set_led(Color::OFF);
static constexpr uint8_t H = RGB_LED_BRIGHTNESS / 2; led_fast_flash_timer_ = false;
static constexpr uint8_t rgb_table[][3] = { #ifndef EMSESP_DEBUG
{0, 0, 0}, // OFF System::command_format(nullptr, 0); // Execute format operation, unless in debug mode
{B, B, B}, // ON (white) #endif
{B, 0, 0}, // RED }
{0, B, 0}, // GREEN }
{0, 0, B}, // BLUE
{B, B, 0}, // YELLOW // set LED on/off or RGB color
{B, H, 0}, // ORANGE // ignores whether the LED is hidden or not (if hide_led_ is set)
{0, B, B}, // CYAN void LED::set_led(Color color) {
{H, 0, H} // PINK if (!led_gpio_) {
}; return;
}
const uint8_t color_idx = static_cast<uint8_t>(color);
const uint8_t idx = (color_idx < sizeof(rgb_table) / sizeof(rgb_table[0])) ? color_idx : static_cast<uint8_t>(Color::OFF); // RGB lookup table indexed by Color enum (must match enum order in led.h)
const uint8_t red = rgb_table[idx][0]; static constexpr uint8_t B = RGB_LED_BRIGHTNESS;
const uint8_t green = rgb_table[idx][1]; static constexpr uint8_t H = RGB_LED_BRIGHTNESS / 2;
const uint8_t blue = rgb_table[idx][2]; static constexpr uint8_t rgb_table[][3] = {
{0, 0, 0}, // OFF
if (led_type_) { {B, B, B}, // ON (white)
rgbLedWrite(led_gpio_, red, green, blue); {B, 0, 0}, // RED
} else { {0, B, 0}, // GREEN
digitalWrite(led_gpio_, (red == 0 && green == 0 && blue == 0) || color == Color::OFF ? !LED_ON : LED_ON); {0, 0, B}, // BLUE
} {B, B, 0}, // YELLOW
} {B, H, 0}, // ORANGE
{0, B, B}, // CYAN
// set LED custom routine {H, 0, H} // PINK
// For example: /api/system/led?data=red:blink1 };
// For older non-RGB models, the colour would default to just being on.
bool LED::set_custom_led_routine(std::string color, std::string pattern) { const uint8_t color_idx = static_cast<uint8_t>(color);
static constexpr struct { const uint8_t idx = (color_idx < sizeof(rgb_table) / sizeof(rgb_table[0])) ? color_idx : static_cast<uint8_t>(Color::OFF);
const char * name; const uint8_t red = rgb_table[idx][0];
Color value; const uint8_t green = rgb_table[idx][1];
} color_map[] = { const uint8_t blue = rgb_table[idx][2];
{"off", Color::OFF},
{"on", Color::ON}, if (led_type_) {
{"white", Color::ON}, rgbLedWrite(led_gpio_, red, green, blue);
{"red", Color::RED}, } else {
{"green", Color::GREEN}, digitalWrite(led_gpio_, (red == 0 && green == 0 && blue == 0) || color == Color::OFF ? !LED_ON : LED_ON);
{"blue", Color::BLUE}, }
{"yellow", Color::YELLOW}, }
{"orange", Color::ORANGE},
{"cyan", Color::CYAN}, // set LED custom routine
{"pink", Color::PINK}, // For example: /api/system/led?data=red:blink1
}; // For older non-RGB models, the colour would default to just being on.
bool LED::set_custom_led_routine(std::string color, std::string pattern) {
Color color_type = Color::OFF; static constexpr struct {
bool color_matched = false; const char * name;
for (const auto & entry : color_map) { Color value;
if (color == entry.name) { } color_map[] = {
color_type = entry.value; {"off", Color::OFF},
color_matched = true; {"on", Color::ON},
break; {"white", Color::ON},
} {"red", Color::RED},
} {"green", Color::GREEN},
if (!color_matched) { {"blue", Color::BLUE},
return false; {"yellow", Color::YELLOW},
} {"orange", Color::ORANGE},
{"cyan", Color::CYAN},
// reset the color steps {"pink", Color::PINK},
color_steps_[0] = Color::OFF; };
color_steps_[1] = Color::OFF;
color_steps_[2] = Color::OFF; Color color_type = Color::OFF;
bool color_matched = false;
// blink patterns for (const auto & entry : color_map) {
if (pattern == "blink1") { if (color == entry.name) {
color_steps_[0] = color_type; color_type = entry.value;
} else if (pattern == "blink2") { color_matched = true;
color_steps_[0] = color_type; break;
color_steps_[1] = color_type; }
} else if (pattern == "blink3") { }
color_steps_[0] = color_type; if (!color_matched) {
color_steps_[1] = color_type; return false;
color_steps_[2] = color_type; }
// special patterns, ignores the user color // reset the color steps
} else if (pattern == "rgb") { color_steps_[0] = Color::OFF;
color_steps_[0] = Color::RED; color_steps_[1] = Color::OFF;
color_steps_[1] = Color::GREEN; color_steps_[2] = Color::OFF;
color_steps_[2] = Color::BLUE;
} else if (pattern == "cpc") { // blink patterns
color_steps_[0] = Color::CYAN; if (pattern == "blink1") {
color_steps_[1] = Color::PINK; color_steps_[0] = color_type;
color_steps_[2] = Color::CYAN; } else if (pattern == "blink2") {
} else { color_steps_[0] = color_type;
return false; // pattern not recognized color_steps_[1] = color_type;
} } else if (pattern == "blink3") {
color_steps_[0] = color_type;
is_user_led_blink_ = true; // user routine is active color_steps_[1] = color_type;
color_steps_[2] = color_type;
// when this is called we want the sequence_led to restart immediately and skip the long pause
led_long_timer_ = uuid::get_uptime() + HEALTHCHECK_LED_FLASH_FAST_DURATION + 200UL; // special patterns, ignores the user color
} else if (pattern == "rgb") {
return true; color_steps_[0] = Color::RED;
} color_steps_[1] = Color::GREEN;
color_steps_[2] = Color::BLUE;
// uses LED to show system health and user-requested LED blinks } else if (pattern == "cpc") {
// it works in a batch of 3 configured flashes, then a long pause color_steps_[0] = Color::CYAN;
// the timing is different for user-requested LED blink and for system healthcheck color_steps_[1] = Color::PINK;
void LED::sequence_led() { color_steps_[2] = Color::CYAN;
// first long pause before we start flashing } else {
auto current_time = uuid::get_uptime(); return false; // pattern not recognized
if (led_long_timer_ }
&& (uint32_t)(current_time - led_long_timer_) >= (is_user_led_blink_ ? HEALTHCHECK_LED_LONG_FAST_DURATION : HEALTHCHECK_LED_LONG_DURATION)) {
led_short_timer_ = current_time; // start the short timer is_user_led_blink_ = true; // user routine is active
led_long_timer_ = 0; // stop long timer
led_flash_step_ = 1; // enable the short flash timer // when this is called we want the sequence_led to restart immediately and skip the long pause
} led_long_timer_ = uuid::get_uptime() + HEALTHCHECK_LED_FLASH_FAST_DURATION + 200UL;
// the flash timer which starts after the long pause return true;
if (led_flash_step_ }
&& (uint32_t)(current_time - led_short_timer_) >= (is_user_led_blink_ ? HEALTHCHECK_LED_FLASH_FAST_DURATION : HEALTHCHECK_LED_FLASH_DURATION)) {
led_long_timer_ = 0; // stop the long timer // uses LED to show system health and user-requested LED blinks
led_short_timer_ = current_time; // it works in a batch of 3 configured flashes, then a long pause
// the timing is different for user-requested LED blink and for system healthcheck
if (++led_flash_step_ == 8) { void LED::sequence_led() {
// finished first iteration, reset the whole sequence, turn off LED // first long pause before we start flashing
led_long_timer_ = uuid::get_uptime(); auto current_time = uuid::get_uptime();
led_flash_step_ = 0; if (led_long_timer_
set_led(Color::OFF); // turn off the LED && (uint32_t)(current_time - led_long_timer_) >= (is_user_led_blink_ ? HEALTHCHECK_LED_LONG_FAST_DURATION : HEALTHCHECK_LED_LONG_DURATION)) {
led_short_timer_ = current_time; // start the short timer
// if we're running a user-requested LED blink, turn it off and go back to the healthcheck sequence led_long_timer_ = 0; // stop long timer
if (is_user_led_blink_) { led_flash_step_ = 1; // enable the short flash timer
is_user_led_blink_ = false; }
previous_healthcheck_ = System::HEALTHCHECK_RESET; // this will force the healthcheck to be checked again
} // the flash timer which starts after the long pause
return; if (led_flash_step_
} && (uint32_t)(current_time - led_short_timer_) >= (is_user_led_blink_ ? HEALTHCHECK_LED_FLASH_FAST_DURATION : HEALTHCHECK_LED_FLASH_DURATION)) {
led_long_timer_ = 0; // stop the long timer
if (led_flash_step_ % 2) { led_short_timer_ = current_time;
// handle the three step events (on odd numbers 3,5,7 etc). see if we need to set a LED color
switch (led_flash_step_) { if (++led_flash_step_ == 8) {
case 3: // first flash // finished first iteration, reset the whole sequence, turn off LED
set_led(color_steps_[0]); led_long_timer_ = uuid::get_uptime();
break; led_flash_step_ = 0;
case 5: // second flash set_led(Color::OFF); // turn off the LED
set_led(color_steps_[1]);
break; // if we're running a user-requested LED blink, turn it off and go back to the healthcheck sequence
case 7: // third flash if (is_user_led_blink_) {
set_led(color_steps_[2]); is_user_led_blink_ = false;
break; previous_healthcheck_ = System::HEALTHCHECK_RESET; // this will force the healthcheck to be checked again
default: }
break; return;
} }
} else {
set_led(Color::OFF); // turn off on even number count, to make it flash if (led_flash_step_ % 2) {
} // handle the three step events (on odd numbers 3,5,7 etc). see if we need to set a LED color
} switch (led_flash_step_) {
} case 3: // first flash
set_led(color_steps_[0]);
// Start the LED flash timer - duration in seconds break;
void LED::start_led_fast_flash(uint8_t duration) { case 5: // second flash
// Don't start if already running set_led(color_steps_[1]);
if (led_fast_flash_timer_) { break;
return; case 7: // third flash
} set_led(color_steps_[2]);
break;
// Reset counter and state default:
led_flash_start_time_ = uuid::get_uptime(); // current time break;
led_flash_duration_ = (uint32_t)duration * 1000; // duration in milliseconds }
led_fast_flash_timer_ = true; // it's active } else {
} set_led(Color::OFF); // turn off on even number count, to make it flash
}
} // namespace emsesp }
}
// Start the LED flash timer - duration in seconds
void LED::start_led_fast_flash(uint8_t duration) {
// Don't start if already running
if (led_fast_flash_timer_) {
return;
}
// Reset counter and state
led_flash_start_time_ = uuid::get_uptime(); // current time
led_flash_duration_ = (uint32_t)duration * 1000; // duration in milliseconds
led_fast_flash_timer_ = true; // it's active
}
} // namespace emsesp