This commit is contained in:
2026-03-26 12:10:21 +01:00
parent 1f4970c17c
commit d4d76db890
877 changed files with 631941 additions and 26195 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,870 @@
substitutions:
alarm_panel_entity: "alarm_control_panel.security"
pin_code: "1234"
shield_home_icon: "\U000F068A" # home
shield_away_icon: "\U000F099D" # away
shield_night_icon: "\U000F1828" # night
shield_vacation_icon: "\U000F06BB" # vacation
shield_disarm_icon: "\U000F099E" # disarmed
shield_arming_icon: "\U000F0498" # arming
globals:
- id: alarm_panel_pending_action
type: std::string
restore_value: no
initial_value: ""
lvgl:
pages:
- id: alarm_panel_page
bg_color: color_slate_blue_gray
widgets:
- obj:
id: alarm_panel_state_bg
x: 20
y: 20
width: 440
height: 80
pad_all: 0
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- obj:
id: alarm_panel_state_icon_bg
x: 20
align: LEFT_MID
width: 60
height: 60
radius: 30
pad_all: 0
bg_color: color_blue
bg_opa: 60%
shadow_opa: transp
border_opa: transp
border_width: 0
- label:
id: alarm_panel_state_icon
x: 30
align: LEFT_MID
text_font: mdi_icons_40
text_color: color_blue
text: "${shield_disarm_icon}"
- label:
x: 110
id: alarm_panel_state_text
align: LEFT_MID
text_font: nunito_18
text_color: color_misty_blue
text: " "
- obj:
id: alarm_panel_state_control
hidden: false
x: 20
y: 120
width: 440
height: 260
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- obj:
id: alarm_panel_button_disarmed
width: 200
height: 200
align: center
pad_all: 0
bg_opa: transp
shadow_opa: transp
border_opa: transp
widgets:
- obj:
width: 160
height: 160
align: center
clickable: true
radius: 80
bg_opa: transp
border_opa: transp
border_width: 0
shadow_width: 25
shadow_spread: 4
shadow_color: color_black
pressed:
bg_color: 0x3A3A4C
shadow_width: 7
on_press:
- lambda: |-
id(alarm_panel_pending_action) = std::string("disarm");
- lvgl.widget.show: alarm_panel_keypad_page
- lvgl.widget.hide: alarm_panel_state_bg
- lvgl.widget.hide: alarm_panel_state_control
- obj:
width: 152
height: 152
align: center
clickable: false
radius: 80
bg_opa: transp
border_opa: transp
shadow_width: 8
shadow_color: 0xFFFFFF
shadow_ofs_x: -6
shadow_ofs_y: -3
shadow_opa: 30%
- obj:
width: 152
height: 152
align: center
clickable: false
radius: 80
bg_opa: transp
border_opa: transp
shadow_width: 8
shadow_color: 0x000000
shadow_ofs_x: 6
shadow_ofs_y: 3
- obj:
width: 156
height: 156
align: center
clickable: false
radius: 80
border_opa: transp
bg_color: color_slate_blue_gray
widgets:
- label:
id: alarm_panel_state_btn_disarmed
align: center
text_font: mdi_icons_68
text_color: color_steel_blue
text: "${shield_disarm_icon}"
- obj:
id: alarm_panel_button_home
hidden: true
width: 110
height: 110
x: -60
y: -60
align: center
pad_all: 0
bg_opa: transp
shadow_opa: transp
border_opa: transp
widgets:
- obj:
width: 90
height: 90
align: center
clickable: true
radius: 50
pad_all: 0
bg_opa: transp
border_opa: transp
border_width: 0
shadow_width: 15
shadow_spread: 2
shadow_color: color_black
pressed:
bg_color: 0x3A3A4C
shadow_width: 5
on_press:
- lambda: |-
id(alarm_panel_pending_action) = std::string("arm_home");
- lvgl.widget.show: alarm_panel_keypad_page
- lvgl.widget.hide: alarm_panel_state_bg
- lvgl.widget.hide: alarm_panel_state_control
- obj:
width: 80
height: 80
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: 80
height: 80
align: center
clickable: false
radius: 45
pad_all: 0
bg_opa: transp
border_opa: transp
shadow_width: 4
shadow_color: 0x000000
shadow_ofs_x: 4
shadow_ofs_y: 2
- obj:
width: 85
height: 85
pad_all: 0
align: center
clickable: false
radius: 50
border_opa: transp
bg_color: color_slate_blue_gray
widgets:
- label:
id: alarm_panel_state_btn_home
align: center
text_font: mdi_icons_52
text_color: color_steel_blue
text: "${shield_home_icon}"
- obj:
id: alarm_panel_button_away
hidden: true
width: 110
height: 110
x: 60
y: -60
align: center
pad_all: 0
bg_opa: transp
shadow_opa: transp
border_opa: transp
widgets:
- obj:
width: 90
height: 90
align: center
clickable: true
radius: 50
pad_all: 0
bg_opa: transp
border_opa: transp
border_width: 0
shadow_width: 15
shadow_spread: 2
shadow_color: color_black
pressed:
bg_color: 0x3A3A4C
shadow_width: 5
on_press:
- lambda: |-
id(alarm_panel_pending_action) = std::string("arm_away");
- lvgl.widget.show: alarm_panel_keypad_page
- lvgl.widget.hide: alarm_panel_state_bg
- lvgl.widget.hide: alarm_panel_state_control
- obj:
width: 80
height: 80
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: 80
height: 80
align: center
clickable: false
radius: 45
pad_all: 0
bg_opa: transp
border_opa: transp
shadow_width: 4
shadow_color: 0x000000
shadow_ofs_x: 4
shadow_ofs_y: 2
- obj:
width: 85
height: 85
pad_all: 0
align: center
clickable: false
radius: 50
border_opa: transp
bg_color: color_slate_blue_gray
widgets:
- label:
id: alarm_panel_state_btn_away
align: center
text_font: mdi_icons_52
text_color: color_steel_blue
text: "${shield_away_icon}"
- obj:
id: alarm_panel_button_night
hidden: true
width: 110
height: 110
x: -60
y: 60
align: center
pad_all: 0
bg_opa: transp
shadow_opa: transp
border_opa: transp
widgets:
- obj:
width: 90
height: 90
align: center
clickable: true
radius: 50
pad_all: 0
bg_opa: transp
border_opa: transp
border_width: 0
shadow_width: 15
shadow_spread: 2
shadow_color: color_black
pressed:
bg_color: 0x3A3A4C
shadow_width: 5
on_press:
- lambda: |-
id(alarm_panel_pending_action) = std::string("arm_night");
- lvgl.widget.show: alarm_panel_keypad_page
- lvgl.widget.hide: alarm_panel_state_bg
- lvgl.widget.hide: alarm_panel_state_control
- obj:
width: 80
height: 80
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: 80
height: 80
align: center
clickable: false
radius: 45
pad_all: 0
bg_opa: transp
border_opa: transp
shadow_width: 4
shadow_color: 0x000000
shadow_ofs_x: 4
shadow_ofs_y: 2
- obj:
width: 85
height: 85
pad_all: 0
align: center
clickable: false
radius: 50
border_opa: transp
bg_color: color_slate_blue_gray
widgets:
- label:
id: alarm_panel_state_btn_night
align: center
text_font: mdi_icons_52
text_color: color_steel_blue
text: "${shield_night_icon}"
- obj:
id: alarm_panel_button_vacation
hidden: true
width: 110
height: 110
x: 60
y: 60
align: center
pad_all: 0
bg_opa: transp
shadow_opa: transp
border_opa: transp
widgets:
- obj:
width: 90
height: 90
align: center
clickable: true
radius: 50
pad_all: 0
bg_opa: transp
border_opa: transp
border_width: 0
shadow_width: 15
shadow_spread: 2
shadow_color: color_black
pressed:
bg_color: 0x3A3A4C
shadow_width: 5
on_press:
- lambda: |-
id(alarm_panel_pending_action) = std::string("arm_vacation");
- lvgl.widget.show: alarm_panel_keypad_page
- lvgl.widget.hide: alarm_panel_state_bg
- lvgl.widget.hide: alarm_panel_state_control
- obj:
width: 80
height: 80
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: 80
height: 80
align: center
clickable: false
radius: 45
pad_all: 0
bg_opa: transp
border_opa: transp
shadow_width: 4
shadow_color: 0x000000
shadow_ofs_x: 4
shadow_ofs_y: 2
- obj:
width: 85
height: 85
pad_all: 0
align: center
clickable: false
radius: 50
border_opa: transp
bg_color: color_slate_blue_gray
widgets:
- label:
id: alarm_panel_state_btn_vacation
align: center
text_font: mdi_icons_52
text_color: color_steel_blue
text: "${shield_vacation_icon}"
- obj:
id: alarm_panel_keypad_page
hidden: true
x: 20
y: 20
width: 440
height: 360
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- obj:
width: 400
height: 40
align: top_mid
border_width: 1
border_color: color_steel_blue
pad_all: 0
bg_opa: transp
shadow_opa: transp
radius: 10
widgets:
- label:
id: alarm_panel_keypad_code_label
align: center
text_font: nunito_18
text: "Enter code"
text_color: color_misty_blue
text_align: center
- buttonmatrix:
id: alarm_panel_keypad
width: 400
height: 270
pad_all: 0
bg_opa: transp
border_opa: transp
x: 2
y: 30
align: center
items:
bg_opa: transp
border_color: color_steel_blue
border_width: 1
shadow_opa: transp
text_font: montserrat_20
text_color: color_misty_blue
pressed:
bg_color: color_steel_blue
rows:
- buttons:
- text: 1
control:
no_repeat: true
- text: 2
control:
no_repeat: true
- text: 3
control:
no_repeat: true
- buttons:
- text: 4
control:
no_repeat: true
- text: 5
control:
no_repeat: true
- text: 6
control:
no_repeat: true
- buttons:
- text: 7
control:
no_repeat: true
- text: 8
control:
no_repeat: true
- text: 9
control:
no_repeat: true
- buttons:
- text: "\uF55A"
key_code: "*"
control:
no_repeat: true
- text: 0
control:
no_repeat: true
- text: "\uF00C"
key_code: "#"
control:
no_repeat: true
# Return
- button:
x: 20
y: 400
width: 60
height: 60
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- 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
# alarm panel entity friendly name
- obj:
id: alarm_panel_bg_name
x: -20
y: 400
width: 360
height: 60
align: TOP_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: alarm_panel_label_name
align: CENTER
text_font: nunito_16
text_color: color_misty_blue
text: "friendly name"
key_collector:
- source_id: alarm_panel_keypad
min_length: 4
max_length: 4
end_keys: "#"
end_key_required: true
back_keys: "*"
allowed_keys: "0123456789*#"
timeout: 5s
on_progress:
- if:
condition:
lambda: return (0 != x.compare(std::string{""}));
then:
- lvgl.label.update:
id: alarm_panel_keypad_code_label
text: !lambda 'return x.c_str();'
else:
- lvgl.label.update:
id: alarm_panel_keypad_code_label
text_font: nunito_18
text_color: color_misty_blue
text: "Enter code"
on_result:
- if:
condition:
lambda: 'return x == "${pin_code}";'
then:
# Если код верный, отправляем команду в Home Assistant для снятия с охраны
- if:
condition:
lambda: 'return id(alarm_panel_pending_action) == "disarm";'
then:
- homeassistant.action:
action: alarm_control_panel.alarm_disarm
data:
entity_id: "${alarm_panel_entity}"
code: "${pin_code}"
- if:
condition:
lambda: 'return id(alarm_panel_pending_action) == "arm_home";'
then:
- homeassistant.action:
action: alarm_control_panel.alarm_arm_home
data:
entity_id: "${alarm_panel_entity}"
code: "${pin_code}"
- if:
condition:
lambda: 'return id(alarm_panel_pending_action) == "arm_away";'
then:
- homeassistant.action:
action: alarm_control_panel.alarm_arm_away
data:
entity_id: "${alarm_panel_entity}"
code: "${pin_code}"
- if:
condition:
lambda: 'return id(alarm_panel_pending_action) == "arm_night";'
then:
- homeassistant.action:
action: alarm_control_panel.alarm_arm_night
data:
entity_id: "${alarm_panel_entity}"
code: "${pin_code}"
- if:
condition:
lambda: 'return id(alarm_panel_pending_action) == "arm_vacation";'
then:
- homeassistant.action:
action: alarm_control_panel.alarm_arm_vacation
data:
entity_id: "${alarm_panel_entity}"
code: "${pin_code}"
- lvgl.widget.hide: alarm_panel_keypad_page
- lvgl.widget.show: alarm_panel_state_bg
- lvgl.widget.show: alarm_panel_state_control
# Получаем статус сигнализации из Home Assistant
text_sensor:
- platform: homeassistant
id: alarm_panel_sensor_state
entity_id: ${alarm_panel_entity}
on_value:
then:
- script.execute:
id: alarm_panel_get_and_set_translated_state
state_str: !lambda 'return id(alarm_panel_sensor_state).state;'
- if:
condition:
lambda: 'return x == "disarmed";'
then:
- lvgl.widget.show: alarm_panel_button_home
- lvgl.widget.show: alarm_panel_button_away
- lvgl.widget.show: alarm_panel_button_night
- lvgl.widget.show: alarm_panel_button_vacation
- lvgl.widget.hide: alarm_panel_button_disarmed
- lvgl.label.update:
id: alarm_panel_status
text_color: color_steel_blue
- lvgl.label.update:
id: alarm_panel_state_icon
text_color: color_blue
text: "${shield_disarm_icon}"
- lvgl.obj.update:
id: alarm_panel_state_icon_bg
bg_color: color_blue
else:
- lvgl.label.update:
id: alarm_panel_status
text_color: color_crimson
- lvgl.widget.hide: alarm_panel_button_home
- lvgl.widget.hide: alarm_panel_button_away
- lvgl.widget.hide: alarm_panel_button_night
- lvgl.widget.hide: alarm_panel_button_vacation
- lvgl.widget.show: alarm_panel_button_disarmed
- if:
condition:
lambda: 'return x == "arming";'
then:
- lvgl.label.update:
id: alarm_panel_state_icon
text_color: color_gray
text: "${shield_arming_icon}"
- lvgl.obj.update:
id: alarm_panel_state_icon_bg
bg_color: color_gray
- if:
condition:
lambda: 'return x == "armed_home";'
then:
- lvgl.label.update:
id: alarm_panel_state_icon
text_color: color_green
text: "${shield_home_icon}"
- lvgl.obj.update:
id: alarm_panel_state_icon_bg
bg_color: color_green
- if:
condition:
lambda: 'return x == "armed_away";'
then:
- lvgl.label.update:
id: alarm_panel_state_icon
text_color: color_green
text: "${shield_away_icon}"
- lvgl.obj.update:
id: alarm_panel_state_icon_bg
bg_color: color_green
- if:
condition:
lambda: 'return x == "armed_night";'
then:
- lvgl.label.update:
id: alarm_panel_state_icon
text_color: color_green
text: "${shield_night_icon}"
- lvgl.obj.update:
id: alarm_panel_state_icon_bg
bg_color: color_green
- if:
condition:
lambda: 'return x == "armed_vacation";'
then:
- lvgl.label.update:
id: alarm_panel_state_icon
text_color: color_green
text: "${shield_vacation_icon}"
- lvgl.obj.update:
id: alarm_panel_state_icon_bg
bg_color: color_green
- platform: homeassistant
id: alarm_panel_sensor_name
entity_id: ${alarm_panel_entity}
attribute: friendly_name
on_value:
then:
- lvgl.label.update:
id: alarm_panel_label_name
text: !lambda return x;
script:
- id: alarm_panel_get_and_set_translated_state
parameters:
state_str: string
then:
- lambda: |-
std::string state = state_str;
auto it = id(alarm_panel_translations).find(state);
std::string translated_state = (it != id(alarm_panel_translations).end()) ? it->second : state;
lv_label_set_text(id(alarm_panel_state_text), translated_state.c_str());
select:
- platform: lvgl
widget: language_dropdown
id: alarm_panel_select_language
on_value:
then:
- delay: 300ms
- script.execute:
id: alarm_panel_get_and_set_translated_state
state_str: !lambda 'return id(alarm_panel_sensor_state).state;'
esphome:
on_boot:
priority: -100
then:
- if:
condition:
lambda: 'return id(alarm_panel_sensor_state).has_state();'
then:
- script.execute:
id: alarm_panel_get_and_set_translated_state
state_str: !lambda 'return id(alarm_panel_sensor_state).state;'
api:
on_client_connected:
- if:
condition:
lambda: 'return id(alarm_panel_sensor_state).has_state();'
then:
- script.execute:
id: alarm_panel_get_and_set_translated_state
state_str: !lambda 'return id(alarm_panel_sensor_state).state;'

View File

@@ -0,0 +1,48 @@
- id: color_sky_blue
hex: 3FA7F3
- id: color_slate_blue_gray
hex: 343645
- id: color_steel_blue
hex: 606682
- id: color_misty_blue
hex: 9BA2BC
- id: color_black
hex: 0d0d0d
- id: color_dark_gray
hex: 333333
- id: color_gray
hex: 666666
- id: color_white
hex: f2f0eb
- id: color_red
hex: ff0000
- id: color_crimson
hex: f5075c
- id: color_light_blue
hex: 2fc0ff
- id: color_blue
hex: 4C9FFF
- id: color_yellow
hex: e7c12c
- id: color_light_yellow
hex: f0d45c
- id: color_amber
hex: f4a900
- id: color_mint
hex: 39d19c
- id: color_light_mint
hex: 66dcb3
- id: color_light_green
hex: 00ff00
- id: color_green
hex: 5CA848
- id: color_orange
hex: f07c40
- id: color_deep_orange
hex: ff6600
- id: color_violet
hex: 926BC7
- id: color_dark_blue
hex: 4867aa
- id: color_deep_purple
hex: 543D72

View File

@@ -0,0 +1,194 @@
substitutions:
music_icon: "\U0000e91b"
#shutter_icon: "\U0000e91d"
vacuum_icon: "\U0000e922"
thermostat_icon: "\U0000e925"
#air_conditioner_icon: "\U0000e93b"
packages:
media_player: !include media_player/media_player.yaml
vacuum: !include vacuum/vacuum_widget.yaml
#shutter: !include shutter/shutter_config.yaml
thermostat: !include thermostat/thermostat_widget.yaml
#air_conditioner: !include air_conditioner/air_conditioner_widget.yaml
#alarm_panel: !include alarm_panel/alarm_panel.yaml
lvgl:
pages:
- id: devices_page
bg_color: color_slate_blue_gray
widgets:
- obj:
id: devices_main
y: 20
width: 440
height: 340
pad_all: 0
align: TOP_MID
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
radius: 10
widgets:
- obj:
y: 10
width: 440
height: 150
pad_all: 0
align: TOP_MID
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
radius: 10
widgets:
- button:
id: media_player_page_btn
x: 35
align: LEFT_MID
width: 100
height: 100
radius: 10
bg_color: color_slate_blue_gray
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_steel_blue
text_font: icons_90
text: "${music_icon}"
on_press:
- lvgl.widget.hide: menu_controls_main
- lvgl.page.show:
id: media_player_page
animation: OUT_RIGHT
time: 300ms
# - button:
# id: shutters_page_btn
# x: 170
# align: LEFT_MID
# width: 100
# height: 100
# radius: 10
# bg_color: color_slate_blue_gray
# shadow_opa: TRANSP
# widgets:
# - label:
# align: CENTER
# text_color: color_steel_blue
# text_font: icons_72
# text: "${shutter_icon}"
# on_press:
# - lvgl.page.show:
# id: shutter_group_page
# animation: OUT_RIGHT
# time: 300ms
- button:
id: vacuum_page_btn
x: 305
align: LEFT_MID
width: 100
height: 100
radius: 10
bg_color: color_slate_blue_gray
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_steel_blue
text_font: icons_72
text: "${vacuum_icon}"
on_press:
- lvgl.widget.hide: menu_controls_main
- lvgl.page.show:
id: vacuum_page
animation: OUT_RIGHT
time: 300ms
- obj:
y: 120
width: 440
height: 150
pad_all: 0
align: TOP_MID
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
radius: 10
widgets:
- button:
id: thermostat_page_btn
x: 35
align: LEFT_MID
width: 100
height: 100
radius: 10
bg_color: color_slate_blue_gray
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_steel_blue
text_font: icons_72
text: "${thermostat_icon}"
on_press:
- lvgl.widget.hide: menu_controls_main
- lvgl.page.show:
id: thermostat_page
animation: OUT_RIGHT
time: 300ms
# - button:
# id: air_conditioner_page_btn
# x: 170
# align: LEFT_MID
# width: 100
# height: 100
# radius: 10
# bg_color: color_slate_blue_gray
# shadow_opa: TRANSP
# widgets:
# - label:
# align: CENTER
# text_color: color_steel_blue
# text_font: icons_72
# text: "${air_conditioner_icon}"
# on_press:
# - lvgl.widget.hide: menu_controls_main
# - lvgl.page.show:
# id: air_conditioner_page
# animation: OUT_RIGHT
# time: 300ms
# - button:
# id: alarm_panel_page_btn
# x: 305
# align: LEFT_MID
# width: 100
# height: 100
# radius: 10
# bg_color: color_slate_blue_gray
# shadow_opa: TRANSP
# widgets:
# - label:
# align: CENTER
# text_color: color_steel_blue
# text_font: mdi_icons_72
# text: "${shield_away_icon}"
# on_press:
# - lvgl.widget.hide: menu_controls_main
# - lvgl.page.show:
# id: alarm_panel_page
# animation: OUT_RIGHT
# time: 300ms

346
esphome/widgets/fonts.yaml Normal file
View File

