Files
esphome-configs/common/status_led.yaml
T
randy 03ca4e98b5 status_led: show component errors only on a dedicated status LED (show_errors)
The ATOM LED gets them; displays and lights that are also normal lights don't,
so a failed component can't take them over.
2026-09-30 10:12:06 +02:00

128 lines
4.2 KiB
YAML

---
# Status on a visible RGB light, which stays a normal light otherwise:
# network down red, slow blink
# no API client (HA) orange, slow blink
# component error purple, steady (only with show_errors: "true", for a dedicated status LED)
# OTA blue, fast blink; green when done, 3 red blinks on error
# back to normal green for 1 s, then the light goes back to how it was
# packages:
# status_led: !include
# file: common/status_led.yaml
# vars:
# light_id: led_light
# Needs the ota id ota_esphome (common/ota_esp32.yaml).
defaults:
brightness: "0.3"
show_errors: "false" # a failed component would take over a display or a shared light for good
globals:
- id: status_led_active
type: bool
initial_value: "false"
- id: status_led_saved
type: light::LightColorValues
- id: status_led_last
type: uint32_t
initial_value: "0xFFFFFFFF"
script:
- id: status_led_show
parameters:
r: float
g: float
b: float
lit: bool
then:
- lambda: |-
auto *l = id(${light_id});
uint32_t key = (lit << 24) | (uint32_t(r * 255) << 16) | (uint32_t(g * 255) << 8) | uint32_t(b * 255);
if (!id(status_led_active)) {
id(status_led_saved) = l->remote_values;
id(status_led_active) = true;
} else if (key == id(status_led_last)) {
return; // unchanged: don't flood Home Assistant with the same state
}
id(status_led_last) = key;
if (lit) {
l->turn_on().set_rgb(r, g, b).set_brightness(${brightness}).set_transition_length(0).set_effect("None").perform();
} else {
l->turn_off().set_transition_length(0).perform();
}
l->loop(); // write now: OTA blocks the main loop
- id: status_led_restore
parameters:
confirm: bool
then:
- lambda: |-
if (id(status_led_active) && confirm) id(status_led_show)->execute(0.0f, 1.0f, 0.0f, true);
- if:
condition:
lambda: 'return id(status_led_active) && confirm;'
then:
- delay: 1s # green: back to normal
- lambda: |-
if (!id(status_led_active)) return;
id(status_led_active) = false;
id(status_led_last) = 0xFFFFFFFF;
id(${light_id})->make_call().from_light_color_values(id(status_led_saved)).set_transition_length(0).perform();
interval:
- interval: 500ms
then:
- lambda: |-
static uint8_t tick = 0;
tick++;
bool slow = (tick / 2) % 2 == 0; // 1 s on, 1 s off
bool error = ${show_errors} && (App.get_app_state() & STATUS_LED_ERROR);
bool problem = !network::is_connected() || !api::global_api_server->is_connected() || error;
if (problem) id(status_led_restore)->stop();
if (!network::is_connected()) {
id(status_led_show)->execute(1.0f, 0.0f, 0.0f, slow);
} else if (!api::global_api_server->is_connected()) {
id(status_led_show)->execute(1.0f, 0.4f, 0.0f, slow);
} else if (error) {
id(status_led_show)->execute(0.6f, 0.0f, 1.0f, true);
} else if (id(status_led_active) && !id(status_led_restore)->is_running()) {
id(status_led_restore)->execute(true);
}
ota:
- id: !extend ota_esphome
on_begin:
- script.execute:
id: status_led_show
r: 0.0
g: 0.0
b: 1.0
lit: true
on_progress:
- lambda: 'id(status_led_show)->execute(0.0f, 0.0f, 1.0f, (millis() / 250) % 2 == 0);'
on_end:
- script.execute:
id: status_led_show
r: 0.0
g: 1.0
b: 0.0
lit: true
on_error:
- repeat:
count: 3
then:
- script.execute:
id: status_led_show
r: 1.0
g: 0.0
b: 0.0
lit: true
- delay: 250ms
- script.execute:
id: status_led_show
r: 1.0
g: 0.0
b: 0.0
lit: false
- delay: 250ms
- script.execute:
id: status_led_restore
confirm: false