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

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;'