@@ -0,0 +1,346 @@
- file: "fonts/Nunito-SemiBold.ttf"
id: nunito_16
size: 16
bpp: 4
glyphsets:
- GF_Latin_Core
# - GF_Greek_Core
- GF_Cyrillic_Core
- GF_Latin_Vietnamese
glyphs: "²"
extras:
- file: "fonts/Jua-Regular.ttf"
glyphs: [
"\U0000C774",
"\U0000B8E8",
"\U0000B9C8",
]
# # - file: "fonts/MPLUSRounded1c-Regular.ttf"
# # id: mplus_16
# # size: 16
# # bpp: 4
# # glyphs: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя .,!?-_:°²³¹"
- file: "fonts/Nunito-SemiBold.ttf"
id: nunito_18
size: 18
bpp: 4
glyphsets:
- GF_Latin_Core
# - GF_Greek_Core
# - GF_Cyrillic_Core
# - GF_Latin_Vietnamese
- file: "fonts/Nunito-SemiBold.ttf"
id: nunito_20
size: 20
bpp: 4
glyphsets:
- GF_Latin_Core
# - GF_Greek_Core
# - GF_Cyrillic_Core
# - GF_Latin_Vietnamese
- file: "fonts/Nunito-SemiBold.ttf"
id: nunito_30
size: 30
bpp: 4
glyphsets:
- GF_Latin_Core
# - GF_Greek_Core
# - GF_Cyrillic_Core
# - GF_Latin_Vietnamese
# - file: "fonts/Nunito-SemiBold.ttf"
# id: nunito_32
# size: 32
# bpp: 4
# glyphsets:
# - GF_Latin_Core
# # - GF_Greek_Core
# - GF_Cyrillic_Core
# - GF_Latin_Vietnamese
- file: "fonts/Nunito-SemiBold.ttf"
id: nunito_36
size: 36
bpp: 4
glyphsets:
- GF_Latin_Core
# - GF_Greek_Core
# - GF_Cyrillic_Core
# - GF_Latin_Vietnamese
- file: "fonts/Nunito-SemiBold.ttf"
id: nunito_48
size: 48
bpp: 4
glyphsets:
- GF_Latin_Core
# - GF_Greek_Core
# - GF_Cyrillic_Core
# - GF_Latin_Vietnamese
# - file: "fonts/Nunito-SemiBold.ttf"
# id: nunito_64
# size: 64
# bpp: 4
# glyphsets:
# - GF_Latin_Core
# # - GF_Greek_Core
# - GF_Cyrillic_Core
# - GF_Latin_Vietnamese
# - file: "fonts/Nunito-SemiBold.ttf"
# id: nunito_72
# size: 72
# bpp: 4
# glyphsets:
# - GF_Latin_Core
# # - GF_Greek_Core
# - GF_Cyrillic_Core
# - GF_Latin_Vietnamese
- file: "fonts/Nunito-SemiBold.ttf"
id: nunito_84
size: 84
bpp: 4
glyphsets:
- GF_Latin_Core
# - GF_Greek_Core
# - GF_Cyrillic_Core
# - GF_Latin_Vietnamese
- file: "fonts/icons_v2.ttf"
id: icons_24
size: 24
bpp: 4
glyphs: [
"\U0000e900", # brightness
"\U0000e901", # saturation
"\U0000e925", # thermostat
"\U0000e93b", # air_conditioner
"\U0000e936", # heating
"\U0000e92e", # lock
"\U0000e935", # home assistant
"\U0000e931", # wifi signal from 25% to 1%
"\U0000e932", # wifi signal from 50% to 25%
"\U0000e933", # wifi signal from 75% to 50%
"\U0000e934", # wifi signal from 100% to 75% or disable
]
- file: "fonts/icons_v2.ttf"
id: icons_28
size: 28
bpp: 4
glyphs: [
"\U0000e902", # exit
"\U0000e926", # fan
"\U0000e927", # room_plan
"\U0000e938", # humidity
"\U0000e937", # co2
"\U0000e93a", # air quality (tvoc)
"\U0000e939", # temperature
"\U0000e92f", # illumination (lux)
]
- file: "fonts/icons_v2.ttf"
id: icons_32
size: 32
bpp: 4
glyphs: [
"\U0000e91f", # arrow_up
"\U0000e920", # arrow_down
"\U0000e91c", # stop
"\U0000e90e", # volume_off
"\U0000e90f", # volume_on
"\U0000e910", # volume_minus
"\U0000e911", # volume_plus
]
- file: "fonts/icons_v2.ttf"
id: icons_36
size: 36
bpp: 4
glyphs: [
"\U0000e903", # settings
"\U0000e904", # info
"\U0000e905", # devices
"\U0000e906", # home
"\U0000e907", # ceiling
"\U0000e908", # lightbulb
"\U0000e91a", # swipe
"\U0000e92e", # lock
]
- file: "fonts/icons_v2.ttf"
id: icons_38
size: 38
bpp: 4
glyphs: [
"\U0000e909", # power
"\U0000e90c", # back step
"\U0000e90d", # forward step
"\U0000e912", # repeat_all
"\U0000e913", # repeat_off
"\U0000e914", # repeat_one
"\U0000e90a", # play
"\U0000e90b", # pause
"\U0000e91c", # stop
"\U0000e923", # locate
"\U0000e924", # docked
]
- file: "fonts/icons_v2.ttf"
id: icons_48
size: 48
bpp: 4
glyphs: [
"\U0000e90a", # play
"\U0000e90b", # pause
"\U0000e91f", # arrow_up
"\U0000e920", # arrow_down
"\U0000e91c", # stop
"\U0000e923", # locate
"\U0000e924", # docked
"\U0000e93b", # air_conditioner
"\U0000e936", # heating
"\U0000e938", # humidity
"\U0000e939", # temperature
]
- file: "fonts/icons_v2.ttf"
id: icons_72
size: 72
bpp: 4
glyphs: [
"\U0000e91b", # music
"\U0000e91d", # shutter_close
"\U0000e922", # vacuum
"\U0000e925", # thermostat
"\U0000e93b", # air_conditioner
]
- file: "fonts/icons_v2.ttf"
id: icons_90
size: 90
bpp: 4
glyphs: [
"\U0000e908", # lightbulb
"\U0000e915", # spotlights_group
"\U0000e916", # desk_lamp
"\U0000e917", # pendant_lamp
"\U0000e918", # ceiling_lamp
"\U0000e921", # ceiling_lamp_variant
"\U0000e919", # night_lamp
"\U0000e91d", # 0 - shutter_closed
# "\U0000e93d", # 10 - shutter
# "\U0000e93e", # 20 - shutter
# "\U0000e93f", # 30 - shutter
# "\U0000e940", # 40 - shutter
# "\U0000e941", # 50 - shutter
# "\U0000e93c", # 60 - shutter
# "\U0000e943", # 70 - shutter
# "\U0000e944", # 80 - shutter
# "\U0000e942", # 90 - shutter
"\U0000e91e", # 100 - shutter_open
"\U0000e91b", # music
"\U0000e91f", # arrow_up
"\U0000e920", # arrow_down
"\U0000e922", # vacuum
]
# - file: "fonts/icons_v2.ttf"
# id: icons_300
# size: 300
# bpp: 4
# glyphs: [
# "\U0000e91d", # 0 - shutter_closed
# "\U0000e93d", # 10 - shutter
# "\U0000e93e", # 20 - shutter
# "\U0000e93f", # 30 - shutter
# "\U0000e940", # 40 - shutter
# "\U0000e941", # 50 - shutter
# "\U0000e93c", # 60 - shutter
# "\U0000e943", # 70 - shutter
# "\U0000e944", # 80 - shutter
# "\U0000e942", # 90 - shutter
# "\U0000e91e" # 100 - shutter_open
# ]
# - file: "fonts/materialdesignicons-webfont.ttf"
# id: mdi_icons_24
# size: 24
# bpp: 4
# glyphs: [
# "\U000F068A", # shield home
# "\U000F1828", # shield moon
# "\U000F099D", # shield lock
# "\U000F06BB", # shield plane
# "\U000F099E", # shield off
# "\U000F0498", # shield
# ]
- file: "fonts/materialdesignicons-webfont.ttf"
id: mdi_icons_28
size: 28
bpp: 4
glyphs: [
"\U000F0238", # heat
"\U000F0717", # cool
"\U000F0210", # fan
"\U000F1B18", # auto
"\U000F0425", # off
]
- file: "fonts/materialdesignicons-webfont.ttf"
id: mdi_icons_40
size: 40
bpp: 4
glyphs: [
"\U000F068A", # shield home
"\U000F1828", # shield moon
"\U000F099D", # shield lock
"\U000F06BB", # shield plane
"\U000F099E", # shield off
"\U000F0498", # shield
]
- file: "fonts/materialdesignicons-webfont.ttf"
id: mdi_icons_52
size: 52
bpp: 4
glyphs: [
"\U000F068A", # shield home
"\U000F1828", # shield moon
"\U000F099D", # shield lock
"\U000F06BB", # shield plane
"\U000F099E", # shield off
"\U000F0498", # shield
"\U000F0826", # home
"\U000F1A46", # away
]
- file: "fonts/materialdesignicons-webfont.ttf"
id: mdi_icons_68
size: 68
bpp: 4
glyphs: [
"\U000F068A", # shield home
"\U000F1828", # shield moon
"\U000F099D", # shield lock
"\U000F06BB", # shield plane
"\U000F099E", # shield off
"\U000F0498", # shield
]
- file: "fonts/materialdesignicons-webfont.ttf"
id: mdi_icons_72
size: 72
bpp: 4
glyphs: [
"\U000F099D", # shield lock
]

View File

@@ -0,0 +1,3 @@
packages:
home: !include home_widget.yaml
info: !include info_page.yaml

View File

