From 917b3d6d090451316217ed5c090c6250a33f0edf Mon Sep 17 00:00:00 2001 From: Max Gramser Date: Sat, 12 Sep 2026 13:31:18 +0200 Subject: [PATCH] Add persistent per-screen brightness, standby and clock settings --- components/smart_display/runtime_tiles.h | 37 ++++++- components/smart_display/screen_settings.h | 38 +++++++ docs/EASY_SETUP.md | 42 ++++++++ docs/RELEASING.md | 14 +++ docs/SETTINGS_TESTRESULTATEN.md | 21 ++++ guition-4848s040.yaml | 111 ++++++++++----------- home-like-2432s028.yaml | 111 ++++++++++----------- packages/cyd.yaml | 111 ++++++++++----------- packages/guition.yaml | 111 ++++++++++----------- screen_manager/CHANGELOG.md | 8 ++ screen_manager/app/core.py | 37 ++++++- screen_manager/app/server.py | 5 + screen_manager/app/static/app.js | 97 ++++++++++++++++++ screen_manager/app/static/index.html | 13 +++ screen_manager/app/static/style.css | 53 ++++++++++ screen_manager/config.yaml | 2 +- tests/test_portal.py | 43 +++++++- tests/test_screen_settings.cpp | 20 ++++ 18 files changed, 634 insertions(+), 240 deletions(-) create mode 100644 components/smart_display/screen_settings.h create mode 100644 docs/SETTINGS_TESTRESULTATEN.md create mode 100644 tests/test_screen_settings.cpp diff --git a/components/smart_display/runtime_tiles.h b/components/smart_display/runtime_tiles.h index d39ac0c..8d04894 100644 --- a/components/smart_display/runtime_tiles.h +++ b/components/smart_display/runtime_tiles.h @@ -1,5 +1,7 @@ #pragma once #include "runtime_model.h" +#include "screen_settings.h" +#include "esphome/core/preferences.h" #include "cyd_ui.h" #include "light_controls.h" #include "esphome/components/json/json_util.h" @@ -14,7 +16,30 @@ inline bool enabled = false; inline Model model; inline int active_index = -1; inline uint32_t last_received = 0; -inline std::function layout_changed, refresh, dismiss; +inline std::function layout_changed, refresh, dismiss, settings_changed; +inline esphome::ESPPreferenceObject settings_preference; +inline void load_settings() { + settings_preference = esphome::global_preferences->make_preference(0x53435231); + screen_settings::Settings saved; + if (settings_preference.load(&saved) && saved.valid()) screen_settings::current = saved; +} +inline bool parse_settings(JsonObject obj, screen_settings::Settings &s) { + // Require the complete known schema; validate before touching any runtime state. + if (obj.size() != 11) return false; + const char *flags[] = {"standby_enabled", "night_enabled", "show_clock", "clock_24h", "home_on_standby"}; + int32_t *flag_values[] = {&s.standby_enabled, &s.night_enabled, &s.show_clock, &s.clock_24h, &s.home_on_standby}; + for (int i = 0; i < 5; ++i) { + if (!obj[flags[i]].is()) return false; + *flag_values[i] = obj[flags[i]].as(); + } + const char *numbers[] = {"standby_seconds", "brightness", "standby_brightness", "night_start", "night_end", "night_brightness"}; + int32_t *values[] = {&s.standby_seconds, &s.brightness, &s.standby_brightness, &s.night_start, &s.night_end, &s.night_brightness}; + for (int i = 0; i < 6; ++i) { + if (!obj[numbers[i]].is() || obj[numbers[i]].is()) return false; + *values[i] = obj[numbers[i]].as(); + } + return s.valid(); +} inline std::function detail, detail_update; struct Widgets { lv_obj_t *tile{}, *title{}, *value{}, *circle{}, *icon{}; size_t index{}; int cached_active = -1; }; inline std::array widgets; @@ -48,6 +73,11 @@ inline std::string receive(const std::string &payload) { auto op = string(root["op"]); if (op == "layout") { if (!root["entities"].is() || !root["title"].is()) return false; + auto settings = screen_settings::current; + if (!root["settings"].isNull() && (!root["settings"].is() || + !parse_settings(root["settings"].as(), settings))) { + result = "Fout: scherminstellingen"; return false; + } std::vector entities; for (JsonVariant entity : root["entities"].as()) { if (!entity.is() || entities.size() == MAX_TILES) return false; @@ -55,6 +85,11 @@ inline std::string receive(const std::string &payload) { } bool changed = false; if (!model.set_layout(entities, string(root["title"], 96), changed)) return false; + if (!(settings == screen_settings::current)) { + screen_settings::current = settings; + settings_preference.save(&settings); // ESPHome batches flash writes; no write on keepalive. + if (settings_changed) settings_changed(); + } if (changed) { active_index = -1; for (auto &w : widgets) w.cached_active = -1; if (dismiss) dismiss(); } last_received = esphome::millis(); if (layout_changed) layout_changed(); diff --git a/components/smart_display/screen_settings.h b/components/smart_display/screen_settings.h new file mode 100644 index 0000000..52f5ade --- /dev/null +++ b/components/smart_display/screen_settings.h @@ -0,0 +1,38 @@ +#pragma once +#include + +namespace screen_settings { +// Stable preference format. Change its version/key only with a migration. +struct Settings { + uint32_t version = 1; + int32_t standby_enabled = 1, standby_seconds = 600, brightness = 100; + int32_t standby_brightness = 20, night_enabled = 1, night_start = 1320; + int32_t night_end = 420, night_brightness = 10, show_clock = 1; + int32_t clock_24h = 1, home_on_standby = 0; + bool valid() const { + return version == 1 && (standby_enabled == 0 || standby_enabled == 1) && + standby_seconds >= 60 && standby_seconds <= 86400 && + brightness >= 5 && brightness <= 100 && standby_brightness >= 0 && + standby_brightness <= brightness && (night_enabled == 0 || night_enabled == 1) && + night_start >= 0 && night_start < 1440 && night_end >= 0 && night_end < 1440 && + night_brightness >= 0 && night_brightness <= brightness && + (show_clock == 0 || show_clock == 1) && (clock_24h == 0 || clock_24h == 1) && + (home_on_standby == 0 || home_on_standby == 1); + } + bool operator==(const Settings &b) const { + return version == b.version && standby_enabled == b.standby_enabled && + standby_seconds == b.standby_seconds && brightness == b.brightness && + standby_brightness == b.standby_brightness && night_enabled == b.night_enabled && + night_start == b.night_start && night_end == b.night_end && + night_brightness == b.night_brightness && show_clock == b.show_clock && + clock_24h == b.clock_24h && home_on_standby == b.home_on_standby; + } + bool night(int minute) const { + if (!night_enabled || minute < 0 || minute >= 1440 || night_start == night_end) return false; + return night_start < night_end ? minute >= night_start && minute < night_end + : minute >= night_start || minute < night_end; + } + int dim_level(int minute) const { return night(minute) ? night_brightness : standby_brightness; } +}; +inline Settings current; +} diff --git a/docs/EASY_SETUP.md b/docs/EASY_SETUP.md index a63ac91..e654141 100644 --- a/docs/EASY_SETUP.md +++ b/docs/EASY_SETUP.md @@ -162,3 +162,45 @@ een los openbaar portal. Gebruikte HA-mechanismen: [Ingress](https://developers.home-assistant.io/docs/apps/presentation/), [interne HA-API](https://developers.home-assistant.io/docs/apps/communication/) en [ESPHome-pakketten](https://esphome.io/components/packages/). + + +## Scherminstellingen aanpassen (vanaf 0.1.2) + +Update ESP Screen Manager naar 0.1.2 en installeer één keer de nieuwe firmware +via je **bestaande** ESPHome-apparaat → Install → Wirelessly. Behoud de eigen YAML +met wifi en sleutels. Open daarna het scherm in ESP Screen Manager en klap +**Scherminstellingen** open. Klik na aanpassen op **Opslaan & naar scherm**. +Hierna vragen wijzigingen aan deze instellingen geen nieuwe firmwareflash. + +| Instelling | Mogelijkheden | Standaard | +|---|---|---| +| Automatisch standby | Aan/uit | Aan | +| Standby na | 1–1440 minuten na de laatste aanraking | 10 minuten | +| Helderheid normaal | 5–100% | 100% | +| Helderheid standby | 0–100%, maximaal normale helderheid | 20% | +| Nachtstand | Aan/uit; geldt tijdens standby | Aan | +| Begin/einde nacht | Uur en minuut, ook over middernacht | 22:00–07:00 | +| Helderheid nacht | 0–100%, maximaal normale helderheid | 10% | +| Klok tonen | Aan/uit | Aan | +| Tijdnotatie | 24 of 12 uur, zonder AM/PM | 24 uur | +| Terug naar pagina 1 | Bij standby; anders huidige pagina onthouden | Uit | + +Nachturen gebruiken de tijdzone van het ESPHome-apparaat en de tijd uit HA. +Zonder geldige tijd gebruikt het scherm de gewone standbyhelderheid; gelijke +begin- en eindtijd schakelen het nachtvenster uit. Op 0% gaat alleen de +achtergrondverlichting uit: dit is geen deep sleep en geen schermbeveiliging. +De eerste tik wekt het scherm zonder een apparaat te bedienen. + +De add-on bewaart alles per scherm in zijn permanente gegevens. Het scherm bewaart +de laatst ontvangen instellingen ook in preferences; ESPHome bundelt die +schrijfacties (normaal maximaal een minuut). Trek daarom niet direct na opslaan +de voeding los. Bestaande CYD-kalibratie, tegels, API- en OTA-sleutels blijven staan. +Een offline scherm krijgt wijzigingen zodra het terugkomt. Gewone HA-statusupdates +wekken het scherm niet en veranderen de standbytimer niet. + +Bij oudere firmware toont de beheerpagina dat eerst een update nodig is. +De tegels blijven bruikbaar. Handmatige YAML-profielen blijven hun substitutions +gebruiken; voor beheer via deze app gebruik je het Easy Setup-pakket. + +De gemelde Guition-paneelstrepen worden apart onderzocht. Deze instellingenupdate +wijzigt geen displaytimings of driver en is geen bewezen oplossing voor die strepen. diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 7790db6..bebebd2 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -56,3 +56,17 @@ Een tijdelijke ontwikkelserver ondersteunt `SCREEN_DEV=1`, `HA_API` (eindigt op `/api`), `HA_TOKEN_FILE` en `SCREEN_DATA`. Hij bindt alleen op localhost. Zet een token nooit in broncode, URL's of Git. Productie gebruikt Supervisor en accepteert alleen het Ingress-proxyadres; er is geen extra publieke poort. + + +### Instellingencompatibiliteit 0.1.2 + +`settings` is een optioneel object binnen een schermindeling en binnen het bestaande +`v: 1, op: layout`-bericht. Een ontbrekend object behoudt eerder gedrag. Oude +firmware negeert dit extra veld en blijft tegels ontvangen. De beheerpagina meldt +dat nieuwe firmware nodig is. Een oude browser die alleen tegels opslaat, wist +opgeslagen instellingen niet. Onbekende instellingen worden geweigerd. + +Firmware gebruikt een afzonderlijke preferences-key `0x53435231` met een vaste +versie-1 structuur (elf int32-velden en een uint32-versie). Wijzig key, structuur +of versie niet zonder migratie. Alleen gewijzigde instellingen worden opgeslagen; +periodieke herhaling veroorzaakt geen flashwrites en geen nieuwe idle-timer. diff --git a/docs/SETTINGS_TESTRESULTATEN.md b/docs/SETTINGS_TESTRESULTATEN.md new file mode 100644 index 0000000..fe54df7 --- /dev/null +++ b/docs/SETTINGS_TESTRESULTATEN.md @@ -0,0 +1,21 @@ +# Scherminstellingen 0.1.2 — 12 september 2026 + +- 33 Python-tests geslaagd: invoergrenzen, oude indelingen, behoud van instellingen + bij herstart en opslaan vanuit een oudere UI, protocol en HTTP/CSRF. +- Zeven native C++-tests geslaagd, inclusief grenzen, nachttijden over middernacht, + ontbreken van tijdsync en ongeldige opgeslagen settings. +- Easy Setup-firmware voor CYD en Guition gebouwd met ESPHome 2026.6.2. +- Beide handmatige YAML-profielen gevalideerd en volledig gebouwd. +- Browser: standby 20 minuten opslaan en herladen; helderheid naar minimum zetten + verlaagt hogere dimniveaus mee; waarden blijven behouden na herladen. +- Guition via bestaande versleutelde OTA bijgewerkt naar 0.1.2. +- Op het echte scherm: 55% helderheid direct toegepast; eigenaar bevestigt fysiek + dimmen. Standby na 60 seconden ondanks herhaalde indelingsberichten. Ongeldige + helderheid 0 geweigerd zonder de geldige instellingen te veranderen. +- Guition herstart: 55%, 60 seconden en verborgen klok uit preferences teruggelezen. + Daarna oorspronkelijke instellingen teruggezet (100%, 600 seconden). + +CYD was niet aangesloten; de fysieke instellingenacceptatie betreft alleen Guition. +Dit is geen lange stabiliteitstest en geen oplossing voor de bestaande paneelstrepen. +De RGB-driver is bytegelijk aan de oorspronkelijke ESPHome-driver; experimentele +wijzigingen aan timing, spiegeling en reset zijn niet onderdeel van deze release. diff --git a/guition-4848s040.yaml b/guition-4848s040.yaml index a476fac..b79d34e 100644 --- a/guition-4848s040.yaml +++ b/guition-4848s040.yaml @@ -582,7 +582,7 @@ esphome: min_version: 2026.6.2 project: name: maxgramser.esp_screen - version: "0.1.0" + version: "0.1.2" platformio_options: build_flags: - -DLV_USE_SNAPSHOT=1 @@ -603,8 +603,19 @@ esphome: lv_obj_set_style_pad_row(id(vacuum_speeds), 4, 0); lv_obj_set_style_pad_column(id(vacuum_speeds), 4, 0); - lambda: |- + auto &settings = screen_settings::current; + settings.standby_enabled = std::string("${AUTO_DIM}") == "true"; + settings.standby_seconds = atoi("${AUTO_DIM_TIMEOUT}"); + settings.brightness = atoi("${MAX_BRIGHTNESS}"); + settings.standby_brightness = atoi("${DIM_BRIGHTNESS}"); + settings.night_enabled = std::string("${NIGHT_MODE}") == "true"; + settings.night_start = atoi("${NIGHT_START_HOUR}") * 60; + settings.night_end = atoi("${NIGHT_END_HOUR}") * 60; + settings.night_brightness = atoi("${NIGHT_DIM_BRIGHTNESS}"); runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true"; if (!runtime_tiles::enabled) return; + runtime_tiles::load_settings(); + runtime_tiles::settings_changed = []() { id(apply_screen_settings).execute(); }; runtime_tiles::dismiss = []() { id(active_entity).clear(); lv_obj_add_flag(id(brightness_overlay), LV_OBJ_FLAG_HIDDEN); @@ -678,7 +689,7 @@ esphome: light_controls::commits[2] = [](int value) { id(color_brightness_commit).execute(value); }; - light.turn_on: id: back_light - brightness: !lambda "return atoi(\"${MAX_BRIGHTNESS}\") / 100.0f;" + brightness: !lambda "return screen_settings::current.brightness / 100.0f;" - script.execute: show_tile_page - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' - if: @@ -748,6 +759,12 @@ api: - action: ui_state then: - lambda: |- + const auto &cfg = screen_settings::current; + ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u dimmed=%d", + cfg.standby_enabled, cfg.standby_seconds, cfg.brightness, cfg.standby_brightness, + cfg.night_enabled, cfg.night_start, cfg.night_end, cfg.night_brightness, + cfg.show_clock, cfg.clock_24h, cfg.home_on_standby, + (unsigned)(millis() - id(last_touch_ms)), id(display_dimmed)); lv_area_t nav, grid; lv_obj_update_layout(id(home_page)->obj); lv_obj_get_coords(id(page_next), &nav); @@ -788,9 +805,9 @@ interval: - if: condition: lambda: |- - if (std::string("${AUTO_DIM}") != "true") return false; + if (!screen_settings::current.standby_enabled) return false; if (id(display_dimmed) || id(touch_down) || id(calibration_active)) return false; - return (millis() - id(last_touch_ms)) > (atoi("${AUTO_DIM_TIMEOUT}") * 1000UL); + return (millis() - id(last_touch_ms)) > (screen_settings::current.standby_seconds * 1000UL); then: - script.execute: dim_display @@ -801,33 +818,15 @@ time: update_interval: 60s on_time_sync: then: + - script.execute: apply_screen_settings - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' on_time: - seconds: 0 minutes: /1 then: + - script.execute: apply_screen_settings - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' - - seconds: 0 - minutes: 0 - hours: ${NIGHT_START_HOUR} - then: - - if: - condition: - lambda: 'return id(display_dimmed);' - then: - - script.execute: dim_display - - - seconds: 0 - minutes: 0 - hours: ${NIGHT_END_HOUR} - then: - - if: - condition: - lambda: 'return id(display_dimmed);' - then: - - script.execute: dim_display - globals: - id: rendered_frames type: uint32_t @@ -1312,7 +1311,7 @@ text_sensor: - platform: template name: "Schermfirmware" entity_category: diagnostic - lambda: 'return {"0.1.0"};' + lambda: 'return {"0.1.2"};' update_interval: 60s - platform: template id: smartdisplay_action @@ -4448,6 +4447,25 @@ script: lv_obj_add_state(id(page_next), LV_STATE_DISABLED); } + # Runtime changes do not count as touches and do not reset the idle timer. + - id: apply_screen_settings + mode: restart + then: + - lambda: |- + const auto &settings = screen_settings::current; + if (id(display_dimmed) && !settings.standby_enabled) { + id(display_dimmed) = false; + } + auto now = id(homeassistant_time).now(); + const int minute = now.is_valid() ? now.hour * 60 + now.minute : -1; + const int level = id(display_dimmed) ? settings.dim_level(minute) : settings.brightness; + id(back_light).turn_on().set_transition_length(300).set_brightness(level / 100.0f).perform(); + if (id(display_dimmed)) lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + if (settings.show_clock) lv_obj_clear_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + if (!id(ui_refresh).is_running()) id(ui_refresh).execute(); + - id: wake_display mode: restart then: @@ -4459,7 +4477,7 @@ script: # Bijna instant omhoog. De ESPHome-standaard is 1s, en dat voelt traag # als je het scherm aanraakt en op licht staat te wachten. transition_length: 80ms - brightness: !lambda "return atoi(\"${MAX_BRIGHTNESS}\") / 100.0f;" + brightness: !lambda "return screen_settings::current.brightness / 100.0f;" - lvgl.widget.hide: dim_wake_overlay - lvgl.widget.hide: color_detail_overlay - lvgl.widget.hide: climate_detail_overlay @@ -4471,41 +4489,14 @@ script: then: - if: condition: - lambda: 'return std::string("${AUTO_DIM}") == "true";' + lambda: 'return screen_settings::current.standby_enabled;' then: - lambda: |- - int dim_value = atoi("${DIM_BRIGHTNESS}"); - int max_value = atoi("${MAX_BRIGHTNESS}"); - - if (std::string("${NIGHT_MODE}") == "true") { - auto now = id(homeassistant_time).now(); - // Vóór de eerste tijd-sync (zie time: hierboven) is now() - // epoch 0 — dan geen nachtmodus-beslissing nemen op basis - // van een ongeldig uur, gewoon normale DIM_BRIGHTNESS. - if (now.is_valid()) { - int hour = now.hour; - int start_hour = atoi("${NIGHT_START_HOUR}"); - int end_hour = atoi("${NIGHT_END_HOUR}"); - bool is_night = false; - if (start_hour < end_hour) { - is_night = (hour >= start_hour && hour < end_hour); - } else { - is_night = (hour >= start_hour || hour < end_hour); - } - if (is_night) { - dim_value = atoi("${NIGHT_DIM_BRIGHTNESS}"); - } - } - } - - // Rustig omlaag faden is prettig; omhoog moet juist snel. - id(back_light).turn_on().set_transition_length(900).set_brightness(dim_value / 100.0f).perform(); - - // Always mark as dimmed to stop the interval from re-firing id(display_dimmed) = true; - // Only show the touch-blocking overlay when brightness actually changes - if (dim_value < max_value) { - lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + id(apply_screen_settings).execute(); + if (screen_settings::current.home_on_standby) { + id(tile_page) = 0; + id(show_tile_page).execute(); } # Terug naar het basisscherm bij standby. Bewust via de ESPHome @@ -5394,7 +5385,9 @@ script: 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")); + if (screen_settings::current.show_clock) lv_obj_clear_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + if (now.is_valid()) runtime_tiles::label(id(lbl_time), now.strftime(screen_settings::current.clock_24h ? "%H:%M" : "%I:%M")); return; } auto set_lbl = [](lv_obj_t* lbl, const std::string& s) { diff --git a/home-like-2432s028.yaml b/home-like-2432s028.yaml index 42bfbe1..97c4e75 100644 --- a/home-like-2432s028.yaml +++ b/home-like-2432s028.yaml @@ -777,7 +777,7 @@ esphome: min_version: 2026.6.2 project: name: maxgramser.esp_screen - version: "0.1.0" + version: "0.1.2" includes: - cyd_ui.h - light_controls.h @@ -796,8 +796,19 @@ esphome: lv_obj_set_style_pad_row(id(vacuum_speeds), 4, 0); lv_obj_set_style_pad_column(id(vacuum_speeds), 4, 0); - lambda: |- + auto &settings = screen_settings::current; + settings.standby_enabled = std::string("${AUTO_DIM}") == "true"; + settings.standby_seconds = atoi("${AUTO_DIM_TIMEOUT}"); + settings.brightness = atoi("${MAX_BRIGHTNESS}"); + settings.standby_brightness = atoi("${DIM_BRIGHTNESS}"); + settings.night_enabled = std::string("${NIGHT_MODE}") == "true"; + settings.night_start = atoi("${NIGHT_START_HOUR}") * 60; + settings.night_end = atoi("${NIGHT_END_HOUR}") * 60; + settings.night_brightness = atoi("${NIGHT_DIM_BRIGHTNESS}"); runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true"; if (!runtime_tiles::enabled) return; + runtime_tiles::load_settings(); + runtime_tiles::settings_changed = []() { id(apply_screen_settings).execute(); }; runtime_tiles::dismiss = []() { id(active_entity).clear(); lv_obj_add_flag(id(brightness_overlay), LV_OBJ_FLAG_HIDDEN); @@ -879,7 +890,7 @@ esphome: ${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;" + brightness: !lambda "return screen_settings::current.brightness / 100.0f;" - script.execute: show_tile_page - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' - if: @@ -940,6 +951,12 @@ api: - action: ui_state then: - lambda: |- + const auto &cfg = screen_settings::current; + ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u dimmed=%d", + cfg.standby_enabled, cfg.standby_seconds, cfg.brightness, cfg.standby_brightness, + cfg.night_enabled, cfg.night_start, cfg.night_end, cfg.night_brightness, + cfg.show_clock, cfg.clock_24h, cfg.home_on_standby, + (unsigned)(millis() - id(last_touch_ms)), id(display_dimmed)); lv_area_t nav, grid; lv_obj_update_layout(id(home_page)->obj); lv_obj_get_coords(id(page_next), &nav); @@ -980,9 +997,9 @@ interval: - if: condition: lambda: |- - if (std::string("${AUTO_DIM}") != "true") return false; + if (!screen_settings::current.standby_enabled) return false; if (id(display_dimmed) || id(touch_down) || id(calibration_active)) return false; - return (millis() - id(last_touch_ms)) > (atoi("${AUTO_DIM_TIMEOUT}") * 1000UL); + return (millis() - id(last_touch_ms)) > (screen_settings::current.standby_seconds * 1000UL); then: - script.execute: dim_display @@ -993,33 +1010,15 @@ time: update_interval: 60s on_time_sync: then: + - script.execute: apply_screen_settings - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' on_time: - seconds: 0 minutes: /1 then: + - script.execute: apply_screen_settings - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' - - seconds: 0 - minutes: 0 - hours: ${NIGHT_START_HOUR} - then: - - if: - condition: - lambda: 'return id(display_dimmed);' - then: - - script.execute: dim_display - - - seconds: 0 - minutes: 0 - hours: ${NIGHT_END_HOUR} - then: - - if: - condition: - lambda: 'return id(display_dimmed);' - then: - - script.execute: dim_display - globals: - id: rendered_frames type: uint32_t @@ -1499,7 +1498,7 @@ text_sensor: - platform: template name: "Schermfirmware" entity_category: diagnostic - lambda: 'return {"0.1.0"};' + lambda: 'return {"0.1.2"};' update_interval: 60s - platform: template id: smartdisplay_action @@ -4636,6 +4635,25 @@ script: lv_obj_add_state(id(page_next), LV_STATE_DISABLED); } + # Runtime changes do not count as touches and do not reset the idle timer. + - id: apply_screen_settings + mode: restart + then: + - lambda: |- + const auto &settings = screen_settings::current; + if (id(display_dimmed) && !settings.standby_enabled) { + id(display_dimmed) = false; + } + auto now = id(homeassistant_time).now(); + const int minute = now.is_valid() ? now.hour * 60 + now.minute : -1; + const int level = id(display_dimmed) ? settings.dim_level(minute) : settings.brightness; + id(back_light).turn_on().set_transition_length(300).set_brightness(level / 100.0f).perform(); + if (id(display_dimmed)) lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + if (settings.show_clock) lv_obj_clear_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + if (!id(ui_refresh).is_running()) id(ui_refresh).execute(); + - id: wake_display mode: restart then: @@ -4647,7 +4665,7 @@ script: # Bijna instant omhoog. De ESPHome-standaard is 1s, en dat voelt traag # als je het scherm aanraakt en op licht staat te wachten. transition_length: 80ms - brightness: !lambda "return atoi(\"${MAX_BRIGHTNESS}\") / 100.0f;" + brightness: !lambda "return screen_settings::current.brightness / 100.0f;" - lvgl.widget.hide: dim_wake_overlay - lvgl.widget.hide: color_detail_overlay - lvgl.widget.hide: climate_detail_overlay @@ -4659,41 +4677,14 @@ script: then: - if: condition: - lambda: 'return std::string("${AUTO_DIM}") == "true";' + lambda: 'return screen_settings::current.standby_enabled;' then: - lambda: |- - int dim_value = atoi("${DIM_BRIGHTNESS}"); - int max_value = atoi("${MAX_BRIGHTNESS}"); - - if (std::string("${NIGHT_MODE}") == "true") { - auto now = id(homeassistant_time).now(); - // Vóór de eerste tijd-sync (zie time: hierboven) is now() - // epoch 0 — dan geen nachtmodus-beslissing nemen op basis - // van een ongeldig uur, gewoon normale DIM_BRIGHTNESS. - if (now.is_valid()) { - int hour = now.hour; - int start_hour = atoi("${NIGHT_START_HOUR}"); - int end_hour = atoi("${NIGHT_END_HOUR}"); - bool is_night = false; - if (start_hour < end_hour) { - is_night = (hour >= start_hour && hour < end_hour); - } else { - is_night = (hour >= start_hour || hour < end_hour); - } - if (is_night) { - dim_value = atoi("${NIGHT_DIM_BRIGHTNESS}"); - } - } - } - - // Rustig omlaag faden is prettig; omhoog moet juist snel. - id(back_light).turn_on().set_transition_length(900).set_brightness(dim_value / 100.0f).perform(); - - // Always mark as dimmed to stop the interval from re-firing id(display_dimmed) = true; - // Only show the touch-blocking overlay when brightness actually changes - if (dim_value < max_value) { - lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + id(apply_screen_settings).execute(); + if (screen_settings::current.home_on_standby) { + id(tile_page) = 0; + id(show_tile_page).execute(); } # Terug naar het basisscherm bij standby. Bewust via de ESPHome @@ -5582,7 +5573,9 @@ script: 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")); + if (screen_settings::current.show_clock) lv_obj_clear_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + if (now.is_valid()) runtime_tiles::label(id(lbl_time), now.strftime(screen_settings::current.clock_24h ? "%H:%M" : "%I:%M")); return; } auto set_lbl = [](lv_obj_t* lbl, const std::string& s) { diff --git a/packages/cyd.yaml b/packages/cyd.yaml index 2773ca5..423816c 100644 --- a/packages/cyd.yaml +++ b/packages/cyd.yaml @@ -781,7 +781,7 @@ esphome: min_version: 2026.6.2 project: name: maxgramser.esp_screen - version: "0.1.0" + version: "0.1.2" includes: - - @@ -800,8 +800,19 @@ esphome: lv_obj_set_style_pad_row(id(vacuum_speeds), 4, 0); lv_obj_set_style_pad_column(id(vacuum_speeds), 4, 0); - lambda: |- + auto &settings = screen_settings::current; + settings.standby_enabled = std::string("${AUTO_DIM}") == "true"; + settings.standby_seconds = atoi("${AUTO_DIM_TIMEOUT}"); + settings.brightness = atoi("${MAX_BRIGHTNESS}"); + settings.standby_brightness = atoi("${DIM_BRIGHTNESS}"); + settings.night_enabled = std::string("${NIGHT_MODE}") == "true"; + settings.night_start = atoi("${NIGHT_START_HOUR}") * 60; + settings.night_end = atoi("${NIGHT_END_HOUR}") * 60; + settings.night_brightness = atoi("${NIGHT_DIM_BRIGHTNESS}"); runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true"; if (!runtime_tiles::enabled) return; + runtime_tiles::load_settings(); + runtime_tiles::settings_changed = []() { id(apply_screen_settings).execute(); }; runtime_tiles::dismiss = []() { id(active_entity).clear(); lv_obj_add_flag(id(brightness_overlay), LV_OBJ_FLAG_HIDDEN); @@ -883,7 +894,7 @@ esphome: ${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;" + brightness: !lambda "return screen_settings::current.brightness / 100.0f;" - script.execute: show_tile_page - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' - if: @@ -944,6 +955,12 @@ api: - action: ui_state then: - lambda: |- + const auto &cfg = screen_settings::current; + ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u dimmed=%d", + cfg.standby_enabled, cfg.standby_seconds, cfg.brightness, cfg.standby_brightness, + cfg.night_enabled, cfg.night_start, cfg.night_end, cfg.night_brightness, + cfg.show_clock, cfg.clock_24h, cfg.home_on_standby, + (unsigned)(millis() - id(last_touch_ms)), id(display_dimmed)); lv_area_t nav, grid; lv_obj_update_layout(id(home_page)->obj); lv_obj_get_coords(id(page_next), &nav); @@ -971,9 +988,9 @@ interval: - if: condition: lambda: |- - if (std::string("${AUTO_DIM}") != "true") return false; + if (!screen_settings::current.standby_enabled) return false; if (id(display_dimmed) || id(touch_down) || id(calibration_active)) return false; - return (millis() - id(last_touch_ms)) > (atoi("${AUTO_DIM_TIMEOUT}") * 1000UL); + return (millis() - id(last_touch_ms)) > (screen_settings::current.standby_seconds * 1000UL); then: - script.execute: dim_display @@ -984,33 +1001,15 @@ time: update_interval: 60s on_time_sync: then: + - script.execute: apply_screen_settings - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' on_time: - seconds: 0 minutes: /1 then: + - script.execute: apply_screen_settings - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' - - seconds: 0 - minutes: 0 - hours: ${NIGHT_START_HOUR} - then: - - if: - condition: - lambda: 'return id(display_dimmed);' - then: - - script.execute: dim_display - - - seconds: 0 - minutes: 0 - hours: ${NIGHT_END_HOUR} - then: - - if: - condition: - lambda: 'return id(display_dimmed);' - then: - - script.execute: dim_display - globals: - id: rendered_frames type: uint32_t @@ -1452,7 +1451,7 @@ text_sensor: - platform: template name: "Schermfirmware" entity_category: diagnostic - lambda: 'return {"0.1.0"};' + lambda: 'return {"0.1.2"};' update_interval: 60s - platform: template id: smartdisplay_action @@ -4569,6 +4568,25 @@ script: lv_obj_add_state(id(page_next), LV_STATE_DISABLED); } + # Runtime changes do not count as touches and do not reset the idle timer. + - id: apply_screen_settings + mode: restart + then: + - lambda: |- + const auto &settings = screen_settings::current; + if (id(display_dimmed) && !settings.standby_enabled) { + id(display_dimmed) = false; + } + auto now = id(homeassistant_time).now(); + const int minute = now.is_valid() ? now.hour * 60 + now.minute : -1; + const int level = id(display_dimmed) ? settings.dim_level(minute) : settings.brightness; + id(back_light).turn_on().set_transition_length(300).set_brightness(level / 100.0f).perform(); + if (id(display_dimmed)) lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + if (settings.show_clock) lv_obj_clear_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + if (!id(ui_refresh).is_running()) id(ui_refresh).execute(); + - id: wake_display mode: restart then: @@ -4580,7 +4598,7 @@ script: # Bijna instant omhoog. De ESPHome-standaard is 1s, en dat voelt traag # als je het scherm aanraakt en op licht staat te wachten. transition_length: 80ms - brightness: !lambda "return atoi(\"${MAX_BRIGHTNESS}\") / 100.0f;" + brightness: !lambda "return screen_settings::current.brightness / 100.0f;" - lvgl.widget.hide: dim_wake_overlay - lvgl.widget.hide: color_detail_overlay - lvgl.widget.hide: climate_detail_overlay @@ -4592,41 +4610,14 @@ script: then: - if: condition: - lambda: 'return std::string("${AUTO_DIM}") == "true";' + lambda: 'return screen_settings::current.standby_enabled;' then: - lambda: |- - int dim_value = atoi("${DIM_BRIGHTNESS}"); - int max_value = atoi("${MAX_BRIGHTNESS}"); - - if (std::string("${NIGHT_MODE}") == "true") { - auto now = id(homeassistant_time).now(); - // Vóór de eerste tijd-sync (zie time: hierboven) is now() - // epoch 0 — dan geen nachtmodus-beslissing nemen op basis - // van een ongeldig uur, gewoon normale DIM_BRIGHTNESS. - if (now.is_valid()) { - int hour = now.hour; - int start_hour = atoi("${NIGHT_START_HOUR}"); - int end_hour = atoi("${NIGHT_END_HOUR}"); - bool is_night = false; - if (start_hour < end_hour) { - is_night = (hour >= start_hour && hour < end_hour); - } else { - is_night = (hour >= start_hour || hour < end_hour); - } - if (is_night) { - dim_value = atoi("${NIGHT_DIM_BRIGHTNESS}"); - } - } - } - - // Rustig omlaag faden is prettig; omhoog moet juist snel. - id(back_light).turn_on().set_transition_length(900).set_brightness(dim_value / 100.0f).perform(); - - // Always mark as dimmed to stop the interval from re-firing id(display_dimmed) = true; - // Only show the touch-blocking overlay when brightness actually changes - if (dim_value < max_value) { - lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + id(apply_screen_settings).execute(); + if (screen_settings::current.home_on_standby) { + id(tile_page) = 0; + id(show_tile_page).execute(); } # Terug naar het basisscherm bij standby. Bewust via de ESPHome @@ -5515,7 +5506,9 @@ script: 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")); + if (screen_settings::current.show_clock) lv_obj_clear_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + if (now.is_valid()) runtime_tiles::label(id(lbl_time), now.strftime(screen_settings::current.clock_24h ? "%H:%M" : "%I:%M")); return; } auto set_lbl = [](lv_obj_t* lbl, const std::string& s) { diff --git a/packages/guition.yaml b/packages/guition.yaml index e614774..8aca33a 100644 --- a/packages/guition.yaml +++ b/packages/guition.yaml @@ -586,7 +586,7 @@ esphome: min_version: 2026.6.2 project: name: maxgramser.esp_screen - version: "0.1.0" + version: "0.1.2" platformio_options: build_flags: - -DLV_USE_SNAPSHOT=1 @@ -607,8 +607,19 @@ esphome: lv_obj_set_style_pad_row(id(vacuum_speeds), 4, 0); lv_obj_set_style_pad_column(id(vacuum_speeds), 4, 0); - lambda: |- + auto &settings = screen_settings::current; + settings.standby_enabled = std::string("${AUTO_DIM}") == "true"; + settings.standby_seconds = atoi("${AUTO_DIM_TIMEOUT}"); + settings.brightness = atoi("${MAX_BRIGHTNESS}"); + settings.standby_brightness = atoi("${DIM_BRIGHTNESS}"); + settings.night_enabled = std::string("${NIGHT_MODE}") == "true"; + settings.night_start = atoi("${NIGHT_START_HOUR}") * 60; + settings.night_end = atoi("${NIGHT_END_HOUR}") * 60; + settings.night_brightness = atoi("${NIGHT_DIM_BRIGHTNESS}"); runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true"; if (!runtime_tiles::enabled) return; + runtime_tiles::load_settings(); + runtime_tiles::settings_changed = []() { id(apply_screen_settings).execute(); }; runtime_tiles::dismiss = []() { id(active_entity).clear(); lv_obj_add_flag(id(brightness_overlay), LV_OBJ_FLAG_HIDDEN); @@ -682,7 +693,7 @@ esphome: light_controls::commits[2] = [](int value) { id(color_brightness_commit).execute(value); }; - light.turn_on: id: back_light - brightness: !lambda "return atoi(\"${MAX_BRIGHTNESS}\") / 100.0f;" + brightness: !lambda "return screen_settings::current.brightness / 100.0f;" - script.execute: show_tile_page - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' - if: @@ -752,6 +763,12 @@ api: - action: ui_state then: - lambda: |- + const auto &cfg = screen_settings::current; + ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u dimmed=%d", + cfg.standby_enabled, cfg.standby_seconds, cfg.brightness, cfg.standby_brightness, + cfg.night_enabled, cfg.night_start, cfg.night_end, cfg.night_brightness, + cfg.show_clock, cfg.clock_24h, cfg.home_on_standby, + (unsigned)(millis() - id(last_touch_ms)), id(display_dimmed)); lv_area_t nav, grid; lv_obj_update_layout(id(home_page)->obj); lv_obj_get_coords(id(page_next), &nav); @@ -779,9 +796,9 @@ interval: - if: condition: lambda: |- - if (std::string("${AUTO_DIM}") != "true") return false; + if (!screen_settings::current.standby_enabled) return false; if (id(display_dimmed) || id(touch_down) || id(calibration_active)) return false; - return (millis() - id(last_touch_ms)) > (atoi("${AUTO_DIM_TIMEOUT}") * 1000UL); + return (millis() - id(last_touch_ms)) > (screen_settings::current.standby_seconds * 1000UL); then: - script.execute: dim_display @@ -792,33 +809,15 @@ time: update_interval: 60s on_time_sync: then: + - script.execute: apply_screen_settings - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' on_time: - seconds: 0 minutes: /1 then: + - script.execute: apply_screen_settings - lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' - - seconds: 0 - minutes: 0 - hours: ${NIGHT_START_HOUR} - then: - - if: - condition: - lambda: 'return id(display_dimmed);' - then: - - script.execute: dim_display - - - seconds: 0 - minutes: 0 - hours: ${NIGHT_END_HOUR} - then: - - if: - condition: - lambda: 'return id(display_dimmed);' - then: - - script.execute: dim_display - globals: - id: rendered_frames type: uint32_t @@ -1265,7 +1264,7 @@ text_sensor: - platform: template name: "Schermfirmware" entity_category: diagnostic - lambda: 'return {"0.1.0"};' + lambda: 'return {"0.1.2"};' update_interval: 60s - platform: template id: smartdisplay_action @@ -4381,6 +4380,25 @@ script: lv_obj_add_state(id(page_next), LV_STATE_DISABLED); } + # Runtime changes do not count as touches and do not reset the idle timer. + - id: apply_screen_settings + mode: restart + then: + - lambda: |- + const auto &settings = screen_settings::current; + if (id(display_dimmed) && !settings.standby_enabled) { + id(display_dimmed) = false; + } + auto now = id(homeassistant_time).now(); + const int minute = now.is_valid() ? now.hour * 60 + now.minute : -1; + const int level = id(display_dimmed) ? settings.dim_level(minute) : settings.brightness; + id(back_light).turn_on().set_transition_length(300).set_brightness(level / 100.0f).perform(); + if (id(display_dimmed)) lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + if (settings.show_clock) lv_obj_clear_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + if (!id(ui_refresh).is_running()) id(ui_refresh).execute(); + - id: wake_display mode: restart then: @@ -4392,7 +4410,7 @@ script: # Bijna instant omhoog. De ESPHome-standaard is 1s, en dat voelt traag # als je het scherm aanraakt en op licht staat te wachten. transition_length: 80ms - brightness: !lambda "return atoi(\"${MAX_BRIGHTNESS}\") / 100.0f;" + brightness: !lambda "return screen_settings::current.brightness / 100.0f;" - lvgl.widget.hide: dim_wake_overlay - lvgl.widget.hide: color_detail_overlay - lvgl.widget.hide: climate_detail_overlay @@ -4404,41 +4422,14 @@ script: then: - if: condition: - lambda: 'return std::string("${AUTO_DIM}") == "true";' + lambda: 'return screen_settings::current.standby_enabled;' then: - lambda: |- - int dim_value = atoi("${DIM_BRIGHTNESS}"); - int max_value = atoi("${MAX_BRIGHTNESS}"); - - if (std::string("${NIGHT_MODE}") == "true") { - auto now = id(homeassistant_time).now(); - // Vóór de eerste tijd-sync (zie time: hierboven) is now() - // epoch 0 — dan geen nachtmodus-beslissing nemen op basis - // van een ongeldig uur, gewoon normale DIM_BRIGHTNESS. - if (now.is_valid()) { - int hour = now.hour; - int start_hour = atoi("${NIGHT_START_HOUR}"); - int end_hour = atoi("${NIGHT_END_HOUR}"); - bool is_night = false; - if (start_hour < end_hour) { - is_night = (hour >= start_hour && hour < end_hour); - } else { - is_night = (hour >= start_hour || hour < end_hour); - } - if (is_night) { - dim_value = atoi("${NIGHT_DIM_BRIGHTNESS}"); - } - } - } - - // Rustig omlaag faden is prettig; omhoog moet juist snel. - id(back_light).turn_on().set_transition_length(900).set_brightness(dim_value / 100.0f).perform(); - - // Always mark as dimmed to stop the interval from re-firing id(display_dimmed) = true; - // Only show the touch-blocking overlay when brightness actually changes - if (dim_value < max_value) { - lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); + id(apply_screen_settings).execute(); + if (screen_settings::current.home_on_standby) { + id(tile_page) = 0; + id(show_tile_page).execute(); } # Terug naar het basisscherm bij standby. Bewust via de ESPHome @@ -5327,7 +5318,9 @@ script: 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")); + if (screen_settings::current.show_clock) lv_obj_clear_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + else lv_obj_add_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN); + if (now.is_valid()) runtime_tiles::label(id(lbl_time), now.strftime(screen_settings::current.clock_24h ? "%H:%M" : "%I:%M")); return; } auto set_lbl = [](lv_obj_t* lbl, const std::string& s) { diff --git a/screen_manager/CHANGELOG.md b/screen_manager/CHANGELOG.md index 3bcf4ff..15b5fef 100644 --- a/screen_manager/CHANGELOG.md +++ b/screen_manager/CHANGELOG.md @@ -1,3 +1,11 @@ +# 0.1.2 + +- Per scherm standbyduur, normale/standby/nachthelderheid en nachturen instellen. +- Klok aan/uit, 12/24 uur en terug naar pagina 1 na standby. +- Instellingen direct doorsturen; firmware 0.1.2 bewaart ze in preferences. +- Bestaande tegels en sleutels blijven behouden. Eenmalig firmware bijwerken. +- De Guition-displaydriver is ongewijzigd; dit is geen oplossing voor paneelstrepen. + # 0.1.1 - Duidelijke foutmelding bij een ongeldige naam, te veel tegels of dubbele entiteiten. diff --git a/screen_manager/app/core.py b/screen_manager/app/core.py index ca7315e..ce53bb1 100644 --- a/screen_manager/app/core.py +++ b/screen_manager/app/core.py @@ -10,6 +10,38 @@ REPO = 'https://github.com/MaxGramser/homeassistant_espscreen' REFS = {'cyd': 'main', 'guition': 'main'} ATTRS = frozenset('brightness percentage current_position current_temperature temperature current_humidity min_temp max_temp target_temp_step supported_color_modes hvac_modes hs_color color_temp_kelvin min_color_temp_kelvin max_color_temp_kelvin fan_speed_list unit_of_measurement'.split()) +# Additive schema 1 extension. An absent object retains old firmware/YAML defaults. +SETTING_RULES = { + 'standby_enabled': (True, None, None), + 'standby_seconds': (600, 60, 86400), + 'brightness': (100, 5, 100), + 'standby_brightness': (20, 0, 100), + 'night_enabled': (True, None, None), + 'night_start': (1320, 0, 1439), + 'night_end': (420, 0, 1439), + 'night_brightness': (10, 0, 100), + 'show_clock': (True, None, None), + 'clock_24h': (True, None, None), + 'home_on_standby': (False, None, None), +} + +def validate_settings(data): + if not isinstance(data, dict) or set(data) - SETTING_RULES.keys(): + raise ValueError('Onbekende scherminstellingen; vernieuw de beheerpagina.') + clean = {} + for key, (default, minimum, maximum) in SETTING_RULES.items(): + value = data.get(key, default) + if minimum is None: + valid = type(value) is bool + else: + valid = type(value) is int and minimum <= value <= maximum + if not valid: + raise ValueError(f'Ongeldige waarde voor {key}.') + clean[key] = value + if max(clean['standby_brightness'], clean['night_brightness']) > clean['brightness']: + raise ValueError('Helderheid in standby en nacht mag niet hoger zijn dan normaal.') + return clean + def entity_id(value): return isinstance(value, str) and len(value) <= 120 and re.fullmatch(r'[a-z0-9_]+\.[a-z0-9_]+', value) and value.split('.')[0] in DOMAINS @@ -35,7 +67,10 @@ def validate_layout(data): raise ValueError('Een tegelnaam mag maximaal 80 bytes bevatten.') seen.add(tile['entity']) clean.append({'entity': tile['entity'], 'name': name.strip()}) - return {'title': title.strip(), 'tiles': clean} + result = {'title': title.strip(), 'tiles': clean} + if 'settings' in data: + result['settings'] = validate_settings(data['settings']) + return result def state_message(index, tile, states): state = states.get(tile['entity'], {}) diff --git a/screen_manager/app/server.py b/screen_manager/app/server.py index f854af7..e9aed39 100644 --- a/screen_manager/app/server.py +++ b/screen_manager/app/server.py @@ -130,6 +130,9 @@ class Manager: if inbox not in {s['id'] for s in self.inventory()[0]}: raise ValueError('Dit is geen gekoppeld ESP-scherm. Vernieuw het overzicht.') layout = validate_layout(data) + # A still-open older UI may save tiles without the new optional settings. + if 'settings' not in layout and 'settings' in self.layouts.get(inbox, {}): + layout['settings'] = self.layouts[inbox]['settings'].copy() known = {e['id'] for e in self.inventory()[1]} if any(t['entity'] not in known for t in layout['tiles']): raise ValueError('Een gekozen entiteit bestaat niet meer. Zoek de nieuwe entiteit op.') @@ -149,6 +152,8 @@ class Manager: async def sync_one(self, inbox, layout, force=False): messages = [{'v': 1, 'op': 'layout', 'title': layout['title'], 'entities': [t['entity'] for t in layout['tiles']]}] + if 'settings' in layout: + messages[0]['settings'] = layout['settings'] messages += [state_message(i, tile, self.ha.states) for i, tile in enumerate(layout['tiles'])] previous = self.sent.get(inbox, []) force = force or not previous or messages[0] != previous[0] diff --git a/screen_manager/app/static/app.js b/screen_manager/app/static/app.js index 318aabd..c769329 100644 --- a/screen_manager/app/static/app.js +++ b/screen_manager/app/static/app.js @@ -65,6 +65,99 @@ function select(id) { renderScreens(); renderTiles(); renderResults(); + renderSettings(); +} +const settingDefinitions = [ + ["standby_enabled", "Automatisch standby", "check", true], + ["standby_seconds", "Standby na", "minutes", 600, 1, 1440], + ["brightness", "Helderheid normaal", "range", 100, 5, 100], + ["standby_brightness", "Helderheid in standby", "range", 20, 0, 100], + ["night_enabled", "Nachtstand gebruiken", "check", true], + ["night_brightness", "Helderheid in nachtstand", "range", 10, 0, 100], + ["night_start", "Nachtstand vanaf", "time", 1320], + ["night_end", "Nachtstand tot", "time", 420], + ["show_clock", "Klok tonen", "check", true], + ["clock_24h", "24-uursklok (uit = 12 uur)", "check", true], + ["home_on_standby", "Na standby terug naar pagina 1", "check", false], +]; +function renderSettings() { + const values = { + ...Object.fromEntries( + settingDefinitions.map(([key, , , value]) => [key, value]), + ), + ...layout.settings, + }; + const container = $("#settings-fields"); + container.replaceChildren(); + for (const [key, title, kind, , min, max] of settingDefinitions) { + const label = node( + "label", + undefined, + `setting ${kind === "check" ? "setting-check" : ""}`, + ); + const caption = node("span", title), + input = node("input"), + output = node("output"); + input.id = `setting-${key}`; + input.type = + kind === "check" ? "checkbox" : kind === "minutes" ? "number" : kind; + input.setAttribute("aria-label", title); + input.required = kind === "minutes" || kind === "time"; + if (kind === "check") input.checked = values[key]; + else if (kind === "time") + input.value = `${String(Math.floor(values[key] / 60)).padStart(2, "0")}:${String(values[key] % 60).padStart(2, "0")}`; + else { + input.min = min; + input.max = max; + input.step = 1; + input.value = kind === "minutes" ? values[key] / 60 : values[key]; + } + const updateOutput = () => + (output.textContent = + kind === "range" + ? `${input.value}%` + : kind === "minutes" + ? "minuten" + : ""); + updateOutput(); + input.oninput = () => { + if (!input.checkValidity() || input.value === "") return; + let value = kind === "check" ? input.checked : Number(input.value); + if (kind === "time") { + const [h, m] = input.value.split(":").map(Number); + value = h * 60 + m; + } + if (kind === "minutes") value *= 60; + layout.settings = { ...values, ...layout.settings, [key]: value }; + // A lower normal brightness also lowers any higher standby levels. + if (key === "brightness") + for (const dim of ["standby_brightness", "night_brightness"]) { + layout.settings[dim] = Math.min(layout.settings[dim], value); + const other = $(`#setting-${dim}`); + other.value = layout.settings[dim]; + other.parentElement.querySelector("output").textContent = + `${other.value}%`; + } + updateOutput(); + markDirty(); + }; + label.append(caption, input, output); + container.append(label); + } + renderSettingsSupport(); +} +function renderSettingsSupport() { + const version = + inventory.screens.find((s) => s.id === selected)?.firmware || ""; + const parts = /^([0-9]+)\.([0-9]+)\.([0-9]+)$/.exec(version); + const supported = + parts && + (Number(parts[1]) > 0 || + Number(parts[2]) > 1 || + (Number(parts[2]) === 1 && Number(parts[3]) >= 2)); + $("#settings-support").textContent = supported + ? "Opslaan stuurt je instellingen direct naar dit scherm. Ze blijven ook na een herstart bewaard." + : "Eenmalig firmware 0.1.2 of nieuwer installeren via ESPHome. Je kunt de instellingen alvast bewaren; oudere firmware gebruikt ze nog niet."; } function renderScreens() { $("#screens").replaceChildren(); @@ -220,6 +313,7 @@ async function refresh() { : "Verbinding met Home Assistant herstellen…"; $("#connection").classList.toggle("online", inventory.connected); renderScreens(); + if (selected) renderSettingsSupport(); if (!selected && inventory.screens.length) select(inventory.screens[0].id); } catch { $("#connection").textContent = @@ -228,6 +322,9 @@ async function refresh() { } $("#save").onclick = async () => { if (busy) return; + for (const input of document.querySelectorAll("#settings-fields input")) { + if (!input.reportValidity()) return; + } busy = true; $("#save").disabled = true; try { diff --git a/screen_manager/app/static/index.html b/screen_manager/app/static/index.html index 523113d..a7d6ca2 100644 --- a/screen_manager/app/static/index.html +++ b/screen_manager/app/static/index.html @@ -86,6 +86,19 @@
+
+ + Scherminstellingen + Helderheid, rustmomenten en weergave + +

+
+

+ Nachturen gelden voor de helderheid tijdens standby. De eerste + aanraking maakt het scherm wakker; de volgende bedient je huis. + Gelijke begin- en eindtijd schakelen het nachtvenster uit. +

+