diff --git a/esphome/esp-bedside-panel.yaml b/esphome/esp-bedside-panel.yaml index e62a09c..6e3c5c0 100644 --- a/esphome/esp-bedside-panel.yaml +++ b/esphome/esp-bedside-panel.yaml @@ -6,6 +6,7 @@ # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-bedside-panel.yaml #:########################################################################################:# # VERSIONS: +# V0.8m 2026-07-16 Added Quiet / Powerful profiles, Night Power toggle and improved Climate state highlighting # V0.8l 2026-07-16 Completed primary Climate controls, ceiling fan cycle and Air Filter Low # V0.8k 2026-07-16 Revised Bedroom header and added five-button navigation row # V0.8j 2026-07-16 Added delayed Home Assistant state synchronisation for button highlighting at startup @@ -80,12 +81,12 @@ substitutions: # Device Naming device_name: "esp-bedside-panel" friendly_name: "ESP Bedside Panel" - description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside clock and Home Assistant control panel. Heat-pump setpoint, ceiling-fan and air-filter controls. (Layout V1.1)" + description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside control panel. Climate modes, profiles, setpoint, ceiling fan, quiet air filter and Night Power. (Layout V1.1)" device_area: "Bedroom" # Project Naming project_name: "Guition.JC1060P470C_I_W_Y" - project_version: "v0.8l" + project_version: "v0.8m" # Passwords & Secrets api_key: !secret esp-api_key @@ -137,15 +138,19 @@ substitutions: # Home Assistant Entities - Climate climate_entity: "climate.master_bedroom" climate_requested_mode_entity: "input_select.master_bedroom_hvac_requested_mode" + climate_profile_entity: "input_select.master_bedroom_heat_pump_profile" climate_off_script: "script.master_bedroom_hvac_off" climate_heat_script: "script.master_bedroom_hvac_heat" climate_cool_script: "script.master_bedroom_hvac_cool" climate_dry_script: "script.master_bedroom_hvac_dry" climate_setpoint_adjust_script: "script.master_bedroom_setpoint_adjust" + climate_quiet_preset: "quiet" + climate_powerful_preset: "boost" - # Home Assistant Entities - Ceiling Fan / Air Filter + # Home Assistant Entities - Ceiling Fan / Air Filter / Night Power ceiling_fan_entity: "fan.tasmo_ifan02_3793_bedrm1_1" air_filter_level_entity: "number.bedroom_1_air_filter_bedroom_1_air_filter_speed_level" + climate_night_power_entity: "input_boolean.master_bedroom_night_power_mode" #:########################################################################################:# # PACKAGES: @@ -406,7 +411,7 @@ font: glyphs: - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - - "0123456789, .:/-%°+()" + - "0123456789, .:/-%°+()↑↓" - file: "gfonts://Roboto" id: font_button @@ -691,6 +696,14 @@ binary_sensor: then: - script.execute: refresh_climate_controls + - platform: homeassistant + id: ha_climate_night_power + entity_id: "${climate_night_power_entity}" + internal: true + on_state: + then: + - script.execute: refresh_climate_controls + - platform: homeassistant id: ha_quiet_time entity_id: "${quiet_time_entity}" @@ -820,44 +833,152 @@ script: - id: refresh_climate_controls mode: restart then: - # Heat-pump mode buttons. + # Heat-pump mode buttons. The requested mode wins while a change is + # pending; when it is "none", the actual climate state is used. - lvgl.widget.update: id: climate_off_button bg_color: !lambda |- - const auto mode = id(ha_climate_requested_mode).state; - return (mode == "Off" || mode == "off") + const auto requested = id(ha_climate_requested_mode).state; + const auto current = id(ha_climate_current_mode).state; + + const bool requested_none = ( + requested.empty() || + requested == "none" || + requested == "None" || + requested == "unknown" || + requested == "unavailable" + ); + + const bool active = ( + requested == "off" || + requested == "Off" || + (requested_none && current == "off") + ); + + return active ? lv_color_hex(0x548C6C) : lv_color_hex(0x28313D); - lvgl.widget.update: id: climate_heat_button bg_color: !lambda |- - const auto mode = id(ha_climate_requested_mode).state; - return (mode == "Heat" || mode == "heat") + const auto requested = id(ha_climate_requested_mode).state; + const auto current = id(ha_climate_current_mode).state; + + const bool requested_none = ( + requested.empty() || + requested == "none" || + requested == "None" || + requested == "unknown" || + requested == "unavailable" + ); + + const bool active = ( + requested == "heat" || + requested == "Heat" || + (requested_none && current == "heat") + ); + + return active ? lv_color_hex(0xB76822) : lv_color_hex(0x28313D); - lvgl.widget.update: id: climate_cool_button bg_color: !lambda |- - const auto mode = id(ha_climate_requested_mode).state; - return (mode == "Cool" || mode == "cool") + const auto requested = id(ha_climate_requested_mode).state; + const auto current = id(ha_climate_current_mode).state; + + const bool requested_none = ( + requested.empty() || + requested == "none" || + requested == "None" || + requested == "unknown" || + requested == "unavailable" + ); + + const bool active = ( + requested == "cool" || + requested == "Cool" || + (requested_none && current == "cool") + ); + + return active ? lv_color_hex(0x2E6A96) : lv_color_hex(0x28313D); - lvgl.widget.update: id: climate_dry_button bg_color: !lambda |- - const auto mode = id(ha_climate_requested_mode).state; - return ( - mode == "Dry" || - mode == "dry" || - mode == "Dehumidify" || - mode == "dehumidify" - ) + const auto requested = id(ha_climate_requested_mode).state; + const auto current = id(ha_climate_current_mode).state; + + const bool requested_none = ( + requested.empty() || + requested == "none" || + requested == "None" || + requested == "unknown" || + requested == "unavailable" + ); + + const bool active = ( + 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 / Powerful profile buttons. The HA profile helper and actual + # climate preset are both checked so the display remains authoritative. + - lvgl.widget.update: + id: climate_quiet_button + bg_color: !lambda |- + const auto profile = id(ha_climate_profile).state; + const auto preset = id(ha_climate_preset_mode).state; + + const bool active = ( + profile == "Quiet" || + profile == "quiet" || + preset == "Quiet" || + preset == "quiet" + ); + + return active + ? lv_color_hex(0x315843) + : lv_color_hex(0x28313D); + + - lvgl.widget.update: + id: climate_powerful_button + bg_color: !lambda |- + const auto profile = id(ha_climate_profile).state; + const auto preset = id(ha_climate_preset_mode).state; + + const bool active = ( + profile == "Boost" || + profile == "boost" || + profile == "Powerful" || + profile == "powerful" || + preset == "Boost" || + preset == "boost" || + preset == "Powerful" || + preset == "powerful" + ); + + return active + ? lv_color_hex(0x6B4A86) + : lv_color_hex(0x28313D); + # Heat-pump target setpoint. - lvgl.label.update: id: climate_setpoint_value_label @@ -899,16 +1020,28 @@ script: ? lv_color_hex(0x315E82) : lv_color_hex(0x28313D); - # Air-filter Low is active when the HA number is set to level 2. + # Air Filter Quiet is highlighted for filter levels 0, 1 or 2. - lvgl.widget.update: - id: air_filter_low_button + id: air_filter_quiet_button bg_color: !lambda |- const float level = id(ha_air_filter_level).state; - return (!isnan(level) && level > 1.5f && level < 2.5f) + 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. - id: startup_button_state_sync mode: restart @@ -970,7 +1103,7 @@ interval: # https://esphome.io/components/text_sensor/ #:########################################################################################:# text_sensor: - # Requested heat-pump mode used for button highlighting. + # Requested heat-pump mode helper used while a mode change is pending. - platform: homeassistant id: ha_climate_requested_mode entity_id: "${climate_requested_mode_entity}" @@ -979,6 +1112,34 @@ text_sensor: then: - script.execute: refresh_climate_controls + # Actual current HVAC mode provides the fallback when requested mode is none. + - platform: homeassistant + id: ha_climate_current_mode + entity_id: "${climate_entity}" + internal: true + on_value: + then: + - script.execute: refresh_climate_controls + + # Existing HA profile helper uses values such as Quiet and Boost. + - platform: homeassistant + id: ha_climate_profile + entity_id: "${climate_profile_entity}" + internal: true + on_value: + then: + - script.execute: refresh_climate_controls + + # Actual climate preset provides an additional authoritative profile state. + - platform: homeassistant + id: ha_climate_preset_mode + entity_id: "${climate_entity}" + attribute: preset_mode + internal: true + on_value: + then: + - script.execute: refresh_climate_controls + - platform: version name: "${friendly_name} ESPHome Version" hide_timestamp: true @@ -1638,20 +1799,74 @@ lvgl: entity_id: "${climate_dry_script}" #:################################################################################:# - # HEAT-PUMP SETPOINT + # HEAT-PUMP PROFILE / SETPOINT #:################################################################################:# - label: - align: TOP_MID + x: 535 y: 187 + width: 270 text: "Setpoint" text_font: font_small + text_align: CENTER text_color: 0x8FA0B3 - button: - id: climate_setpoint_down_button - x: 100 + id: climate_quiet_button + x: 25 y: 218 - width: 190 + width: 170 + height: 105 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0x548C6C + radius: 18 + pressed: + bg_color: 0x315843 + widgets: + - label: + align: CENTER + text: "Quiet" + text_font: font_button + text_color: 0xFFFFFF + on_click: + - homeassistant.action: + action: climate.set_preset_mode + data: + entity_id: "${climate_entity}" + preset_mode: "${climate_quiet_preset}" + + - button: + id: climate_powerful_button + x: 205 + y: 218 + width: 170 + height: 105 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0x80619B + radius: 18 + pressed: + bg_color: 0x6B4A86 + widgets: + - label: + align: CENTER + text: "Powerful" + text_font: font_button + text_color: 0xFFFFFF + on_click: + - homeassistant.action: + action: climate.set_preset_mode + data: + entity_id: "${climate_entity}" + preset_mode: "${climate_powerful_preset}" + + - button: + id: climate_setpoint_down_button + x: 405 + y: 218 + width: 120 height: 105 bg_color: 0x28313D bg_opa: COVER @@ -1663,7 +1878,7 @@ lvgl: widgets: - label: align: CENTER - text: "-0.5" + text: "↓" text_font: font_title text_color: 0xFFFFFF on_click: @@ -1674,9 +1889,9 @@ lvgl: - button: id: climate_setpoint_display - x: 315 + x: 535 y: 218 - width: 394 + width: 270 height: 105 bg_color: 0x1C2632 bg_opa: COVER @@ -1693,9 +1908,9 @@ lvgl: - button: id: climate_setpoint_up_button - x: 734 + x: 815 y: 218 - width: 190 + width: 120 height: 105 bg_color: 0x28313D bg_opa: COVER @@ -1707,7 +1922,7 @@ lvgl: widgets: - label: align: CENTER - text: "+0.5" + text: "↑" text_font: font_title text_color: 0xFFFFFF on_click: @@ -1717,13 +1932,13 @@ lvgl: step: "0.5" #:################################################################################:# - # CEILING FAN / AIR FILTER + # CEILING FAN / AIR FILTER / NIGHT POWER #:################################################################################:# - button: id: ceiling_fan_cycle_button - x: 65 + x: 25 y: 356 - width: 430 + width: 310 height: 100 bg_color: 0x28313D bg_opa: COVER @@ -1798,10 +2013,10 @@ lvgl: entity_id: "${ceiling_fan_entity}" - button: - id: air_filter_low_button - x: 529 + id: air_filter_quiet_button + x: 357 y: 356 - width: 430 + width: 310 height: 100 bg_color: 0x28313D bg_opa: COVER @@ -1813,7 +2028,7 @@ lvgl: widgets: - label: align: CENTER - text: "Air Filter Low" + text: "Air Filter Quiet" text_font: font_button text_color: 0xFFFFFF on_click: @@ -1823,6 +2038,31 @@ lvgl: entity_id: "${air_filter_level_entity}" value: "2" + - button: + id: climate_night_power_button + x: 689 + y: 356 + width: 310 + height: 100 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0x80619B + radius: 18 + pressed: + bg_color: 0x5A4778 + widgets: + - label: + align: CENTER + text: "Climate 9pm-12" + text_font: font_button + text_color: 0xFFFFFF + on_click: + - homeassistant.action: + action: input_boolean.toggle + data: + entity_id: "${climate_night_power_entity}" + - button: id: climate_back_button x: 365