@@ -0,0 +1,768 @@
substitutions:
weather_entity: "weather.forecast_home"
temperature_entity: "sensor.outside_temperature"
humidity_entity: "sensor.outside_humidity"
co2_entity: "sensor.carbon_dioxide"
lock_icon: "\U0000e92e"
air_conditioner_icon: "\U0000e93b"
heating_icon: "\U0000e936"
info_icon: "\U0000e904"
ha_icon: "\U0000e935" # home assistant
wifi_25_icon: "\U0000e931" # wifi signal from 25% to 1%
wifi_50_icon: "\U0000e932" # wifi signal from 50% to 25%
wifi_75_icon: "\U0000e933" # wifi signal from 75% to 50%
wifi_100_icon: "\U0000e934" # wifi signal from 100% to 75% or disable
humidity_icon: "\U0000e938"
co2_icon: "\U0000e937"
tvoc: "\U0000e93a" # air quality
temperature_icon: "\U0000e939"
illumination: "\U0000e92f" # lux
lightbulb_icon: "\U0000e908"
globals:
- id: display_lock
type: bool
restore_value: true
initial_value: "false"
sensor:
# WI-FI Signal
- platform: wifi_signal
id: wifi_signal_percent
update_interval: 30s
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "%"
on_value:
then:
- lvgl.label.update:
id: wifi_status
text_color: !lambda |-
if (id(wifi_signal_percent).state > 0) {
return lv_color_hex(0x5CA848);
}
return lv_color_hex(0x5CA848);
text: !lambda |-
if (id(wifi_signal_percent).state > 0 && id(wifi_signal_percent).state < 26) {
return "${wifi_25_icon}";
} else if (id(wifi_signal_percent).state > 25 && id(wifi_signal_percent).state < 51) {
return "${wifi_50_icon}";
} else if (id(wifi_signal_percent).state > 50 && id(wifi_signal_percent).state < 76) {
return "${wifi_75_icon}";
} else if (id(wifi_signal_percent).state > 75) {
return "${wifi_100_icon}";
}
return "${wifi_100_icon}";
# Weather temperature sensor
- platform: homeassistant
id: weather_temp
entity_id: "${weather_entity}"
attribute: temperature
on_value:
then:
- lvgl.label.update:
id: weather_temperature
text:
format: "%.0f°"
args: [id(weather_temp).state]
# Temperature Home Sensor
- platform: homeassistant
id: temperature_sensor
entity_id: "${temperature_entity}"
on_value:
then:
- lvgl.label.update:
id: temperature_sensor_label
text:
format: "%.1f °C"
args: [id(temperature_sensor).state]
# Humidity Home Sensor
- platform: homeassistant
id: humidity_sensor
entity_id: "${humidity_entity}"
on_value:
then:
- lvgl.label.update:
id: humidity_sensor_label
text:
format: "%.1f %%"
args: [id(humidity_sensor).state]
# CO2 Home Sensor
- platform: homeassistant
id: co2_sensor
entity_id: "${co2_entity}"
on_value:
then:
- lvgl.label.update:
id: co2_sensor_label
text:
format: "%.0f PPM"
args: [id(co2_sensor).state]
text_sensor:
# Sun horizon sensor
- platform: homeassistant
id: sun_state_sensor
entity_id: sun.sun
# Weather state sensor
- platform: homeassistant
id: weather_state_sensor
entity_id: "${weather_entity}"
on_value:
then:
- delay: 1s
- script.execute:
id: weather_get_and_set_translated_state
state_str: !lambda 'return id(weather_state_sensor).state;'
- script.execute: update_weather_image
lvgl:
pages:
- id: home_page
bg_color: color_slate_blue_gray
widgets:
# indicators
- obj:
id: home_bg_indicators
y: 20
width: 440
height: 40
align: TOP_MID
pad_all: 0
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
# date
- label:
id: display_date
x: 20
align: LEFT_MID
text_font: nunito_16
text_color: color_misty_blue
text: "31.01"
# Wi-Fi status
- label:
id: wifi_status
align: RIGHT_MID
x: -20
text: "${wifi_100_icon}"
text_color: color_steel_blue
text_font: icons_24
# Home Assistant status
- label:
id: ha_status
align: RIGHT_MID
x: -60
text: "${ha_icon}"
text_color: color_steel_blue
text_font: icons_24
# Heating status
- label:
id: heating_status
align: RIGHT_MID
x: -100
text: "${heating_icon}"
text_color: color_steel_blue
text_font: icons_24
# AC status
- label:
id: ac_status
align: RIGHT_MID
x: -140
y: 2
text: "${air_conditioner_icon}"
text_color: color_steel_blue
text_font: icons_24
# Lock status
- label:
id: lock_status
align: RIGHT_MID
x: -180
text: "${lock_icon}"
text_color: color_steel_blue
text_font: icons_24
# Alarm panel status
# - label:
# id: alarm_panel_status
# align: RIGHT_MID
# x: -220
# text: "${shield_arming_icon}"
# text_color: color_steel_blue
# text_font: mdi_icons_24
# time
- obj:
id: home_bg_weather_time
x: 20
y: 80
width: 440
height: 220
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: display_time
align: CENTER
text_font: nunito_84 #nunito_64
text_color: color_misty_blue
text: "23:59"
# weather
- obj:
id: home_bg_weather
x: 20
y: 340
width: 440
height: 300
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:
- image:
id: weather_sunny_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: sunny_img
- image:
id: weather_clear_night_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: clear_night_img
- image:
id: weather_cloudy_image
hidden: true
y: 20
x: 10
align: TOP_LEFT
src: cloudy_img
- image:
id: weather_partlycloudy_sun_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: partlycloudy_sun_img
- image:
id: weather_partlycloudy_moon_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: partlycloudy_moon_img
- image:
id: weather_rainy_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: rainy_img
- image:
id: weather_pouring_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: pouring_img
- image:
id: weather_snowy_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: snowy_img
- image:
id: weather_snowy_rainy_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: snowy_rainy_img
- image:
id: weather_fog_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: fog_img
- image:
id: weather_hail_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: hail_img
- image:
id: weather_lightning_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: lightning_img
- image:
id: weather_lightning_rainy_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: lightning_rainy_img
- image:
id: weather_windy_image
hidden: true
y: 10
x: 25
align: TOP_LEFT
src: windy_img
- image:
id: weather_windy_variant_image
hidden: true
y: 10
x: 10
align: TOP_LEFT
src: windy_variant_img
- label:
x: -10
y: 10
align: TOP_RIGHT
id: weather_temperature
text_font: nunito_48
text_color: color_misty_blue
text: "-25°"
- label:
id: weather_state_label
y: -10
width: 180
height: 30
align: BOTTOM_MID
text_font: nunito_16
text_color: color_misty_blue
long_mode: SCROLL_CIRCULAR
text: " "
# sensors
- obj:
id: home_bg_sensors
x: -20
y: 80
width: 200
height: 200
align: TOP_RIGHT
pad_all: 0
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
# temperature
- obj:
y: 20
width: 160
height: 40
align: TOP_MID
pad_all: 0
bg_opa: TRANSP
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
widgets:
- label:
id: temperature_sensor_icon
align: LEFT_MID
x: 20
text: "${temperature_icon}"
text_color: color_green
text_font: icons_28
- label:
id: temperature_sensor_label
align: LEFT_MID
x: 55
text: " "
text_color: color_misty_blue
text_font: nunito_16
# humidity
- obj:
y: 80
width: 160
height: 40
align: TOP_MID
pad_all: 0
bg_opa: TRANSP
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
widgets:
- label:
id: humidity_sensor_icon
align: LEFT_MID
x: 20
text: "${humidity_icon}"
text_color: color_blue
text_font: icons_28
- label:
id: humidity_sensor_label
align: LEFT_MID
x: 55
text: " "
text_color: color_misty_blue
text_font: nunito_16
# co2
- obj:
y: 140
width: 160
height: 40
align: TOP_MID
pad_all: 0
bg_opa: TRANSP
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
widgets:
- label:
id: co2_sensor_icon
align: LEFT_MID
x: 20
text: "${co2_icon}"
text_color: color_yellow
text_font: icons_28
- label:
id: co2_sensor_label
align: LEFT_MID
x: 55
text: " "
text_color: color_misty_blue
text_font: nunito_16
# display lock
- obj:
id: home_bg_display_lock
x: 480
y: 580
width: 60
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: display_lock_btn
align: CENTER
text_font: icons_36
text_color: color_misty_blue
text: "${lock_icon}"
on_long_press:
then:
- if:
condition:
lambda: 'return id(display_lock) == false;'
then:
- switch.turn_on: display_lock_switch
else:
- switch.turn_off: display_lock_switch
# display backlight off
- obj:
id: home_bg_display_backlight_off
x: 550
y: 580
width: 60
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: display_backlight_off_btn
align: CENTER
text_font: icons_36
text_color: color_misty_blue
text: "${lightbulb_icon}"
on_click:
- delay: 1s
- light.turn_off: display_backlight
- lvgl.pause:
# display info
- obj:
id: home_bg_display_info
x: 620
y: 580
width: 60
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: display_info_btn
align: CENTER
text_font: icons_36
text_color: color_misty_blue
text: "${info_icon}"
on_press:
- lvgl.page.show:
id: system_info_page
animation: OUT_RIGHT
time: 300ms
time:
- platform: sntp
id: sntp_time
timezone: Europe/Moscow
servers:
- ntp0.ntp-servers.net
- ntp1.ntp-servers.net
- ntp2.ntp-servers.net
on_time_sync:
- script.execute: time_update
on_time:
- minutes: '*'
seconds: 0
then:
- script.execute: time_update
script:
- id: time_update
then:
- lvgl.label.update:
id: display_time
text: !lambda |-
static char time_buf[16];
auto now = id(sntp_time).now();
snprintf(time_buf, sizeof(time_buf), "%02d:%02d", now.hour, now.minute);
return time_buf;
- lvgl.label.update:
id: display_date
text: !lambda |-
static char date_buf[16];
auto now = id(sntp_time).now();
snprintf(date_buf, sizeof(date_buf), "%02d.%02d", now.day_of_month, now.month);
return date_buf;
- id: weather_get_and_set_translated_state
parameters:
state_str: string
then:
- lambda: |-
std::string state = state_str;
auto it = id(weather_translations).find(state);
std::string translated_state = (it != id(weather_translations).end()) ? it->second : state;
lv_label_set_text(id(weather_state_label), translated_state.c_str());
- id: update_weather_image
then:
- lambda: |-
std::string weather = id(weather_state_sensor).state;
bool is_night = (id(sun_state_sensor).state == "below_horizon");
if (weather == "cloudy") {
lv_obj_clear_flag(id(weather_cloudy_image), LV_OBJ_FLAG_HIDDEN);
} else if (weather == "partlycloudy") {
if (is_night) {
lv_obj_clear_flag(id(weather_partlycloudy_moon_image), LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_clear_flag(id(weather_partlycloudy_sun_image), LV_OBJ_FLAG_HIDDEN);
}
} else if (weather == "sunny" || weather == "clear-night") {
if (is_night) {
lv_obj_clear_flag(id(weather_clear_night_image), LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_clear_flag(id(weather_sunny_image), LV_OBJ_FLAG_HIDDEN);
}
} else if (weather == "rainy") {
lv_obj_clear_flag(id(weather_rainy_image), LV_OBJ_FLAG_HIDDEN);
} else if (weather == "lightning-rainy") {
lv_obj_clear_flag(id(weather_lightning_rainy_image), LV_OBJ_FLAG_HIDDEN);
} else if (weather == "lightning") {
lv_obj_clear_flag(id(weather_lightning_image), LV_OBJ_FLAG_HIDDEN);
} else if (weather == "pouring") {
lv_obj_clear_flag(id(weather_pouring_image), LV_OBJ_FLAG_HIDDEN);
} else if (weather == "snowy") {
lv_obj_clear_flag(id(weather_snowy_image), LV_OBJ_FLAG_HIDDEN);
} else if (weather == "snowy-rainy") {
lv_obj_clear_flag(id(weather_snowy_rainy_image), LV_OBJ_FLAG_HIDDEN);
} else if (weather == "fog") {
lv_obj_clear_flag(id(weather_fog_image), LV_OBJ_FLAG_HIDDEN);
} else if (weather == "hail") {
lv_obj_clear_flag(id(weather_hail_image), LV_OBJ_FLAG_HIDDEN);
} else if (weather == "windy") {
lv_obj_clear_flag(id(weather_windy_image), LV_OBJ_FLAG_HIDDEN);
} else if (weather == "windy-variant") {
lv_obj_clear_flag(id(weather_windy_variant_image), LV_OBJ_FLAG_HIDDEN);
}
select:
- platform: lvgl
widget: language_dropdown
id: weather_select_language
on_value:
then:
- lambda: |-
ESP_LOGI("LANGUAGE", "Language changed, updating weather translations");
- delay: 300ms
- script.execute:
id: weather_get_and_set_translated_state
state_str: !lambda 'return id(weather_state_sensor).state;'
- lambda: |-
ESP_LOGI("LANGUAGE", "Weather translations updated");
esphome:
on_boot:
priority: -100
then:
- if:
condition:
lambda: 'return id(weather_state_sensor).has_state();'
then:
- script.execute:
id: weather_get_and_set_translated_state
state_str: !lambda 'return id(weather_state_sensor).state;'
# - if:
# condition:
# lambda: 'return id(display_lock) == true;'
# then:
# - switch.turn_on: display_lock_switch
# else:
# - switch.turn_off: display_lock_switch
api:
on_client_connected:
- if:
condition:
lambda: 'return (0 == client_info.find("Home Assistant "));'
then:
- lvgl.label.update:
id: ha_status
text_color: color_blue
- if:
condition:
lambda: 'return id(weather_state_sensor).has_state();'
then:
- script.execute:
id: weather_get_and_set_translated_state
state_str: !lambda 'return id(weather_state_sensor).state;'
# - if:
# condition:
# lambda: 'return id(display_lock) == true;'
# then:
# - switch.turn_on: display_lock_switch
# else:
# - switch.turn_off: display_lock_switch
on_client_disconnected:
- if:
condition:
lambda: 'return (0 == client_info.find("Home Assistant "));'
then:
- lvgl.label.update:
id: ha_status
text_color: color_steel_blue
switch:
- platform: template
name: "Touchscreen block"
id: display_lock_switch
lambda: |-
return id(display_lock);
turn_on_action:
- lambda: 'id(display_lock) = true;'
- lvgl.button.update:
id: home_page_btn
clickable: false
- lvgl.button.update:
id: lights_group_page_btn
clickable: false
- lvgl.button.update:
id: devices_page_btn
clickable: false
- lvgl.button.update:
id: settings_page_btn
clickable: false
- lvgl.label.update:
id: lock_status
text_color: color_green
turn_off_action:
- lambda: 'id(display_lock) = false;'
- lvgl.button.update:
id: home_page_btn
clickable: true
- lvgl.button.update:
id: lights_group_page_btn
clickable: true
- lvgl.button.update:
id: devices_page_btn
clickable: true
- lvgl.button.update:
id: settings_page_btn
clickable: true
- lvgl.label.update:
id: lock_status
text_color: color_steel_blue

View File

@@ -0,0 +1,380 @@
# System sensors
sensor:
# 1. Total SRAM memory usage
- platform: template
name: "SRAM Usage"
id: sram_usage
icon: mdi:memory
unit_of_measurement: "KB"
device_class: data_size
state_class: measurement
accuracy_decimals: 1
update_interval: 10s
lambda: |-
// Total SRAM size for ESP32-S3 = 512KB
const size_t total_sram = 512 * 1024;
size_t free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
size_t used_sram = total_sram - free_sram;
return used_sram / 1024.0; // Return used memory in KB
on_value:
then:
- lvgl.bar.update:
id: sram_memory_bar
value: !lambda |-
const size_t total_sram = 512 * 1024; // 512KB for ESP32-S3
size_t free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
size_t used_sram = total_sram - free_sram;
return (int)(((double)used_sram / total_sram) * 100);
- lvgl.label.update:
id: sram_memory_label
text: !lambda |-
const size_t total_sram = 512 * 1024;
size_t free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
size_t used_sram = total_sram - free_sram;
static char sram_buf[32];
snprintf(sram_buf, sizeof(sram_buf), "%.1f/%.0f KB",
used_sram/1024.0, total_sram/1024.0);
return sram_buf;
# 2. Total PSRAM memory usage
- platform: template
name: "PSRAM Usage"
id: psram_usage
icon: mdi:memory
unit_of_measurement: "MB"
device_class: data_size
state_class: measurement
accuracy_decimals: 1
update_interval: 10s
lambda: |-
// Total PSRAM size = 8MB
const size_t total_psram = 8 * 1024 * 1024;
size_t free_psram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
// If PSRAM is not available, return 0
if (heap_caps_get_total_size(MALLOC_CAP_SPIRAM) == 0) {
return 0.0;
}
size_t used_psram = total_psram - free_psram;
return used_psram / (1024.0 * 1024.0); // Return used memory in MB
on_value:
then:
- lvgl.bar.update:
id: psram_memory_bar
value: !lambda |-
const size_t total_psram = 8 * 1024 * 1024; // 8MB
size_t free_psram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
if (heap_caps_get_total_size(MALLOC_CAP_SPIRAM) == 0) return 0;
size_t used_psram = total_psram - free_psram;
return (int)(((double)used_psram / total_psram) * 100);
- lvgl.label.update:
id: psram_memory_label
text: !lambda |-
const size_t total_psram = 8 * 1024 * 1024;
size_t free_psram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
static char psram_buf[32];
if (heap_caps_get_total_size(MALLOC_CAP_SPIRAM) == 0) {
snprintf(psram_buf, sizeof(psram_buf), "No PSRAM");
} else {
size_t used_psram = total_psram - free_psram;
snprintf(psram_buf, sizeof(psram_buf), "%.1f/%.0f MB",
used_psram/(1024.0*1024.0), total_psram/(1024.0*1024.0));
}
return psram_buf;
# 3. Chip temperature
- platform: internal_temperature
name: "ESP32 Internal Temperature"
id: esp32_temperature
update_interval: 30s
on_value:
then:
- lvgl.label.update:
id: chip_temperature_label
text:
format: "%.1f°C"
args: [id(esp32_temperature).state]
# 4. WiFi Signal Strength in dBm
- platform: wifi_signal
name: "WiFi Signal"
id: wifi_signal_db
update_interval: 30s
unit_of_measurement: "dBm"
on_value:
then:
- lvgl.label.update:
id: wifi_signal_label
text:
format: "%.0f dBm"
args: [id(wifi_signal_db).state]
# Text sensors for WiFi information
text_sensor:
# 5. IP address
- platform: wifi_info
ip_address:
name: "IP Address"
id: wifi_ip_address
on_value:
then:
- lvgl.label.update:
id: ip_address_label
text: !lambda return id(wifi_ip_address).state.c_str();
# 6. MAC address
- platform: wifi_info
mac_address:
name: "WiFi MAC"
id: wifi_mac_address
on_value:
then:
- lvgl.label.update:
id: wifi_mac_label
text: !lambda return id(wifi_mac_address).state.c_str();
# 7. ESPHome version with compilation date
- platform: template
name: "ESPHome Version"
id: esphome_version
lambda: |-
return {ESPHOME_VERSION};
on_value:
then:
- lvgl.label.update:
id: esphome_version_label
text: !lambda 'return id(esphome_version).state;'
# LVGL interface for displaying system information
lvgl:
pages:
- id: system_info_page
bg_color: color_slate_blue_gray
widgets:
- obj:
id: system_info_container
x: 20
y: 20
width: 440
height: 340
align: TOP_LEFT
pad_all: 0
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
radius: 10
widgets:
# SRAM memory (show used from total)
- obj:
y: 10
width: 400
height: 50
align: TOP_MID
bg_opa: TRANSP
pad_all: 0
border_opa: TRANSP
widgets:
- label:
align: TOP_LEFT
text: "SRAM Used:"
text_font: nunito_16
text_color: color_misty_blue
- label:
id: sram_memory_label
align: TOP_RIGHT
text: "0/512 KB"
text_font: nunito_16
text_color: color_green
- bar:
id: sram_memory_bar
y: 30
width: 390
height: 12
align: TOP_MID
pad_all: 0
min_value: 0
max_value: 100
value: 0
bg_color: color_gray
bg_opa: 50%
indicator:
bg_color: color_green
# PSRAM memory (show used from total)
- obj:
y: 70
width: 400
height: 50
align: TOP_MID
bg_opa: TRANSP
pad_all: 0
border_opa: TRANSP
widgets:
- label:
align: TOP_LEFT
text: "PSRAM Used:"
text_font: nunito_16
text_color: color_misty_blue
- label:
id: psram_memory_label
align: TOP_RIGHT
text: "0/8 MB"
text_font: nunito_16
text_color: color_blue
- bar:
id: psram_memory_bar
y: 30
width: 390
height: 12
align: TOP_MID
pad_all: 0
min_value: 0
max_value: 100
value: 0
bg_color: color_gray
bg_opa: 50%
indicator:
bg_color: color_blue
# Chip temperature
- obj:
y: 130
width: 400
height: 30
align: TOP_MID
pad_all: 0
bg_opa: TRANSP
border_opa: TRANSP
widgets:
- label:
align: LEFT_MID
text: "CPU Temperature:"
text_font: nunito_16
text_color: color_misty_blue
- label:
id: chip_temperature_label
align: RIGHT_MID
text: "0°C"
text_font: nunito_16
text_color: color_yellow
# WiFi signal
- obj:
y: 170
width: 400
height: 30
align: TOP_MID
pad_all: 0
bg_opa: TRANSP
border_opa: TRANSP
widgets:
- label:
align: LEFT_MID
text: "WiFi Signal:"
text_font: nunito_16
text_color: color_misty_blue
- label:
id: wifi_signal_label
align: RIGHT_MID
text: "0 dBm"
text_font: nunito_16
text_color: color_misty_blue
# IP Address
- obj:
y: 210
width: 400
height: 30
align: TOP_MID
pad_all: 0
bg_opa: TRANSP
border_opa: TRANSP
widgets:
- label:
align: LEFT_MID
text: "IP Address:"
text_font: nunito_16
text_color: color_misty_blue
- label:
id: ip_address_label
align: RIGHT_MID
text: "192.168.3.3"
text_font: nunito_16
text_color: color_misty_blue
# MAC Address
- obj:
y: 250
width: 400
height: 30
align: TOP_MID
pad_all: 0
bg_opa: TRANSP
border_color: color_white
border_width: 1
border_opa: TRANSP
widgets:
- label:
align: LEFT_MID
text: "MAC Address:"
text_font: nunito_16
text_color: color_misty_blue
- label:
id: wifi_mac_label
align: RIGHT_MID
text: "11:22:A1:B2:C3:D4"
text_font: nunito_16
text_color: color_misty_blue
# ESPHome version
- obj:
y: 290
width: 400
height: 30
align: TOP_MID
pad_all: 0
bg_opa: TRANSP
border_opa: TRANSP
widgets:
- label:
align: LEFT_MID
text: "ESPHome Version:"
text_font: nunito_16
text_color: color_misty_blue
- label:
id: esphome_version_label
align: RIGHT_MID
text: "2020.0.0"
text_font: nunito_16
text_color: color_misty_blue
# Interval for periodic logging
interval:
- interval: 60s
then:
- lambda: |-
// Log real memory usage
const size_t total_sram = 512 * 1024;
const size_t total_psram = 8 * 1024 * 1024;
size_t free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
size_t used_sram = total_sram - free_sram;
size_t free_psram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
size_t used_psram = total_psram - free_psram;
ESP_LOGI("system_info", "SRAM: %u/%u KB used (%.1f%%)",
used_sram/1024, total_sram/1024,
(used_sram * 100.0) / total_sram);
if (heap_caps_get_total_size(MALLOC_CAP_SPIRAM) > 0) {
ESP_LOGI("system_info", "PSRAM: %u/%u MB used (%.1f%%)",
used_psram/(1024*1024), total_psram/(1024*1024),
(used_psram * 100.0) / total_psram);
} else {
ESP_LOGI("system_info", "PSRAM: Not available");
}

413
esphome/widgets/image.yaml Normal file
View File

@@ -0,0 +1,413 @@
#############################
########### LOGO ############
#############################
- file: 'img/loading/homeassistant.png'
id: ha_img
type: rgb565
resize: 400x82
transparency: alpha_channel
- file: 'img/loading/esphome.png'
id: esphome_img
type: rgb565
resize: 320x95
transparency: alpha_channel
#############################
########## LIGHT ############
#############################
- file: 'img/light/lightbulb.png'
id: lightbulb_image
type: rgb565
transparency: alpha_channel
- file: 'img/light/rgb_ring.png'
id: rgb_img
type: rgb565
transparency: alpha_channel
#############################
########## VACUUM ###########
#############################
- file: 'img/vacuum/vacuum.png'
id: vacuum_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_0_10.png'
id: vacuum_0_10_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_0_20.png'
id: vacuum_0_20_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_0_30.png'
id: vacuum_0_30_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_5.png'
id: vacuum_5_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_10.png'
id: vacuum_10_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_15.png'
id: vacuum_15_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_20.png'
id: vacuum_20_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_20_10.png'
id: vacuum_20_10_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_20_20.png'
id: vacuum_20_20_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_20_30.png'
id: vacuum_20_30_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_355.png'
id: vacuum_355_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_350.png'
id: vacuum_350_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_345.png'
id: vacuum_345_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_340.png'
id: vacuum_340_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_340_10.png'
id: vacuum_340_10_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_340_20.png'
id: vacuum_340_20_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/vacuum/vacuum_340_30.png'
id: vacuum_340_30_img
type: rgb565
resize: 200x200
transparency: alpha_channel
#############################
########## WEATHER ##########
#############################
- file: 'img/weather/sunny.png'
id: sunny_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/clear_night.png'
id: clear_night_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/cloudy.png'
id: cloudy_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/partlycloudy_sun.png'
id: partlycloudy_sun_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/partlycloudy_moon.png'
id: partlycloudy_moon_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/rainy.png'
id: rainy_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/pouring.png'
id: pouring_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/snowy.png'
id: snowy_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/snowy_rainy.png'
id: snowy_rainy_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/fog.png'
id: fog_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/hail.png'
id: hail_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/lightning.png'
id: lightning_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/lightning_rainy.png'
id: lightning_rainy_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/windy.png'
id: windy_img
type: rgb565
resize: 200x200
transparency: alpha_channel
- file: 'img/weather/windy_variant.png'
id: windy_variant_img
type: rgb565
resize: 200x200
transparency: alpha_channel
#############################
########## FLAGS ############
#############################
- file: 'img/flags/de.png'
id: flags_de_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/es.png'
id: flags_es_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/fr.png'
id: flags_fr_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/gb.png'
id: flags_gb_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/it.png'
id: flags_it_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/kr.png'
id: flags_kr_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/pt.png'
id: flags_pt_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/ru.png'
id: flags_ru_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/us.png'
id: flags_us_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/pl.png'
id: flags_pl_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/tr.png'
id: flags_tr_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/sv.png'
id: flags_sv_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/id.png'
id: flags_id_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/vi.png'
id: flags_vi_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/ro.png'
id: flags_ro_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/nl.png'
id: flags_nl_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/hu.png'
id: flags_hu_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/cs.png'
id: flags_cs_img
type: rgb565
resize: 60x80
transparency: alpha_channel
- file: 'img/flags/fi.png'
id: flags_fi_img
type: rgb565
resize: 60x80
transparency: alpha_channel
#############################
########## BATTERY ##########
#############################
- file: 'img/battery/battery_very_high.png'
id: battery_very_high_img
type: rgb565
transparency: alpha_channel
- file: 'img/battery/battery_high.png'
id: battery_high_img
type: rgb565
transparency: alpha_channel
- file: 'img/battery/battery_middle.png'
id: battery_middle_img
type: rgb565
transparency: alpha_channel
- file: 'img/battery/battery_low.png'
id: battery_low_img
type: rgb565
transparency: alpha_channel
- file: 'img/battery/battery_very_low.png'
id: battery_very_low_img
type: rgb565
transparency: alpha_channel
- file: 'img/battery/battery_empty.png'
id: battery_empty_img
type: rgb565
transparency: alpha_channel
- file: 'img/battery/battery_80.png'
id: battery_80_img
type: rgb565
transparency: alpha_channel
- file: 'img/battery/battery_60.png'
id: battery_60_img
type: rgb565
transparency: alpha_channel
- file: 'img/battery/battery_40.png'
id: battery_40_img
type: rgb565
transparency: alpha_channel
- file: 'img/battery/battery_20.png'
id: battery_20_img
type: rgb565
transparency: alpha_channel
- file: 'img/battery/battery_0.png'
id: battery_0_img
type: rgb565
transparency: alpha_channel

View File

@@ -0,0 +1,292 @@
substitutions:
exit_icon: "\U0000e902"
globals:
- id: ${widget_name}_light_is_on
type: bool
initial_value: 'false'
- id: ${widget_name}_current_brightness
type: float
initial_value: '1.0'
- id: ${widget_name}_updating_from_ha
type: bool
initial_value: 'false'
- id: ${widget_name}_ha_data_received
type: bool
initial_value: 'false'
- id: ${widget_name}_sensors_ready_count
type: int
initial_value: '0'
sensor:
# Brightness sensor
- platform: homeassistant
id: ${widget_name}_light_rgb_sensor_brightness
entity_id: "${light_entity}"
attribute: brightness
on_value:
- if:
condition:
lambda: 'return !std::isnan(x);'
then:
- lambda: |-
static bool first_brightness_received = false;
if (!first_brightness_received) {
first_brightness_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha);'
then:
- globals.set:
id: ${widget_name}_current_brightness
value: !lambda return x;
- lvgl.slider.update:
id: ${widget_name}_light_rgb_brightness_slider
value: !lambda return int(x);
else:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha);'
then:
- globals.set:
id: ${widget_name}_current_brightness
value: '0.0'
- lvgl.slider.update:
id: ${widget_name}_light_rgb_brightness_slider
value: 0
- script.execute: ${widget_name}_check_all_data_ready
text_sensor:
- platform: homeassistant
id: ${widget_name}_light_rgb_state
entity_id: "${light_entity}"
on_value:
- lambda: |-
bool is_on = (x == "on");
bool was_on = id(${widget_name}_light_is_on);
id(${widget_name}_light_is_on) = is_on;
static bool first_state_received = false;
if (!first_state_received) {
first_state_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- if:
condition:
lambda: 'return id(${widget_name}_light_is_on);'
then:
- lvgl.button.update:
id: ${widget_name}_light_rgb_light_on
bg_color: color_amber
shadow_color: color_amber
shadow_opa: 100%
- lvgl.label.update:
id: ${widget_name}_light_on
text_color: color_amber
else:
- lvgl.button.update:
id: ${widget_name}_light_rgb_light_on
bg_color: color_white
shadow_opa: TRANSP
- lvgl.label.update:
id: ${widget_name}_light_on
text_color: color_steel_blue
- script.execute: ${widget_name}_check_all_data_ready
- platform: homeassistant
id: ${widget_name}_light_rgb_name
entity_id: "${light_entity}"
attribute: friendly_name
on_value:
then:
- lvgl.label.update:
id: ${widget_name}_light_rgb_lable_name
text: !lambda return x;
lvgl:
pages:
- id: ${widget_name}_light_rgb_page
bg_color: color_slate_blue_gray
widgets:
- button:
id: ${widget_name}_light_rgb_bg_name
y: 20
width: 440
height: 40
align: TOP_MID
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${widget_name}_light_rgb_lable_name
align: CENTER
text_font: nunito_16
text_color: color_misty_blue
text: "lightbulb"
- button:
id: ${widget_name}_light_rgb_bg_lightbulb
x: 20
y: 80
width: 200
height: 280
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- button:
id: ${widget_name}_light_rgb_light_on
width: 70
height: 70
y: 74
align: TOP_MID
bg_color: color_white
shadow_color: color_white
shadow_ofs_y: -3
shadow_spread: 3
shadow_width: 15
radius: 40
- image:
y: 30
align: CENTER
src: lightbulb_image
id: ${widget_name}_lightbulb_image
- button:
id: ${widget_name}_light_rgb_light_on_btn
width: 200
height: 280
align: CENTER
bg_opa: TRANSP
shadow_opa: TRANSP
on_press:
- homeassistant.service:
service: light.toggle
data:
entity_id: "${light_entity}"
- obj:
id: ${widget_name}_light_rgb_bg_slider
x: -20
y: 80
width: 220
height: 380
align: TOP_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 20
widgets:
- button:
id: ${widget_name}_light_rgb_brightness_gradient
radius: 20
width: 100
height: 300
align: CENTER
bg_color: color_slate_blue_gray
bg_opa: 100%
shadow_opa: TRANSP
- slider:
id: ${widget_name}_light_rgb_brightness_slider
radius: 20
width: 100
height: 300
align: CENTER
bg_opa: TRANSP
min_value: 3
max_value: 255
indicator:
bg_color: color_amber
radius: 10
knob:
bg_opa: TRANSP
on_value:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);'
then:
- globals.set:
id: ${widget_name}_current_brightness
value: !lambda return x;
- if:
condition:
lambda: 'return !id(${widget_name}_light_is_on) && x > 3;'
then:
- homeassistant.service:
service: light.turn_on
data:
entity_id: "${light_entity}"
brightness: !lambda return int(x);
on_release:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- if:
condition:
lambda: 'return x <= 3;'
then:
- homeassistant.service:
service: light.turn_off
data:
entity_id: "${light_entity}"
else:
- homeassistant.service:
service: light.turn_on
data:
entity_id: "${light_entity}"
brightness: !lambda return int(x);
- button:
id: ${widget_name}_light_rgb_exit_on_btn
x: 20
y: -20
width: 200
height: 80
align: BOTTOM_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${widget_name}_light_rgb_exit_on_icon
align: CENTER
text_font: icons_28
text_color: color_misty_blue
text: "${exit_icon}"
on_press:
then:
- lvgl.widget.show: menu_controls_main
- lvgl.page.show:
id: lights_group_page
animation: OUT_RIGHT
time: 300ms
script:
- id: ${widget_name}_check_all_data_ready
mode: restart
then:
- delay: 500ms
- lambda: |-
if (id(${widget_name}_sensors_ready_count) >= 2) {
if (!id(${widget_name}_ha_data_received)) {
id(${widget_name}_ha_data_received) = true;
id(${widget_name}_updating_from_ha) = false;
}
}

View File

@@ -0,0 +1,798 @@
substitutions:
brightness_icon: "\U0000e900"
saturation_icon: "\U0000e901"
exit_icon: "\U0000e902"
globals:
- id: ${widget_name}_light_is_on
type: bool
initial_value: 'false'
- id: ${widget_name}_current_hue
type: float
initial_value: '0.0'
- id: ${widget_name}_current_saturation
type: float
initial_value: '0.0'
- id: ${widget_name}_current_brightness
type: float
initial_value: '1.0'
- id: ${widget_name}_current_color_temp
type: float
initial_value: '3000.0'
- id: ${widget_name}_updating_from_ha
type: bool
initial_value: 'false'
- id: ${widget_name}_ha_data_received
type: bool
initial_value: 'false'
- id: ${widget_name}_sensors_ready_count
type: int
initial_value: '0'
- id: ${widget_name}_is_temp_mode
type: bool
initial_value: 'true'
- id: ${widget_name}_mode_switching
type: bool
initial_value: 'false'
sensor:
# Brightness sensor
- platform: homeassistant
id: ${widget_name}_light_rgb_sensor_brightness
entity_id: "${light_entity}"
attribute: brightness
on_value:
- if:
condition:
lambda: 'return !std::isnan(x);'
then:
- lambda: |-
static bool first_brightness_received = false;
if (!first_brightness_received) {
first_brightness_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha);'
then:
- globals.set:
id: ${widget_name}_current_brightness
value: !lambda return x;
- lvgl.slider.update:
id: ${widget_name}_light_rgb_brightness_slider
value: !lambda return int(x);
- lvgl.label.update:
id: ${widget_name}_light_rgb_brightness_state
text: !lambda |-
int percent = int((x / 255.0f) * 100 + 0.5f);
char buf[8];
snprintf(buf, sizeof(buf), "%d%%", percent);
return std::string(buf);
- script.execute: ${widget_name}_check_all_data_ready
# Color temp sensor
- platform: homeassistant
id: ${widget_name}_light_rgb_sensor_color_temp
entity_id: "${light_entity}"
attribute: color_temp_kelvin
on_value:
- if:
condition:
lambda: 'return !std::isnan(x);'
then:
- lambda: |-
static bool first_temp_received = false;
if (!first_temp_received) {
first_temp_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha);'
then:
- globals.set:
id: ${widget_name}_current_color_temp
value: !lambda return x;
- lvgl.slider.update:
id: ${widget_name}_light_rgb_color_temp_slider
value: !lambda return int(x);
- if:
condition:
lambda: 'return id(${widget_name}_is_temp_mode) && !id(${widget_name}_mode_switching);'
then:
- script.execute:
id: ${widget_name}_update_color_temp_preview
kelvin: !lambda return x;
- script.execute: ${widget_name}_check_all_data_ready
text_sensor:
- platform: homeassistant
id: ${widget_name}_light_rgb_state
entity_id: "${light_entity}"
on_value:
- lambda: |-
bool is_on = (x == "on");
bool was_on = id(${widget_name}_light_is_on);
id(${widget_name}_light_is_on) = is_on;
static bool first_state_received = false;
if (!first_state_received) {
first_state_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- if:
condition:
lambda: 'return !id(${widget_name}_light_is_on);'
then:
- lvgl.label.update:
id: ${widget_name}_light_on
text_color: color_steel_blue
- lvgl.button.update:
id: ${widget_name}_light_rgb_light_on
bg_color: color_white
shadow_opa: TRANSP
else:
- lambda: |-
if (id(${widget_name}_mode_switching)) return;
std::string rgb_str = id(${widget_name}_light_rgb_color).state;
size_t start = rgb_str.find('(') + 1;
size_t first_comma = rgb_str.find(',', start);
size_t second_comma = rgb_str.find(',', first_comma + 1);
size_t end = rgb_str.find(')', second_comma);
if (start < first_comma && first_comma < second_comma && second_comma < end) {
int red = atoi(rgb_str.substr(start, first_comma - start).c_str());
int green = atoi(rgb_str.substr(first_comma + 1, second_comma - first_comma - 1).c_str());
int blue = atoi(rgb_str.substr(second_comma + 1, end - second_comma - 1).c_str());
id(${widget_name}_update_light_button_color).execute(red, green, blue);
}
- script.execute: ${widget_name}_check_all_data_ready
- platform: homeassistant
id: ${widget_name}_light_rgb_sensor_hs
entity_id: "${light_entity}"
attribute: hs_color
on_value:
- if:
condition:
lambda: "return x != \"None\" && !x.empty();"
then:
- lambda: |-
static bool first_hs_received = false;
if (!first_hs_received) {
first_hs_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- lambda: |-
id(${widget_name}_updating_from_ha) = true;
std::string s = x;
size_t start = s.find('(') + 1;
size_t comma = s.find(',');
size_t end = s.find(')');
float hue = 0.0f, sat = 0.0f;
if (start < comma && comma < end) {
hue = atof(s.substr(start, comma - start).c_str());
sat = atof(s.substr(comma + 1, end - comma - 1).c_str());
}
id(${widget_name}_current_hue) = hue;
id(${widget_name}_current_saturation) = sat;
- lvgl.arc.update:
id: ${widget_name}_light_rgb_hue_arc
value: !lambda "return id(${widget_name}_current_hue);"
- lvgl.slider.update:
id: ${widget_name}_light_rgb_saturation_slider
value: !lambda "return id(${widget_name}_current_saturation) * 255.0f / 100.0f;"
- lvgl.label.update:
id: ${widget_name}_light_rgb_saturation_state
text: !lambda |-
return (std::to_string((int)id(${widget_name}_current_saturation)) + "%").c_str();
- if:
condition:
lambda: 'return !id(${widget_name}_is_temp_mode) && !id(${widget_name}_mode_switching);'
then:
- script.execute: ${widget_name}_update_light_color_from_current_values
- lambda: |-
id(${widget_name}_updating_from_ha) = false;
else:
- lambda: |-
static bool first_hs_received = false;
if (!first_hs_received) {
first_hs_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- script.execute: ${widget_name}_check_all_data_ready
- platform: homeassistant
id: ${widget_name}_light_rgb_color
entity_id: "${light_entity}"
attribute: rgb_color
- platform: homeassistant
id: ${widget_name}_light_rgb_name
entity_id: "${light_entity}"
attribute: friendly_name
on_value:
then:
- lvgl.label.update:
id: ${widget_name}_light_rgb_lable_name
text: !lambda return x;
lvgl:
gradients:
- id: ${widget_name}_color_temp_gradient
direction: ver
dither: none
stops:
- color: 0xA0C8FF
position: 0
- color: 0xE0F7FF
position: 80
- color: 0xFFFFFF
position: 128
- color: 0xFFFFE0
position: 175
- color: 0xFFD6A0
position: 255
pages:
- id: ${widget_name}_light_rgb_page
bg_color: color_slate_blue_gray
widgets:
- button:
id: ${widget_name}_light_rgb_bg_name
y: 20
width: 440
height: 40
align: TOP_MID
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${widget_name}_light_rgb_lable_name
align: CENTER
text_font: nunito_16
text_color: color_misty_blue
text: "lightbulb"
- button:
id: ${widget_name}_light_rgb_bg_lightbulb
x: 20
y: 80
width: 200
height: 180
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- button:
id: ${widget_name}_light_rgb_light_on
width: 70
height: 70
y: 24
align: TOP_MID
bg_color: color_white
shadow_color: color_white
shadow_ofs_y: -3
shadow_spread: 3
shadow_width: 15
radius: 40
- image:
y: 30
align: CENTER
src: lightbulb_image
id: ${widget_name}_lightbulb_image
- button:
id: ${widget_name}_light_rgb_light_on_btn
width: 200
height: 200
align: CENTER
bg_opa: TRANSP
shadow_opa: TRANSP
on_press:
- homeassistant.service:
service: light.toggle
data:
entity_id: "${light_entity}"
- button:
id: ${widget_name}_light_rgb_mode_btn
x: 20
y: 60
width: 200
height: 40
align: LEFT_MID
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
checkable: true
state:
checked: false
checked:
bg_color: color_misty_blue
bg_opa: 30%
widgets:
- label:
id: ${widget_name}_light_rgb_mode_label
text_color: color_misty_blue
align: CENTER
text_font: nunito_16
text: "TEMP MODE"
on_press:
- lambda: |-
id(${widget_name}_is_temp_mode) = !id(${widget_name}_is_temp_mode);
- if:
condition:
lambda: 'return id(${widget_name}_is_temp_mode);'
then:
- lvgl.label.update:
id: ${widget_name}_light_rgb_mode_label
text: "TEMP MODE"
- script.execute: ${widget_name}_switch_to_temp_mode
else:
- lvgl.label.update:
id: ${widget_name}_light_rgb_mode_label
text: "RGB MODE"
- script.execute: ${widget_name}_switch_to_rgb_mode
- obj:
id: ${widget_name}_light_rgb_bg_slider
x: -20
y: 80
width: 220
height: 240
align: TOP_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- image:
id: ${widget_name}_light_rgb_hue_gradient
align: CENTER
src: rgb_img
hidden: true
- arc:
id: ${widget_name}_light_rgb_hue_arc
arc_opa: TRANSP
arc_width: 50
start_angle: 0
end_angle: 360
adjustable: true
adv_hittest: true
align: CENTER
width: 190
height: 190
min_value: 0
max_value: 360
hidden: true
indicator:
arc_opa: TRANSP
arc_width: 50
knob:
border_color: color_white
border_width: 3
bg_opa: TRANSP
pad_all: -7
on_value:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);'
then:
- globals.set:
id: ${widget_name}_current_hue
value: !lambda "return (float)x;"
- script.execute: ${widget_name}_update_light_color_from_current_values
on_release:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- script.execute: ${widget_name}_send_hs_color_to_ha
- button:
id: ${widget_name}_light_rgb_color_temp_gradient
radius: 20
width: 100
height: 200
align: CENTER
bg_grad: ${widget_name}_color_temp_gradient
bg_grad_dir: ver
shadow_opa: TRANSP
- slider:
id: ${widget_name}_light_rgb_color_temp_slider
radius: 20
width: 100
height: 200
align: CENTER
bg_opa: TRANSP
min_value: 2000
max_value: 6500
indicator:
bg_opa: TRANSP
knob:
bg_opa: TRANSP
on_value:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);'
then:
- globals.set:
id: ${widget_name}_current_color_temp
value: !lambda return x;
- script.execute:
id: ${widget_name}_update_color_temp_preview
kelvin: !lambda return x;
on_release:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- homeassistant.service:
service: light.turn_on
data:
entity_id: "${light_entity}"
color_temp_kelvin: !lambda return int(x);
- obj:
id: ${widget_name}_light_rgb_bg_bs_sliders
x: -20
y: -20
width: 360
height: 120
align: BOTTOM_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- obj:
id: ${widget_name}_light_rgb_brightness_pos
pad_all: 0
width: 320
height: 40
align: CENTER
bg_opa: TRANSP
border_width: 0
widgets:
- label:
id: ${widget_name}_light_rgb_brightness_icon
x: 0
y: 0
align: LEFT_MID
text_font: icons_24
text_color: color_misty_blue
text: "${brightness_icon}"
- label:
id: ${widget_name}_light_rgb_brightness_state
x: 0
y: 0
align: RIGHT_MID
text_font: nunito_16
text_color: color_misty_blue
text: "100%"
- slider:
id: ${widget_name}_light_rgb_brightness_slider
bg_color: color_slate_blue_gray
bg_opa: 100%
align: CENTER
x: -10
y: 1
width: 220
height: 10
min_value: 3
max_value: 255
indicator:
bg_color: color_misty_blue
radius: 30
knob:
bg_color: color_misty_blue
on_value:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);'
then:
- globals.set:
id: ${widget_name}_current_brightness
value: !lambda return x;
- lvgl.label.update:
id: ${widget_name}_light_rgb_brightness_state
text: !lambda |-
int percent = int((x / 255.0f) * 100 + 0.5f);
char buf[8];
snprintf(buf, sizeof(buf), "%d%%", percent);
return std::string(buf);
on_release:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- homeassistant.service:
service: light.turn_on
data:
entity_id: "${light_entity}"
brightness: !lambda return int(x);
- obj:
id: ${widget_name}_light_rgb_saturation_container
pad_all: 0
width: 320
height: 40
align: BOTTOM_MID
bg_opa: TRANSP
border_width: 0
hidden: true
widgets:
- label:
id: ${widget_name}_light_rgb_saturation_icon
x: 0
y: 0
align: LEFT_MID
text_font: icons_24
text_color: color_misty_blue
text: "${saturation_icon}"
- label:
id: ${widget_name}_light_rgb_saturation_state
x: 0
y: 0
align: RIGHT_MID
text_font: nunito_16
text_color: color_misty_blue
text: "100%"
- slider:
id: ${widget_name}_light_rgb_saturation_slider
bg_color: color_slate_blue_gray
bg_opa: 100%
align: CENTER
x: -10
y: 0
width: 220
height: 10
min_value: 1
max_value: 255
indicator:
bg_color: color_misty_blue
knob:
bg_color: color_misty_blue
on_value:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);'
then:
- globals.set:
id: ${widget_name}_current_saturation
value: !lambda "return (float)x * 100.0f / 255.0f;"
- lvgl.label.update:
id: ${widget_name}_light_rgb_saturation_state
text: !lambda |-
int percent = int((float)x * 100.0f / 255.0f + 0.5f);
return (std::to_string(percent) + "%").c_str();
- script.execute: ${widget_name}_update_light_color_from_current_values
on_release:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- script.execute: ${widget_name}_send_hs_color_to_ha
- button:
id: ${widget_name}_light_rgb_exit_on_btn
x: 20
y: -20
width: 60
height: 120
align: BOTTOM_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${widget_name}_light_rgb_exit_on_icon
align: CENTER
text_font: icons_28
text_color: color_misty_blue
text: "${exit_icon}"
on_press:
then:
- lvgl.widget.show: menu_controls_main
- lvgl.page.show:
id: lights_group_page
animation: OUT_RIGHT
time: 300ms
script:
- id: ${widget_name}_check_all_data_ready
mode: restart
then:
- delay: 500ms
- lambda: |-
if (id(${widget_name}_sensors_ready_count) >= 4) {
if (!id(${widget_name}_ha_data_received)) {
id(${widget_name}_ha_data_received) = true;
id(${widget_name}_updating_from_ha) = false;
id(${widget_name}_switch_to_temp_mode).execute();
}
}
- id: ${widget_name}_switch_to_temp_mode
then:
- globals.set:
id: ${widget_name}_mode_switching
value: 'true'
- lvgl.widget.hide: ${widget_name}_light_rgb_hue_arc
- lvgl.widget.hide: ${widget_name}_light_rgb_hue_gradient
- lvgl.widget.hide: ${widget_name}_light_rgb_saturation_container
- lvgl.widget.show: ${widget_name}_light_rgb_color_temp_slider
- lvgl.widget.show: ${widget_name}_light_rgb_color_temp_gradient
- lvgl.obj.update:
id: ${widget_name}_light_rgb_brightness_pos
align: CENTER
- delay: 500ms
- globals.set:
id: ${widget_name}_mode_switching
value: 'false'
- id: ${widget_name}_switch_to_rgb_mode
then:
- globals.set:
id: ${widget_name}_mode_switching
value: 'true'
- lvgl.widget.show: ${widget_name}_light_rgb_hue_arc
- lvgl.widget.show: ${widget_name}_light_rgb_hue_gradient
- lvgl.widget.show: ${widget_name}_light_rgb_saturation_container
- lvgl.widget.hide: ${widget_name}_light_rgb_color_temp_slider
- lvgl.widget.hide: ${widget_name}_light_rgb_color_temp_gradient
- lvgl.obj.update:
id: ${widget_name}_light_rgb_brightness_pos
align: TOP_MID
- delay: 500ms
- globals.set:
id: ${widget_name}_mode_switching
value: 'false'
- id: ${widget_name}_update_light_color_from_current_values
then:
- if:
condition:
lambda: 'return id(${widget_name}_light_is_on) && !id(${widget_name}_mode_switching);'
then:
- script.execute:
id: ${widget_name}_hsv_to_rgb_and_update
hue: !lambda "return id(${widget_name}_current_hue);"
saturation: !lambda "return id(${widget_name}_current_saturation);"
brightness: 1.0
- id: ${widget_name}_update_color_temp_preview
parameters:
kelvin: float
then:
- if:
condition:
lambda: 'return id(${widget_name}_light_is_on) && !id(${widget_name}_mode_switching);'
then:
- lambda: |-
float temp = kelvin / 100.0f;
float r, g, b;
if (temp <= 66.0f) {
r = 255.0f;
g = 99.4708025861f * logf(temp) - 161.1195681661f;
if (temp <= 19.0f) {
b = 0.0f;
} else {
b = 138.5177312231f * logf(temp - 10.0f) - 305.0447927307f;
}
} else {
r = 329.698727446f * powf(temp - 60.0f, -0.1332047592f);
g = 288.1221695283f * powf(temp - 60.0f, -0.0755148492f);
b = 255.0f;
}
r = std::clamp(r, 0.0f, 255.0f);
g = std::clamp(g, 0.0f, 255.0f);
b = std::clamp(b, 0.0f, 255.0f);
int red = static_cast<int>(r);
int green = static_cast<int>(g);
int blue = static_cast<int>(b);
id(${widget_name}_update_light_button_color).execute(red, green, blue);
- id: ${widget_name}_update_light_button_color
parameters:
red: int
green: int
blue: int
then:
- if:
condition:
lambda: 'return id(${widget_name}_light_is_on);'
then:
- lvgl.button.update:
id: ${widget_name}_light_rgb_light_on
bg_color: !lambda |-
return lv_color_make(red, green, blue);
shadow_color: !lambda |-
return lv_color_make(red, green, blue);
shadow_opa: 100%
- lvgl.label.update:
id: ${widget_name}_light_on
text_color: !lambda |-
return lv_color_make(red, green, blue);
- id: ${widget_name}_hsv_to_rgb_and_update
parameters:
hue: float
saturation: float
brightness: float
then:
- if:
condition:
lambda: 'return id(${widget_name}_light_is_on);'
then:
- lambda: |-
float h = hue;
float s = saturation / 100.0f;
float v = brightness;
float c = v * s;
float x_val = c * (1.0f - abs(fmod(h / 60.0f, 2.0f) - 1.0f));
float m = v - c;
float r, g, b;
if (h >= 0 && h < 60) {
r = c; g = x_val; b = 0;
} else if (h >= 60 && h < 120) {
r = x_val; g = c; b = 0;
} else if (h >= 120 && h < 180) {
r = 0; g = c; b = x_val;
} else if (h >= 180 && h < 240) {
r = 0; g = x_val; b = c;
} else if (h >= 240 && h < 300) {
r = x_val; g = 0; b = c;
} else {
r = c; g = 0; b = x_val;
}
int red = (int)((r + m) * 255.0f);
int green = (int)((g + m) * 255.0f);
int blue = (int)((b + m) * 255.0f);
id(${widget_name}_update_light_button_color).execute(red, green, blue);
- id: ${widget_name}_send_hs_color_to_ha
then:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- homeassistant.service:
service: light.turn_on
data:
entity_id: "${light_entity}"
data_template:
hs_color: "{{ (hue|float, sat|float)|list }}"
variables:
hue: !lambda "return id(${widget_name}_current_hue);"
sat: !lambda "return id(${widget_name}_current_saturation);"

