diff --git a/esphome/esp-bedside-panel.yaml b/esphome/esp-bedside-panel.yaml index a2e5694..0f4bfa4 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: +# V1.8 2026-07-17 Added hold-to-open Atto Vehicles page and vehicle controls # V1.7 2026-07-17 Added Bedroom button icons and corrected Panasonic Nanoe-X naming # V1.6 2026-07-17 Reordered the nine Bedroom control buttons # V1.5 2026-07-16 Added RX8130 battery-backed RTC time restore and synchronisation @@ -105,12 +106,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. Bedroom control icons, Nanoe-X naming and RTC-backed time. (Layout V1.1)" + description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside control panel. Bedroom, Vehicles, Climate, Security and Clock pages. (Layout V1.1)" device_area: "Bedroom" # Project Naming project_name: "Guition.JC1060P470C_I_W_Y" - project_version: "v1.7" + project_version: "v1.8" # Passwords & Secrets api_key: !secret esp-api_key @@ -183,6 +184,20 @@ substitutions: climate_night_power_entity: "input_boolean.master_bedroom_night_power_mode" nanoe_entity: "switch.master_bedroom_nanoe" + # Home Assistant Entities - Vehicles / Atto 3 + atto_locked_sensor_entity: "binary_sensor.atto_3_locked" + # BYD Vehicle integration lock entity. Adjust only if HA generated a + # different entity_id for the Atto 3 lock control. + atto_lock_entity: "lock.atto_3_lock" + atto_driver_seat_heating_entity: "select.atto_3_driver_seat_heating" + atto_passenger_seat_heating_entity: "select.atto_3_passenger_seat_heating" + atto_charging_entity: "binary_sensor.atto_3_charging" + atto_schedule_enabled_entity: "switch.atto_3_schedule_enabled" + atto_range_entity: "sensor.atto_3_range" + atto_battery_level_entity: "sensor.atto_3_battery_level" + atto_cabin_temperature_entity: "sensor.atto_3_cabin_temperature" + atto_climate_entity: "climate.atto_3_climate" + #:########################################################################################:# # PACKAGES: # Included Common Packages @@ -630,11 +645,74 @@ sensor: then: - script.execute: refresh_climate_controls + #:######################################################################################:# + # ATTO 3 NUMERIC VALUES + #:######################################################################################:# + - platform: homeassistant + id: ha_atto_range + entity_id: "${atto_range_entity}" + internal: true + on_value: + then: + - script.execute: refresh_vehicle_controls + + - platform: homeassistant + id: ha_atto_battery_level + entity_id: "${atto_battery_level_entity}" + internal: true + on_value: + then: + - script.execute: refresh_vehicle_controls + + - platform: homeassistant + id: ha_atto_cabin_temperature + entity_id: "${atto_cabin_temperature_entity}" + internal: true + on_value: + then: + - script.execute: refresh_vehicle_controls + + - platform: homeassistant + id: ha_atto_setpoint + entity_id: "${atto_climate_entity}" + attribute: temperature + internal: true + on_value: + then: + - script.execute: refresh_vehicle_controls + #:########################################################################################:# # HOME ASSISTANT BINARY SENSORS: # Imported states used to colour LVGL buttons #:########################################################################################:# binary_sensor: + #:######################################################################################:# + # ATTO 3 BINARY STATES + #:######################################################################################:# + - platform: homeassistant + id: ha_atto_locked + entity_id: "${atto_locked_sensor_entity}" + internal: true + on_state: + then: + - script.execute: refresh_vehicle_controls + + - platform: homeassistant + id: ha_atto_charging + entity_id: "${atto_charging_entity}" + internal: true + on_state: + then: + - script.execute: refresh_vehicle_controls + + - platform: homeassistant + id: ha_atto_schedule_enabled + entity_id: "${atto_schedule_enabled_entity}" + internal: true + on_state: + then: + - script.execute: refresh_vehicle_controls + - platform: homeassistant id: ha_north_lights entity_id: "${north_lights_entity}" @@ -990,6 +1068,178 @@ script: : std::string(""); - script.execute: refresh_climate_controls + - script.execute: refresh_vehicle_controls + + #:######################################################################################:# + # VEHICLES PAGE STATE REFRESH + #:######################################################################################:# + - id: refresh_vehicle_controls + mode: restart + then: + - if: + condition: + lambda: |- + return id(lvgl_ready); + + then: + - lvgl.label.update: + id: atto_lock_button_label + text: !lambda |- + return id(ha_atto_locked).state + ? std::string("Locked (Hold)") + : std::string("Unlocked (Hold)"); + + - lvgl.widget.update: + id: atto_lock_button + bg_color: !lambda |- + return id(ha_atto_locked).state + ? lv_color_hex(0x315843) + : lv_color_hex(0x6B3A3A); + + - lvgl.label.update: + id: atto_charging_label + text: !lambda |- + return id(ha_atto_charging).state + ? std::string("Atto Charging\nCharging") + : std::string("Atto Charging\nNot Charging"); + + - lvgl.widget.update: + id: atto_charging_status + bg_color: !lambda |- + return id(ha_atto_charging).state + ? lv_color_hex(0x315843) + : lv_color_hex(0x28313D); + + - lvgl.widget.update: + id: atto_schedule_button + bg_color: !lambda |- + return id(ha_atto_schedule_enabled).state + ? lv_color_hex(0x315E82) + : lv_color_hex(0x28313D); + + - lvgl.label.update: + id: atto_range_label + text: !lambda |- + const float value = id(ha_atto_range).state; + + if (isnan(value)) { + return std::string("Atto Range\n-- km"); + } + + char buffer[32]; + snprintf(buffer, sizeof(buffer), "Atto Range\n%.0f km", value); + return std::string(buffer); + + - lvgl.label.update: + id: atto_battery_label + text: !lambda |- + const float value = id(ha_atto_battery_level).state; + + if (isnan(value)) { + return std::string("Battery Level\n--%"); + } + + char buffer[32]; + snprintf(buffer, sizeof(buffer), "Battery Level\n%.0f%%", value); + return std::string(buffer); + + - lvgl.label.update: + id: atto_cabin_temperature_label + text: !lambda |- + const float value = id(ha_atto_cabin_temperature).state; + + if (isnan(value)) { + return std::string("Cabin Temp\n--.-°C"); + } + + char buffer[32]; + snprintf(buffer, sizeof(buffer), "Cabin Temp\n%.1f°C", value); + return std::string(buffer); + + - lvgl.label.update: + id: atto_driver_seat_button_label + text: !lambda |- + const auto value = id(ha_atto_driver_seat_heating).state; + + if (value == "low" || value == "Low") { + return std::string("Drive Seat Heat (Hold)\nLow"); + } + + if (value == "high" || value == "High") { + return std::string("Drive Seat Heat (Hold)\nHigh"); + } + + return std::string("Drive Seat Heat (Hold)\nOff"); + + - lvgl.widget.update: + id: atto_driver_seat_button + bg_color: !lambda |- + const auto value = id(ha_atto_driver_seat_heating).state; + + return ( + value == "low" || + value == "Low" || + value == "high" || + value == "High" + ) + ? lv_color_hex(0x7A4A22) + : lv_color_hex(0x28313D); + + - lvgl.label.update: + id: atto_passenger_seat_button_label + text: !lambda |- + const auto value = id(ha_atto_passenger_seat_heating).state; + + if (value == "low" || value == "Low") { + return std::string("Passenger Seat Heat (Hold)\nLow"); + } + + if (value == "high" || value == "High") { + return std::string("Passenger Seat Heat (Hold)\nHigh"); + } + + return std::string("Passenger Seat Heat (Hold)\nOff"); + + - lvgl.widget.update: + id: atto_passenger_seat_button + bg_color: !lambda |- + const auto value = id(ha_atto_passenger_seat_heating).state; + + return ( + value == "low" || + value == "Low" || + value == "high" || + value == "High" + ) + ? lv_color_hex(0x7A4A22) + : lv_color_hex(0x28313D); + + - lvgl.widget.update: + id: atto_heat_button + bg_color: !lambda |- + return id(ha_atto_climate_mode).state == "heat" + ? lv_color_hex(0xB76822) + : lv_color_hex(0x28313D); + + - lvgl.widget.update: + id: atto_cool_button + bg_color: !lambda |- + return id(ha_atto_climate_mode).state == "cool" + ? lv_color_hex(0x2E6A96) + : lv_color_hex(0x28313D); + + - lvgl.label.update: + id: atto_setpoint_value_label + text: !lambda |- + const float value = id(ha_atto_setpoint).state; + + if (isnan(value)) { + return std::string("--.-°C"); + } + + char buffer[16]; + snprintf(buffer, sizeof(buffer), "%.1f°C", value); + return std::string(buffer); # Update Climate-page labels and highlight the currently selected settings. - id: refresh_climate_controls @@ -1535,6 +1785,33 @@ interval: # https://esphome.io/components/text_sensor/ #:########################################################################################:# text_sensor: + #:######################################################################################:# + # ATTO 3 TEXT STATES + #:######################################################################################:# + - platform: homeassistant + id: ha_atto_driver_seat_heating + entity_id: "${atto_driver_seat_heating_entity}" + internal: true + on_value: + then: + - script.execute: refresh_vehicle_controls + + - platform: homeassistant + id: ha_atto_passenger_seat_heating + entity_id: "${atto_passenger_seat_heating_entity}" + internal: true + on_value: + then: + - script.execute: refresh_vehicle_controls + + - platform: homeassistant + id: ha_atto_climate_mode + entity_id: "${atto_climate_entity}" + internal: true + on_value: + then: + - script.execute: refresh_vehicle_controls + # Requested heat-pump mode helper used while a mode change is pending. - platform: homeassistant id: ha_climate_requested_mode @@ -2208,25 +2485,39 @@ lvgl: time: 220ms - button: - id: future_1_navigation_button + id: atto_navigation_button x: 414 y: 475 width: 190 height: 92 widgets: - label: - id: future_1_navigation_button_label + id: atto_navigation_button_label align: CENTER - text: "Future1" + text: "Atto (Hold)" text_font: font_button - text_color: 0xAEB8C4 - bg_color: 0x28313D + text_color: 0xFFFFFF + bg_color: 0x3E4652 bg_opa: COVER border_width: 2 - border_color: 0x445365 + border_color: 0x687587 radius: 16 pressed: - bg_color: 0x34404F + bg_color: 0x566170 + + on_long_press: + then: + - lvgl.page.show: + id: vehicles_page + animation: MOVE_LEFT + time: 220ms + + - light.turn_on: + id: display_backlight + brightness: "${control_brightness}" + transition_length: 250ms + + - script.execute: refresh_vehicle_controls - button: id: security_navigation_button @@ -2298,6 +2589,506 @@ lvgl: brightness: "${clock_brightness}" transition_length: 1500ms + #:####################################################################################:# + # VEHICLES PAGE + #:####################################################################################:# + - id: vehicles_page + skip: true + bg_color: 0x111923 + bg_opa: COVER + + widgets: + #:################################################################################:# + # VEHICLE STATUS / PRIMARY CONTROLS + #:################################################################################:# + - button: + id: atto_lock_button + x: 25 + y: 22 + width: 220 + height: 92 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0x548C6C + radius: 17 + pressed: + bg_color: 0x416F55 + widgets: + - label: + id: atto_lock_button_label + align: CENTER + text: "Locked (Hold)" + text_font: font_small + text_align: CENTER + text_color: 0xFFFFFF + on_long_press: + then: + - if: + condition: + lambda: |- + return id(ha_atto_locked).state; + + then: + - homeassistant.action: + action: lock.unlock + data: + entity_id: "${atto_lock_entity}" + + else: + - homeassistant.action: + action: lock.lock + data: + entity_id: "${atto_lock_entity}" + + - button: + id: atto_charging_status + x: 272 + y: 22 + width: 220 + height: 92 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0x548C6C + radius: 17 + widgets: + - label: + id: atto_charging_label + align: CENTER + text: "Atto Charging\nNot Charging" + text_font: font_small + text_align: CENTER + text_color: 0xFFFFFF + + - button: + id: atto_schedule_button + x: 519 + y: 22 + width: 220 + height: 92 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0x47789B + radius: 17 + pressed: + bg_color: 0x315E82 + widgets: + - label: + align: CENTER + text: "Schedule Enabled" + text_font: font_small + text_align: CENTER + text_color: 0xFFFFFF + on_click: + - homeassistant.action: + action: switch.toggle + data: + entity_id: "${atto_schedule_enabled_entity}" + + - button: + id: atto_range_status + x: 766 + y: 22 + width: 220 + height: 92 + bg_color: 0x1C2632 + bg_opa: COVER + border_width: 2 + border_color: 0x526276 + radius: 17 + widgets: + - label: + id: atto_range_label + align: CENTER + text: "Atto Range\n-- km" + text_font: font_small + text_align: CENTER + text_color: 0xFFFFFF + + #:################################################################################:# + # VEHICLE VALUES / SEAT HEATING + #:################################################################################:# + - button: + id: atto_battery_status + x: 25 + y: 130 + width: 220 + height: 92 + bg_color: 0x1C2632 + bg_opa: COVER + border_width: 2 + border_color: 0x526276 + radius: 17 + widgets: + - label: + id: atto_battery_label + align: CENTER + text: "Battery Level\n--%" + text_font: font_small + text_align: CENTER + text_color: 0xFFFFFF + + - button: + id: atto_cabin_temperature_status + x: 272 + y: 130 + width: 220 + height: 92 + bg_color: 0x1C2632 + bg_opa: COVER + border_width: 2 + border_color: 0x526276 + radius: 17 + widgets: + - label: + id: atto_cabin_temperature_label + align: CENTER + text: "Cabin Temp\n--.-°C" + text_font: font_small + text_align: CENTER + text_color: 0xFFFFFF + + - button: + id: atto_driver_seat_button + x: 519 + y: 130 + width: 220 + height: 92 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0xA6672B + radius: 17 + pressed: + bg_color: 0x7A4A22 + widgets: + - label: + id: atto_driver_seat_button_label + align: CENTER + text: "Drive Seat Heat (Hold)\nOff" + text_font: font_small + text_align: CENTER + text_color: 0xFFFFFF + on_long_press: + then: + - if: + condition: + lambda: |- + const auto value = + id(ha_atto_driver_seat_heating).state; + + return ( + value == "off" || + value == "Off" || + value == "unknown" || + value == "unavailable" || + value.empty() + ); + + then: + - homeassistant.action: + action: select.select_option + data: + entity_id: "${atto_driver_seat_heating_entity}" + option: "low" + + else: + - if: + condition: + lambda: |- + const auto value = + id(ha_atto_driver_seat_heating).state; + + return value == "low" || value == "Low"; + + then: + - homeassistant.action: + action: select.select_option + data: + entity_id: "${atto_driver_seat_heating_entity}" + option: "high" + + else: + - homeassistant.action: + action: select.select_option + data: + entity_id: "${atto_driver_seat_heating_entity}" + option: "off" + + - button: + id: atto_passenger_seat_button + x: 766 + y: 130 + width: 220 + height: 92 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0xA6672B + radius: 17 + pressed: + bg_color: 0x7A4A22 + widgets: + - label: + id: atto_passenger_seat_button_label + align: CENTER + text: "Passenger Seat Heat (Hold)\nOff" + text_font: font_small + text_align: CENTER + text_color: 0xFFFFFF + on_long_press: + then: + - if: + condition: + lambda: |- + const auto value = + id(ha_atto_passenger_seat_heating).state; + + return ( + value == "off" || + value == "Off" || + value == "unknown" || + value == "unavailable" || + value.empty() + ); + + then: + - homeassistant.action: + action: select.select_option + data: + entity_id: "${atto_passenger_seat_heating_entity}" + option: "low" + + else: + - if: + condition: + lambda: |- + const auto value = + id(ha_atto_passenger_seat_heating).state; + + return value == "low" || value == "Low"; + + then: + - homeassistant.action: + action: select.select_option + data: + entity_id: "${atto_passenger_seat_heating_entity}" + option: "high" + + else: + - homeassistant.action: + action: select.select_option + data: + entity_id: "${atto_passenger_seat_heating_entity}" + option: "off" + + #:################################################################################:# + # ATTO CLIMATE / SETPOINT + #:################################################################################:# + - label: + x: 639 + y: 230 + width: 227 + text: "Setpoint" + text_font: font_small + text_align: CENTER + text_color: 0x8FA0B3 + + - button: + id: atto_heat_button + x: 25 + y: 260 + width: 220 + height: 105 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0xA6672B + radius: 18 + pressed: + bg_color: 0xB76822 + widgets: + - label: + align: CENTER + text: "Heat (Hold)" + text_font: font_button + text_color: 0xFFFFFF + on_long_press: + then: + - homeassistant.action: + action: climate.set_hvac_mode + data: + entity_id: "${atto_climate_entity}" + hvac_mode: "heat" + + - button: + id: atto_cool_button + x: 272 + y: 260 + width: 220 + height: 105 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0x3E7FA7 + radius: 18 + pressed: + bg_color: 0x2E6A96 + widgets: + - label: + align: CENTER + text: "Cool (Hold)" + text_font: font_button + text_color: 0xFFFFFF + on_long_press: + then: + - homeassistant.action: + action: climate.set_hvac_mode + data: + entity_id: "${atto_climate_entity}" + hvac_mode: "cool" + + - button: + id: atto_setpoint_down_button + x: 519 + y: 260 + width: 110 + height: 105 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0x526276 + radius: 18 + pressed: + bg_color: 0x38485A + widgets: + - label: + align: CENTER + text: "\U000F0140" + text_font: font_mdi_setpoint + text_color: 0xFFFFFF + on_click: + - homeassistant.action: + action: climate.set_temperature + data: + entity_id: "${atto_climate_entity}" + data_template: + temperature: "{{ target_temperature }}" + variables: + target_temperature: |- + const float current = id(ha_atto_setpoint).state; + return isnan(current) ? 22.0f : current - 0.5f; + + - button: + id: atto_setpoint_display + x: 639 + y: 260 + width: 227 + height: 105 + bg_color: 0x1C2632 + bg_opa: COVER + border_width: 2 + border_color: 0x5A6C82 + radius: 18 + widgets: + - label: + id: atto_setpoint_value_label + align: CENTER + text: "--.-°C" + text_font: font_title + text_color: 0xFFFFFF + + - button: + id: atto_setpoint_up_button + x: 876 + y: 260 + width: 110 + height: 105 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0x526276 + radius: 18 + pressed: + bg_color: 0x38485A + widgets: + - label: + align: CENTER + text: "\U000F0143" + text_font: font_mdi_setpoint + text_color: 0xFFFFFF + on_click: + - homeassistant.action: + action: climate.set_temperature + data: + entity_id: "${atto_climate_entity}" + data_template: + temperature: "{{ target_temperature }}" + variables: + target_temperature: |- + const float current = id(ha_atto_setpoint).state; + return isnan(current) ? 22.0f : current + 0.5f; + + #:################################################################################:# + # BOTTOM NAVIGATION + #:################################################################################:# + - button: + id: vehicles_back_button + x: 612 + y: 475 + width: 190 + height: 92 + bg_color: 0x315843 + bg_opa: COVER + border_width: 2 + border_color: 0x548C6C + radius: 16 + pressed: + bg_color: 0x416F55 + widgets: + - label: + align: CENTER + text: "Back" + text_font: font_button + text_color: 0xFFFFFF + on_click: + - lvgl.page.show: + id: bedroom_page + animation: MOVE_RIGHT + time: 220ms + + - button: + id: vehicles_clock_button + x: 810 + y: 475 + width: 190 + height: 92 + bg_color: 0x214F72 + bg_opa: COVER + border_width: 2 + border_color: 0x3E7FA7 + radius: 16 + pressed: + bg_color: 0x2E6A96 + widgets: + - label: + align: CENTER + text: "Clock" + text_font: font_button + text_color: 0xFFFFFF + on_click: + - lvgl.page.show: + id: clock_page + animation: FADE_IN + time: 250ms + + - light.turn_on: + id: display_backlight + brightness: "${clock_brightness}" + transition_length: 1500ms + #:####################################################################################:# # CLIMATE PAGE #:####################################################################################:#