Add Home Assistant screen manager and runtime tile configuration

This commit is contained in:
Max Gramser
2026-09-12 12:09:58 +02:00
parent 427e4049ab
commit d271a63241
43 changed files with 13991 additions and 388 deletions
+156 -35
View File
@@ -58,6 +58,8 @@
############################################################
substitutions:
DYNAMIC_TILES: "false"
DASHBOARD_INBOX_INTERNAL: "true"
DEVICE_NAME: "cyd-new"
DEVICE_FRIENDLY_NAME: "Mijn CYD"
CALIBRATION_ON_BOOT: "false"
@@ -769,9 +771,13 @@ external_components:
- source:
type: local
path: components
components: [xpt2046]
components: [xpt2046, smart_display]
esphome:
min_version: 2026.6.2
project:
name: maxgramser.esp_screen
version: "0.1.0"
includes:
- cyd_ui.h
- light_controls.h
@@ -789,13 +795,88 @@ esphome:
lv_obj_set_style_pad_column(id(vacuum_actions), 4, 0);
lv_obj_set_style_pad_row(id(vacuum_speeds), 4, 0);
lv_obj_set_style_pad_column(id(vacuum_speeds), 4, 0);
- lambda: |-
runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true";
if (!runtime_tiles::enabled) return;
runtime_tiles::dismiss = []() {
id(active_entity).clear();
lv_obj_add_flag(id(brightness_overlay), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(id(color_detail_overlay), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(id(climate_detail_overlay), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(id(climate_mode_overlay), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(id(vacuum_overlay), LV_OBJ_FLAG_HIDDEN);
};
runtime_tiles::layout_changed = []() { id(show_tile_page).execute(); };
runtime_tiles::refresh = []() { if (!id(ui_refresh).is_running()) id(ui_refresh).execute(); };
runtime_tiles::bind(0, id(tile1), id(t1_title), id(t1_value), id(tile1_icon_circle), id(tile1_icon_lbl));
runtime_tiles::bind(1, id(tile2), id(t2_title), id(t2_value), id(tile2_icon_circle), id(tile2_icon_lbl));
runtime_tiles::bind(2, id(tile3), id(t3_title), id(t3_value), id(tile3_icon_circle), id(tile3_icon_lbl));
runtime_tiles::bind(3, id(tile4), id(t4_title), id(t4_value), id(tile4_icon_circle), id(tile4_icon_lbl));
runtime_tiles::bind(4, id(tile5), id(t5_title), id(t5_value), id(tile5_icon_circle), id(tile5_icon_lbl));
runtime_tiles::bind(5, id(tile6), id(t6_title), id(t6_value), id(tile6_icon_circle), id(tile6_icon_lbl));
runtime_tiles::bind(6, id(tile7), id(t7_title), id(t7_value), id(tile7_icon_circle), id(tile7_icon_lbl));
runtime_tiles::bind(7, id(tile8), id(t8_title), id(t8_value), id(tile8_icon_circle), id(tile8_icon_lbl));
runtime_tiles::bind(8, id(tile9), id(t9_title), id(t9_value), id(tile9_icon_circle), id(tile9_icon_lbl));
runtime_tiles::bind(9, id(tile10), id(t10_title), id(t10_value), id(tile10_icon_circle), id(tile10_icon_lbl));
runtime_tiles::detail = [](runtime_tiles::Tile &t) {
if (t.domain() == "vacuum") {
lv_obj_t *buttons[] = {id(vacspd_quiet), id(vacspd_balanced), id(vacspd_turbo), id(vacspd_max)};
for (unsigned i = 0; i < 4; ++i) {
if (i < t.fan_speed_count) {
lv_obj_clear_flag(buttons[i], LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(lv_obj_get_child(buttons[i], 0), t.fan_speeds[i].c_str());
} else lv_obj_add_flag(buttons[i], LV_OBJ_FLAG_HIDDEN);
}
id(open_vacuum_overlay).execute(t.name, t.entity);
return;
}
light_controls::fallback = light_controls::State{};
light_controls::fallback.hue = t.hue;
light_controls::fallback.kelvin = t.kelvin;
light_controls::fallback.minimum = t.min_kelvin;
light_controls::fallback.maximum = t.max_kelvin;
id(open_value_overlay).execute(t.name, t.entity, t.domain(), "auto", t.active(), 0,
t.brightness, t.percentage, t.position, runtime_tiles::icon_for(t),
0x565C66, 0xE4B23C, 0xFFFFFF, "Uit", "Aan", t.modes,
t.current, t.target, t.humidity, t.state, t.hvac_modes,
std::to_string(t.minimum), std::to_string(t.maximum), std::to_string(t.step));
};
runtime_tiles::detail_update = [](runtime_tiles::Tile &t) {
if (id(active_entity) != t.entity || id(touch_down)) return;
if (t.domain() == "climate" && !lv_obj_has_flag(id(climate_detail_overlay), LV_OBJ_FLAG_HIDDEN)) {
if (id(climate_edit_pending) && millis() - id(climate_edit_ms) < 3000) return;
id(climate_edit_pending) = false;
id(active_climate_mode) = t.state;
id(active_climate_supported_modes) = t.hvac_modes;
lv_label_set_text(id(climate_mode_label), t.state.c_str());
char buf[72];
if (std::isfinite(t.current)) snprintf(buf, sizeof(buf), "Nu: %.1f°", t.current);
else snprintf(buf, sizeof(buf), "Nu: --");
lv_label_set_text(id(climate_current_label), buf);
if (std::isfinite(t.target)) {
int target = cyd::quantize_temperature(lround(t.target * 10), id(active_climate_min_x10), id(active_climate_max_x10), id(active_climate_step_x10));
id(active_climate_target_x10) = target;
snprintf(buf, sizeof(buf), "%.1f°", target / 10.0f);
lv_label_set_text(id(climate_target_label), buf);
lv_arc_set_value(id(climate_arc), target);
}
}
};
- lambda: |-
static const char *entities[] = {"${TILE1_ENTITY}", "${TILE2_ENTITY}", "${TILE3_ENTITY}", "${TILE4_ENTITY}", "${TILE5_ENTITY}", "${TILE6_ENTITY}", "${TILE7_ENTITY}", "${TILE9_ENTITY}"};
light_controls::subscribe(entities, 8);
if (!runtime_tiles::enabled) light_controls::subscribe(entities, 8);
light_controls::setup(id(color_detail_overlay), id(sublabel)->get_lv_font(), atoi("${DISPLAY_W}"), atoi("${DISPLAY_H}"));
light_controls::commits[0] = [](int value) { id(set_light_hue).execute(value); };
light_controls::commits[1] = [](int value) { id(set_light_color_temperature).execute(value); };
light_controls::commits[2] = [](int value) { id(color_brightness_commit).execute(value); };
- lambda: |-
if (!runtime_tiles::enabled) return;
screen_calibration::apply = [](const screen_calibration::Calibration &cal) {
id(ts_touch).set_raw_correction(cal.c[0],cal.c[1],cal.c[2],cal.c[3],cal.c[4],cal.c[5]);
};
screen_calibration::isolation = [](bool active) { id(calibration_active) = active; };
screen_calibration::setup(id(home_page)->obj, id(sublabel)->get_lv_font(), id(headline)->get_lv_font(),
${TOUCH_CAL_X_MIN}, ${TOUCH_CAL_X_MAX}, ${TOUCH_CAL_Y_MIN}, ${TOUCH_CAL_Y_MAX});
- light.turn_on:
id: back_light
brightness: !lambda "return atoi(\"${MAX_BRIGHTNESS}\") / 100.0f;"
@@ -1120,6 +1201,10 @@ font:
id: materialdesign_icons
size: 28
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
@@ -1411,6 +1496,11 @@ binary_sensor:
# - Used by the UI to display fan presets when applicable
# ----------------------------------------------------------
text_sensor:
- platform: template
name: "Schermfirmware"
entity_category: diagnostic
lambda: 'return {"0.1.0"};'
update_interval: 60s
- platform: template
id: smartdisplay_action
name: "SmartDisplay Action"
@@ -1733,6 +1823,7 @@ touchscreen:
id(last_touch_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y);
screen_calibration::press();
ESP_LOGI("touch", "press native=%u,%u raw=%d,%d pressure=%d calibration=%d", touch.x, touch.y, id(ts_touch).filtered_raw_x(), id(ts_touch).filtered_raw_y(), touch.z_raw, id(calibration_active));
on_update:
- lambda: |-
@@ -1744,6 +1835,7 @@ touchscreen:
- lambda: |-
id(last_touch_ms) = millis();
id(touch_down) = false;
screen_calibration::release(id(ts_touch).filtered_raw_x(), id(ts_touch).filtered_raw_y());
# Display configuration (ILI9341)
display:
@@ -1975,7 +2067,7 @@ lvgl:
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 1);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 1);'
then:
- script.execute: { id: publish_action, action: "tile1_press" }
- script.execute:
@@ -1993,7 +2085,7 @@ lvgl:
on_long_press:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 1);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 1);'
then:
- script.execute: { id: publish_action, action: "tile1_long_press" }
- if:
@@ -2106,7 +2198,7 @@ lvgl:
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 2);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 2);'
then:
- script.execute: { id: publish_action, action: "tile2_press" }
- script.execute:
@@ -2124,7 +2216,7 @@ lvgl:
on_long_press:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 2);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 2);'
then:
- script.execute: { id: publish_action, action: "tile2_long_press" }
- if:
@@ -2237,7 +2329,7 @@ lvgl:
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 3);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 3);'
then:
- script.execute: { id: publish_action, action: "tile3_press" }
- script.execute:
@@ -2255,7 +2347,7 @@ lvgl:
on_long_press:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 3);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 3);'
then:
- script.execute: { id: publish_action, action: "tile3_long_press" }
- if:
@@ -2368,7 +2460,7 @@ lvgl:
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 4);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 4);'
then:
- script.execute: { id: publish_action, action: "tile4_press" }
- script.execute:
@@ -2386,7 +2478,7 @@ lvgl:
on_long_press:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 4);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 4);'
then:
- script.execute: { id: publish_action, action: "tile4_long_press" }
- if:
@@ -2499,7 +2591,7 @@ lvgl:
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 5);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 5);'
then:
- script.execute: { id: publish_action, action: "tile5_press" }
- script.execute:
@@ -2517,7 +2609,7 @@ lvgl:
on_long_press:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 5);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 5);'
then:
- script.execute: { id: publish_action, action: "tile5_long_press" }
- if:
@@ -2630,7 +2722,7 @@ lvgl:
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 6);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 6);'
then:
- script.execute: { id: publish_action, action: "tile6_press" }
- script.execute:
@@ -2648,7 +2740,7 @@ lvgl:
on_long_press:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 6);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 6);'
then:
- script.execute: { id: publish_action, action: "tile6_long_press" }
- if:
@@ -2764,7 +2856,7 @@ lvgl:
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 7);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 7);'
then:
- script.execute: { id: publish_action, action: "tile7_press" }
- script.execute:
@@ -2779,7 +2871,7 @@ lvgl:
on_long_press:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 7);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 7);'
then:
- script.execute: { id: publish_action, action: "tile7_long_press" }
- if:
@@ -2883,7 +2975,7 @@ lvgl:
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 8);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 8);'
then:
- script.execute: { id: publish_action, action: "tile8_press" }
- script.execute:
@@ -2898,7 +2990,7 @@ lvgl:
on_long_press:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 8);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 8);'
then:
- script.execute: { id: publish_action, action: "tile8_long_press" }
# Slider-modus kan hier niet zonder eerst de bijbehorende
@@ -2968,7 +3060,7 @@ lvgl:
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 9);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 9);'
then:
- script.execute: { id: publish_action, action: "tile9_press" }
- script.execute:
@@ -2983,7 +3075,7 @@ lvgl:
on_long_press:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 9);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 9);'
then:
- script.execute: { id: publish_action, action: "tile9_long_press" }
- if:
@@ -3082,7 +3174,7 @@ lvgl:
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 10);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 10);'
then:
- script.execute: { id: publish_action, action: "tile10_press" }
- script.execute:
@@ -3097,7 +3189,7 @@ lvgl:
on_long_press:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 10);'
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 10);'
then:
- script.execute: { id: publish_action, action: "tile10_long_press" }
- if:
@@ -4226,7 +4318,9 @@ lvgl:
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: vacuum_fan_speed, speed: "quiet" }
- script.execute:
id: vacuum_fan_speed
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(0) : "quiet";'
widgets:
- label:
align: CENTER
@@ -4246,7 +4340,9 @@ lvgl:
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: vacuum_fan_speed, speed: "balanced" }
- script.execute:
id: vacuum_fan_speed
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(1) : "balanced";'
widgets:
- label:
align: CENTER
@@ -4266,7 +4362,9 @@ lvgl:
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: vacuum_fan_speed, speed: "turbo" }
- script.execute:
id: vacuum_fan_speed
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(2) : "turbo";'
widgets:
- label:
align: CENTER
@@ -4286,7 +4384,9 @@ lvgl:
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: vacuum_fan_speed, speed: "max" }
- script.execute:
id: vacuum_fan_speed
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(3) : "max";'
widgets:
- label:
align: CENTER
@@ -4468,7 +4568,7 @@ script:
- delay: 150ms
- lambda: |-
lv_obj_update_layout(id(tile_scroll));
const int count = std::clamp(atoi("${TILE_COUNT}"), 1, 10);
const int count = runtime_tiles::enabled ? runtime_tiles::model.count : std::clamp(atoi("${TILE_COUNT}"), 1, 10);
const bool paginated = count > 6;
lv_obj_t* tiles[] = {id(tile1), id(tile2), id(tile3), id(tile4), id(tile5),
id(tile6), id(tile7), id(tile8), id(tile9), id(tile10)};
@@ -4511,7 +4611,7 @@ script:
lv_obj_t* tiles[] = {id(tile1), id(tile2), id(tile3), id(tile4), id(tile5),
id(tile6), id(tile7), id(tile8), id(tile9), id(tile10)};
const int rows[] = {${GRID_ROW1_Y}, ${GRID_ROW2_Y}, ${GRID_ROW3_Y}};
const int count = std::clamp(atoi("${TILE_COUNT}"), 1, 10);
const int count = runtime_tiles::enabled ? runtime_tiles::model.count : std::clamp(atoi("${TILE_COUNT}"), 1, 10);
const bool paginated = count > 6;
if (!paginated) id(tile_page) = 0;
for (lv_obj_t* control : {id(page_prev), id(page_next), id(page_number)}) {
@@ -4649,7 +4749,7 @@ script:
value: !lambda "return value;"
- if:
condition:
lambda: 'return std::string("${DIRECT_ACTIONS}") == "true";'
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
then:
- homeassistant.action:
action: climate.set_temperature
@@ -4694,7 +4794,7 @@ script:
then:
- if:
condition:
lambda: 'return std::string("${DIRECT_ACTIONS}") == "true";'
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
then:
- homeassistant.action:
action: !lambda "return service;"
@@ -4709,7 +4809,7 @@ script:
then:
- if:
condition:
lambda: 'return std::string("${DIRECT_ACTIONS}") == "true";'
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
then:
- homeassistant.action:
action: vacuum.set_fan_speed
@@ -4771,7 +4871,7 @@ script:
- lvgl.widget.hide: climate_mode_overlay
- if:
condition:
lambda: 'return std::string("${DIRECT_ACTIONS}") == "true";'
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
then:
- homeassistant.action:
action: climate.set_hvac_mode
@@ -4817,7 +4917,7 @@ script:
- lambda: 'id(active_brightness_pct) = value;'
- if:
condition:
lambda: 'return std::string("${DIRECT_ACTIONS}") == "true" && id(active_entity).rfind("light.", 0) == 0;'
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && id(active_entity).rfind("light.", 0) == 0;'
then:
- homeassistant.action:
action: light.turn_on
@@ -4831,7 +4931,7 @@ script:
then:
- if:
condition:
lambda: 'return std::string("${DIRECT_ACTIONS}") == "true" && id(active_supports_color) && id(active_entity).rfind("light.", 0) == 0;'
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && id(active_supports_color) && id(active_entity).rfind("light.", 0) == 0;'
then:
- homeassistant.action:
action: light.turn_on
@@ -4848,7 +4948,7 @@ script:
then:
- if:
condition:
lambda: 'return std::string("${DIRECT_ACTIONS}") == "true" && id(active_supports_temperature) && id(active_entity).rfind("light.", 0) == 0 && light_controls::active->temperature_ready();'
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && id(active_supports_temperature) && id(active_entity).rfind("light.", 0) == 0 && light_controls::active->temperature_ready();'
then:
- homeassistant.action:
action: light.turn_on
@@ -5479,6 +5579,12 @@ script:
then:
- delay: 100ms
- lambda: |-
if (runtime_tiles::enabled) {
runtime_tiles::render(id(lbl_room));
auto now = id(homeassistant_time).now();
if (now.is_valid()) runtime_tiles::label(id(lbl_time), now.strftime("%H:%M"));
return;
}
auto set_lbl = [](lv_obj_t* lbl, const std::string& s) {
if (lbl && s != lv_label_get_text(lbl)) lv_label_set_text(lbl, s.c_str());
};
@@ -5917,3 +6023,18 @@ script:
${TILE10_TITLE_ACTIVE_COLOR}, ${TILE10_TITLE_DISABLED_COLOR},
${TILE10_VALUE_ACTIVE_COLOR}, ${TILE10_VALUE_DISABLED_COLOR});
# HA blueprint transport; never publishes the JSON command as an HA state.
text:
- platform: smart_display
id: dashboard_inbox
name: "Tegelinstellingen"
internal: ${DASHBOARD_INBOX_INTERNAL}
button:
- platform: template
name: "Touch kalibreren"
internal: ${DASHBOARD_INBOX_INTERNAL}
entity_category: config
on_press:
- lambda: 'if (runtime_tiles::enabled) screen_calibration::begin();'