View File

@@ -0,0 +1,815 @@
substitutions:
brightness_icon: "\U0000e900"
saturation_icon: "\U0000e901"
exit_icon: "\U0000e902"
globals:
- id: ${widget_name}_light_is_on
type: bool
initial_value: 'false'
- id: ${widget_name}_current_hue
type: float
initial_value: '0.0'
- id: ${widget_name}_current_saturation
type: float
initial_value: '0.0'
- id: ${widget_name}_current_brightness
type: float
initial_value: '1.0'
- id: ${widget_name}_current_color_temp
type: float
initial_value: '3000.0'
- id: ${widget_name}_updating_from_ha
type: bool
initial_value: 'false'
- id: ${widget_name}_ha_data_received
type: bool
initial_value: 'false'
- id: ${widget_name}_sensors_ready_count
type: int
initial_value: '0'
- id: ${widget_name}_is_temp_mode
type: bool
initial_value: 'true'
- id: ${widget_name}_mode_switching
type: bool
initial_value: 'false'
sensor:
# Brightness sensor
- platform: homeassistant
id: ${widget_name}_light_rgb_sensor_brightness
entity_id: "${light_entity}"
attribute: brightness
on_value:
- if:
condition:
lambda: 'return !std::isnan(x);'
then:
- lambda: |-
static bool first_brightness_received = false;
if (!first_brightness_received) {
first_brightness_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha);'
then:
- globals.set:
id: ${widget_name}_current_brightness
value: !lambda return x;
- lvgl.slider.update:
id: ${widget_name}_light_rgb_brightness_slider
value: !lambda return int(x);
- lvgl.label.update:
id: ${widget_name}_light_rgb_brightness_state
text: !lambda |-
int percent = int((x / 255.0f) * 100 + 0.5f);
char buf[8];
snprintf(buf, sizeof(buf), "%d%%", percent);
return std::string(buf);
- script.execute: ${widget_name}_check_all_data_ready
# Color temp sensor
- platform: homeassistant
id: ${widget_name}_light_rgb_sensor_color_temp
entity_id: "${light_entity}"
attribute: color_temp_kelvin
on_value:
- if:
condition:
lambda: 'return !std::isnan(x);'
then:
- lambda: |-
static bool first_temp_received = false;
if (!first_temp_received) {
first_temp_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha);'
then:
- globals.set:
id: ${widget_name}_current_color_temp
value: !lambda return x;
- lvgl.slider.update:
id: ${widget_name}_light_rgb_color_temp_slider
value: !lambda return int(x);
- if:
condition:
lambda: 'return id(${widget_name}_is_temp_mode) && !id(${widget_name}_mode_switching);'
then:
- script.execute:
id: ${widget_name}_update_color_temp_preview
kelvin: !lambda return x;
- script.execute: ${widget_name}_check_all_data_ready
text_sensor:
- platform: homeassistant
id: ${widget_name}_light_rgb_state
entity_id: "${light_entity}"
on_value:
- lambda: |-
bool is_on = (x == "on");
bool was_on = id(${widget_name}_light_is_on);
id(${widget_name}_light_is_on) = is_on;
static bool first_state_received = false;
if (!first_state_received) {
first_state_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- if:
condition:
lambda: 'return !id(${widget_name}_light_is_on);'
then:
- lvgl.label.update:
id: ${widget_name}_light_on
text_color: color_steel_blue
- lvgl.button.update:
id: ${widget_name}_light_rgb_light_on
bg_color: color_white
shadow_opa: TRANSP
else:
- lambda: |-
if (id(${widget_name}_mode_switching)) return;
std::string rgb_str = id(${widget_name}_light_rgb_color).state;
size_t start = rgb_str.find('(') + 1;
size_t first_comma = rgb_str.find(',', start);
size_t second_comma = rgb_str.find(',', first_comma + 1);
size_t end = rgb_str.find(')', second_comma);
if (start < first_comma && first_comma < second_comma && second_comma < end) {
int red = atoi(rgb_str.substr(start, first_comma - start).c_str());
int green = atoi(rgb_str.substr(first_comma + 1, second_comma - first_comma - 1).c_str());
int blue = atoi(rgb_str.substr(second_comma + 1, end - second_comma - 1).c_str());
id(${widget_name}_update_light_button_color).execute(red, green, blue);
}
- script.execute: ${widget_name}_check_all_data_ready
- platform: homeassistant
id: ${widget_name}_light_rgb_sensor_hs
entity_id: "${light_entity}"
attribute: hs_color
on_value:
- if:
condition:
lambda: "return x != \"None\" && !x.empty();"
then:
- lambda: |-
static bool first_hs_received = false;
if (!first_hs_received) {
first_hs_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- lambda: |-
id(${widget_name}_updating_from_ha) = true;
std::string s = x;
size_t start = s.find('(') + 1;
size_t comma = s.find(',');
size_t end = s.find(')');
float hue = 0.0f, sat = 0.0f;
if (start < comma && comma < end) {
hue = atof(s.substr(start, comma - start).c_str());
sat = atof(s.substr(comma + 1, end - comma - 1).c_str());
}
id(${widget_name}_current_hue) = hue;
id(${widget_name}_current_saturation) = sat;
- lvgl.slider.update:
id: ${widget_name}_light_rgb_hue_slider
value: !lambda "return id(${widget_name}_current_hue);"
- lvgl.slider.update:
id: ${widget_name}_light_rgb_saturation_slider
value: !lambda "return id(${widget_name}_current_saturation) * 255.0f / 100.0f;"
- lvgl.label.update:
id: ${widget_name}_light_rgb_saturation_state
text: !lambda |-
return (std::to_string((int)id(${widget_name}_current_saturation)) + "%").c_str();
- if:
condition:
lambda: 'return !id(${widget_name}_is_temp_mode) && !id(${widget_name}_mode_switching);'
then:
- script.execute: ${widget_name}_update_light_color_from_current_values
- lambda: |-
id(${widget_name}_updating_from_ha) = false;
else:
- lambda: |-
static bool first_hs_received = false;
if (!first_hs_received) {
first_hs_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- script.execute: ${widget_name}_check_all_data_ready
- platform: homeassistant
id: ${widget_name}_light_rgb_color
entity_id: "${light_entity}"
attribute: rgb_color
- platform: homeassistant
id: ${widget_name}_light_rgb_name
entity_id: "${light_entity}"
attribute: friendly_name
on_value:
then:
- lvgl.label.update:
id: ${widget_name}_light_rgb_lable_name
text: !lambda return x;
lvgl:
gradients:
- id: ${widget_name}_hue_gradient
direction: ver
dither: none
stops:
- color: 0xFF0000
position: 0
- color: 0xFF00FF
position: 42
- color: 0x0000FF
position: 84
- color: 0x00FFFF
position: 127
- color: 0x00FF00
position: 169
- color: 0xFFFF00
position: 212
- color: 0xFF0000
position: 255
- id: ${widget_name}_color_temp_gradient
direction: ver
dither: none
stops:
- color: 0xA0C8FF
position: 0
- color: 0xE0F7FF
position: 80
- color: 0xFFFFFF
position: 128
- color: 0xFFFFE0
position: 175
- color: 0xFFD6A0
position: 255
pages:
- id: ${widget_name}_light_rgb_page
bg_color: color_slate_blue_gray
widgets:
- button:
id: ${widget_name}_light_rgb_bg_name
y: 20
width: 440
height: 40
align: TOP_MID
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${widget_name}_light_rgb_lable_name
align: CENTER
text_font: nunito_16
text_color: color_misty_blue
text: "lightbulb"
- button:
id: ${widget_name}_light_rgb_bg_lightbulb
x: 20
y: 80
width: 200
height: 180
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- button:
id: ${widget_name}_light_rgb_light_on
width: 70
height: 70
y: 24
align: TOP_MID
bg_color: color_white
shadow_color: color_white
shadow_ofs_y: -3
shadow_spread: 3
shadow_width: 15
radius: 40
- image:
y: 30
align: CENTER
src: lightbulb_image
id: ${widget_name}_lightbulb_image
- button:
id: ${widget_name}_light_rgb_light_on_btn
width: 200
height: 200
align: CENTER
bg_opa: TRANSP
shadow_opa: TRANSP
on_press:
- homeassistant.service:
service: light.toggle
data:
entity_id: "${light_entity}"
- button:
id: ${widget_name}_light_rgb_mode_btn
x: 20
y: 60
width: 200
height: 40
align: LEFT_MID
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
checkable: true
state:
checked: false
checked:
bg_color: color_misty_blue
bg_opa: 30%
widgets:
- label:
id: ${widget_name}_light_rgb_mode_label
text_color: color_misty_blue
align: CENTER
text_font: nunito_16
text: "TEMP MODE"
on_press:
- lambda: |-
id(${widget_name}_is_temp_mode) = !id(${widget_name}_is_temp_mode);
- if:
condition:
lambda: 'return id(${widget_name}_is_temp_mode);'
then:
- lvgl.label.update:
id: ${widget_name}_light_rgb_mode_label
text: "TEMP MODE"
- script.execute: ${widget_name}_switch_to_temp_mode
else:
- lvgl.label.update:
id: ${widget_name}_light_rgb_mode_label
text: "RGB MODE"
- script.execute: ${widget_name}_switch_to_rgb_mode
- obj:
id: ${widget_name}_light_rgb_bg_slider
x: -20
y: 80
width: 220
height: 240
align: TOP_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- button:
id: ${widget_name}_light_rgb_hue_gradient
radius: 20
width: 100
height: 200
align: CENTER
bg_grad: ${widget_name}_hue_gradient
bg_grad_dir: ver
shadow_opa: TRANSP
hidden: true
- slider:
id: ${widget_name}_light_rgb_hue_slider
radius: 20
align: CENTER
bg_opa: TRANSP
width: 100
height: 200
min_value: 0
max_value: 360
hidden: true
indicator:
bg_opa: TRANSP
knob:
bg_opa: TRANSP
on_value:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);'
then:
- globals.set:
id: ${widget_name}_current_hue
value: !lambda "return (float)x;"
- script.execute: ${widget_name}_update_light_color_from_current_values
on_release:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- script.execute: ${widget_name}_send_hs_color_to_ha
- button:
id: ${widget_name}_light_rgb_color_temp_gradient
radius: 20
width: 100
height: 200
align: CENTER
bg_grad: ${widget_name}_color_temp_gradient
bg_grad_dir: ver
shadow_opa: TRANSP
- slider:
id: ${widget_name}_light_rgb_color_temp_slider
radius: 20
width: 100
height: 200
align: CENTER
bg_opa: TRANSP
min_value: 2000
max_value: 6500
indicator:
bg_opa: TRANSP
knob:
bg_opa: TRANSP
on_value:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);'
then:
- globals.set:
id: ${widget_name}_current_color_temp
value: !lambda return x;
- script.execute:
id: ${widget_name}_update_color_temp_preview
kelvin: !lambda return x;
on_release:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- homeassistant.service:
service: light.turn_on
data:
entity_id: "${light_entity}"
color_temp_kelvin: !lambda return int(x);
- obj:
id: ${widget_name}_light_rgb_bg_bs_sliders
x: -20
y: -20
width: 360
height: 120
align: BOTTOM_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- obj:
id: ${widget_name}_light_rgb_brightness_pos
pad_all: 0
width: 320
height: 40
align: CENTER
bg_opa: TRANSP
border_width: 0
widgets:
- label:
id: ${widget_name}_light_rgb_brightness_icon
x: 0
y: 0
align: LEFT_MID
text_font: icons_24
text_color: color_misty_blue
text: "${brightness_icon}"
- label:
id: ${widget_name}_light_rgb_brightness_state
x: 0
y: 0
align: RIGHT_MID
text_font: nunito_16
text_color: color_misty_blue
text: "100%"
- slider:
id: ${widget_name}_light_rgb_brightness_slider
bg_color: color_slate_blue_gray
bg_opa: 100%
align: CENTER
x: -10
y: 1
width: 220
height: 10
min_value: 3
max_value: 255
indicator:
bg_color: color_misty_blue
radius: 30
knob:
bg_color: color_misty_blue
on_value:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);'
then:
- globals.set:
id: ${widget_name}_current_brightness
value: !lambda return x;
- lvgl.label.update:
id: ${widget_name}_light_rgb_brightness_state
text: !lambda |-
int percent = int((x / 255.0f) * 100 + 0.5f);
char buf[8];
snprintf(buf, sizeof(buf), "%d%%", percent);
return std::string(buf);
on_release:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- homeassistant.service:
service: light.turn_on
data:
entity_id: "${light_entity}"
brightness: !lambda return int(x);
- obj:
id: ${widget_name}_light_rgb_saturation_container
pad_all: 0
width: 320
height: 40
align: BOTTOM_MID
bg_opa: TRANSP
border_width: 0
hidden: true
widgets:
- label:
id: ${widget_name}_light_rgb_saturation_icon
x: 0
y: 0
align: LEFT_MID
text_font: icons_24
text_color: color_misty_blue
text: "${saturation_icon}"
- label:
id: ${widget_name}_light_rgb_saturation_state
x: 0
y: 0
align: RIGHT_MID
text_font: nunito_16
text_color: color_misty_blue
text: "100%"
- slider:
id: ${widget_name}_light_rgb_saturation_slider
bg_color: color_slate_blue_gray
bg_opa: 100%
align: CENTER
x: -10
y: 0
width: 220
height: 10
min_value: 1
max_value: 255
indicator:
bg_color: color_misty_blue
knob:
bg_color: color_misty_blue
on_value:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);'
then:
- globals.set:
id: ${widget_name}_current_saturation
value: !lambda "return (float)x * 100.0f / 255.0f;"
- lvgl.label.update:
id: ${widget_name}_light_rgb_saturation_state
text: !lambda |-
int percent = int((float)x * 100.0f / 255.0f + 0.5f);
return (std::to_string(percent) + "%").c_str();
- script.execute: ${widget_name}_update_light_color_from_current_values
on_release:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- script.execute: ${widget_name}_send_hs_color_to_ha
- button:
id: ${widget_name}_light_rgb_exit_on_btn
x: 20
y: -20
width: 60
height: 120
align: BOTTOM_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${widget_name}_light_rgb_exit_on_icon
align: CENTER
text_font: icons_28
text_color: color_misty_blue
text: "${exit_icon}"
on_press:
then:
- lvgl.widget.show: menu_controls_main
- lvgl.page.show:
id: lights_group_page
animation: OUT_RIGHT
time: 300ms
script:
- id: ${widget_name}_check_all_data_ready
mode: restart
then:
- delay: 500ms
- lambda: |-
if (id(${widget_name}_sensors_ready_count) >= 4) {
if (!id(${widget_name}_ha_data_received)) {
id(${widget_name}_ha_data_received) = true;
id(${widget_name}_updating_from_ha) = false;
id(${widget_name}_switch_to_temp_mode).execute();
}
}
- id: ${widget_name}_switch_to_temp_mode
then:
- globals.set:
id: ${widget_name}_mode_switching
value: 'true'
- lvgl.widget.hide: ${widget_name}_light_rgb_hue_slider
- lvgl.widget.hide: ${widget_name}_light_rgb_hue_gradient
- lvgl.widget.hide: ${widget_name}_light_rgb_saturation_container
- lvgl.widget.show: ${widget_name}_light_rgb_color_temp_slider
- lvgl.widget.show: ${widget_name}_light_rgb_color_temp_gradient
- lvgl.obj.update:
id: ${widget_name}_light_rgb_brightness_pos
align: CENTER
- delay: 500ms
- globals.set:
id: ${widget_name}_mode_switching
value: 'false'
- id: ${widget_name}_switch_to_rgb_mode
then:
- globals.set:
id: ${widget_name}_mode_switching
value: 'true'
- lvgl.widget.show: ${widget_name}_light_rgb_hue_slider
- lvgl.widget.show: ${widget_name}_light_rgb_hue_gradient
- lvgl.widget.show: ${widget_name}_light_rgb_saturation_container
- lvgl.widget.hide: ${widget_name}_light_rgb_color_temp_slider
- lvgl.widget.hide: ${widget_name}_light_rgb_color_temp_gradient
- lvgl.obj.update:
id: ${widget_name}_light_rgb_brightness_pos
align: TOP_MID
- delay: 500ms
- globals.set:
id: ${widget_name}_mode_switching
value: 'false'
- id: ${widget_name}_update_light_color_from_current_values
then:
- if:
condition:
lambda: 'return id(${widget_name}_light_is_on) && !id(${widget_name}_mode_switching);'
then:
- script.execute:
id: ${widget_name}_hsv_to_rgb_and_update
hue: !lambda "return id(${widget_name}_current_hue);"
saturation: !lambda "return id(${widget_name}_current_saturation);"
brightness: 1.0
- id: ${widget_name}_update_color_temp_preview
parameters:
kelvin: float
then:
- if:
condition:
lambda: 'return id(${widget_name}_light_is_on) && !id(${widget_name}_mode_switching);'
then:
- lambda: |-
float temp = kelvin / 100.0f;
float r, g, b;
if (temp <= 66.0f) {
r = 255.0f;
g = 99.4708025861f * logf(temp) - 161.1195681661f;
if (temp <= 19.0f) {
b = 0.0f;
} else {
b = 138.5177312231f * logf(temp - 10.0f) - 305.0447927307f;
}
} else {
r = 329.698727446f * powf(temp - 60.0f, -0.1332047592f);
g = 288.1221695283f * powf(temp - 60.0f, -0.0755148492f);
b = 255.0f;
}
r = std::clamp(r, 0.0f, 255.0f);
g = std::clamp(g, 0.0f, 255.0f);
b = std::clamp(b, 0.0f, 255.0f);
int red = static_cast<int>(r);
int green = static_cast<int>(g);
int blue = static_cast<int>(b);
id(${widget_name}_update_light_button_color).execute(red, green, blue);
- id: ${widget_name}_update_light_button_color
parameters:
red: int
green: int
blue: int
then:
- if:
condition:
lambda: 'return id(${widget_name}_light_is_on);'
then:
- lvgl.button.update:
id: ${widget_name}_light_rgb_light_on
bg_color: !lambda |-
return lv_color_make(red, green, blue);
shadow_color: !lambda |-
return lv_color_make(red, green, blue);
shadow_opa: 100%
- lvgl.label.update:
id: ${widget_name}_light_on
text_color: !lambda |-
return lv_color_make(red, green, blue);
- id: ${widget_name}_hsv_to_rgb_and_update
parameters:
hue: float
saturation: float
brightness: float
then:
- if:
condition:
lambda: 'return id(${widget_name}_light_is_on);'
then:
- lambda: |-
float h = hue;
float s = saturation / 100.0f;
float v = brightness;
float c = v * s;
float x_val = c * (1.0f - abs(fmod(h / 60.0f, 2.0f) - 1.0f));
float m = v - c;
float r, g, b;
if (h >= 0 && h < 60) {
r = c; g = x_val; b = 0;
} else if (h >= 60 && h < 120) {
r = x_val; g = c; b = 0;
} else if (h >= 120 && h < 180) {
r = 0; g = c; b = x_val;
} else if (h >= 180 && h < 240) {
r = 0; g = x_val; b = c;
} else if (h >= 240 && h < 300) {
r = x_val; g = 0; b = c;
} else {
r = c; g = 0; b = x_val;
}
int red = (int)((r + m) * 255.0f);
int green = (int)((g + m) * 255.0f);
int blue = (int)((b + m) * 255.0f);
id(${widget_name}_update_light_button_color).execute(red, green, blue);
- id: ${widget_name}_send_hs_color_to_ha
then:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- homeassistant.service:
service: light.turn_on
data:
entity_id: "${light_entity}"
data_template:
hs_color: "{{ (hue|float, sat|float)|list }}"
variables:
hue: !lambda "return id(${widget_name}_current_hue);"
sat: !lambda "return id(${widget_name}_current_saturation);"

