Edit esp-bedside-panel.yaml

This commit is contained in:
ESPHome Device Builder
2026-07-17 22:17:32 +12:00
parent 824fd3fac6
commit 034c87317f
+462 -131
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-bedside-panel.yaml
#:########################################################################################:#
# VERSIONS:
# V1.9 2026-07-17 Corrected Atto lock/climate/setpoint controls and added vehicle icons
# 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
@@ -106,12 +107,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, Vehicles, Climate, Security and Clock pages. (Layout V1.1)"
description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside control panel. Vehicles page with corrected BYD lock, climate and setpoint controls. (Layout V1.1)"
device_area: "Bedroom"
# Project Naming
project_name: "Guition.JC1060P470C_I_W_Y"
project_version: "v1.8"
project_version: "v1.9"
# Passwords & Secrets
api_key: !secret esp-api_key
@@ -185,9 +186,7 @@ substitutions:
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.
# BYD Vehicle integration lock entity; used for both status and 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"
@@ -529,6 +528,23 @@ font:
- "\U000F06D9" # mdi-garage
- "\U000F1054" # mdi-outdoor-lamp
# Compact Vehicles-page icon font.
- file: "https://raw.githubusercontent.com/Templarian/MaterialDesign-Webfont/master/fonts/materialdesignicons-webfont.ttf"
id: font_mdi_vehicle
size: 27
bpp: 4
glyphs:
- "\U000F033E" # mdi-lock
- "\U000F0FC6" # mdi-lock-open-variant
- "\U000F0084" # mdi-battery-charging
- "\U000F00F0" # mdi-calendar-clock
- "\U000F08F0" # mdi-map-marker-distance
- "\U000F0079" # mdi-battery
- "\U000F050F" # mdi-thermometer
- "\U000F0FA6" # mdi-car-seat-heater
- "\U000F0238" # mdi-fire
- "\U000F0717" # mdi-snowflake
#:########################################################################################:#
# HOME ASSISTANT NUMERIC SENSORS:
# https://esphome.io/components/sensor/homeassistant.html
@@ -679,6 +695,12 @@ sensor:
internal: true
on_value:
then:
- lambda: |-
if (!isnan(x)) {
id(atto_setpoint_local) = x;
id(atto_setpoint_optimistic) = false;
}
- script.execute: refresh_vehicle_controls
#:########################################################################################:#
@@ -689,14 +711,6 @@ 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}"
@@ -921,6 +935,29 @@ globals:
restore_value: false
initial_value: "-1"
# Atto lock optimistic state: -1 follows HA, 0 unlocked, 1 locked.
- id: atto_lock_optimistic
type: int
restore_value: false
initial_value: "-1"
# Atto climate direction: -1 follows HA preset, 1 max heat, 2 max cool.
- id: atto_climate_optimistic
type: int
restore_value: false
initial_value: "-1"
# Local target is used so repeated presses do not work from stale cloud data.
- id: atto_setpoint_local
type: float
restore_value: false
initial_value: "21.0"
- id: atto_setpoint_optimistic
type: bool
restore_value: false
initial_value: "false"
# Unix timestamp at which the 2-hour Sleep timer expires.
# A value of 0 means that the timer is inactive.
- id: sleep_timer_end_epoch
@@ -1082,20 +1119,63 @@ script:
return id(lvgl_ready);
then:
# Lock state and optimistic lock/unlock feedback.
- lvgl.label.update:
id: atto_lock_button_label
text: !lambda |-
return id(ha_atto_locked).state
? std::string("Locked (Hold)")
: std::string("Unlocked (Hold)");
if (id(atto_lock_optimistic) == 1) {
return std::string("Locked (Hold)");
}
if (id(atto_lock_optimistic) == 0) {
return std::string("Unlocked (Hold)");
}
const auto state = id(ha_atto_lock_state).state;
if (state == "locked") {
return std::string("Locked (Hold)");
}
if (state == "locking") {
return std::string("Locking...");
}
if (state == "unlocking") {
return std::string("Unlocking...");
}
return std::string("Unlocked (Hold)");
- lvgl.label.update:
id: atto_lock_icon_label
text: !lambda |-
bool locked = id(atto_lock_optimistic) == 1;
if (id(atto_lock_optimistic) < 0) {
const auto state = id(ha_atto_lock_state).state;
locked = state == "locked" || state == "locking";
}
return locked
? std::string("\U000F033E")
: std::string("\U000F0FC6");
- lvgl.widget.update:
id: atto_lock_button
bg_color: !lambda |-
return id(ha_atto_locked).state
bool locked = id(atto_lock_optimistic) == 1;
if (id(atto_lock_optimistic) < 0) {
const auto state = id(ha_atto_lock_state).state;
locked = state == "locked" || state == "locking";
}
return locked
? lv_color_hex(0x315843)
: lv_color_hex(0x6B3A3A);
# Charging and schedule status.
- lvgl.label.update:
id: atto_charging_label
text: !lambda |-
@@ -1117,6 +1197,7 @@ script:
? lv_color_hex(0x315E82)
: lv_color_hex(0x28313D);
# Vehicle values.
- lvgl.label.update:
id: atto_range_label
text: !lambda |-
@@ -1156,49 +1237,21 @@ script:
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);
# Seat-heating states.
- 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");
return std::string("Passenger Seat (Hold)\nLow");
}
if (value == "high" || value == "High") {
return std::string("Passenger Seat Heat (Hold)\nHigh");
return std::string("Passenger Seat (Hold)\nHigh");
}
return std::string("Passenger Seat Heat (Hold)\nOff");
return std::string("Passenger Seat (Hold)\nOff");
- lvgl.widget.update:
id: atto_passenger_seat_button
@@ -1214,33 +1267,124 @@ script:
? lv_color_hex(0x7A4A22)
: lv_color_hex(0x28313D);
- 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("Driver Seat (Hold)\nLow");
}
if (value == "high" || value == "High") {
return std::string("Driver Seat (Hold)\nHigh");
}
return std::string("Driver Seat (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);
# BYD heating/cooling intent is represented by max presets.
- lvgl.widget.update:
id: atto_heat_button
bg_color: !lambda |-
return id(ha_atto_climate_mode).state == "heat"
int selected = id(atto_climate_optimistic);
if (selected < 0) {
selected =
id(ha_atto_climate_preset).state == "max_heat"
? 1
: (
id(ha_atto_climate_preset).state == "max_cool"
? 2
: 0
);
}
return selected == 1
? 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"
int selected = id(atto_climate_optimistic);
if (selected < 0) {
selected =
id(ha_atto_climate_preset).state == "max_heat"
? 1
: (
id(ha_atto_climate_preset).state == "max_cool"
? 2
: 0
);
}
return selected == 2
? lv_color_hex(0x2E6A96)
: lv_color_hex(0x28313D);
# Use the local value while a command is pending, then HA again.
- lvgl.label.update:
id: atto_setpoint_value_label
text: !lambda |-
const float value = id(ha_atto_setpoint).state;
float value = id(atto_setpoint_local);
if (!id(atto_setpoint_optimistic)) {
value = id(ha_atto_setpoint).state;
}
if (isnan(value)) {
return std::string("--.-°C");
}
char buffer[16];
snprintf(buffer, sizeof(buffer), "%.1f°C", value);
snprintf(buffer, sizeof(buffer), "%.0f°C", value);
return std::string(buffer);
- id: release_atto_lock_optimistic
mode: restart
then:
- delay: 8s
- lambda: |-
id(atto_lock_optimistic) = -1;
- script.execute: refresh_vehicle_controls
- id: release_atto_climate_optimistic
mode: restart
then:
- delay: 10s
- lambda: |-
id(atto_climate_optimistic) = -1;
- script.execute: refresh_vehicle_controls
- id: release_atto_setpoint_optimistic
mode: restart
then:
- delay: 12s
- lambda: |-
id(atto_setpoint_optimistic) = false;
const float actual = id(ha_atto_setpoint).state;
if (!isnan(actual)) {
id(atto_setpoint_local) = actual;
}
- script.execute: refresh_vehicle_controls
# Update Climate-page labels and highlight the currently selected settings.
- id: refresh_climate_controls
mode: restart
@@ -1788,6 +1932,18 @@ text_sensor:
#:######################################################################################:#
# ATTO 3 TEXT STATES
#:######################################################################################:#
# The lock entity state is locked/unlocked/locking/unlocking.
- platform: homeassistant
id: ha_atto_lock_state
entity_id: "${atto_lock_entity}"
internal: true
on_value:
then:
- lambda: |-
id(atto_lock_optimistic) = -1;
- script.execute: refresh_vehicle_controls
- platform: homeassistant
id: ha_atto_driver_seat_heating
entity_id: "${atto_driver_seat_heating_entity}"
@@ -1812,6 +1968,19 @@ text_sensor:
then:
- script.execute: refresh_vehicle_controls
# BYD exposes heating/cooling intent as max_heat / max_cool presets.
- platform: homeassistant
id: ha_atto_climate_preset
entity_id: "${atto_climate_entity}"
attribute: preset_mode
internal: true
on_value:
then:
- lambda: |-
id(atto_climate_optimistic) = -1;
- script.execute: refresh_vehicle_controls
# Requested heat-pump mode helper used while a mode change is pending.
- platform: homeassistant
id: ha_climate_requested_mode
@@ -2615,32 +2784,58 @@ lvgl:
pressed:
bg_color: 0x416F55
widgets:
- label:
id: atto_lock_icon_label
align: TOP_LEFT
x: 10
y: 8
text: "\U000F033E"
text_font: font_mdi_vehicle
text_color: 0xFFFFFF
- label:
id: atto_lock_button_label
align: CENTER
x: 10
text: "Locked (Hold)"
text_font: font_small
text_align: CENTER
text_color: 0xFFFFFF
on_long_press:
then:
- lambda: |-
bool locked = false;
if (id(atto_lock_optimistic) >= 0) {
locked = id(atto_lock_optimistic) == 1;
} else {
const auto state = id(ha_atto_lock_state).state;
locked = state == "locked" || state == "locking";
}
id(atto_lock_optimistic) = locked ? 0 : 1;
- script.execute: refresh_vehicle_controls
- if:
condition:
lambda: |-
return id(ha_atto_locked).state;
return id(atto_lock_optimistic) == 1;
then:
- homeassistant.action:
action: lock.unlock
action: lock.lock
data:
entity_id: "${atto_lock_entity}"
else:
- homeassistant.action:
action: lock.lock
action: lock.unlock
data:
entity_id: "${atto_lock_entity}"
- script.execute: release_atto_lock_optimistic
- button:
id: atto_charging_status
x: 272
@@ -2653,9 +2848,18 @@ lvgl:
border_color: 0x548C6C
radius: 17
widgets:
- label:
align: TOP_LEFT
x: 10
y: 8
text: "\U000F0084"
text_font: font_mdi_vehicle
text_color: 0xFFFFFF
- label:
id: atto_charging_label
align: CENTER
x: 10
text: "Atto Charging\nNot Charging"
text_font: font_small
text_align: CENTER
@@ -2675,8 +2879,17 @@ lvgl:
pressed:
bg_color: 0x315E82
widgets:
- label:
align: TOP_LEFT
x: 10
y: 8
text: "\U000F00F0"
text_font: font_mdi_vehicle
text_color: 0xFFFFFF
- label:
align: CENTER
x: 10
text: "Schedule Enabled"
text_font: font_small
text_align: CENTER
@@ -2699,9 +2912,18 @@ lvgl:
border_color: 0x526276
radius: 17
widgets:
- label:
align: TOP_LEFT
x: 10
y: 8
text: "\U000F08F0"
text_font: font_mdi_vehicle
text_color: 0xFFFFFF
- label:
id: atto_range_label
align: CENTER
x: 10
text: "Atto Range\n-- km"
text_font: font_small
text_align: CENTER
@@ -2722,9 +2944,18 @@ lvgl:
border_color: 0x526276
radius: 17
widgets:
- label:
align: TOP_LEFT
x: 10
y: 8
text: "\U000F0079"
text_font: font_mdi_vehicle
text_color: 0xFFFFFF
- label:
id: atto_battery_label
align: CENTER
x: 10
text: "Battery Level\n--%"
text_font: font_small
text_align: CENTER
@@ -2742,16 +2973,26 @@ lvgl:
border_color: 0x526276
radius: 17
widgets:
- label:
align: TOP_LEFT
x: 10
y: 8
text: "\U000F050F"
text_font: font_mdi_vehicle
text_color: 0xFFFFFF
- label:
id: atto_cabin_temperature_label
align: CENTER
x: 10
text: "Cabin Temp\n--.-°C"
text_font: font_small
text_align: CENTER
text_color: 0xFFFFFF
# Passenger seat is intentionally before Driver seat.
- button:
id: atto_driver_seat_button
id: atto_passenger_seat_button
x: 519
y: 130
width: 220
@@ -2765,76 +3006,18 @@ lvgl:
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
align: TOP_LEFT
x: 10
y: 8
text: "\U000F0FA6"
text_font: font_mdi_vehicle
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"
x: 10
text: "Passenger Seat (Hold)\nOff"
text_font: font_small
text_align: CENTER
text_color: 0xFFFFFF
@@ -2884,6 +3067,82 @@ lvgl:
entity_id: "${atto_passenger_seat_heating_entity}"
option: "off"
- button:
id: atto_driver_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:
align: TOP_LEFT
x: 10
y: 8
text: "\U000F0FA6"
text_font: font_mdi_vehicle
text_color: 0xFFFFFF
- label:
id: atto_driver_seat_button_label
align: CENTER
x: 10
text: "Driver Seat (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"
#:################################################################################:#
# ATTO CLIMATE / SETPOINT
#:################################################################################:#
@@ -2910,18 +3169,33 @@ lvgl:
pressed:
bg_color: 0xB76822
widgets:
- label:
align: LEFT_MID
x: 18
text: "\U000F0238"
text_font: font_mdi_vehicle
text_color: 0xFFFFFF
- label:
align: CENTER
x: 15
text: "Heat (Hold)"
text_font: font_button
text_color: 0xFFFFFF
on_long_press:
then:
- lambda: |-
id(atto_climate_optimistic) = 1;
- script.execute: refresh_vehicle_controls
- homeassistant.action:
action: climate.set_hvac_mode
action: climate.set_preset_mode
data:
entity_id: "${atto_climate_entity}"
hvac_mode: "heat"
preset_mode: "max_heat"
- script.execute: release_atto_climate_optimistic
- button:
id: atto_cool_button
@@ -2937,18 +3211,33 @@ lvgl:
pressed:
bg_color: 0x2E6A96
widgets:
- label:
align: LEFT_MID
x: 18
text: "\U000F0717"
text_font: font_mdi_vehicle
text_color: 0xFFFFFF
- label:
align: CENTER
x: 15
text: "Cool (Hold)"
text_font: font_button
text_color: 0xFFFFFF
on_long_press:
then:
- lambda: |-
id(atto_climate_optimistic) = 2;
- script.execute: refresh_vehicle_controls
- homeassistant.action:
action: climate.set_hvac_mode
action: climate.set_preset_mode
data:
entity_id: "${atto_climate_entity}"
hvac_mode: "cool"
preset_mode: "max_cool"
- script.execute: release_atto_climate_optimistic
- button:
id: atto_setpoint_down_button
@@ -2970,6 +3259,22 @@ lvgl:
text_font: font_mdi_setpoint
text_color: 0xFFFFFF
on_click:
- lambda: |-
float current = id(atto_setpoint_local);
if (!id(atto_setpoint_optimistic)) {
const float actual = id(ha_atto_setpoint).state;
if (!isnan(actual)) {
current = actual;
}
}
id(atto_setpoint_local) =
std::max(15.0f, current - 1.0f);
id(atto_setpoint_optimistic) = true;
- script.execute: refresh_vehicle_controls
- homeassistant.action:
action: climate.set_temperature
data:
@@ -2978,8 +3283,9 @@ lvgl:
temperature: "{{ target_temperature }}"
variables:
target_temperature: |-
const float current = id(ha_atto_setpoint).state;
return isnan(current) ? 22.0f : current - 0.5f;
return id(atto_setpoint_local);
- script.execute: release_atto_setpoint_optimistic
- button:
id: atto_setpoint_display
@@ -2993,10 +3299,18 @@ lvgl:
border_color: 0x5A6C82
radius: 18
widgets:
- label:
align: LEFT_MID
x: 18
text: "\U000F050F"
text_font: font_mdi_vehicle
text_color: 0xFFFFFF
- label:
id: atto_setpoint_value_label
align: CENTER
text: "--.-°C"
x: 15
text: "--°C"
text_font: font_title
text_color: 0xFFFFFF
@@ -3020,6 +3334,22 @@ lvgl:
text_font: font_mdi_setpoint
text_color: 0xFFFFFF
on_click:
- lambda: |-
float current = id(atto_setpoint_local);
if (!id(atto_setpoint_optimistic)) {
const float actual = id(ha_atto_setpoint).state;
if (!isnan(actual)) {
current = actual;
}
}
id(atto_setpoint_local) =
std::min(31.0f, current + 1.0f);
id(atto_setpoint_optimistic) = true;
- script.execute: refresh_vehicle_controls
- homeassistant.action:
action: climate.set_temperature
data:
@@ -3028,8 +3358,9 @@ lvgl:
temperature: "{{ target_temperature }}"
variables:
target_temperature: |-
const float current = id(ha_atto_setpoint).state;
return isnan(current) ? 22.0f : current + 0.5f;
return id(atto_setpoint_local);
- script.execute: release_atto_setpoint_optimistic
#:################################################################################:#
# BOTTOM NAVIGATION