Edit esp-bedside-panel.yaml

This commit is contained in:
ESPHome Device Builder
2026-07-16 21:00:16 +12:00
parent 95835550de
commit 5f540de6af
+227 -192
View File
@@ -84,12 +84,12 @@ substitutions:
# Device Naming # Device Naming
device_name: "esp-bedside-panel" device_name: "esp-bedside-panel"
friendly_name: "ESP Bedside Panel" friendly_name: "ESP Bedside Panel"
description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside control panel. Direct Quiet / Boost / Normal selection, Nano-X and aligned Climate controls. (Layout V1.1)" description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside control panel. Climate layout with guarded LVGL startup and additional memory headroom. (Layout V1.1)"
device_area: "Bedroom" device_area: "Bedroom"
# Project Naming # Project Naming
project_name: "Guition.JC1060P470C_I_W_Y" project_name: "Guition.JC1060P470C_I_W_Y"
project_version: "v0.8q" project_version: "v0.8r"
# Passwords & Secrets # Passwords & Secrets
api_key: !secret esp-api_key api_key: !secret esp-api_key
@@ -205,6 +205,18 @@ esphome:
name: "${project_name}" name: "${project_name}"
version: "${project_version}" version: "${project_version}"
# Do not update LVGL widgets until page construction has completed.
on_boot:
priority: -100
then:
- delay: 2s
- lambda: |-
id(lvgl_ready) = true;
- script.execute: refresh_api_indicator
- script.execute: refresh_button_states
#:########################################################################################:# #:########################################################################################:#
# ESP PLATFORM AND FRAMEWORK: # ESP PLATFORM AND FRAMEWORK:
# https://esphome.io/components/esp32.html # https://esphome.io/components/esp32.html
@@ -278,9 +290,7 @@ api:
level: INFO level: INFO
format: "API connected: ESP Bedside Panel is online" format: "API connected: ESP Bedside Panel is online"
- lvgl.widget.update: - script.execute: refresh_api_indicator
id: bedroom_connection_indicator
bg_color: 0x65D483
# HA may deliver imported entity states before all LVGL widgets are ready. # HA may deliver imported entity states before all LVGL widgets are ready.
# Run a delayed two-pass refresh so startup colours always match HA. # Run a delayed two-pass refresh so startup colours always match HA.
@@ -295,18 +305,7 @@ api:
# Allow another API client to remain connected before showing offline. # Allow another API client to remain connected before showing offline.
- delay: 500ms - delay: 500ms
- if: - script.execute: refresh_api_indicator
condition:
api.connected:
then:
- lvgl.widget.update:
id: bedroom_connection_indicator
bg_color: 0x65D483
else:
- lvgl.widget.update:
id: bedroom_connection_indicator
bg_color: 0xF06A6A
#:########################################################################################:# #:########################################################################################:#
# OTA: # OTA:
@@ -748,6 +747,12 @@ binary_sensor:
# Local optimistic Heat Pump profile state # Local optimistic Heat Pump profile state
#:########################################################################################:# #:########################################################################################:#
globals: globals:
# Prevent state callbacks from touching widgets while LVGL creates pages.
- id: lvgl_ready
type: bool
restore_value: false
initial_value: "false"
# 0 = Normal, 1 = Quiet, 2 = Boost. # 0 = Normal, 1 = Quiet, 2 = Boost.
- id: climate_profile_optimistic - id: climate_profile_optimistic
type: int type: int
@@ -759,6 +764,30 @@ globals:
# Refresh all state-dependent LVGL button colours # Refresh all state-dependent LVGL button colours
#:########################################################################################:# #:########################################################################################:#
script: script:
# Update the API status indicator only after LVGL has finished constructing.
- id: refresh_api_indicator
mode: restart
then:
- if:
condition:
lambda: |-
return id(lvgl_ready);
then:
- if:
condition:
api.connected:
then:
- lvgl.widget.update:
id: bedroom_connection_indicator
bg_color: 0x65D483
else:
- lvgl.widget.update:
id: bedroom_connection_indicator
bg_color: 0xF06A6A
# Immediate refresh using the latest imported Home Assistant states. # Immediate refresh using the latest imported Home Assistant states.
- id: refresh_button_states - id: refresh_button_states
mode: restart mode: restart
@@ -863,196 +892,202 @@ script:
- id: refresh_climate_controls - id: refresh_climate_controls
mode: restart mode: restart
then: then:
# Heat-pump mode buttons. The requested mode wins while a change is - if:
# pending; when it is "none", the actual climate state is used. condition:
- lvgl.widget.update: lambda: |-
id: climate_off_button return id(lvgl_ready);
bg_color: !lambda |-
const auto requested = id(ha_climate_requested_mode).state;
const auto current = id(ha_climate_current_mode).state;
const bool requested_none = ( then:
requested.empty() || # Heat-pump mode buttons. The requested mode wins while a change is
requested == "none" || # pending; when it is "none", the actual climate state is used.
requested == "None" || - lvgl.widget.update:
requested == "unknown" || id: climate_off_button
requested == "unavailable" bg_color: !lambda |-
); const auto requested = id(ha_climate_requested_mode).state;
const auto current = id(ha_climate_current_mode).state;
const bool active = ( const bool requested_none = (
requested == "off" || requested.empty() ||
requested == "Off" || requested == "none" ||
(requested_none && current == "off") requested == "None" ||
); requested == "unknown" ||
requested == "unavailable"
);
return active const bool active = (
? lv_color_hex(0x548C6C) requested == "off" ||
: lv_color_hex(0x28313D); requested == "Off" ||
(requested_none && current == "off")
);
- lvgl.widget.update: return active
id: climate_heat_button ? lv_color_hex(0x548C6C)
bg_color: !lambda |- : lv_color_hex(0x28313D);
const auto requested = id(ha_climate_requested_mode).state;
const auto current = id(ha_climate_current_mode).state;
const bool requested_none = ( - lvgl.widget.update:
requested.empty() || id: climate_heat_button
requested == "none" || bg_color: !lambda |-
requested == "None" || const auto requested = id(ha_climate_requested_mode).state;
requested == "unknown" || const auto current = id(ha_climate_current_mode).state;
requested == "unavailable"
);
const bool active = ( const bool requested_none = (
requested == "heat" || requested.empty() ||
requested == "Heat" || requested == "none" ||
(requested_none && current == "heat") requested == "None" ||
); requested == "unknown" ||
requested == "unavailable"
);
return active const bool active = (
? lv_color_hex(0xB76822) requested == "heat" ||
: lv_color_hex(0x28313D); requested == "Heat" ||
(requested_none && current == "heat")
);
- lvgl.widget.update: return active
id: climate_cool_button ? lv_color_hex(0xB76822)
bg_color: !lambda |- : lv_color_hex(0x28313D);
const auto requested = id(ha_climate_requested_mode).state;
const auto current = id(ha_climate_current_mode).state;
const bool requested_none = ( - lvgl.widget.update:
requested.empty() || id: climate_cool_button
requested == "none" || bg_color: !lambda |-
requested == "None" || const auto requested = id(ha_climate_requested_mode).state;
requested == "unknown" || const auto current = id(ha_climate_current_mode).state;
requested == "unavailable"
);
const bool active = ( const bool requested_none = (
requested == "cool" || requested.empty() ||
requested == "Cool" || requested == "none" ||
(requested_none && current == "cool") requested == "None" ||
); requested == "unknown" ||
requested == "unavailable"
);
return active const bool active = (
? lv_color_hex(0x2E6A96) requested == "cool" ||
: lv_color_hex(0x28313D); requested == "Cool" ||
(requested_none && current == "cool")
);
- lvgl.widget.update: return active
id: climate_dry_button ? lv_color_hex(0x2E6A96)
bg_color: !lambda |- : lv_color_hex(0x28313D);
const auto requested = id(ha_climate_requested_mode).state;
const auto current = id(ha_climate_current_mode).state;
const bool requested_none = ( - lvgl.widget.update:
requested.empty() || id: climate_dry_button
requested == "none" || bg_color: !lambda |-
requested == "None" || const auto requested = id(ha_climate_requested_mode).state;
requested == "unknown" || const auto current = id(ha_climate_current_mode).state;
requested == "unavailable"
);
const bool active = ( const bool requested_none = (
requested == "dry" || requested.empty() ||
requested == "Dry" || requested == "none" ||
requested == "dehumidify" || requested == "None" ||
requested == "Dehumidify" || requested == "unknown" ||
( requested == "unavailable"
requested_none && );
(
current == "dry" || const bool active = (
current == "dehumidify" requested == "dry" ||
requested == "Dry" ||
requested == "dehumidify" ||
requested == "Dehumidify" ||
(
requested_none &&
(
current == "dry" ||
current == "dehumidify"
)
)
);
return active
? lv_color_hex(0x32677F)
: lv_color_hex(0x28313D);
# Quiet / Boost are mutually exclusive. Normal means neither is lit.
- lvgl.widget.update:
id: climate_quiet_button
bg_color: !lambda |-
return id(climate_profile_optimistic) == 1
? lv_color_hex(0x315843)
: lv_color_hex(0x28313D);
- lvgl.widget.update:
id: climate_boost_button
bg_color: !lambda |-
return id(climate_profile_optimistic) == 2
? lv_color_hex(0x6B4A86)
: lv_color_hex(0x28313D);
- lvgl.widget.update:
id: climate_nanoe_button
bg_color: !lambda |-
return id(ha_nanoe).state
? lv_color_hex(0x315E82)
: lv_color_hex(0x28313D);
# Heat-pump target setpoint.
- lvgl.label.update:
id: climate_setpoint_value_label
text: !lambda |-
const float value = id(ha_climate_setpoint).state;
if (isnan(value)) {
return std::string("--.-°C");
}
char buffer[16];
snprintf(buffer, sizeof(buffer), "%.1f°C", value);
return std::string(buffer);
# One-button ceiling-fan cycle: Off -> 1 -> 2 -> 3 -> Off.
- lvgl.label.update:
id: ceiling_fan_cycle_label
text: !lambda |-
if (!id(ha_ceiling_fan_on).state) {
return std::string("Ceiling Fan: Off");
}
const float percentage = id(ha_ceiling_fan_percentage).state;
if (isnan(percentage) || percentage < 50.0f) {
return std::string("Ceiling Fan: 1");
}
if (percentage < 85.0f) {
return std::string("Ceiling Fan: 2");
}
return std::string("Ceiling Fan: 3");
- lvgl.widget.update:
id: ceiling_fan_cycle_button
bg_color: !lambda |-
return id(ha_ceiling_fan_on).state
? lv_color_hex(0x315E82)
: lv_color_hex(0x28313D);
# Air Filter Quiet is highlighted for filter levels 0, 1 or 2.
- lvgl.widget.update:
id: air_filter_quiet_button
bg_color: !lambda |-
const float level = id(ha_air_filter_level).state;
return (
!isnan(level) &&
level >= 0.0f &&
level <= 2.0f
) )
) ? lv_color_hex(0x315843)
); : lv_color_hex(0x28313D);
return active # Climate 9pm-12 follows the Night Power helper state.
? lv_color_hex(0x32677F) - lvgl.widget.update:
: lv_color_hex(0x28313D); id: climate_night_power_button
bg_color: !lambda |-
# Quiet / Boost are mutually exclusive. Normal means neither is lit. return id(ha_climate_night_power).state
- lvgl.widget.update: ? lv_color_hex(0x5A4778)
id: climate_quiet_button : lv_color_hex(0x28313D);
bg_color: !lambda |-
return id(climate_profile_optimistic) == 1
? lv_color_hex(0x315843)
: lv_color_hex(0x28313D);
- lvgl.widget.update:
id: climate_boost_button
bg_color: !lambda |-
return id(climate_profile_optimistic) == 2
? lv_color_hex(0x6B4A86)
: lv_color_hex(0x28313D);
- lvgl.widget.update:
id: climate_nanoe_button
bg_color: !lambda |-
return id(ha_nanoe).state
? lv_color_hex(0x315E82)
: lv_color_hex(0x28313D);
# Heat-pump target setpoint.
- lvgl.label.update:
id: climate_setpoint_value_label
text: !lambda |-
const float value = id(ha_climate_setpoint).state;
if (isnan(value)) {
return std::string("--.-°C");
}
char buffer[16];
snprintf(buffer, sizeof(buffer), "%.1f°C", value);
return std::string(buffer);
# One-button ceiling-fan cycle: Off -> 1 -> 2 -> 3 -> Off.
- lvgl.label.update:
id: ceiling_fan_cycle_label
text: !lambda |-
if (!id(ha_ceiling_fan_on).state) {
return std::string("Ceiling Fan: Off");
}
const float percentage = id(ha_ceiling_fan_percentage).state;
if (isnan(percentage) || percentage < 50.0f) {
return std::string("Ceiling Fan: 1");
}
if (percentage < 85.0f) {
return std::string("Ceiling Fan: 2");
}
return std::string("Ceiling Fan: 3");
- lvgl.widget.update:
id: ceiling_fan_cycle_button
bg_color: !lambda |-
return id(ha_ceiling_fan_on).state
? lv_color_hex(0x315E82)
: lv_color_hex(0x28313D);
# Air Filter Quiet is highlighted for filter levels 0, 1 or 2.
- lvgl.widget.update:
id: air_filter_quiet_button
bg_color: !lambda |-
const float level = id(ha_air_filter_level).state;
return (
!isnan(level) &&
level >= 0.0f &&
level <= 2.0f
)
? lv_color_hex(0x315843)
: lv_color_hex(0x28313D);
# Climate 9pm-12 follows the Night Power helper state.
- lvgl.widget.update:
id: climate_night_power_button
bg_color: !lambda |-
return id(ha_climate_night_power).state
? lv_color_hex(0x5A4778)
: lv_color_hex(0x28313D);
# Two passes allow time for LVGL creation and Home Assistant state delivery. # Two passes allow time for LVGL creation and Home Assistant state delivery.
- id: startup_button_state_sync - id: startup_button_state_sync
@@ -1184,7 +1219,7 @@ lvgl:
long_press_repeat_time: 500ms long_press_repeat_time: 500ms
rotation: 0 rotation: 0
buffer_size: 8% buffer_size: 6%
log_level: WARN log_level: WARN
default_font: font_button default_font: font_button
page_wrap: false page_wrap: false