View File

@@ -0,0 +1,18 @@
text_sensor:
- platform: homeassistant
id: ${widget_name}_light_rgb_state
entity_id: "${light_entity}"
on_value:
- if:
condition:
lambda: 'return x == "on";'
then:
- lvgl.label.update:
id: ${widget_name}_light_on
text_color: color_amber
else:
- lvgl.label.update:
id: ${widget_name}_light_on
text_color: color_steel_blue

View File

@@ -0,0 +1,470 @@
substitutions:
brightness_icon: "\U0000e900"
exit_icon: "\U0000e902"
globals:
- id: ${widget_name}_light_is_on
type: bool
initial_value: 'false'
- id: ${widget_name}_current_brightness
type: float
initial_value: '1.0'
- id: ${widget_name}_current_color_temp
type: float
initial_value: '3000.0'
- id: ${widget_name}_updating_from_ha
type: bool
initial_value: 'false'
- id: ${widget_name}_ha_data_received
type: bool
initial_value: 'false'
- id: ${widget_name}_sensors_ready_count
type: int
initial_value: '0'
sensor:
# Brightness sensor
- platform: homeassistant
id: ${widget_name}_light_rgb_sensor_brightness
entity_id: "${light_entity}"
attribute: brightness
on_value:
- if:
condition:
lambda: 'return !std::isnan(x);'
then:
- lambda: |-
static bool first_brightness_received = false;
if (!first_brightness_received) {
first_brightness_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha);'
then:
- globals.set:
id: ${widget_name}_current_brightness
value: !lambda return x;
- lvgl.slider.update:
id: ${widget_name}_light_rgb_brightness_slider
value: !lambda return int(x);
- lvgl.label.update:
id: ${widget_name}_light_rgb_brightness_state
text: !lambda |-
int percent = int((x / 255.0f) * 100 + 0.5f);
char buf[8];
snprintf(buf, sizeof(buf), "%d%%", percent);
return std::string(buf);
- script.execute: ${widget_name}_check_all_data_ready
# Color temp sensor
- platform: homeassistant
id: ${widget_name}_light_rgb_sensor_color_temp
entity_id: "${light_entity}"
attribute: color_temp_kelvin
on_value:
- if:
condition:
lambda: 'return !std::isnan(x);'
then:
- lambda: |-
static bool first_temp_received = false;
if (!first_temp_received) {
first_temp_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha);'
then:
- globals.set:
id: ${widget_name}_current_color_temp
value: !lambda return x;
- lvgl.slider.update:
id: ${widget_name}_light_rgb_color_temp_slider
value: !lambda return int(x);
- script.execute:
id: ${widget_name}_update_color_temp_preview
kelvin: !lambda return x;
- script.execute: ${widget_name}_check_all_data_ready
text_sensor:
- platform: homeassistant
id: ${widget_name}_light_rgb_state
entity_id: "${light_entity}"
on_value:
- lambda: |-
bool is_on = (x == "on");
bool was_on = id(${widget_name}_light_is_on);
id(${widget_name}_light_is_on) = is_on;
static bool first_state_received = false;
if (!first_state_received) {
first_state_received = true;
id(${widget_name}_sensors_ready_count) += 1;
}
# Обновляем UI только при выключении
- if:
condition:
lambda: 'return !id(${widget_name}_light_is_on);'
then:
- lvgl.label.update:
id: ${widget_name}_light_on
text_color: color_steel_blue
- lvgl.button.update:
id: ${widget_name}_light_rgb_light_on
bg_color: color_white
shadow_opa: TRANSP
else:
- lambda: |-
std::string rgb_str = id(${widget_name}_light_rgb_color).state;
size_t start = rgb_str.find('(') + 1;
size_t first_comma = rgb_str.find(',', start);
size_t second_comma = rgb_str.find(',', first_comma + 1);
size_t end = rgb_str.find(')', second_comma);
if (start < first_comma && first_comma < second_comma && second_comma < end) {
int red = atoi(rgb_str.substr(start, first_comma - start).c_str());
int green = atoi(rgb_str.substr(first_comma + 1, second_comma - first_comma - 1).c_str());
int blue = atoi(rgb_str.substr(second_comma + 1, end - second_comma - 1).c_str());
id(${widget_name}_update_light_button_color).execute(red, green, blue);
}
- script.execute: ${widget_name}_check_all_data_ready
- platform: homeassistant
id: ${widget_name}_light_rgb_color
entity_id: "${light_entity}"
attribute: rgb_color
- platform: homeassistant
id: ${widget_name}_light_rgb_name
entity_id: "${light_entity}"
attribute: friendly_name
on_value:
then:
- lvgl.label.update:
id: ${widget_name}_light_rgb_lable_name
text: !lambda return x;
lvgl:
gradients:
- id: ${widget_name}_color_temp_gradient
direction: ver
dither: none
stops:
- color: 0xA0C8FF
position: 0
- color: 0xE0F7FF
position: 80
- color: 0xFFFFFF
position: 128
- color: 0xFFFFE0
position: 175
- color: 0xFFD6A0
position: 255
pages:
- id: ${widget_name}_light_rgb_page
bg_color: color_slate_blue_gray
widgets:
- button:
id: ${widget_name}_light_rgb_bg_name
y: 20
width: 440
height: 40
align: TOP_MID
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${widget_name}_light_rgb_lable_name
align: CENTER
text_font: nunito_16
text_color: color_misty_blue
text: "lightbulb"
- button:
id: ${widget_name}_light_rgb_bg_lightbulb
x: 20
y: 80
width: 200
height: 280
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- button:
id: ${widget_name}_light_rgb_light_on
width: 70
height: 70
y: 74
align: TOP_MID
bg_color: color_white
shadow_color: color_white
shadow_ofs_y: -3
shadow_spread: 3
shadow_width: 15
radius: 40
- image:
y: 30
align: CENTER
src: lightbulb_image
id: ${widget_name}_lightbulb_image
- button:
id: ${widget_name}_light_rgb_light_on_btn
width: 200
height: 280
align: CENTER
bg_opa: TRANSP
shadow_opa: TRANSP
on_press:
- homeassistant.service:
service: light.toggle
data:
entity_id: "${light_entity}"
- obj:
id: ${widget_name}_light_rgb_bg_slider
x: -20
y: 80
width: 220
height: 280
align: TOP_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- button:
id: ${widget_name}_light_rgb_color_temp_gradient
radius: 20
width: 100
height: 200
align: CENTER
bg_grad: ${widget_name}_color_temp_gradient
bg_grad_dir: ver
shadow_opa: TRANSP
- slider:
id: ${widget_name}_light_rgb_color_temp_slider
radius: 20
width: 100
height: 200
align: CENTER
bg_opa: TRANSP
min_value: 2000
max_value: 6500
indicator:
bg_opa: TRANSP
knob:
bg_opa: TRANSP
on_value:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);'
then:
- globals.set:
id: ${widget_name}_current_color_temp
value: !lambda return x;
- script.execute:
id: ${widget_name}_update_color_temp_preview
kelvin: !lambda return x;
on_release:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- homeassistant.service:
service: light.turn_on
data:
entity_id: "${light_entity}"
color_temp_kelvin: !lambda return int(x);
- obj:
id: ${widget_name}_light_rgb_bg_bs_sliders
x: -20
y: -20
width: 360
height: 80
align: BOTTOM_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- obj:
id: ${widget_name}_light_rgb_brightness_pos
pad_all: 0
width: 320
height: 40
align: CENTER
bg_opa: TRANSP
border_width: 0
widgets:
- label:
id: ${widget_name}_light_rgb_brightness_icon
x: 0
y: 0
align: LEFT_MID
text_font: icons_24
text_color: color_misty_blue
text: "${brightness_icon}"
- label:
id: ${widget_name}_light_rgb_brightness_state
x: 0
y: 0
align: RIGHT_MID
text_font: nunito_16
text_color: color_misty_blue
text: "100%"
- slider:
id: ${widget_name}_light_rgb_brightness_slider
bg_color: color_slate_blue_gray
bg_opa: 100%
align: CENTER
x: -10
y: 1
width: 220
height: 10
min_value: 3
max_value: 255
indicator:
bg_color: color_misty_blue
radius: 30
knob:
bg_color: color_misty_blue
on_value:
- if:
condition:
lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);'
then:
- globals.set:
id: ${widget_name}_current_brightness
value: !lambda return x;
- lvgl.label.update:
id: ${widget_name}_light_rgb_brightness_state
text: !lambda |-
int percent = int((x / 255.0f) * 100 + 0.5f);
char buf[8];
snprintf(buf, sizeof(buf), "%d%%", percent);
return std::string(buf);
on_release:
- if:
condition:
lambda: 'return id(${widget_name}_ha_data_received);'
then:
- homeassistant.service:
service: light.turn_on
data:
entity_id: "${light_entity}"
brightness: !lambda return int(x);
- button:
id: ${widget_name}_light_rgb_exit_on_btn
x: 20
y: -20
width: 60
height: 80
align: BOTTOM_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${widget_name}_light_rgb_exit_on_icon
align: CENTER
text_font: icons_28
text_color: color_misty_blue
text: "${exit_icon}"
on_press:
then:
- lvgl.widget.show: menu_controls_main
- lvgl.page.show:
id: lights_group_page
animation: OUT_RIGHT
time: 300ms
script:
- id: ${widget_name}_check_all_data_ready
mode: restart
then:
- delay: 500ms
- lambda: |-
if (id(${widget_name}_sensors_ready_count) >= 3) {
if (!id(${widget_name}_ha_data_received)) {
id(${widget_name}_ha_data_received) = true;
id(${widget_name}_updating_from_ha) = false;
}
}
- id: ${widget_name}_update_color_temp_preview
parameters:
kelvin: float
then:
- if:
condition:
lambda: 'return id(${widget_name}_light_is_on);'
then:
- lambda: |-
float temp = kelvin / 100.0f;
float r, g, b;
if (temp <= 66.0f) {
r = 255.0f;
g = 99.4708025861f * logf(temp) - 161.1195681661f;
if (temp <= 19.0f) {
b = 0.0f;
} else {
b = 138.5177312231f * logf(temp - 10.0f) - 305.0447927307f;
}
} else {
r = 329.698727446f * powf(temp - 60.0f, -0.1332047592f);
g = 288.1221695283f * powf(temp - 60.0f, -0.0755148492f);
b = 255.0f;
}
r = std::clamp(r, 0.0f, 255.0f);
g = std::clamp(g, 0.0f, 255.0f);
b = std::clamp(b, 0.0f, 255.0f);
int red = static_cast<int>(r);
int green = static_cast<int>(g);
int blue = static_cast<int>(b);
id(${widget_name}_update_light_button_color).execute(red, green, blue);
- id: ${widget_name}_update_light_button_color
parameters:
red: int
green: int
blue: int
then:
- if:
condition:
lambda: 'return id(${widget_name}_light_is_on);'
then:
- lvgl.button.update:
id: ${widget_name}_light_rgb_light_on
bg_color: !lambda |-
return lv_color_make(red, green, blue);
shadow_color: !lambda |-
return lv_color_make(red, green, blue);
shadow_opa: 100%
- lvgl.label.update:
id: ${widget_name}_light_on
text_color: !lambda |-
return lv_color_make(red, green, blue);

View File

@@ -0,0 +1,46 @@
substitutions:
light_entity_1: "light.lamp_tafel"
light_entity_2: "light.ikea_of_sweden_tradfri_bulb_e27_ww_clear_250lm"
light_entity_3: "light.ikea_of_sweden_stoftmoln_ceiling_wall_lamp_ww37"
light_entity_4: "light.lamp_kast"
light_widget_name_1: "lamptafel"
light_widget_name_2: "lampbank"
light_widget_name_3: "lampzitkamer"
light_widget_name_4: "lampkast"
light_label_name_1: "Tafel"
light_label_name_2: "Bank"
light_label_name_3: "Zithoek"
light_label_name_4: "Kast"
packages:
light_panel: !include lights_panel.yaml
light_1: !include
file: light_rgb_widget.yaml
vars:
light_entity: "${light_entity_1}"
widget_name: "${light_widget_name_1}"
light_2: !include
file: light_rgb_widget_slider.yaml
vars:
light_entity: "${light_entity_2}"
widget_name: "${light_widget_name_2}"
light_3: !include
file: light_temp_color_widget.yaml
vars:
light_entity: "${light_entity_3}"
widget_name: "${light_widget_name_3}"
light_4: !include
file: light_brightness_widget.yaml
vars:
light_entity: "${light_entity_4}"
widget_name: "${light_widget_name_4}"

View File

