From cf857f322f26e8b67d84c01ca9601db9377cecfd Mon Sep 17 00:00:00 2001 From: ESPHome Device Builder Date: Thu, 16 Jul 2026 20:16:10 +1200 Subject: [PATCH] Edit esp-bedside-panel.yaml --- esphome/esp-bedside-panel.yaml | 269 ++++++++++++++++++++++++++++----- 1 file changed, 227 insertions(+), 42 deletions(-) diff --git a/esphome/esp-bedside-panel.yaml b/esphome/esp-bedside-panel.yaml index 171e83f..967b01a 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.8o 2026-07-16 Added reliable MDI setpoint arrows and optimistic mutually-exclusive Heat Pump profiles # V0.8n 2026-07-16 Fixed setpoint arrow icons using LVGL built-in FontAwesome symbols # 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 @@ -82,12 +83,12 @@ substitutions: # Device Naming device_name: "esp-bedside-panel" friendly_name: "ESP Bedside Panel" - description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside control panel. Climate controls with LVGL built-in setpoint arrow icons. (Layout V1.1)" + description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside control panel. Reliable setpoint icons and optimistic Quiet / Powerful / None profile controls. (Layout V1.1)" device_area: "Bedroom" # Project Naming project_name: "Guition.JC1060P470C_I_W_Y" - project_version: "v0.8n" + project_version: "v0.8o" # Passwords & Secrets api_key: !secret esp-api_key @@ -145,8 +146,7 @@ substitutions: 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" + climate_profile_next_script: "script.master_bedroom_heat_pump_profile_next" # Home Assistant Entities - Ceiling Fan / Air Filter / Night Power ceiling_fan_entity: "fan.tasmo_ifan02_3793_bedrm1_1" @@ -430,6 +430,15 @@ font: - "abcdefghijklmnopqrstuvwxyz" - "0123456789, .:/-%°+()" + # Dedicated MDI font for reliable setpoint chevron icons. + - file: "https://raw.githubusercontent.com/Templarian/MaterialDesign-Webfont/master/fonts/materialdesignicons-webfont.ttf" + id: font_mdi_setpoint + size: 48 + bpp: 4 + glyphs: + - "\U000F0140" # mdi-chevron-down + - "\U000F0143" # mdi-chevron-up + #:########################################################################################:# # HOME ASSISTANT NUMERIC SENSORS: # https://esphome.io/components/sensor/homeassistant.html @@ -725,6 +734,22 @@ binary_sensor: ? std::string("Quiet Time active") : std::string(""); +#:########################################################################################:# +# GLOBALS: +# Local optimistic Heat Pump profile state +#:########################################################################################:# +globals: + # -1 = follow HA, 0 = None, 1 = Quiet, 2 = Powerful / Boost. + - id: climate_profile_optimistic + type: int + restore_value: false + initial_value: "-1" + + - id: climate_profile_cycle_attempts + type: int + restore_value: false + initial_value: "0" + #:########################################################################################:# # SCRIPTS: # Refresh all state-dependent LVGL button colours @@ -940,43 +965,78 @@ script: ? 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. + # Quiet / Powerful are mutually exclusive. While a button action is + # pending, the optimistic local value is shown immediately. Otherwise, + # the HA profile helper is authoritative, with actual preset as fallback. - 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; + int selected = id(climate_profile_optimistic); - const bool active = ( - profile == "Quiet" || - profile == "quiet" || - preset == "Quiet" || - preset == "quiet" - ); + if (selected < 0) { + const auto profile = id(ha_climate_profile).state; + const auto preset = id(ha_climate_preset_mode).state; - return active + if (profile == "Quiet" || profile == "quiet") { + selected = 1; + } else if ( + profile == "Boost" || + profile == "boost" || + profile == "Powerful" || + profile == "powerful" + ) { + selected = 2; + } else if (preset == "quiet" || preset == "Quiet") { + selected = 1; + } else if ( + preset == "boost" || + preset == "Boost" || + preset == "powerful" || + preset == "Powerful" + ) { + selected = 2; + } else { + selected = 0; + } + } + + return selected == 1 ? 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; + int selected = id(climate_profile_optimistic); - const bool active = ( - profile == "Boost" || - profile == "boost" || - profile == "Powerful" || - profile == "powerful" || - preset == "Boost" || - preset == "boost" || - preset == "Powerful" || - preset == "powerful" - ); + if (selected < 0) { + const auto profile = id(ha_climate_profile).state; + const auto preset = id(ha_climate_preset_mode).state; - return active + if (profile == "Quiet" || profile == "quiet") { + selected = 1; + } else if ( + profile == "Boost" || + profile == "boost" || + profile == "Powerful" || + profile == "powerful" + ) { + selected = 2; + } else if (preset == "quiet" || preset == "Quiet") { + selected = 1; + } else if ( + preset == "boost" || + preset == "Boost" || + preset == "powerful" || + preset == "Powerful" + ) { + selected = 2; + } else { + selected = 0; + } + } + + return selected == 2 ? lv_color_hex(0x6B4A86) : lv_color_hex(0x28313D); @@ -1043,6 +1103,69 @@ script: ? lv_color_hex(0x5A4778) : lv_color_hex(0x28313D); + # Apply the optimistic target through the same HA profile-cycle script used + # by the existing dashboard. It checks after each cycle, so no cycle order + # is assumed. At most three cycles are attempted. + - id: apply_climate_profile_target + mode: restart + then: + - lambda: |- + id(climate_profile_cycle_attempts) = 0; + + - while: + condition: + lambda: |- + if (id(climate_profile_cycle_attempts) >= 3) { + return false; + } + + int current = 0; + const auto profile = id(ha_climate_profile).state; + const auto preset = id(ha_climate_preset_mode).state; + + if (profile == "Quiet" || profile == "quiet") { + current = 1; + } else if ( + profile == "Boost" || + profile == "boost" || + profile == "Powerful" || + profile == "powerful" + ) { + current = 2; + } else if (preset == "quiet" || preset == "Quiet") { + current = 1; + } else if ( + preset == "boost" || + preset == "Boost" || + preset == "powerful" || + preset == "Powerful" + ) { + current = 2; + } + + return current != id(climate_profile_optimistic); + + then: + - homeassistant.action: + action: script.turn_on + data: + entity_id: "${climate_profile_next_script}" + + - lambda: |- + id(climate_profile_cycle_attempts) += 1; + + # Allow the HA script and Panasonic integration to update the + # profile helper before deciding whether another cycle is needed. + - delay: 1800ms + + # Keep the optimistic highlight briefly, then return to authoritative HA. + - delay: 1500ms + + - lambda: |- + id(climate_profile_optimistic) = -1; + + - script.execute: refresh_climate_controls + # Two passes allow time for LVGL creation and Home Assistant state delivery. - id: startup_button_state_sync mode: restart @@ -1831,11 +1954,42 @@ lvgl: 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}" + # Quiet -> None when already active; otherwise select Quiet. + - lambda: |- + int selected = id(climate_profile_optimistic); + + if (selected < 0) { + const auto profile = id(ha_climate_profile).state; + const auto preset = id(ha_climate_preset_mode).state; + + if ( + profile == "Quiet" || + profile == "quiet" || + preset == "Quiet" || + preset == "quiet" + ) { + selected = 1; + } else if ( + profile == "Boost" || + profile == "boost" || + profile == "Powerful" || + profile == "powerful" || + preset == "Boost" || + preset == "boost" || + preset == "Powerful" || + preset == "powerful" + ) { + selected = 2; + } else { + selected = 0; + } + } + + id(climate_profile_optimistic) = + selected == 1 ? 0 : 1; + + - script.execute: refresh_climate_controls + - script.execute: apply_climate_profile_target - button: id: climate_powerful_button @@ -1857,11 +2011,42 @@ lvgl: 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}" + # Powerful -> None when already active; otherwise select Powerful. + - lambda: |- + int selected = id(climate_profile_optimistic); + + if (selected < 0) { + const auto profile = id(ha_climate_profile).state; + const auto preset = id(ha_climate_preset_mode).state; + + if ( + profile == "Quiet" || + profile == "quiet" || + preset == "Quiet" || + preset == "quiet" + ) { + selected = 1; + } else if ( + profile == "Boost" || + profile == "boost" || + profile == "Powerful" || + profile == "powerful" || + preset == "Boost" || + preset == "boost" || + preset == "Powerful" || + preset == "powerful" + ) { + selected = 2; + } else { + selected = 0; + } + } + + id(climate_profile_optimistic) = + selected == 2 ? 0 : 2; + + - script.execute: refresh_climate_controls + - script.execute: apply_climate_profile_target - button: id: climate_setpoint_down_button @@ -1879,8 +2064,8 @@ lvgl: widgets: - label: align: CENTER - text: "\uF063" - text_font: montserrat_36 + text: "\U000F0140" + text_font: font_mdi_setpoint text_color: 0xFFFFFF on_click: - homeassistant.action: @@ -1923,8 +2108,8 @@ lvgl: widgets: - label: align: CENTER - text: "\uF062" - text_font: montserrat_36 + text: "\U000F0143" + text_font: font_mdi_setpoint text_color: 0xFFFFFF on_click: - homeassistant.action: