Files
hassos_config/esphome/widgets/thermostat/thermostat_widget.yaml
2026-03-26 12:10:21 +01:00

813 lines
25 KiB
YAML

# https://esphome.io/components/climate/thermostat.html
substitutions:
thermostat_name: "thermostat"
thermostat_temperature_entity: "sensor.outside_temperature"
thermostat_entity: "climate.smart_radiator_thermostat_x_2"
heating_icon: "\U0000e936"
temperature_icon: "\U0000e939"
pm_home_icon: "\U000F0826"
pm_away_icon: "\U000F1A46"
climate:
- platform: thermostat
name: "${thermostat_name}"
id: climate_thermostat
sensor: thermostate_temperature_sensor
on_boot_restore_from: memory
min_heating_off_time: 5s
min_heating_run_time: 5s
min_idle_time: 5s
startup_delay: true
heat_deadband: 0.5
heat_overrun: 0.5
visual:
min_temperature: 10
max_temperature: 30
temperature_step: 0.5
heat_action:
- delay: 500ms
idle_action:
- delay: 500ms
default_preset: Home
preset:
- name: Home
default_target_temperature_low: 22 °C
- name: Away
default_target_temperature_low: 18 °C
# switch:
# - platform: gpio
# id: thermostat_relay
# name: "Heating Relay"
# pin: GPIO40
# on_turn_on:
# - lvgl.meter.update:
# id: thermostat_meter
# text_color: color_deep_orange
# - lvgl.label.update:
# id: heating_status
# text_color: color_deep_orange
# - lvgl.label.update:
# id: thermostat_state_icon
# text_color: color_deep_orange
# - lvgl.label.update:
# id: thermostat_target_temperature_icon
# text_color: color_deep_orange
# - lvgl.label.update:
# id: thermostat_target_temperature_value
# text_color: color_deep_orange
# - lvgl.label.update:
# id: thermostat_target_temperature_whole
# text_color: color_deep_orange
# - lvgl.label.update:
# id: thermostat_target_temperature_fraction
# text_color: color_deep_orange
# - lvgl.label.update:
# id: thermostat_target_temperature_measurement
# text_color: color_deep_orange
# on_turn_off:
# - lvgl.meter.update:
# id: thermostat_meter
# text_color: color_steel_blue
# - lvgl.label.update:
# id: heating_status
# text_color: color_steel_blue
# - lvgl.label.update:
# id: thermostat_state_icon
# text_color: color_steel_blue
# - lvgl.label.update:
# id: thermostat_target_temperature_icon
# text_color: color_steel_blue
# - lvgl.label.update:
# id: thermostat_target_temperature_value
# text_color: color_steel_blue
# - lvgl.label.update:
# id: thermostat_target_temperature_whole
# text_color: color_steel_blue
# - lvgl.label.update:
# id: thermostat_target_temperature_fraction
# text_color: color_steel_blue
# - lvgl.label.update:
# id: thermostat_target_temperature_measurement
# text_color: color_steel_blue
# restore_mode: RESTORE_DEFAULT_ON
sensor:
# Thermostat temperature sensor
- platform: homeassistant
id: thermostate_temperature_sensor
entity_id: "${thermostat_temperature_entity}"
# Thermostat target temperature
- platform: homeassistant
id: thermostat_sensor_target_temp
entity_id: "${thermostat_entity}"
attribute: temperature
on_value:
then:
- lvgl.arc.update:
id: thermostat_arc_main
value: !lambda return x;
- lvgl.label.update:
id: thermostat_target_temperature_whole
text: !lambda |-
static char buf[10];
int whole_part = static_cast<int>(id(thermostat_sensor_target_temp).state);
snprintf(buf, 10, "%d", whole_part);
return buf;
- lvgl.label.update:
id: thermostat_target_temperature_fraction
text: !lambda |-
static char buf[10];
int whole_part = static_cast<int>(id(thermostat_sensor_target_temp).state);
int fractional_part = static_cast<int>((id(thermostat_sensor_target_temp).state - whole_part) * 10);
snprintf(buf, 10, ".%01d", fractional_part);
return buf;
# Thermostat current temperature
- platform: homeassistant
id: thermostat_sensor_current_temp
entity_id: "${thermostat_entity}"
attribute: current_temperature
on_value:
- lvgl.label.update:
id: thermostat_target_temperature_value
text: !lambda |-
char buf[8];
sprintf(buf, "%.1f", x);
return std::string(buf);
- lvgl.arc.update:
id: thermostat_arc_current_temp
value: !lambda return x;
text_sensor:
# Thermostat name
- platform: homeassistant
id: thermostat_sensor_name
entity_id: "${thermostat_entity}"
attribute: friendly_name
on_value:
- lvgl.label.update:
id: thermostat_name_label
text: !lambda return x;
# Thermostat preset mode
- platform: homeassistant
id: thermostat_sensor_preset_mode
entity_id: "${thermostat_entity}"
attribute: preset_mode
on_value:
- if:
condition:
lambda: 'return id(thermostat_sensor_preset_mode).state == "home";'
then:
- lvgl.obj.update:
id: thermostat_preset_mode_bg
bg_color: color_green
- lvgl.label.update:
id: thermostat_preset_mode_icon
text: "${pm_home_icon}"
text_color: color_green
else:
- lvgl.obj.update:
id: thermostat_preset_mode_bg
bg_color: color_red
- lvgl.label.update:
id: thermostat_preset_mode_icon
text: "${pm_away_icon}"
text_color: color_red
# Thermostat hvac mode
- platform: homeassistant
id: thermostat_sensor_hvac_mode
entity_id: "${thermostat_entity}"
attribute: hvac_action
on_value:
- script.execute:
id: thermostat_get_and_set_translated_state
state_str: !lambda 'return id(thermostat_sensor_hvac_mode).state;'
# Thermostat state
- platform: homeassistant
id: thermostat_sensor_state
entity_id: "${thermostat_entity}"
on_value:
- if:
condition:
lambda: 'return id(thermostat_sensor_state).state == "off";'
then:
- lvgl.obj.update:
id: thermostat_power_bg
bg_color: color_misty_blue
- lvgl.label.update:
id: thermostat_power_icon
text_color: color_misty_blue
else:
- lvgl.obj.update:
id: thermostat_power_bg
bg_color: color_deep_orange
- lvgl.label.update:
id: thermostat_power_icon
text_color: color_deep_orange
lvgl:
gradients:
- id: thermostat_temp_gradient
direction: ver
dither: none
stops:
- color: 0xFF0000
position: 0
- color: 0xFF3300
position: 32
- color: 0xFF6600
position: 64
- color: 0xFF8000
position: 96
- color: 0xFF9900
position: 128
- color: 0xFFB300
position: 160
- color: 0xFFCC00
position: 192
- color: 0xFFE600
position: 224
- color: 0xFFFF00
position: 255
pages:
- id: thermostat_page
bg_color: color_slate_blue_gray
widgets:
# State
- obj:
id: thermostat_state
x: 20
y: 20
width: 400
height: 60
align: TOP_LEFT
pad_all: 0
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: thermostat_state_label
x: 20
align: LEFT_MID
text_font: nunito_18
text_color: color_misty_blue
text: " "
# Preset Mode and Power Toggle
- obj:
id: thermostat_controls
x: 535
y: 100
width: 260
height: 280
#align: TOP_LEFT
pad_all: 0
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- obj:
id: thermostat_preset_mode_bg
x: 35
y: -60
align: left_mid
width: 70
height: 70
radius: 35
pad_all: 0
bg_color: color_green
bg_opa: 60%
shadow_opa: transp
border_opa: transp
border_width: 0
widgets:
- label:
id: thermostat_preset_mode_icon
align: center
text_font: mdi_icons_52
text_color: color_green
text: "${pm_home_icon}"
on_click:
- if:
condition:
lambda: 'return id(thermostat_sensor_preset_mode).state == "home";'
then:
- homeassistant.action:
action: climate.set_preset_mode
data:
entity_id: "${thermostat_entity}"
preset_mode: away
else:
- homeassistant.action:
action: climate.set_preset_mode
data:
entity_id: "${thermostat_entity}"
preset_mode: home
- obj:
id: thermostat_power_bg
x: 35
y: 60
align: left_mid
width: 70
height: 70
radius: 35
pad_all: 0
bg_color: color_deep_orange
bg_opa: 30%
shadow_opa: transp
border_opa: transp
border_width: 0
widgets:
- label:
id: thermostat_power_icon
align: center
text_font: icons_38
text_color: color_deep_orange
text: "${power_icon}"
on_click:
- homeassistant.action:
action: climate.toggle
data:
entity_id: "${thermostat_entity}"
# Temperature controls
- obj:
x: 5
y: -60
width: 90
height: 90
align: left_mid
pad_all: 0
bg_opa: transp
shadow_opa: transp
border_opa: transp
widgets:
- obj:
width: 70
height: 70
align: center
clickable: true
radius: 50
pad_all: 0
bg_opa: transp
border_opa: transp
border_width: 0
shadow_width: 8
shadow_spread: 2
shadow_color: color_black
pressed:
bg_color: 0x3A3A4C
shadow_width: 5
on_press:
then:
- lambda: |-
auto temp = id(thermostat_sensor_target_temp).state + 0.5;
id(thermostat_sensor_target_temp).publish_state(temp);
- homeassistant.service:
service: climate.set_temperature
data:
entity_id: "${thermostat_entity}"
temperature: !lambda 'return id(thermostat_sensor_target_temp).state;'
- obj:
width: 60
height: 60
align: center
clickable: false
radius: 45
pad_all: 0
bg_opa: transp
border_opa: transp
shadow_width: 4
shadow_color: 0xFFFFFF
shadow_ofs_x: -4
shadow_ofs_y: -2
shadow_opa: 30%
- obj:
width: 65
height: 65
pad_all: 0
align: center
clickable: false
radius: 50
border_opa: transp
bg_color: color_slate_blue_gray
widgets:
- label:
y: -2
align: center
text_font: nunito_48
text_color: color_misty_blue
text: "+"
- obj:
x: 5
y: 60
width: 90
height: 90
align: left_mid
pad_all: 0
bg_opa: transp
shadow_opa: transp
border_opa: transp
widgets:
- obj:
width: 70
height: 70
align: center
clickable: true
radius: 50
pad_all: 0
bg_opa: transp
border_opa: transp
border_width: 0
shadow_width: 8
shadow_spread: 2
shadow_color: color_black
pressed:
bg_color: 0x3A3A4C
shadow_width: 5
on_press:
then:
- lambda: |-
auto temp = id(thermostat_sensor_target_temp).state - 0.5;
id(thermostat_sensor_target_temp).publish_state(temp);
- homeassistant.service:
service: climate.set_temperature
data:
entity_id: "${thermostat_entity}"
temperature: !lambda 'return id(thermostat_sensor_target_temp).state;'
- obj:
width: 60
height: 60
align: center
clickable: false
radius: 45
pad_all: 0
bg_opa: transp
border_opa: transp
shadow_width: 4
shadow_color: 0xFFFFFF
shadow_ofs_x: -4
shadow_ofs_y: -2
shadow_opa: 30%
- obj:
width: 65
height: 65
pad_all: 0
align: center
clickable: false
radius: 50
border_opa: transp
bg_color: color_slate_blue_gray
widgets:
- label:
y: -2
align: CENTER
text_font: nunito_48
text_color: color_misty_blue
text: "-"
# Name
- obj:
id: thermostat_name
x: 100
y: 400
width: 360
height: 60
align: TOP_LEFT
pad_all: 0
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: thermostat_name_label
x: 20
align: LEFT_MID
text_font: nunito_16
text_color: color_misty_blue
text: "friendly name"
# main
- obj:
id: thermostat_bg_main
width: 250
height: 480
y: 20
align: TOP_RIGHT
pad_all: 0
bg_opa: TRANSP
scrollable: false
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- obj:
id: thermostat_bg_placeholder
radius: 240
x: 235
width: 480
height: 480
align: TOP_RIGHT
bg_color: color_slate_blue_gray
pad_all: 0
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
- obj:
id: thermostat_temp_arc_bg
radius: 230
x: 230
y: 250
width: 460
height: 460
align: TOP_RIGHT
bg_grad: thermostat_temp_gradient
bg_grad_dir: ver
pad_all: 0
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
- meter:
id: thermostat_meter
x: 320 # was 200
y: 400 # was 250
height: 640 # was 400
width: 640 # was 400
border_width: 0
bg_color: color_slate_blue_gray
align: TOP_RIGHT
text_color: color_steel_blue
scales:
- range_from: 10
range_to: 30
angle_range: 160
rotation: 100.0
ticks:
width: 1
count: 21
length: 10
color: color_white
major:
stride: 5
width: 5
length: 15
color: color_white
label_gap: 15
indicators:
- tick_style:
start_value: 10
end_value: 30
color_start: color_yellow
color_end: color_red
width: 1
- arc:
id: thermostat_arc_main
align: TOP_RIGHT
x: 375 # was 235
y: 400 # was 250
width: 750 # was 470
height: 750 # was 470
arc_width: 64 # was 40
min_value: 10
max_value: 30
adjustable: true
adv_hittest: true
rotation: 90.0
start_angle: 10
end_angle: 170
arc_opa: TRANSP
indicator:
arc_opa: TRANSP
arc_width: 40
knob:
border_color: color_misty_blue
border_width: 7
bg_color: color_dark_gray
bg_opa: 80%
on_release:
- homeassistant.service:
service: climate.turn_on
data:
entity_id: "${thermostat_entity}"
- homeassistant.service:
service: climate.set_temperature
data:
entity_id: "${thermostat_entity}"
temperature: !lambda return x;
- arc:
id: thermostat_arc_current_temp
clickable: false
align: TOP_RIGHT
arc_width: 10
x: 230
y: 250
width: 450
height: 450
min_value: 10
max_value: 30
adjustable: true
adv_hittest: true
rotation: 90.0
start_angle: 10
end_angle: 170
arc_opa: TRANSP
indicator:
arc_opa: TRANSP
knob:
bg_color: color_dark_gray
border_width: 0
bg_opa: 80%
- obj:
width: 140
height: 120
align: TOP_RIGHT
bg_color: color_slate_blue_gray
pad_all: 0
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
widgets:
- label:
id: thermostat_target_temperature_whole
x: -40
y: 240
align: TOP_RIGHT
text_font: nunito_84
text_color: color_steel_blue
text: " "
- label:
id: thermostat_target_temperature_fraction
x: -10
y: 255
align: TOP_RIGHT
text_font: nunito_36
text_color: color_steel_blue
text: " "
- label:
id: thermostat_target_temperature_measurement
x: -12
y: 220
align: TOP_RIGHT
text_font: nunito_30
text_color: color_steel_blue
text: "°C"
- label:
id: thermostat_state_icon
x: -10
y: 150
align: TOP_RIGHT
text_font: icons_48
text_color: color_steel_blue
text: "${heating_icon}"
- label:
id: thermostat_target_temperature_icon
x: -70
y: 300
align: TOP_RIGHT
text_font: icons_28
text_color: color_steel_blue
text: "${temperature_icon}"
- label:
id: thermostat_target_temperature_value
x: -10
y: 300
align: TOP_RIGHT
text_font: nunito_30
text_color: color_steel_blue
text: " "
# Return
- button:
id: thermostat_back
x: 20
y: 400
width: 60
height: 60
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: thermostat_back_label
align: CENTER
text_font: icons_28
text_color: color_misty_blue
text: "${exit_icon}"
on_press:
- lvgl.page.show: devices_page
- lvgl.widget.show: menu_controls_main
script:
- id: thermostat_get_and_set_translated_state
parameters:
state_str: string
then:
- lambda: |-
std::string state = state_str;
auto it = id(climate_translations).find(state);
std::string translated_state = (it != id(climate_translations).end()) ? it->second : state;
lv_label_set_text(id(thermostat_state_label), translated_state.c_str());
select:
- platform: lvgl
widget: language_dropdown
id: thermostat_select_language
on_value:
then:
- delay: 300ms
- script.execute:
id: thermostat_get_and_set_translated_state
state_str: !lambda 'return id(thermostat_sensor_hvac_mode).state;'
esphome:
on_boot:
priority: -100
then:
- if:
condition:
lambda: 'return id(thermostat_sensor_hvac_mode).has_state();'
then:
- script.execute:
id: thermostat_get_and_set_translated_state
state_str: !lambda 'return id(thermostat_sensor_hvac_mode).state;'
api:
on_client_connected:
- if:
condition:
lambda: 'return id(thermostat_sensor_hvac_mode).has_state();'
then:
- script.execute:
id: thermostat_get_and_set_translated_state
state_str: !lambda 'return id(thermostat_sensor_hvac_mode).state;'