@@ -0,0 +1,208 @@
substitutions:
exit_icon: "\U0000e902"
settings_icon: "\U0000e903"
info_icon: "\U0000e904"
devices_icon: "\U0000e905"
home_icon: "\U0000e906"
ceiling_icon: "\U0000e907"
lightbulb_icon: "\U0000e908"
spotlight_group_icon: "\U0000e915"
desk_lamp_icon: "\U0000e916"
pendant_lamp_icon: "\U0000e917"
ceiling_lamp_icon: "\U0000e918"
ceiling_lamp_variant_icon: "\U0000e921"
night_lamp_icon: "\U0000e919"
swipe_icon: "\U0000e91a"
lvgl:
pages:
- id: lights_group_page
bg_color: color_slate_blue_gray
widgets:
- button:
id: ${light_widget_name_1}_btn
x: 20
y: 20
width: 210
height: 160
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${light_widget_name_1}_light_on
align: CENTER
text_color: color_steel_blue
text_font: icons_90
text: "${spotlight_group_icon}"
- label:
id: ${light_widget_name_1}_lable_name
align: TOP_MID
text_font: nunito_16
text_color: color_misty_blue
text: "${light_label_name_1}"
- button:
id: ${light_widget_name_2}_btn
x: 250
y: 20
width: 210
height: 160
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${light_widget_name_2}_light_on
align: CENTER
text_color: color_steel_blue
text_font: icons_90
text: "${pendant_lamp_icon}"
- label:
id: ${light_widget_name_2}_lable_name
align: TOP_MID
text_font: nunito_16
text_color: color_misty_blue
text: "${light_label_name_2}"
- button:
id: ${light_widget_name_3}_btn
x: 20
y: 200
width: 210
height: 160
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${light_widget_name_3}_light_on
align: CENTER
text_color: color_steel_blue
text_font: icons_90
text: "${ceiling_lamp_variant_icon}"
- label:
id: ${light_widget_name_3}_lable_name
align: TOP_MID
text_font: nunito_16
text_color: color_misty_blue
text: "${light_label_name_3}"
- button:
id: ${light_widget_name_4}_btn
x: 250
y: 200
width: 210
height: 160
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${light_widget_name_4}_light_on
align: CENTER
text_color: color_steel_blue
text_font: icons_90
text: "${night_lamp_icon}"
- label:
id: ${light_widget_name_4}_lable_name
align: TOP_MID
text_font: nunito_16
text_color: color_misty_blue
text: "${light_label_name_4}"
binary_sensor:
- platform: lvgl
id: ${light_widget_name_1}_btn_long_sensor
widget: ${light_widget_name_1}_btn
on_click:
- min_length: 50ms
max_length: 500ms
then:
- homeassistant.service:
service: light.toggle
data:
entity_id: "${light_entity_1}"
- min_length: 800ms
max_length: 3000ms
then:
- lvgl.widget.hide: menu_controls_main
- lvgl.page.show:
id: ${light_widget_name_1}_light_rgb_page
animation: OUT_RIGHT
time: 300ms
- platform: lvgl
id: ${light_widget_name_2}_btn_long_sensor
widget: ${light_widget_name_2}_btn
on_click:
- min_length: 50ms
max_length: 500ms
then:
- homeassistant.service:
service: light.toggle
data:
entity_id: "${light_entity_2}"
- min_length: 800ms
max_length: 3000ms
then:
- lvgl.widget.hide: menu_controls_main
- lvgl.page.show:
id: ${light_widget_name_2}_light_rgb_page
animation: OUT_RIGHT
time: 300ms
- platform: lvgl
id: ${light_widget_name_3}_btn_long_sensor
widget: ${light_widget_name_3}_btn
on_click:
- min_length: 50ms
max_length: 500ms
then:
- homeassistant.service:
service: light.toggle
data:
entity_id: "${light_entity_3}"
- min_length: 800ms
max_length: 3000ms
then:
- lvgl.widget.hide: menu_controls_main
- lvgl.page.show:
id: ${light_widget_name_3}_light_rgb_page
animation: OUT_RIGHT
time: 300ms
- platform: lvgl
id: ${light_widget_name_4}_btn_long_sensor
widget: ${light_widget_name_4}_btn
on_click:
- min_length: 50ms
max_length: 500ms
then:
- homeassistant.service:
service: light.toggle
data:
entity_id: "${light_entity_4}"
- min_length: 800ms
max_length: 3000ms
then:
- lvgl.widget.hide: menu_controls_main
- lvgl.page.show:
id: ${light_widget_name_4}_light_rgb_page
animation: OUT_RIGHT
time: 300ms

View File

@@ -0,0 +1,139 @@
globals:
- id: ha_connected
type: bool
initial_value: 'false'
lvgl:
top_layer:
widgets:
- button:
id: loading_page
bg_color: color_slate_blue_gray
shadow_opa: TRANSP
width: 100%
height: 100%
widgets:
- image:
y: 30
align: TOP_MID
src: ha_img
- obj:
id: boot_homeassistant
y: 180
width: 300
height: 100
pad_all: 0
align: TOP_MID
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
radius: 10
widgets:
- label:
id: boot_homeassistant_label
align: LEFT_MID
text_font: nunito_20
text_color: color_misty_blue
text: "Connecting to API..."
- spinner:
id: boot_homeassistant_spiner
width: 50
height: 50
align: RIGHT_MID
spin_time: 2s
arc_length: 60deg
arc_width: 5
arc_color: color_steel_blue
indicator:
arc_color: color_misty_blue
arc_width: 5
- obj:
id: boot_synchronization
y: 180
width: 300
height: 60
pad_all: 0
align: TOP_MID
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
radius: 10
hidden: true
widgets:
- label:
id: boot_synchronization_label
align: TOP_MID
y: 0
text_font: nunito_20
text_color: color_misty_blue
text: "Synchronization..."
- bar:
id: boot_synchronization_bar
y: 0
width: 300
value: 0
align: BOTTOM_MID
min_value: 0
max_value: 100
animated: true
bg_color: color_steel_blue
indicator:
bg_color: color_misty_blue
- image:
y: -30
align: BOTTOM_MID
src: esphome_img
script:
- id: check_boot_status
then:
- lambda: |-
if (id(ha_connected)) {
lv_obj_add_flag(id(boot_homeassistant), LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(id(boot_synchronization), LV_OBJ_FLAG_HIDDEN);
id(start_sync_animation).execute();
}
- id: start_sync_animation
then:
- delay: 1s
- lvgl.bar.update:
id: boot_synchronization_bar
value: 50
- delay: 1s
- lvgl.bar.update:
id: boot_synchronization_bar
value: 100
- delay: 500ms
- lvgl.widget.hide: loading_page
- id: on_ha_connected
then:
- lambda: |-
if (!id(ha_connected)) {
id(ha_connected) = true;
lv_label_set_text(id(boot_homeassistant_label), "Home Assistant Connected!");
lv_obj_add_flag(id(boot_homeassistant_spiner), LV_OBJ_FLAG_HIDDEN);
id(check_boot_status).execute();
}
api:
on_client_connected:
- lambda: |-
if (0 == client_info.find("Home Assistant ")) {
id(on_ha_connected).execute();
}
on_client_disconnected:
- lambda: |-
if (0 == client_info.find("Home Assistant ")) {
id(ha_connected) = false;
}

View File

@@ -0,0 +1,560 @@
substitutions:
ha_server: "http://192.168.1.44:8123"
media_player_entity: "media_player.yandex_station_m00bv9j00dc7sg"
power_icon: "\U0000e909"
play_icon: "\U0000e90a"
pause_icon: "\U0000e90b"
back_step_icon: "\U0000e90c"
forward_step_icon: "\U0000e90d"
volume_off_icon: "\U0000e90e"
volume_on_icon: "\U0000e90f"
volume_minus_icon: "\U0000e910"
volume_plus_icon: "\U0000e911"
repeat_all_icon: "\U0000e912"
repeat_off_icon: "\U0000e913"
repeat_one_icon: "\U0000e914"
globals:
- id: repeat_modes
type: std::vector<std::string>
initial_value: '{"all", "one", "off"}'
- id: repeat_icons
type: std::vector<std::string>
initial_value: '{"${repeat_all_icon}", "${repeat_one_icon}", "${repeat_off_icon}"}'
- id: current_repeat_mode_idx
type: int
initial_value: '0'
- id: tmp_repeat_mode
type: std::string
restore_value: no
initial_value: '"all"'
- id: tmp_repeat_icon
type: std::string
restore_value: no
initial_value: '"${repeat_all_icon}"'
- id: cover_url
type: std::string
restore_value: no
initial_value: '""'
- id: duration_slider_user_interaction
type: bool
restore_value: no
initial_value: 'false'
- id: slider_position
type: float
restore_value: no
initial_value: '0.0'
- id: pending_cover_url
type: std::string
restore_value: no
initial_value: '""'
- id: pending_repeat_value
type: std::string
restore_value: no
initial_value: '""'
script:
- id: update_cover_script
mode: restart
then:
- lambda: |-
std::string url = id(pending_cover_url);
id(cover_url) = url;
- homeassistant.action:
action: display_tools.save_media_cover
data:
entity_id: "${media_player_entity}"
size: small
- delay: 1s
- online_image.release: media_image_jpeg
- online_image.set_url:
id: media_image_jpeg
url: "${ha_server}/local/display_tools/cover.jpeg"
- id: update_repeat_mode_script
mode: restart
then:
- lambda: |-
std::string x = id(pending_repeat_value);
auto modes = id(repeat_modes);
auto icons = id(repeat_icons);
for (int i = 0; i < modes.size(); i++) {
if (modes[i] == x) {
id(current_repeat_mode_idx) = i;
id(tmp_repeat_mode) = modes[i];
id(tmp_repeat_icon) = icons[i];
break;
}
}
- lvgl.label.update:
id: media_player_repeat_label
text: !lambda 'return id(tmp_repeat_icon);'
- id: handle_repeat_press_script
mode: restart
then:
- lambda: |-
int idx = id(current_repeat_mode_idx);
auto modes = id(repeat_modes);
auto icons = id(repeat_icons);
idx = (idx + 1) % modes.size();
id(current_repeat_mode_idx) = idx;
std::string new_mode = modes[idx];
std::string new_icon = icons[idx];
id(tmp_repeat_mode) = new_mode;
id(tmp_repeat_icon) = new_icon;
- lvgl.label.update:
id: media_player_repeat_label
text: !lambda 'return id(tmp_repeat_icon);'
- homeassistant.action:
action: media_player.repeat_set
data:
entity_id: "${media_player_entity}"
repeat: !lambda 'return id(tmp_repeat_mode);'
- id: handle_duration_slider_release_script
mode: restart
then:
- homeassistant.service:
service: media_player.media_seek
data:
entity_id: "${media_player_entity}"
seek_position: !lambda 'return std::to_string(int(id(slider_position)));'
- delay: 2s
- lambda: |-
id(duration_slider_user_interaction) = false;
lvgl:
pages:
- id: media_player_page
bg_color: color_slate_blue_gray
widgets:
# Cover
- obj:
id: media_cover
x: 10
y: 10
width: 140
height: 140
align: TOP_LEFT
bg_color: color_steel_blue
bg_image_src: media_image_jpeg
border_color: color_slate_blue_gray
border_width: 10
shadow_opa: TRANSP
radius: 20
# Title/Artist/Duration
- obj:
x: -20
y: 20
width: 300
height: 120
align: TOP_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: media_player_title_label
height: 40
width: 260
long_mode: DOT
y: -25
align: CENTER
text_color: color_misty_blue
text_font: nunito_20
text: "Title"
- label:
id: media_player_artist_label
height: 30
width: 260
long_mode: DOT
y: 3
align: CENTER
text_color: color_misty_blue
text_font: nunito_16
text: "Artist"
- slider:
id: media_player_duration_pos_slider
radius: 2
y: 35
bg_color: color_slate_blue_gray
align: CENTER
width: 260
height: 5
min_value: 0
max_value: 100
indicator:
bg_color: color_misty_blue
radius: 2
knob:
bg_opa: TRANSP
on_press:
- lambda: |-
id(duration_slider_user_interaction) = true;
on_release:
- lambda: |-
id(slider_position) = x;
- script.execute: handle_duration_slider_release_script
# Controls
- obj:
y: 160
width: 440
height: 120
align: TOP_MID
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
# Off
- label:
x: 10
align: LEFT_MID
text_color: color_steel_blue
text_font: icons_38
text: "${power_icon}"
on_press:
- homeassistant.action:
action: media_player.turn_off
data:
entity_id: "${media_player_entity}"
# back
- label:
x: -80
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${back_step_icon}"
on_press:
- homeassistant.action:
action: media_player.media_previous_track
data:
entity_id: "${media_player_entity}"
# play / pause
- obj:
id: media_player_control_play_bg
height: 90
width: 90
bg_color: color_steel_blue
bg_opa: 80%
border_opa: TRANSP
radius: 50
align: CENTER
widgets:
- label:
id: media_player_state_label
align: CENTER
text_color: color_misty_blue
text_font: icons_48
text: "${play_icon}"
on_press:
- if:
condition:
lambda: 'return id(media_player_state).state == "playing";'
then:
- homeassistant.action:
action: media_player.media_pause
data:
entity_id: "${media_player_entity}"
else:
- homeassistant.action:
action: media_player.media_play
data:
entity_id: "${media_player_entity}"
# forward
- label:
x: 80
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${forward_step_icon}"
on_press:
- homeassistant.action:
action: media_player.media_next_track
data:
entity_id: "${media_player_entity}"
# repeat
- label:
id: media_player_repeat_label
x: -10
align: RIGHT_MID
text_color: color_steel_blue
text_font: icons_38
text: "${repeat_all_icon}"
on_press:
- script.execute: handle_repeat_press_script
# Volume
- obj:
y: 300
width: 440
height: 80
align: TOP_MID
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
# mute
- label:
id: media_player_mute_label
x: 10
align: LEFT_MID
text_color: color_steel_blue
text_font: icons_32
text: "${volume_on_icon}"
on_press:
- if:
condition:
lambda: 'return id(media_player_is_volume_muted).state;'
then:
- homeassistant.action:
action: media_player.volume_mute
data:
entity_id: "${media_player_entity}"
is_volume_muted: "false"
else:
- homeassistant.action:
action: media_player.volume_mute
data:
entity_id: "${media_player_entity}"
is_volume_muted: "true"
# vol down
- label:
id: media_player_vol_down_label
x: -280
align: RIGHT_MID
text_color: color_steel_blue
text_font: icons_32
text: "${volume_minus_icon}"
on_press:
- homeassistant.action:
action: media_player.volume_down
data:
entity_id: "${media_player_entity}"
# set vol
- slider:
id: media_player_volume_slider
bg_color: color_slate_blue_gray
bg_opa: 100%
align: RIGHT_MID
x: -60
width: 200
height: 10
min_value: 0
max_value: 100
indicator:
bg_color: color_misty_blue
knob:
bg_color: color_misty_blue
on_release:
- homeassistant.action:
action: media_player.volume_set
data:
entity_id: "${media_player_entity}"
volume_level: !lambda 'return float(x) / 100.0;'
# vol up
- label:
id: media_player_vol_up_label
x: -10
align: RIGHT_MID
text_color: color_steel_blue
text_font: icons_32
text: "${volume_plus_icon}"
on_press:
- homeassistant.action:
action: media_player.volume_up
data:
entity_id: "${media_player_entity}"
# Media player name
- obj:
x: -20
y: 400
width: 360
height: 60
align: TOP_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: media_player_name_label
align: CENTER
long_mode: DOT
text_color: color_misty_blue
text_font: nunito_16
text: "Friendly name"
# Return
- button:
x: 20
y: 400
width: 60
height: 60
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- 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
text_sensor:
- platform: homeassistant
id: media_player_state
entity_id: "${media_player_entity}"
on_value:
- if:
condition:
lambda: 'return x == "paused";'
then:
- lvgl.label.update:
id: media_player_state_label
x: 0
text: "${play_icon}"
else:
- lvgl.label.update:
id: media_player_state_label
x: 0
text: "${pause_icon}"
- platform: homeassistant
id: media_player_cover
entity_id: "${media_player_entity}"
attribute: entity_picture
on_value:
- lambda: |-
id(pending_cover_url) = x;
- script.execute: update_cover_script
- platform: homeassistant
id: media_player_title_sensor
entity_id: "${media_player_entity}"
attribute: media_title
on_value:
- delay: 1s
- lvgl.label.update:
id: media_player_title_label
text: !lambda return x;
- platform: homeassistant
id: media_player_artist_sensor
entity_id: "${media_player_entity}"
attribute: media_artist
on_value:
- delay: 1s
- lvgl.label.update:
id: media_player_artist_label
text: !lambda return x;
- platform: homeassistant
id: media_player_repeat
entity_id: "${media_player_entity}"
attribute: repeat
on_value:
- lambda: |-
id(pending_repeat_value) = x;
- script.execute: update_repeat_mode_script
- platform: homeassistant
id: media_player_friendly_name
entity_id: "${media_player_entity}"
attribute: friendly_name
on_value:
- lvgl.label.update:
id: media_player_name_label
text: !lambda return x;
sensor:
- platform: homeassistant
id: media_player_volume_level
entity_id: "${media_player_entity}"
attribute: volume_level
on_value:
- lvgl.slider.update:
id: media_player_volume_slider
value: !lambda 'return int(x * 100);'
- platform: homeassistant
id: media_player_media_duration
entity_id: "${media_player_entity}"
attribute: media_duration
on_value:
- lambda: |-
lv_slider_set_range(id(media_player_duration_pos_slider), 0, x);
- platform: homeassistant
id: media_player_media_position
entity_id: "${media_player_entity}"
attribute: media_position
on_value:
- if:
condition:
lambda: 'return !id(duration_slider_user_interaction);'
then:
- lvgl.slider.update:
id: media_player_duration_pos_slider
value: !lambda return x;
binary_sensor:
- platform: homeassistant
id: media_player_is_volume_muted
entity_id: "${media_player_entity}"
attribute: is_volume_muted
on_state:
- if:
condition:
lambda: 'return x;'
then:
- lvgl.label.update:
id: media_player_mute_label
text: "${volume_off_icon}"
else:
- lvgl.label.update:
id: media_player_mute_label
text: "${volume_on_icon}"
online_image:
- url: "https://www.example.com/example.jpeg"
format: JPEG
type: RGB565
resize: 120x120
id: media_image_jpeg
on_download_finished:
- lvgl.obj.update:
id: media_cover
bg_image_src: media_image_jpeg

View File

@@ -0,0 +1,151 @@
lvgl:
top_layer:
widgets:
- obj:
id: menu_controls_main
x: 0
y: -20
width: 440
height: 80
pad_all: 10
align: BOTTOM_MID
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
radius: 10
layout:
type: FLEX
flex_align_main: SPACE_AROUND
flex_align_cross: CENTER
widgets:
- button:
id: home_page_btn
clickable: true
width: 60
height: 60
radius: 10
bg_color: color_slate_blue_gray
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_steel_blue
text_font: icons_36
text: "${home_icon}"
on_press:
- lvgl.page.show:
id: home_page
animation: OUT_RIGHT
time: 300ms
- lvgl.button.update:
id: home_page_btn
bg_color: color_slate_blue_gray
bg_opa: 100%
- lvgl.button.update:
id: lights_group_page_btn
bg_opa: TRANSP
- lvgl.button.update:
id: devices_page_btn
bg_opa: TRANSP
- lvgl.button.update:
id: settings_page_btn
bg_opa: TRANSP
- button:
id: lights_group_page_btn
clickable: true
width: 60
height: 60
radius: 10
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_steel_blue
text_font: icons_36
text: "${ceiling_icon}"
on_press:
- lvgl.page.show:
id: lights_group_page
animation: OUT_RIGHT
time: 300ms
- lvgl.button.update:
id: lights_group_page_btn
bg_color: color_slate_blue_gray
bg_opa: 100%
- lvgl.button.update:
id: home_page_btn
bg_opa: TRANSP
- lvgl.button.update:
id: devices_page_btn
bg_opa: TRANSP
- lvgl.button.update:
id: settings_page_btn
bg_opa: TRANSP
- button:
id: devices_page_btn
clickable: true
width: 60
height: 60
radius: 10
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_steel_blue
text_font: icons_36
text: "${devices_icon}"
on_press:
- lvgl.page.show:
id: devices_page
animation: OUT_RIGHT
time: 300ms
- lvgl.button.update:
id: devices_page_btn
bg_color: color_slate_blue_gray
bg_opa: 100%
- lvgl.button.update:
id: home_page_btn
bg_opa: TRANSP
- lvgl.button.update:
id: lights_group_page_btn
bg_opa: TRANSP
- lvgl.button.update:
id: settings_page_btn
bg_opa: TRANSP
- button:
id: settings_page_btn
clickable: true
width: 60
height: 60
radius: 10
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_steel_blue
text_font: icons_36
text: "${settings_icon}"
on_press:
- lvgl.page.show:
id: settings_page
animation: OUT_RIGHT
time: 300ms
- lvgl.button.update:
id: settings_page_btn
bg_color: color_slate_blue_gray
bg_opa: 100%
- lvgl.button.update:
id: home_page_btn
bg_opa: TRANSP
- lvgl.button.update:
id: lights_group_page_btn
bg_opa: TRANSP
- lvgl.button.update:
id: devices_page_btn
bg_opa: TRANSP

View File

@@ -0,0 +1,622 @@
globals:
- id: display_timeout
type: int
restore_value: true
initial_value: "2"
- id: current_language
type: int
restore_value: yes
initial_value: '6'
- id: vacuum_translations
type: std::map<std::string, std::string>
- id: cover_translations
type: std::map<std::string, std::string>
- id: climate_translations
type: std::map<std::string, std::string>
- id: weather_translations
type: std::map<std::string, std::string>
- id: alarm_panel_translations
type: std::map<std::string, std::string>
text_sensor:
- platform: homeassistant
id: translations_lang
entity_id: sensor.display_tools
on_value:
- lambda: |-
std::string lang_code = x;
int lang_index = id(flag_icon_mapping_index).get(lang_code);
if (lang_index != 0 || lang_code == "de") {
id(current_language) = lang_index;
}
- lvgl.dropdown.update:
id: language_dropdown
selected_index: !lambda |-
int index = id(flag_icon_mapping_index).get(x);
if (index != 0 || x == "de") {
return index;
}
return 6;
- lvgl.image.update:
id: flag_language_image
src: !lambda |-
int index = id(flag_icon_mapping_index).get(x);
if (index != 0 || x == "de") {
return id(flag_icon_mapping_image)[index];
}
return id(flag_icon_mapping_image)[6];
- platform: homeassistant
id: translations_vacuum
entity_id: sensor.display_tools
attribute: vacuum
on_value:
then:
- lambda: |-
if (x.length() > 0) {
JsonDocument doc;
DeserializationError error = deserializeJson(doc, x);
if (!error) {
id(vacuum_translations).clear();
for (auto kv : doc.as<JsonObject>()) {
id(vacuum_translations)[kv.key().c_str()] = kv.value().as<std::string>();
}
}
}
- platform: homeassistant
id: translations_cover
entity_id: sensor.display_tools
attribute: cover
on_value:
then:
- lambda: |-
if (x.length() > 0) {
JsonDocument doc;
DeserializationError error = deserializeJson(doc, x);
if (!error) {
id(cover_translations).clear();
for (auto kv : doc.as<JsonObject>()) {
id(cover_translations)[kv.key().c_str()] = kv.value().as<std::string>();
}
}
}
- platform: homeassistant
id: translations_climate
entity_id: sensor.display_tools
attribute: climate
on_value:
then:
- lambda: |-
if (x.length() > 0) {
JsonDocument doc;
DeserializationError error = deserializeJson(doc, x);
if (!error) {
id(climate_translations).clear();
for (auto kv : doc.as<JsonObject>()) {
id(climate_translations)[kv.key().c_str()] = kv.value().as<std::string>();
}
}
}
- platform: homeassistant
id: translations_weather
entity_id: sensor.display_tools
attribute: weather
on_value:
then:
- lambda: |-
if (x.length() > 0) {
JsonDocument doc;
DeserializationError error = deserializeJson(doc, x);
if (!error) {
id(weather_translations).clear();
for (auto kv : doc.as<JsonObject>()) {
id(weather_translations)[kv.key().c_str()] = kv.value().as<std::string>();
}
}
}
- platform: homeassistant
id: translations_alarm_panel
entity_id: sensor.display_tools
attribute: alarm_control_panel
on_value:
then:
- lambda: |-
if (x.length() > 0) {
JsonDocument doc;
DeserializationError error = deserializeJson(doc, x);
if (!error) {
id(alarm_panel_translations).clear();
for (auto kv : doc.as<JsonObject>()) {
id(alarm_panel_translations)[kv.key().c_str()] = kv.value().as<std::string>();
}
}
}
script:
- id: get_translation
parameters:
component_param: string
key_param: string
then:
- lambda: |-
std::string component = component_param;
std::string key = key_param;
std::string result = key;
if (component == "vacuum") {
auto it = id(vacuum_translations).find(key);
if (it != id(vacuum_translations).end()) {
result = it->second;
}
} else if (component == "cover") {
auto it = id(cover_translations).find(key);
if (it != id(cover_translations).end()) {
result = it->second;
}
} else if (component == "climate") {
auto it = id(climate_translations).find(key);
if (it != id(climate_translations).end()) {
result = it->second;
}
} else if (component == "weather") {
auto it = id(weather_translations).find(key);
if (it != id(weather_translations).end()) {
result = it->second;
}
} else if (component == "alarm_panel") {
auto it = id(alarm_panel_translations).find(key);
if (it != id(alarm_panel_translations).end()) {
result = it->second;
}
}
lvgl:
on_idle:
timeout: !lambda "return (id(display_timeout_number).state * 60 * 1000);"
then:
- if:
condition:
lambda: 'return id(display_timeout_number).state >= 0;'
then:
- logger.log: "LVGL is idle"
- light.turn_off: display_backlight
- lvgl.pause:
else:
- logger.log: "LVGL idle, but backlight is on"
pages:
- id: settings_page
bg_color: color_slate_blue_gray
widgets:
# language:
- obj:
id: language_bg
x: 20
y: 20
width: 440
height: 100
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: language_label
x: 20
align: LEFT_MID
text_font: nunito_20
text_color: color_misty_blue
text: "Language"
- image:
id: flag_language_image
x: 160
align: LEFT_MID
src: !lambda return id(flag_icon_mapping_image)[id(current_language)];
- dropdown:
id: language_dropdown
x: 240
width: 200
align: LEFT_MID
options:
- Deutsch # de
- Español # es
- Français # fr
- English (GB) # en-GB
- Italiano # it
- Português # pt
- Русский # ru
- English (US) # en
- Polski # pl
- Türkçe # tr
- Svenska # sv
- Română # ro
- Nederlands # nl
- Magyar # hu
- Bahasa Indonesia # id
- Tiếng Việt # vi
- Čeština # cs
- Suomi # fi
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
text_font: nunito_20
text_color: color_misty_blue
dropdown_list:
bg_color: color_slate_blue_gray
shadow_opa: TRANSP
border_color: color_misty_blue
border_width: 2
text_font: nunito_20
text_color: color_misty_blue
text_line_space: 7
pad_all: 8
max_height: 280
selected:
checked:
bg_color: color_steel_blue
bg_opa: 20%
text_color: color_misty_blue
pressed:
bg_opa: TRANSP
text_color: color_misty_blue
indicator:
text_opa: TRANSP
on_value:
- lvgl.image.update:
id: flag_language_image
src: !lambda return id(flag_icon_mapping_image)[x];
- homeassistant.action:
action: display_tools.get_translations_esphome
data:
language: !lambda return id(flag_icon_mapping_name).get(x).c_str();
category: entity_component
keys: '[
"component.vacuum.entity_component._.state.cleaning",
"component.vacuum.entity_component._.state.docked",
"component.vacuum.entity_component._.state.error",
"component.vacuum.entity_component._.state.idle",
"component.vacuum.entity_component._.state.off",
"component.vacuum.entity_component._.state.on",
"component.vacuum.entity_component._.state.paused",
"component.vacuum.entity_component._.state.returning",
"component.cover.entity_component._.state.closed",
"component.cover.entity_component._.state.closing",
"component.cover.entity_component._.state.open",
"component.cover.entity_component._.state.opening",
"component.cover.entity_component._.state.stopped",
"component.climate.entity_component._.state_attributes.hvac_action.state.cooling",
"component.climate.entity_component._.state_attributes.hvac_action.state.defrosting",
"component.climate.entity_component._.state_attributes.hvac_action.state.drying",
"component.climate.entity_component._.state_attributes.hvac_action.state.fan",
"component.climate.entity_component._.state_attributes.hvac_action.state.heating",
"component.climate.entity_component._.state_attributes.hvac_action.state.idle",
"component.climate.entity_component._.state_attributes.hvac_action.state.off",
"component.weather.entity_component._.state.clear-night",
"component.weather.entity_component._.state.cloudy",
"component.weather.entity_component._.state.exceptional",
"component.weather.entity_component._.state.fog",
"component.weather.entity_component._.state.hail",
"component.weather.entity_component._.state.lightning",
"component.weather.entity_component._.state.lightning-rainy",
"component.weather.entity_component._.state.partlycloudy",
"component.weather.entity_component._.state.pouring",
"component.weather.entity_component._.state.rainy",
"component.weather.entity_component._.state.snowy",
"component.weather.entity_component._.state.snowy-rainy",
"component.weather.entity_component._.state.sunny",
"component.weather.entity_component._.state.windy",
"component.weather.entity_component._.state.windy-variant",
"component.alarm_control_panel.entity_component._.state.armed",
"component.alarm_control_panel.entity_component._.state.armed_away",
"component.alarm_control_panel.entity_component._.state.armed_custom_bypass",
"component.alarm_control_panel.entity_component._.state.armed_home",
"component.alarm_control_panel.entity_component._.state.armed_night",
"component.alarm_control_panel.entity_component._.state.armed_vacation",
"component.alarm_control_panel.entity_component._.state.arming",
"component.alarm_control_panel.entity_component._.state.disarmed",
"component.alarm_control_panel.entity_component._.state.disarming",
"component.alarm_control_panel.entity_component._.state.pending",
"component.alarm_control_panel.entity_component._.state.triggered"
]'
# backlight:
- obj:
id: backlight_bg
x: 20
y: 140
width: 440
height: 100
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: display_backlight_label
x: 20
align: LEFT_MID
text_font: nunito_20
text_color: color_misty_blue
text: "Backlight"
- slider:
id: display_backlight_brightness_slider
x: -20
radius: 10
bg_color: color_slate_blue_gray
align: RIGHT_MID
width: 260
height: 60
min_value: 35
max_value: 100
value: 100
indicator:
bg_color: color_misty_blue
radius: 10
knob:
bg_opa: TRANSP
on_release:
then:
- light.turn_on:
id: display_backlight
brightness: !lambda return int(x)/ 100.0;
# sleep mode:
- obj:
id: sleep_mode_bg
x: 20
y: 260
width: 440
height: 100
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: display_timeout_label
x: 20
align: LEFT_MID
text_font: nunito_20
text_color: color_misty_blue
text: "Sleep mode"
- dropdown:
id: display_timeout_dropdown
x: 220
width: 200
align: LEFT_MID
selected_index: !lambda return id(display_timeout);
options:
- Never
- 1 minute
- 5 minutes
- 10 minutes
- 30 minutes
- 1 hour
- 6 hours
- 12 hours
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
text_font: nunito_20
text_color: color_misty_blue
dropdown_list:
align: CENTER
bg_color: color_slate_blue_gray
shadow_opa: TRANSP
border_color: color_misty_blue
border_width: 2
text_font: nunito_20
text_color: color_misty_blue
text_line_space: 10
pad_all: 8
max_height: 360
selected:
checked:
bg_color: color_steel_blue
bg_opa: 20%
text_color: color_misty_blue
pressed:
bg_opa: TRANSP
text_color: color_misty_blue
indicator:
text_opa: TRANSP
on_value:
then:
- number.set:
id: display_timeout_number
value: !lambda |-
static const int backlight_time[] = {-1, 1, 5, 10, 30, 60, 360, 720};
return backlight_time[x];
- globals.set:
id: display_timeout
value: !lambda return x;
number:
- platform: template
id: display_timeout_number
name: LVGL screen timeout
optimistic: true
unit_of_measurement: "m"
initial_value: 10
restore_value: true
min_value: -1
max_value: 720
step: 1
mode: box
mapping:
- id: flag_icon_mapping_image
from: int
to: image
entries:
0: flags_de_img
1: flags_es_img
2: flags_fr_img
3: flags_gb_img
4: flags_it_img
5: flags_pt_img
6: flags_ru_img
7: flags_us_img
8: flags_pl_img
9: flags_tr_img
10: flags_sv_img
11: flags_ro_img
12: flags_nl_img
13: flags_hu_img
14: flags_id_img
15: flags_vi_img
16: flags_cs_img
17: flags_fi_img
- id: flag_icon_mapping_name
from: int
to: string
entries:
0: de
1: es
2: fr
3: en-GB
4: it
5: pt
6: ru
7: en
8: pl
9: tr
10: sv
11: ro
12: nl
13: hu
14: id
15: vi
16: cs
17: fi
- id: flag_icon_mapping_index
from: string
to: int
entries:
de: 0
es: 1
fr: 2
en-GB: 3
it: 4
pt: 5
ru: 6
en: 7
pl: 8
tr: 9
sv: 10
ro: 11
nl: 12
hu: 13
id: 14
vi: 15
cs: 16
fi: 17
esphome:
on_boot:
- priority: -100
then:
- homeassistant.action:
action: display_tools.get_translations_esphome
data:
language: !lambda return id(flag_icon_mapping_name).get(id(current_language)).c_str();
category: entity_component
keys: '[
"component.vacuum.entity_component._.state.cleaning",
"component.vacuum.entity_component._.state.docked",
"component.vacuum.entity_component._.state.error",
"component.vacuum.entity_component._.state.idle",
"component.vacuum.entity_component._.state.off",
"component.vacuum.entity_component._.state.on",
"component.vacuum.entity_component._.state.paused",
"component.vacuum.entity_component._.state.returning",
"component.cover.entity_component._.state.closed",
"component.cover.entity_component._.state.closing",
"component.cover.entity_component._.state.open",
"component.cover.entity_component._.state.opening",
"component.cover.entity_component._.state.stopped",
"component.climate.entity_component._.state_attributes.hvac_action.state.cooling",
"component.climate.entity_component._.state_attributes.hvac_action.state.defrosting",
"component.climate.entity_component._.state_attributes.hvac_action.state.drying",
"component.climate.entity_component._.state_attributes.hvac_action.state.fan",
"component.climate.entity_component._.state_attributes.hvac_action.state.heating",
"component.climate.entity_component._.state_attributes.hvac_action.state.idle",
"component.climate.entity_component._.state_attributes.hvac_action.state.off",
"component.weather.entity_component._.state.clear-night",
"component.weather.entity_component._.state.cloudy",
"component.weather.entity_component._.state.exceptional",
"component.weather.entity_component._.state.fog",
"component.weather.entity_component._.state.hail",
"component.weather.entity_component._.state.lightning",
"component.weather.entity_component._.state.lightning-rainy",
"component.weather.entity_component._.state.partlycloudy",
"component.weather.entity_component._.state.pouring",
"component.weather.entity_component._.state.rainy",
"component.weather.entity_component._.state.snowy",
"component.weather.entity_component._.state.snowy-rainy",
"component.weather.entity_component._.state.sunny",
"component.weather.entity_component._.state.windy",
"component.weather.entity_component._.state.windy-variant",
"component.alarm_control_panel.entity_component._.state.armed",
"component.alarm_control_panel.entity_component._.state.armed_away",
"component.alarm_control_panel.entity_component._.state.armed_custom_bypass",
"component.alarm_control_panel.entity_component._.state.armed_home",
"component.alarm_control_panel.entity_component._.state.armed_night",
"component.alarm_control_panel.entity_component._.state.armed_vacation",
"component.alarm_control_panel.entity_component._.state.arming",
"component.alarm_control_panel.entity_component._.state.disarmed",
"component.alarm_control_panel.entity_component._.state.disarming",
"component.alarm_control_panel.entity_component._.state.pending",
"component.alarm_control_panel.entity_component._.state.triggered"
]'
- lvgl.dropdown.update:
id: display_timeout_dropdown
selected_index: !lambda return id(display_timeout);
api:
on_client_connected:
- lvgl.dropdown.update:
id: display_timeout_dropdown
selected_index: !lambda return id(display_timeout);

View File

@@ -0,0 +1,46 @@
substitutions:
shutter_entity_1: "cover.hall_window"
shutter_entity_2: "cover.living_room_window"
shutter_entity_3: "cover.hall_window"
shutter_entity_4: "cover.living_room_window"
shutter_widget_name_1: "shutter1"
shutter_widget_name_2: "shutter2"
shutter_widget_name_3: "shutter3"
shutter_widget_name_4: "shutter4"
shutter_label_name_1: "shutter 1"
shutter_label_name_2: "shutter 2"
shutter_label_name_3: "shutter 3"
shutter_label_name_4: "shutter 4"
packages:
shutter_panel: !include shutter_panel.yaml
shutter_1: !include
file: shutter_widget.yaml
vars:
shutter_entity: "${shutter_entity_1}"
shutter_widget_name: "${shutter_widget_name_1}"
shutter_2: !include
file: shutter_widget.yaml
vars:
shutter_entity: "${shutter_entity_2}"
shutter_widget_name: "${shutter_widget_name_2}"
shutter_3: !include
file: shutter_widget.yaml
vars:
shutter_entity: "${shutter_entity_3}"
shutter_widget_name: "${shutter_widget_name_3}"
shutter_4: !include
file: shutter_widget.yaml
vars:
shutter_entity: "${shutter_entity_4}"
shutter_widget_name: "${shutter_widget_name_4}"

View File

@@ -0,0 +1,203 @@
substitutions:
exit_icon: "\U0000e902"
shutter_close_icon: "\U0000e91d"
swipe_icon: "\U0000e91a"
lvgl:
pages:
- id: shutter_group_page
bg_color: color_slate_blue_gray
widgets:
- button:
id: ${shutter_widget_name_1}_btn
x: 20
y: 20
width: 210
height: 160
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${shutter_widget_name_1}_shutter
y: 15
align: CENTER
text_color: color_steel_blue
text_font: icons_90
text: "${shutter_close_icon}"
- label:
id: ${shutter_widget_name_1}_lable_name
align: TOP_MID
text_font: nunito_16
text_color: color_misty_blue
text: "${shutter_label_name_1}"
- button:
id: ${shutter_widget_name_2}_btn
x: -20
y: 20
width: 210
height: 160
align: TOP_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${shutter_widget_name_2}_shutter
y: 15
align: CENTER
text_color: color_steel_blue
text_font: icons_90
text: "${shutter_close_icon}"
- label:
id: ${shutter_widget_name_2}_lable_name
align: TOP_MID
text_font: nunito_16
text_color: color_misty_blue
text: "${shutter_label_name_2}"
- button:
id: ${shutter_widget_name_3}_btn
x: 20
y: 200
width: 210
height: 160
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${shutter_widget_name_3}_shutter
y: 15
align: CENTER
text_color: color_steel_blue
text_font: icons_90
text: "${shutter_close_icon}"
- label:
id: ${shutter_widget_name_3}_lable_name
align: TOP_MID
text_font: nunito_16
text_color: color_misty_blue
text: "${shutter_label_name_3}"
- button:
id: ${shutter_widget_name_4}_btn
x: -20
y: 200
width: 210
height: 160
align: TOP_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${shutter_widget_name_4}_shutter
y: 15
align: CENTER
text_color: color_steel_blue
text_font: icons_90
text: "${shutter_close_icon}"
- label:
id: ${shutter_widget_name_4}_lable_name
align: TOP_MID
text_font: nunito_16
text_color: color_misty_blue
text: "${shutter_label_name_4}"
binary_sensor:
- platform: lvgl
id: ${shutter_widget_name_1}_btn_long_sensor
widget: ${shutter_widget_name_1}_btn
on_click:
- min_length: 50ms
max_length: 500ms
then:
- homeassistant.service:
service: cover.toggle
data:
entity_id: "${shutter_entity_1}"
- min_length: 800ms
max_length: 3000ms
then:
- lvgl.widget.hide: menu_controls_main
- lvgl.page.show:
id: ${shutter_widget_name_1}_shutter_page
animation: OUT_RIGHT
time: 300ms
- platform: lvgl
id: ${shutter_widget_name_2}_btn_long_sensor
widget: ${shutter_widget_name_2}_btn
on_click:
- min_length: 50ms
max_length: 500ms
then:
- homeassistant.service:
service: cover.toggle
data:
entity_id: "${shutter_entity_2}"
- min_length: 800ms
max_length: 3000ms
then:
- lvgl.widget.hide: menu_controls_main
- lvgl.page.show:
id: ${shutter_widget_name_2}_shutter_page
animation: OUT_RIGHT
time: 300ms
- platform: lvgl
id: ${shutter_widget_name_3}_btn_long_sensor
widget: ${shutter_widget_name_3}_btn
on_click:
- min_length: 50ms
max_length: 500ms
then:
- homeassistant.service:
service: cover.toggle
data:
entity_id: "${shutter_entity_3}"
- min_length: 800ms
max_length: 3000ms
then:
- lvgl.widget.hide: menu_controls_main
- lvgl.page.show:
id: ${shutter_widget_name_3}_shutter_page
animation: OUT_RIGHT
time: 300ms
- platform: lvgl
id: ${shutter_widget_name_4}_btn_long_sensor
widget: ${shutter_widget_name_4}_btn
on_click:
- min_length: 50ms
max_length: 500ms
then:
- homeassistant.service:
service: cover.toggle
data:
entity_id: "${shutter_entity_4}"
- min_length: 800ms
max_length: 3000ms
then:
- lvgl.widget.hide: menu_controls_main
- lvgl.page.show:
id: ${shutter_widget_name_4}_shutter_page
animation: OUT_RIGHT
time: 300ms

View File

@@ -0,0 +1,543 @@
substitutions:
arrow_up_icon: "\U0000e91f"
arrow_down_icon: "\U0000e920"
stop_icon: "\U0000e91c"
shutter_open_icon: "\U0000e91e"
shutter_close_icon: "\U0000e91d"
sensor:
# Shutter Current Position
- platform: homeassistant
id: ${shutter_widget_name}_shutter_current_pos
entity_id: "${shutter_entity}"
attribute: current_position
on_value:
then:
- script.execute: ${shutter_widget_name}_shutter_state_update
- lambda: |-
int pos = round(id(${shutter_widget_name}_shutter_current_pos).state / 10) * 10;
static const char* glyphs[] = {
"\U0000e91d", // 0 - shutter_closed
"\U0000e93d", // 10
"\U0000e93e", // 20
"\U0000e93f", // 30
"\U0000e940", // 40
"\U0000e941", // 50
"\U0000e93c", // 60
"\U0000e943", // 70
"\U0000e944", // 80
"\U0000e942", // 90
"\U0000e91e" // 100 - shutter_open
};
int idx = pos / 10;
if (idx < 0) idx = 0;
if (idx > 10) idx = 10;
lv_label_set_text(id(${shutter_widget_name}_shutter_icon), glyphs[idx]);
lv_label_set_text(id(${shutter_widget_name}_shutter), glyphs[idx]);
text_sensor:
# Shutter Name
- platform: homeassistant
id: ${shutter_widget_name}_shutter_name
entity_id: "${shutter_entity}"
attribute: friendly_name
on_value:
- lvgl.label.update:
id: ${shutter_widget_name}_shutter_label_name
text: !lambda return x;
# Shutter State
- platform: homeassistant
id: ${shutter_widget_name}_shutter_state
entity_id: "${shutter_entity}"
on_value:
then:
- script.execute:
id: ${shutter_widget_name}_get_and_set_translated_state
state_str: !lambda 'return id(${shutter_widget_name}_shutter_state).state;'
- lvgl.label.update:
id: ${shutter_widget_name}_shutter_label_state
y: 5
align: TOP_MID
- if:
condition:
lambda: 'return x == "opening";'
then:
- lvgl.widget.show: ${shutter_widget_name}_shutter_label_pos
- if:
condition:
lambda: 'return x == "closing";'
then:
- lvgl.widget.show: ${shutter_widget_name}_shutter_label_pos
- if:
condition:
lambda: 'return x == "open";'
then:
- lvgl.widget.show: ${shutter_widget_name}_shutter_label_pos
- if:
condition:
lambda: 'return x == "closed";'
then:
- lvgl.widget.hide: ${shutter_widget_name}_shutter_label_pos
- lvgl.label.update:
id: ${shutter_widget_name}_shutter_label_state
y: 0
align: CENTER
lvgl:
pages:
- id: ${shutter_widget_name}_shutter_page
bg_color: color_slate_blue_gray
widgets:
- obj:
id: ${shutter_widget_name}_shutter_bg_state
x: -20
y: 40
width: 320
height: 54
align: TOP_RIGHT
bg_opa: transp
pad_all: 0
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${shutter_widget_name}_shutter_label_state
y: 5
align: TOP_MID
text_font: nunito_16
text_color: color_misty_blue
text: " "
- label:
id: ${shutter_widget_name}_shutter_label_pos
y: -5
align: BOTTOM_MID
text_font: nunito_16
text_color: color_misty_blue
text: " "
- obj:
id: ${shutter_widget_name}_shutter_bg_controls
x: 20
y: 20
width: 100
height: 320
align: TOP_LEFT
# bg_color: color_steel_blue
bg_opa: transp
pad_all: 0
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- obj:
y: 20
width: 90
height: 90
align: top_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:
- homeassistant.action:
action: cover.open_cover
data:
entity_id: "${shutter_entity}"
- 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:
align: CENTER
text_font: icons_32
text_color: color_misty_blue
text: "${arrow_up_icon}"
- obj:
width: 90
height: 90
align: center
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:
- homeassistant.action:
action: cover.stop_cover
data:
entity_id: "${shutter_entity}"
- 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:
align: CENTER
text_font: icons_32
text_color: color_misty_blue
text: "${stop_icon}"
- obj:
y: -20
width: 90
height: 90
align: bottom_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:
- homeassistant.action:
action: cover.close_cover
data:
entity_id: "${shutter_entity}"
- 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:
align: CENTER
text_font: icons_32
text_color: color_misty_blue
text: "${arrow_down_icon}"
- obj:
id: ${shutter_widget_name}_shutter_bg_slider
x: -20
y: 94
width: 320
height: 310
align: TOP_RIGHT
# bg_color: color_steel_blue
bg_opa: transp
pad_all: 0
border_opa: transp
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${shutter_widget_name}_shutter_icon
align: CENTER
text_font: icons_300
text_color: color_misty_blue
text: "\U0000e91d"
- slider:
id: ${shutter_widget_name}_shutter_set_pos_slider
hidden: false
radius: 0
bg_opa: TRANSP
y: -32
align: BOTTOM_MID
width: 210
height: 220
min_value: 0
max_value: 100
indicator:
bg_opa: TRANSP
radius: 0
knob:
bg_opa: TRANSP
on_value:
then:
- script.execute:
id: ${shutter_widget_name}_shutter_slider_preview_update
value: !lambda 'return x;'
on_release:
- homeassistant.action:
action: cover.set_cover_position
data:
entity_id: "${shutter_entity}"
position: !lambda return int(x);
- obj:
id: ${shutter_widget_name}_shutter_back
x: 20
y: 360
width: 100
height: 100
align: TOP_LEFT
bg_opa: transp
pad_all: 0
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- obj:
width: 90
height: 90
align: center
pad_all: 0
bg_opa: transp
shadow_opa: transp
border_opa: transp
widgets:
- obj:
width: 70
height: 70
align: center
clickable: true
radius: 10
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:
- lvgl.widget.show: menu_controls_main
- lvgl.page.show:
id: shutter_group_page
animation: OUT_RIGHT
time: 300ms
- label:
id: ${shutter_widget_name}_shutter_back_label
align: center
text_font: icons_28
text_color: color_misty_blue
text: "${exit_icon}"
- obj:
id: ${shutter_widget_name}_shutter_bg_name
x: -20
y: 400
width: 320
height: 36
align: TOP_RIGHT
bg_opa: transp
pad_all: 0
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: ${shutter_widget_name}_shutter_label_name
align: CENTER
text_font: nunito_16
text_color: color_misty_blue
text: "shutter"
script:
- id: ${shutter_widget_name}_shutter_state_update
then:
- lvgl.slider.update:
id: ${shutter_widget_name}_shutter_set_pos_slider
value: !lambda |-
return round(id(${shutter_widget_name}_shutter_current_pos).state / 10) * 10;
- lvgl.label.update:
id: ${shutter_widget_name}_shutter_label_pos
text: !lambda |-
return to_string((int)id(${shutter_widget_name}_shutter_current_pos).state) + "%";
- id: ${shutter_widget_name}_shutter_slider_preview_update
parameters:
value: int
then:
- lvgl.label.update:
id: ${shutter_widget_name}_shutter_label_pos
text: !lambda |-
return to_string((int)value) + "%";
- lambda: |-
int pos = round(value / 10) * 10;
static const char* glyphs[] = {
"\U0000e91d", // 0 - shutter_closed
"\U0000e93d", // 10
"\U0000e93e", // 20
"\U0000e93f", // 30
"\U0000e940", // 40
"\U0000e941", // 50
"\U0000e93c", // 60
"\U0000e943", // 70
"\U0000e944", // 80
"\U0000e942", // 90
"\U0000e91e" // 100 - shutter_open
};
int idx = pos / 10;
if (idx < 0) idx = 0;
if (idx > 10) idx = 10;
lv_label_set_text(id(${shutter_widget_name}_shutter_icon), glyphs[idx]);
lv_label_set_text(id(${shutter_widget_name}_shutter), glyphs[idx]);
- id: ${shutter_widget_name}_get_and_set_translated_state
parameters:
state_str: string
then:
- lambda: |-
std::string state = state_str;
auto it = id(cover_translations).find(state);
std::string translated_state = (it != id(cover_translations).end()) ? it->second : state;
lv_label_set_text(id(${shutter_widget_name}_shutter_label_state), translated_state.c_str());
select:
- platform: lvgl
widget: language_dropdown
id: ${shutter_widget_name}_select_language
on_value:
then:
- delay: 300ms
- script.execute:
id: ${shutter_widget_name}_get_and_set_translated_state
state_str: !lambda 'return id(${shutter_widget_name}_shutter_state).state;'
esphome:
on_boot:
priority: -100
then:
- if:
condition:
lambda: 'return id(${shutter_widget_name}_shutter_state).has_state();'
then:
- script.execute:
id: ${shutter_widget_name}_get_and_set_translated_state
state_str: !lambda 'return id(${shutter_widget_name}_shutter_state).state;'
api:
on_client_connected:
- if:
condition:
lambda: 'return id(${shutter_widget_name}_shutter_state).has_state();'
then:
- script.execute:
id: ${shutter_widget_name}_get_and_set_translated_state
state_str: !lambda 'return id(${shutter_widget_name}_shutter_state).state;'

View File

@@ -0,0 +1,95 @@
lvgl:
theme:
label:
text_font: my_font # set all your labels to use your custom defined font
button:
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
text_color: 0xFFFFFF
pressed: # set some button colors to be different in pressed state
bg_color: 0x006699
bg_grad_color: 0x00334d
checked: # set some button colors to be different in checked state
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
text_color: 0xfff300
buttonmatrix:
bg_opa: TRANSP
border_color: 0x0077b3
border_width: 0
text_color: 0xFFFFFF
pad_all: 0
items: # set all your buttonmatrix buttons to use your custom defined styles and font
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
text_color: 0xFFFFFF
text_font: my_font
pressed:
bg_color: 0x006699
bg_grad_color: 0x00334d
checked:
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
text_color: 0x005580
switch:
bg_color: 0xC0C0C0
bg_grad_color: 0xb0b0b0
bg_grad_dir: VER
bg_opa: COVER
checked:
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
bg_grad_dir: VER
bg_opa: COVER
knob:
bg_color: 0xFFFFFF
bg_grad_color: 0xC0C0C0
bg_grad_dir: VER
bg_opa: COVER
slider:
border_width: 1
border_opa: 15%
bg_color: 0xcccaca
bg_opa: 15%
indicator:
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
bg_grad_dir: VER
bg_opa: COVER
knob:
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
text_color: 0xFFFFFF
spinbox:
cursor:
bg_opa: 0x00
bg_color: 0x00
text_color: 0xFFFFFF
style_definitions:
- id: header_footer
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_opa: TRANSP
radius: 0
pad_all: 0
pad_row: 0
pad_column: 0
border_color: 0x0077b3
text_color: 0xFFFFFF
width: 100%
height: 30

View File

@@ -0,0 +1,79 @@
lvgl:
- style_definitions:
- id: button_menu
width: 60
height: 60
pad_all: 0
bg_color: 0x000000
border_color: 0x000000
align: BOTTOM_RIGHT
- id: button_more
width: 60
height: 60
pad_all: 0
bg_color: 0x000000
border_color: 0x000000
align: TOP_RIGHT
- id: button_device_1
width: 130
height: 130
pad_all: 0
translate_x: 30
bg_color: 0x000000
border_color: 0x000000
align: LEFT_MID
- id: button_device_2
width: 130
height: 130
pad_all: 0
translate_x: 160
bg_color: 0x000000
border_color: 0x000000
align: LEFT_MID
- id: button_device_3
width: 130
height: 130
pad_all: 0
translate_x: 290
bg_color: 0x000000
border_color: 0x000000
align: LEFT_MID
- id: button_device_4
width: 130
height: 130
pad_all: 0
translate_x: 420
bg_color: 0x000000
border_color: 0x000000
align: LEFT_MID
- id: slider_front_cover
width: 530
height: 160
translate_x: -25
bg_color: 0x9c9c79
radius: 10
align: CENTER
- id: slider_back_cover
width: 530
height: 160
translate_x: -25
bg_color: 0xb4b4a9
radius: 10
align: CENTER
- id: slider_label_values_cover
align: CENTER
translate_x: -10
text_font: font_slider_value
text_color: 0xffffff
- id: device_status_top
align: TOP_LEFT
pad_left: 20
text_font: font_device_status
text_color: ${text_color_secondary}
- id: device_status_bottom
align: BOTTOM_LEFT
pad_left: 20
text_font: font_device_status
text_color: 0xffffff

View File

@@ -0,0 +1,63 @@
sensor:
- platform: homeassistant
id: room_thermostat
entity_id: ${climate}
attribute: temperature
on_value:
- lvgl.spinbox.update:
id: spinbox_id
value: !lambda return x;
lvgl:
top_layer:
widgets:
- obj:
align: BOTTOM_MID
y: -50
layout:
type: FLEX
flex_flow: ROW
flex_align_cross: CENTER
width: SIZE_CONTENT
height: SIZE_CONTENT
widgets:
- button:
id: spin_down
width: 100
height: 70
on_click:
- lvgl.spinbox.decrement: spinbox_id
widgets:
- label:
text: "-"
text_font: my_font
- spinbox:
id: spinbox_id
align: CENTER
text_align: CENTER
width: 100
height: 70
range_from: 15
range_to: 35
selected_digit: 0
rollover: false
digits: 2
decimal_places: 0
text_font: my_font
on_value:
then:
- homeassistant.action:
action: climate.set_temperature
data:
temperature: !lambda return x;
entity_id: ${climate}
- button:
id: spin_up
width: 100
height: 70
on_click:
- lvgl.spinbox.increment: spinbox_id
widgets:
- label:
text: "+"
text_font: my_font

View File

@@ -0,0 +1,813 @@
# 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;'

View File

@@ -0,0 +1,922 @@
substitutions:
vacuum_entity: "vacuum.roborock_qrevo_s"
start_icon: "\U0000e90a"
play_icon: "\U0000e90a"
pause_icon: "\U0000e90b"
stop_icon: "\U0000e91c"
locate_icon: "\U0000e923"
docked_icon: "\U0000e924"
fan_icon: "\U0000e926"
room_plan_icon: "\U0000e927"
battery_0_icon: "\U0000e928"
battery_20_icon: "\U0000e929"
battery_40_icon: "\U0000e92a"
battery_60_icon: "\U0000e92b"
battery_80_icon: "\U0000e92c"
battery_100_icon: "\U0000e92d"
globals:
- id: vacuum_fan_speeds
type: std::vector<std::string>
restore_value: false
- id: vacuum_selected_fan_speed_index
type: int
restore_value: false
sensor:
# Battery level
- platform: homeassistant
id: vacuum_battery_level_sensor
entity_id: "${vacuum_entity}"
attribute: battery_level
on_value:
- lvgl.label.update:
id: vacuum_battery_level_label
text: !lambda |-
if (isnan(x)) return " ";
char buffer[32];
snprintf(buffer, sizeof(buffer), "%.0f%%", x);
return std::string(buffer);
- if:
condition:
lambda: 'return id(vacuum_battery_level_sensor).state >= 80;'
then:
- lvgl.image.update:
id: vacuum_battery_level_img
src: battery_very_high_img
else:
- if:
condition:
lambda: 'return id(vacuum_battery_level_sensor).state >= 60;'
then:
- lvgl.image.update:
id: vacuum_battery_level_img
src: battery_high_img
else:
- if:
condition:
lambda: 'return id(vacuum_battery_level_sensor).state >= 40;'
then:
- lvgl.image.update:
id: vacuum_battery_level_img
src: battery_middle_img
else:
- if:
condition:
lambda: 'return id(vacuum_battery_level_sensor).state >= 20;'
then:
- lvgl.image.update:
id: vacuum_battery_level_img
src: battery_low_img
else:
- if:
condition:
lambda: 'return id(vacuum_battery_level_sensor).state >= 1;'
then:
- lvgl.image.update:
id: vacuum_battery_level_img
src: battery_very_low_img
else:
- lvgl.image.update:
id: vacuum_battery_level_img
src: battery_empty_img
# Cleaned area
- platform: homeassistant
id: vacuum_cleaned_area_sensor
entity_id: "${vacuum_entity}"
attribute: cleaned_area
on_value:
- lvgl.label.update:
id: vacuum_cleaned_area_label
text: !lambda |-
if (isnan(x)) return " ";
char buffer[32];
snprintf(buffer, sizeof(buffer), "%.2f m²", x);
return std::string(buffer);
text_sensor:
- platform: homeassistant
id: vacuum_fan_speed_list_sensor
entity_id: "${vacuum_entity}"
attribute: fan_speed_list
on_value:
- lambda: |-
ESP_LOGD("vacuum", "Fan speed list received: %s", x.c_str());
std::vector<std::string> options;
std::string input = x;
if (!input.empty() && input != "unknown") {
input.erase(std::remove(input.begin(), input.end(), '['), input.end());
input.erase(std::remove(input.begin(), input.end(), ']'), input.end());
input.erase(std::remove(input.begin(), input.end(), '\''), input.end());
input.erase(std::remove(input.begin(), input.end(), '"'), input.end());
std::stringstream ss(input);
std::string item;
while (std::getline(ss, item, ',')) {
item.erase(0, item.find_first_not_of(" \t"));
item.erase(item.find_last_not_of(" \t") + 1);
if (!item.empty()) {
options.push_back(item);
}
}
}
if (options.empty()) {
options = {"Silent", "Standard", "Medium", "High"};
}
id(vacuum_fan_speeds) = options;
auto dropdown = id(vacuum_fan_speed_dropdown);
if (dropdown != nullptr) {
dropdown->set_options(options);
}
- platform: homeassistant
id: vacuum_fan_speed_sensor
entity_id: "${vacuum_entity}"
attribute: fan_speed
on_value:
- lambda: |-
auto& speeds = id(vacuum_fan_speeds);
if (!speeds.empty()) {
for (int i = 0; i < (int)speeds.size(); i++) {
if (speeds[i] == x) {
id(vacuum_selected_fan_speed_index) = i;
break;
}
}
}
- lvgl.dropdown.update:
id: vacuum_fan_speed_dropdown
selected_index: !lambda return id(vacuum_selected_fan_speed_index);
- platform: homeassistant
id: vacuum_name_sensor
entity_id: "${vacuum_entity}"
attribute: friendly_name
on_value:
- lvgl.label.update:
id: vacuum_label_name
text: !lambda return x;
- platform: homeassistant
id: vacuum_state_sensor
entity_id: "${vacuum_entity}"
on_value:
then:
- script.execute:
id: vacuum_get_and_set_translated_state
state_str: !lambda 'return id(vacuum_state_sensor).state;'
- lvgl.widget.hide: vacuum_battery_level_img
- lvgl.widget.hide: vacuum_charging_animation
- lvgl.widget.hide: vacuum_cleaning_animation
- lvgl.widget.hide: vacuum_go_home_animation
- lvgl.widget.hide: vacuum_not_animation
- lvgl.widget.hide: vacuum_bg_controls_cleaning
- lvgl.widget.hide: vacuum_bg_controls_paused
- lvgl.widget.hide: vacuum_bg_controls_docked
- lvgl.widget.hide: vacuum_bg_controls_idle
- lvgl.widget.hide: vacuum_bg_controls_returning
- if:
condition:
lambda: 'return x == "cleaning";'
then:
- lvgl.widget.show: vacuum_cleaning_animation
- lvgl.widget.show: vacuum_bg_controls_cleaning
- lvgl.widget.show: vacuum_battery_level_img
- if:
condition:
lambda: 'return x == "returning";'
then:
- lvgl.widget.show: vacuum_go_home_animation
- lvgl.widget.show: vacuum_bg_controls_returning
- lvgl.widget.show: vacuum_battery_level_img
- if:
condition:
lambda: 'return x == "docked";'
then:
- lvgl.widget.show: vacuum_not_animation
- lvgl.widget.show: vacuum_bg_controls_docked
- if:
condition:
lambda: 'return id(vacuum_battery_level_sensor).state != 100;'
then:
- lvgl.widget.show: vacuum_charging_animation
else:
- lvgl.widget.show: vacuum_battery_level_img
- if:
condition:
lambda: 'return x == "paused";'
then:
- lvgl.widget.show: vacuum_not_animation
- lvgl.widget.show: vacuum_bg_controls_paused
- lvgl.widget.show: vacuum_battery_level_img
- if:
condition:
lambda: 'return x == "idle";'
then:
- lvgl.widget.show: vacuum_not_animation
- lvgl.widget.show: vacuum_bg_controls_idle
- lvgl.widget.show: vacuum_battery_level_img
lvgl:
pages:
- id: vacuum_page
bg_color: color_slate_blue_gray
widgets:
# main
- obj:
id: vacuum_bg_main
y: 20
width: 440
height: 240
align: TOP_MID
pad_all: 0
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- obj:
x: 10
y: 0
width: 140
height: 50
align: TOP_LEFT
pad_all: 0
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
widgets:
- label:
align: LEFT_MID
text_font: icons_28
text_color: color_misty_blue
text: "${fan_icon}"
- dropdown:
id: vacuum_fan_speed_dropdown
x: 30
width: 80
height: 40
align: LEFT_MID
options: []
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
text_font: nunito_16
text_color: color_misty_blue
dropdown_list:
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
text_font: nunito_16
text_color: color_misty_blue
text_line_space: 5
pad_all: 8
max_height: 200
selected:
checked:
bg_color: color_steel_blue
bg_opa: 20%
text_color: color_misty_blue
pressed:
bg_opa: TRANSP
indicator:
text_opa: TRANSP
on_value:
- homeassistant.action:
action: vacuum.set_fan_speed
data:
entity_id: "${vacuum_entity}"
fan_speed: !lambda |-
auto& speeds = id(vacuum_fan_speeds);
if (x >= 0 && x < (int)speeds.size()) {
return speeds[x];
}
return std::string("");
# battery level label
- obj:
x: 10
y: 0
width: 140
height: 50
align: BOTTOM_LEFT
pad_all: 0
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
widgets:
# state
- image:
align: LEFT_MID
id: vacuum_battery_level_img
src: battery_very_high_img
# charging animation
- animimg:
id: vacuum_charging_animation
align: LEFT_MID
hidden: true
src: [
battery_0_img,
battery_20_img,
battery_40_img,
battery_60_img,
battery_80_img,
battery_very_high_img,
]
duration: 1500ms
- label:
id: vacuum_battery_level_label
x: 20
align: LEFT_MID
text_font: nunito_16
text_color: color_misty_blue
text: " "
# state label
- obj:
x: -10
y: 0
width: 340
height: 50
align: TOP_RIGHT
pad_all: 0
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
widgets:
- label:
align: RIGHT_MID
id: vacuum_state_label
text_font: nunito_16
text_color: color_misty_blue
text: ""
# cleaned area label
- obj:
x: -10
y: 0
width: 140
height: 50
align: BOTTOM_RIGHT
pad_all: 0
bg_opa: TRANSP
shadow_opa: TRANSP
border_opa: TRANSP
border_width: 0
widgets:
- label:
align: RIGHT_MID
text_font: icons_28
text_color: color_misty_blue
text: "${room_plan_icon}"
- label:
x: -40
align: RIGHT_MID
id: vacuum_cleaned_area_label
text_font: nunito_16
text_color: color_misty_blue
text: ""
# without animation
- image:
id: vacuum_not_animation
y: 10
align: CENTER
src: vacuum_img
# return to base animation
- animimg:
id: vacuum_go_home_animation
hidden: true
y: 10
align: CENTER
src: [
vacuum_img,
vacuum_5_img,
vacuum_10_img,
vacuum_15_img,
vacuum_10_img,
vacuum_5_img,
vacuum_img,
vacuum_355_img,
vacuum_350_img,
vacuum_345_img,
vacuum_350_img,
vacuum_355_img,
]
duration: 2000ms
# cleaning animation
- animimg:
id: vacuum_cleaning_animation
hidden: true
y: 0
align: CENTER
src: [
vacuum_img,
vacuum_0_10_img,
vacuum_0_20_img,
vacuum_0_30_img,
vacuum_0_20_img,
vacuum_0_10_img,
vacuum_img,
vacuum_5_img,
vacuum_10_img,
vacuum_15_img,
vacuum_20_img,
vacuum_20_10_img,
vacuum_20_20_img,
vacuum_20_30_img,
vacuum_20_20_img,
vacuum_20_10_img,
vacuum_20_img,
vacuum_15_img,
vacuum_10_img,
vacuum_5_img,
vacuum_img,
vacuum_0_10_img,
vacuum_0_20_img,
vacuum_0_30_img,
vacuum_0_20_img,
vacuum_0_10_img,
vacuum_img,
vacuum_355_img,
vacuum_350_img,
vacuum_345_img,
vacuum_340_img,
vacuum_340_10_img,
vacuum_340_20_img,
vacuum_340_30_img,
vacuum_340_20_img,
vacuum_340_10_img,
vacuum_340_img,
vacuum_345_img,
vacuum_350_img,
vacuum_355_img,
]
duration: 6000ms
# docked state controls
- obj:
id: vacuum_bg_controls_docked
x: 20
y: 280
width: 440
height: 100
pad_all: 10
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
layout:
type: FLEX
flex_align_main: SPACE_AROUND
flex_align_cross: CENTER
widgets:
- button:
width: 80
height: 80
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${play_icon}"
on_press:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).state == "docked";'
then:
- homeassistant.action:
action: vacuum.start
data:
entity_id: "${vacuum_entity}"
- button:
width: 80
height: 80
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${locate_icon}"
on_press:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).state == "docked";'
then:
- homeassistant.action:
action: vacuum.locate
data:
entity_id: "${vacuum_entity}"
# cleaning state controls
- obj:
id: vacuum_bg_controls_cleaning
hidden: true
x: 20
y: 280
width: 440
height: 100
pad_all: 10
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
layout:
type: FLEX
flex_align_main: SPACE_AROUND
flex_align_cross: CENTER
widgets:
- button:
width: 80
height: 80
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${pause_icon}"
on_press:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).state == "cleaning";'
then:
- homeassistant.action:
action: vacuum.pause
data:
entity_id: "${vacuum_entity}"
- button:
width: 80
height: 80
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${stop_icon}"
on_press:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).state == "cleaning";'
then:
- homeassistant.action:
action: vacuum.stop
data:
entity_id: "${vacuum_entity}"
- button:
width: 80
height: 80
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${docked_icon}"
on_press:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).state == "cleaning";'
then:
- homeassistant.action:
action: vacuum.return_to_base
data:
entity_id: "${vacuum_entity}"
# paused state controls
- obj:
id: vacuum_bg_controls_paused
hidden: true
x: 20
y: 280
width: 440
height: 100
pad_all: 10
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
layout:
type: FLEX
flex_align_main: SPACE_AROUND
flex_align_cross: CENTER
widgets:
- button:
width: 80
height: 80
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${play_icon}"
on_press:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).state == "paused";'
then:
- homeassistant.action:
action: vacuum.start
data:
entity_id: "${vacuum_entity}"
- button:
width: 80
height: 80
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${docked_icon}"
on_press:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).state == "paused";'
then:
- homeassistant.action:
action: vacuum.return_to_base
data:
entity_id: "${vacuum_entity}"
# idle state controls
- obj:
id: vacuum_bg_controls_idle
hidden: true
x: 20
y: 280
width: 440
height: 100
pad_all: 10
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
layout:
type: FLEX
flex_align_main: SPACE_AROUND
flex_align_cross: CENTER
widgets:
- button:
width: 80
height: 80
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${play_icon}"
on_press:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).state == "idle";'
then:
- homeassistant.action:
action: vacuum.start
data:
entity_id: "${vacuum_entity}"
- button:
width: 80
height: 80
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${locate_icon}"
on_press:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).state == "idle";'
then:
- homeassistant.action:
action: vacuum.locate
data:
entity_id: "${vacuum_entity}"
- button:
width: 80
height: 80
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${docked_icon}"
on_press:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).state == "idle";'
then:
- homeassistant.action:
action: vacuum.return_to_base
data:
entity_id: "${vacuum_entity}"
# returning state controls
- obj:
id: vacuum_bg_controls_returning
hidden: true
x: 20
y: 280
width: 440
height: 100
pad_all: 10
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
layout:
type: FLEX
flex_align_main: SPACE_AROUND
flex_align_cross: CENTER
widgets:
- button:
width: 80
height: 80
bg_opa: TRANSP
shadow_opa: TRANSP
widgets:
- label:
align: CENTER
text_color: color_misty_blue
text_font: icons_38
text: "${play_icon}"
on_press:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).state == "returning";'
then:
- homeassistant.action:
action: vacuum.start
data:
entity_id: "${vacuum_entity}"
# Return
- button:
x: 20
y: 400
width: 60
height: 60
align: TOP_LEFT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
shadow_opa: TRANSP
radius: 10
widgets:
- 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
# vacuum entity friendly name
- obj:
id: vacuum_bg_name
x: -20
y: 400
width: 360
height: 60
align: TOP_RIGHT
bg_color: color_steel_blue
bg_opa: 20%
border_opa: TRANSP
border_width: 0
shadow_opa: TRANSP
radius: 10
widgets:
- label:
id: vacuum_label_name
align: CENTER
text_font: nunito_16
text_color: color_misty_blue
text: "friendly name"
script:
- id: vacuum_get_and_set_translated_state
parameters:
state_str: string
then:
- lambda: |-
std::string state = state_str;
auto it = id(vacuum_translations).find(state);
std::string translated_state = (it != id(vacuum_translations).end()) ? it->second : state;
lv_label_set_text(id(vacuum_state_label), translated_state.c_str());
select:
- platform: lvgl
widget: language_dropdown
id: vacuum_select_language
on_value:
then:
- delay: 300ms
- script.execute:
id: vacuum_get_and_set_translated_state
state_str: !lambda 'return id(vacuum_state_sensor).state;'
esphome:
on_boot:
priority: -100
then:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).has_state();'
then:
- script.execute:
id: vacuum_get_and_set_translated_state
state_str: !lambda 'return id(vacuum_state_sensor).state;'
api:
on_client_connected:
- if:
condition:
lambda: 'return id(vacuum_state_sensor).has_state();'
then:
- script.execute:
id: vacuum_get_and_set_translated_state
state_str: !lambda 'return id(vacuum_state_sensor).state;'