From 53bf1bc228396b5139496f795f9fef1565c146e0 Mon Sep 17 00:00:00 2001 From: Max Gramser Date: Sun, 13 Sep 2026 01:14:39 +0200 Subject: [PATCH] Refine large-value tiles and fade Guition backlight in hardware Large-value (watch) tiles show a small domain icon next to the title, centre title and value vertically, use a dedicated larger digit font, truncate long values with dots and keep the unit in its own label. The geometry self-test checks the unit label too. The Guition backlight now dims through the LEDC hardware fader (1.5 s to standby, 80 ms wake) so full LVGL redraws no longer make the transition stutter; ESPHome's light state is synced after each fade. Firmware 0.2.13; app 0.2.11 stays compatible. Document the shared build directory pitfall. Co-Authored-By: Claude Fable 5.1 --- AGENTS.md | 4 ++ README.md | 2 + backlight_fade.h | 6 +++ components/smart_display/backlight_fade.h | 60 ++++++++++++++++++++++ components/smart_display/runtime_tiles.h | 45 ++++++++++++---- docs/GUITION.md | 4 ++ docs/RELEASING.md | 4 +- docs/TEST_RESULTS_0213.md | 46 +++++++++++++++++ guition-4848s040.yaml | 62 ++++++++++++++++++++--- home-like-2432s028.yaml | 34 ++++++++++++- packages/cyd.yaml | 34 ++++++++++++- packages/guition.yaml | 62 ++++++++++++++++++++--- screen_manager/CHANGELOG.md | 6 +++ tools/generate_packages.py | 2 +- 14 files changed, 342 insertions(+), 29 deletions(-) create mode 100644 backlight_fade.h create mode 100644 components/smart_display/backlight_fade.h create mode 100644 docs/TEST_RESULTS_0213.md diff --git a/AGENTS.md b/AGENTS.md index 49dfec2..eac2505 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,6 +64,10 @@ fysiek tikken; een agent kan dat niet vervangen door softwarecoördinaten. binaries, logs, buildcache of lokale apparaatprofielen. - Geen automatische firmware-upload naar een willekeurige aangesloten poort. Bij meerdere borden eerst de bedoelde poort vaststellen. +- Profielen met dezelfde `DEVICE_NAME` (Easy Setup en handmatig) delen + `.esphome/build/`. Compileer of upload ze nooit gelijktijdig; controleer + in het uploadlog het pad van `firmware.bin` en daarna de compileertijd via + `device_info`. Een verkeerd profiel haalt het scherm uit ESP Screen Manager. Historische diagnose is achtergrond, geen bewijs dat een nieuw paneel goed werkt. Maak tijdens onboarding geen claims over niet-uitgevoerde fysieke tests. diff --git a/README.md b/README.md index 40db564..82c83c6 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ De firmware en ingebouwde CLI zijn getest met **ESPHome 2026.6.2**. tegels. Zoek op entiteit, apparaat of ruimte en sleep om te ordenen. - **Instellingen per tegel:** eigen naam, klikgedrag, een kleine slider waar ondersteund, of een grote waarde voor bijvoorbeeld temperatuur en verbruik. + De grote waarde toont vanaf firmware 0.2.13 een klein domeinicoon naast de + titel; een te lang getal wordt met puntjes afgekort, de eenheid blijft staan. - **Pastel achtergronden per tegel:** kies rood voor een alles-uit-script, groen voor alles-aan, of een andere kleur. Titel en status blijven donker en leesbaar. De kleur verschijnt ook in het schermvoorbeeld; **Standaard** herstelt diff --git a/backlight_fade.h b/backlight_fade.h new file mode 100644 index 0000000..575b513 --- /dev/null +++ b/backlight_fade.h @@ -0,0 +1,6 @@ +#pragma once +#if __has_include("esphome/components/smart_display/backlight_fade.h") +#include "esphome/components/smart_display/backlight_fade.h" +#else +#include "components/smart_display/backlight_fade.h" +#endif diff --git a/components/smart_display/backlight_fade.h b/components/smart_display/backlight_fade.h new file mode 100644 index 0000000..a5e404f --- /dev/null +++ b/components/smart_display/backlight_fade.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include +#include "esphome/components/ledc/ledc_output.h" +#include "esphome/components/light/light_state.h" + +// Backlight fades through the LEDC hardware fader. ESPHome's software +// transition steps the duty from the main loop, and the full LVGL redraws that +// standby triggers on the Guition's RGB panel stall that loop long enough to +// make dimming visibly jerky. The peripheral steps on its own clock instead. +namespace backlight_fade { +template struct Peek : T { + static uint8_t channel(T &o) { return o.*(&Peek::channel_); } + static uint8_t bits(T &o) { return o.*(&Peek::bit_depth_); } +}; +inline bool installed = false, hardware_owns = false; +inline ledc_mode_t mode(uint8_t channel) { +#ifdef SOC_LEDC_SUPPORT_HS_MODE + return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; +#else + return LEDC_LOW_SPEED_MODE; +#endif +} +// Returns false when the fader is unavailable; the caller keeps ESPHome's transition. +inline bool start(esphome::ledc::LEDCOutput &output, esphome::light::LightState &light, float level, uint32_t ms) { +#ifndef SOC_LEDC_SUPPORT_FADE_STOP + return false; // Classic ESP32 cannot interrupt a running fade for a touch wake. +#else + using P = Peek; + const auto channel = static_cast(P::channel(output)); + const uint8_t bits = P::bits(output); + if (!bits) return false; + if (!installed) installed = ledc_fade_func_install(0) == ESP_OK; + if (!installed) return false; + const auto speed = mode(channel); + const uint32_t max_duty = (1u << bits) - 1; + ledc_fade_stop(speed, channel); + // ESPHome parks 0 % and 100 % with ledc_stop and leaves a stale duty register. + // Restart the channel at the level it believes, unless a fade already owns it. + float current = 0; + light.current_values_as_brightness(¤t); + const uint32_t from = hardware_owns ? ledc_get_duty(speed, channel) : static_cast(std::lround(current * max_duty)); + ledc_set_duty(speed, channel, from); + ledc_update_duty(speed, channel); + hardware_owns = true; + const auto target = static_cast(std::lround(light.gamma_correct_lut(std::clamp(level, 0.0f, 1.0f)) * max_duty)); + return ledc_set_fade_with_time(speed, channel, target, ms) == ESP_OK && + ledc_fade_start(speed, channel, LEDC_FADE_NO_WAIT) == ESP_OK; +#endif +} +// Hand the finished level back to ESPHome so HA and later transitions start from the truth. +inline void sync(esphome::ledc::LEDCOutput &output, esphome::light::LightState &light, float level) { +#ifdef SOC_LEDC_SUPPORT_FADE_STOP + if (hardware_owns) ledc_fade_stop(mode(Peek::channel(output)), static_cast(Peek::channel(output))); +#endif + light.turn_on().set_transition_length(0).set_brightness(std::clamp(level, 0.0f, 1.0f)).perform(); + hardware_owns = false; +} +} // namespace backlight_fade diff --git a/components/smart_display/runtime_tiles.h b/components/smart_display/runtime_tiles.h index e9ccea4..67164ad 100644 --- a/components/smart_display/runtime_tiles.h +++ b/components/smart_display/runtime_tiles.h @@ -24,6 +24,7 @@ inline Model model; inline std::string inbox; inline const lv_font_t *watch_font = nullptr; inline const lv_font_t *mini_icon_font = nullptr; +inline const lv_font_t *watch_value_font = nullptr, *watch_icon_font = nullptr; inline void tick(); inline void refresh_detail(unsigned index); inline int active_index = -1; @@ -61,7 +62,7 @@ inline bool parse_settings(JsonObject obj, screen_settings::Settings &s) { 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; lv_obj_t *slider{}, *progress{}; int title_x=0,title_y=0,value_x=0,value_y=0; const lv_font_t *value_font{}, *icon_font{}; }; +struct Widgets { lv_obj_t *tile{}, *title{}, *value{}, *circle{}, *icon{}; size_t index{}; int cached_active = -1; lv_obj_t *slider{}, *progress{}, *unit{}; int title_x=0,title_y=0,value_x=0,value_y=0; const lv_font_t *value_font{}, *icon_font{}; }; inline std::array widgets; inline bool fresh() { return model.ready() && esphome::millis() - last_received < 95000; } inline float number(JsonVariant value, float fallback = NAN) { @@ -462,6 +463,7 @@ inline void event(lv_event_t *event) { if (d == "button" || d == "input_button") action(d + ".press", tile.entity); } inline void bind(size_t index, lv_obj_t *tile, lv_obj_t *title, lv_obj_t *value, lv_obj_t *circle, lv_obj_t *icon) { + lv_obj_update_layout(tile); // Compact cards need room for two text lines and a separate dimmer track. if(lv_obj_get_height(tile)<=80){lv_obj_set_style_pad_top(tile,4,0);lv_obj_set_style_pad_bottom(tile,4,0);} lv_obj_set_style_border_width(tile,1,0); @@ -469,6 +471,7 @@ inline void bind(size_t index, lv_obj_t *tile, lv_obj_t *title, lv_obj_t *value, widgets[index] = {tile, title, value, circle, icon, index}; auto &w=widgets[index]; w.title_x=lv_obj_get_x(title);w.title_y=lv_obj_get_y(title);w.value_x=lv_obj_get_x(value);w.value_y=lv_obj_get_y(value);w.value_font=lv_obj_get_style_text_font(value,LV_PART_MAIN); w.icon_font=lv_obj_get_style_text_font(icon,LV_PART_MAIN); + w.unit=lv_label_create(tile);lv_obj_set_style_text_font(w.unit,w.value_font,0);lv_obj_remove_flag(w.unit,LV_OBJ_FLAG_CLICKABLE);lv_obj_add_flag(w.unit,LV_OBJ_FLAG_HIDDEN); // Fixed one-line boxes prevent wrapped names from overlapping the state on both boards. lv_obj_set_height(title,lv_font_get_line_height(lv_obj_get_style_text_font(title,LV_PART_MAIN))); lv_obj_set_height(value,lv_font_get_line_height(w.value_font)); @@ -523,6 +526,8 @@ inline void render(lv_obj_t *room) { const auto &t = model.tiles[w.index]; label(w.title, t.name.empty() ? t.entity : t.name); label(w.icon, icon_for(t)); + bool watch=t.display=="watch"; + std::string unit=watch?t.unit:""; std::string value = t.state; if (!fresh() || !t.available()) value = "Niet beschikbaar"; else if (t.domain() == "light" && t.state == "on" && std::isfinite(t.brightness)) value = std::to_string(static_cast(std::lround(std::clamp(t.brightness, 0.0f, 255.0f) * 100 / 255))) + " %"; @@ -531,17 +536,17 @@ inline void render(lv_obj_t *room) { else if (value == "off") value = "Uit"; else if (value == "cleaning") value = "Bezig"; else if (value == "docked") value = "In dock"; - else if (!t.unit.empty()) value += " " + t.unit; + else if (!t.unit.empty() && !watch) value += " " + t.unit; bool pending=t.loading(esphome::millis()); - if(t.domain()=="weather" && std::isfinite(t.current)) {char b[32];snprintf(b,sizeof(b),"%.1f %s",t.current,t.unit.c_str());value=b;} + if(t.domain()=="weather" && std::isfinite(t.current)) {char b[32];snprintf(b,sizeof(b),"%.1f %s",t.current,t.unit.c_str());value=b;if(watch){snprintf(b,sizeof(b),"%.1f",t.current);value=b;}} if(t.domain()=="vacuum" && std::isfinite(t.battery))value += " / "+std::to_string((int)t.battery)+"%"; label(w.value, pending ? "Bezig..." : value); lv_obj_set_width(w.progress,pending ? (esphome::millis()/120%5+1)*(lv_obj_get_width(w.tile)-24)/5 : 0); lv_obj_set_style_text_opa(w.icon,pending ? LV_OPA_40 : LV_OPA_COVER,0); - bool watch=t.display=="watch"; bool mini=t.inline_control=="slider" && !watch && t.available(); bool large_tile=lv_obj_get_height(w.tile)>80; - lv_obj_set_style_text_font(w.value,watch && watch_font ? watch_font : w.value_font,0); + if(!large_tile){lv_obj_set_style_pad_top(w.tile,watch?2:4,0);lv_obj_set_style_pad_bottom(w.tile,watch?2:4,0);} + lv_obj_set_style_text_font(w.value,watch && watch_value_font ? watch_value_font : w.value_font,0); lv_obj_set_height(w.value,lv_font_get_line_height(lv_obj_get_style_text_font(w.value,LV_PART_MAIN))); lv_obj_update_layout(w.tile); int content_width=lv_obj_get_content_width(w.tile),content_height=lv_obj_get_content_height(w.tile); @@ -550,11 +555,11 @@ inline void render(lv_obj_t *room) { int slider_height=large_tile?28:8; int header_height=mini?content_height-slider_height-(large_tile?6:3):content_height; int text_y=std::max(0,(header_height-text_height)/2); - int circle_size=mini?(large_tile?36:24):(large_tile?54:36); + int circle_size=watch?(large_tile?26:18):mini?(large_tile?36:24):(large_tile?54:36); lv_obj_set_size(w.circle,circle_size,circle_size); - lv_obj_set_style_text_font(w.icon,mini && mini_icon_font ? mini_icon_font : w.icon_font,0); + lv_obj_set_style_text_font(w.icon,watch && watch_icon_font ? watch_icon_font : mini && mini_icon_font ? mini_icon_font : w.icon_font,0); lv_obj_center(w.icon); - if(watch)lv_obj_add_flag(w.circle,LV_OBJ_FLAG_HIDDEN);else lv_obj_remove_flag(w.circle,LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(w.circle,LV_OBJ_FLAG_HIDDEN); int text_x=watch?0:mini?circle_size+(large_tile?8:6):w.title_x; lv_obj_set_pos(w.title,text_x,watch?0:(mini || !large_tile)?text_y:w.title_y); lv_obj_set_pos(w.value,watch?0:mini?text_x:w.value_x, @@ -564,6 +569,22 @@ inline void render(lv_obj_t *room) { // layout until its next pass when a slot changes from watch/slider to normal. lv_obj_set_width(w.title,std::max(1,content_width-text_x)); lv_obj_set_width(w.value,std::max(1,content_width-(watch?0:mini?text_x:w.value_x))); + if(watch){ + int gap=large_tile?6:2,header=std::max(circle_size,title_height); + int group_y=std::max(0,(content_height-header-gap-value_height)/2); + int value_y=group_y+header+gap; + lv_obj_set_pos(w.circle,0,group_y+(header-circle_size)/2); + lv_obj_set_pos(w.title,circle_size+(large_tile?6:4),group_y+(header-title_height)/2); + lv_obj_set_width(w.title,content_width-circle_size-(large_tile?6:4)); + label(w.unit,unit); + lv_point_t size;lv_text_get_size(&size,unit.c_str(),w.value_font,0,0,LV_COORD_MAX,LV_TEXT_FLAG_EXPAND); + int unit_width=unit.empty()?0:std::min((int)size.x,content_width-20); + int number_width=content_width-(unit_width?unit_width+(large_tile?6:3):0); + lv_obj_set_pos(w.value,0,value_y);lv_obj_set_width(w.value,number_width); + lv_obj_set_pos(w.unit,content_width-unit_width,value_y+value_height-lv_font_get_line_height(w.value_font)); + lv_obj_set_size(w.unit,unit_width,lv_font_get_line_height(w.value_font)); + if(unit_width)lv_obj_remove_flag(w.unit,LV_OBJ_FLAG_HIDDEN);else lv_obj_add_flag(w.unit,LV_OBJ_FLAG_HIDDEN); + }else lv_obj_add_flag(w.unit,LV_OBJ_FLAG_HIDDEN); lv_obj_set_size(w.slider,content_width,slider_height); lv_obj_align(w.slider,LV_ALIGN_BOTTOM_MID,0,0); if(mini){lv_obj_remove_flag(w.slider,LV_OBJ_FLAG_HIDDEN);if(!lv_obj_has_state(w.slider,LV_STATE_PRESSED))lv_slider_set_value(w.slider,slider_value(t),LV_ANIM_OFF);} @@ -589,6 +610,7 @@ inline void render(lv_obj_t *room) { lv_obj_set_style_border_color(w.tile, t.background ? lv_color_mix(lv_color_hex(t.background),lv_color_hex(0x000000),220) : lv_color_hex(light_theme ? 0xDDDDDD : (on ? 0xF5F1E8 : 0x9CB3C8)), 0); lv_obj_set_style_bg_color(w.circle,circle_color,0); lv_obj_set_style_text_color(w.icon,icon_color,0); + lv_obj_set_style_text_color(w.unit,lv_color_hex(dark_text?0x46525E:(on?0x46525E:0xF0F4F8)),0); lv_obj_set_style_text_color(w.title, lv_color_hex(dark_text ? 0x1B1B1B : (on ? 0x172232 : 0xF3F5F7)), 0); lv_obj_set_style_text_color(w.value, lv_color_hex(t.background ? 0x46525E : (light_theme ? 0x616161 : (on ? 0x46525E : 0xF0F4F8))), 0); } @@ -616,13 +638,18 @@ inline bool check_tile_geometry() { if(mini)fits=fits && circle.y280?6:3)-1:content.y2; int center_twice=content.y1+header_bottom; fits=fits && std::abs(circle.y1+circle.y2-center_twice)<=2 && std::abs(title.y1+value.y2-center_twice)<=2; } } + if(!lv_obj_has_flag(w.unit,LV_OBJ_FLAG_HIDDEN)){ + lv_area_t unit;lv_obj_get_coords(w.unit,&unit); + fits=fits && value.x2title.y2; + } if(!fits)ESP_LOGE("ui_test","Tile geometry FAIL slot=%u title_y=%d..%d value_y=%d..%d content_y=%d..%d",(unsigned)w.index,title.y1,title.y2,value.y1,value.y2,content.y1,content.y2); if(w.indexget_lv_font(); + runtime_tiles::watch_value_font = id(watch_value)->get_lv_font(); + runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font(); runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font(); runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true"; if (!runtime_tiles::enabled) return; @@ -1007,6 +1010,16 @@ font: ' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', 'ä','ö','ü','Ä','Ö','Ü','ß','/'] + - file: "fonts/Roboto-500.ttf" + id: watch_value + size: 38 + bpp: 4 + glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°', + '0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + ' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', + 'ä','ö','ü','Ä','Ö','Ü','ß','/'] + - file: "fonts/Roboto-700.ttf" id: label size: 18 @@ -1079,6 +1092,26 @@ font: - "${MDI_GLYPH_10}" - "\U000F012C" # check + - file: 'fonts/materialdesignicons-webfont.ttf' + id: watch_icon + size: 18 + glyphs: + - "\U000F0335" + - "\U000F0210" + - "\U000F111C" + - "\U000F029A" + - "${MDI_GLYPH_1}" + - "${MDI_GLYPH_2}" + - "${MDI_GLYPH_3}" + - "${MDI_GLYPH_4}" + - "${MDI_GLYPH_5}" + - "${MDI_GLYPH_6}" + - "${MDI_GLYPH_7}" + - "${MDI_GLYPH_8}" + - "${MDI_GLYPH_9}" + - "${MDI_GLYPH_10}" + - "\U000F012C" # check + # Geheugen zichtbaar maken: dit bord heeft geen PSRAM, dus de vrije heap # bepaalt hoeveel UI erbij kan. Verschijnt ook als entity in Home Assistant. debug: @@ -4587,7 +4620,8 @@ script: 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(); + // Standby dims slowly; the tile redraws below no longer interrupt the hardware fade. + id(set_backlight).execute(level, id(display_dimmed) ? 1500 : 300); if (id(display_dimmed)) { lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); lv_obj_move_foreground(id(dim_wake_overlay)); } 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); @@ -4600,18 +4634,30 @@ script: - lambda: |- id(last_touch_ms) = millis(); id(display_dimmed) = false; - - light.turn_on: - id: back_light - # 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 screen_settings::current.brightness / 100.0f;" + // Bijna instant omhoog. De ESPHome-standaard is 1s, en dat voelt traag + // als je het scherm aanraakt en op licht staat te wachten. + id(set_backlight).execute(screen_settings::current.brightness, 80); - lvgl.widget.hide: dim_wake_overlay - lvgl.widget.hide: color_detail_overlay - lvgl.widget.hide: climate_detail_overlay - lvgl.widget.hide: climate_mode_overlay - lvgl.widget.hide: vacuum_overlay + # One owner for the backlight level: the LEDC fader steps the duty itself, so + # LVGL redraws cannot make the transition stutter. A new call interrupts the + # running fade from its current duty; afterwards ESPHome's light state is synced. + - id: set_backlight + mode: restart + parameters: + level: int + ms: int + then: + - lambda: |- + if (!backlight_fade::start(*id(gpio_backlight_pwm), *id(back_light), level / 100.0f, ms)) + id(back_light).turn_on().set_transition_length(ms).set_brightness(level / 100.0f).perform(); + - delay: !lambda "return ms + 40;" + - lambda: backlight_fade::sync(*id(gpio_backlight_pwm), *id(back_light), level / 100.0f); + - id: dim_display mode: restart then: diff --git a/home-like-2432s028.yaml b/home-like-2432s028.yaml index cb5b7db..58c6f2f 100644 --- a/home-like-2432s028.yaml +++ b/home-like-2432s028.yaml @@ -58,7 +58,7 @@ ############################################################ substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.12" + SCREEN_FIRMWARE_VERSION: "0.2.13" DYNAMIC_TILES: "false" DASHBOARD_INBOX_INTERNAL: "true" DEVICE_NAME: "cyd-new" @@ -810,6 +810,8 @@ esphome: settings.night_end = atoi("${NIGHT_END_HOUR}") * 60; settings.night_brightness = atoi("${NIGHT_DIM_BRIGHTNESS}"); runtime_tiles::watch_font = id(headline)->get_lv_font(); + runtime_tiles::watch_value_font = id(watch_value)->get_lv_font(); + runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font(); runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font(); runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true"; if (!runtime_tiles::enabled) return; @@ -1197,6 +1199,16 @@ font: ' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', 'ä','ö','ü','Ä','Ö','Ü','ß','/'] + - file: "fonts/Roboto-500.ttf" + id: watch_value + size: 22 + bpp: 4 + glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°', + '0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + ' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', + 'ä','ö','ü','Ä','Ö','Ü','ß','/'] + - file: "fonts/Roboto-700.ttf" id: label size: 11 @@ -1269,6 +1281,26 @@ font: - "${MDI_GLYPH_10}" - "\U000F012C" # check + - file: 'fonts/materialdesignicons-webfont.ttf' + id: watch_icon + size: 12 + glyphs: + - "\U000F0335" + - "\U000F0210" + - "\U000F111C" + - "\U000F029A" + - "${MDI_GLYPH_1}" + - "${MDI_GLYPH_2}" + - "${MDI_GLYPH_3}" + - "${MDI_GLYPH_4}" + - "${MDI_GLYPH_5}" + - "${MDI_GLYPH_6}" + - "${MDI_GLYPH_7}" + - "${MDI_GLYPH_8}" + - "${MDI_GLYPH_9}" + - "${MDI_GLYPH_10}" + - "\U000F012C" # check + # Geheugen zichtbaar maken: dit bord heeft geen PSRAM, dus de vrije heap # bepaalt hoeveel UI erbij kan. Verschijnt ook als entity in Home Assistant. debug: diff --git a/packages/cyd.yaml b/packages/cyd.yaml index f089964..4777937 100644 --- a/packages/cyd.yaml +++ b/packages/cyd.yaml @@ -59,7 +59,7 @@ ############################################################ substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.12" + SCREEN_FIRMWARE_VERSION: "0.2.13" DYNAMIC_TILES: "true" DASHBOARD_INBOX_INTERNAL: "false" DEVICE_NAME: "cyd-new" @@ -814,6 +814,8 @@ esphome: settings.night_end = atoi("${NIGHT_END_HOUR}") * 60; settings.night_brightness = atoi("${NIGHT_DIM_BRIGHTNESS}"); runtime_tiles::watch_font = id(headline)->get_lv_font(); + runtime_tiles::watch_value_font = id(watch_value)->get_lv_font(); + runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font(); runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font(); runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true"; if (!runtime_tiles::enabled) return; @@ -1188,6 +1190,16 @@ font: ' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', 'ä','ö','ü','Ä','Ö','Ü','ß','/'] + - file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/Roboto-500.ttf" + id: watch_value + size: 22 + bpp: 4 + glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°', + '0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + ' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', + 'ä','ö','ü','Ä','Ö','Ü','ß','/'] + - file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/Roboto-700.ttf" id: label size: 11 @@ -1260,6 +1272,26 @@ font: - "${MDI_GLYPH_10}" - "\U000F012C" # check + - file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/materialdesignicons-webfont.ttf" + id: watch_icon + size: 12 + glyphs: + - "\U000F0335" + - "\U000F0210" + - "\U000F111C" + - "\U000F029A" + - "${MDI_GLYPH_1}" + - "${MDI_GLYPH_2}" + - "${MDI_GLYPH_3}" + - "${MDI_GLYPH_4}" + - "${MDI_GLYPH_5}" + - "${MDI_GLYPH_6}" + - "${MDI_GLYPH_7}" + - "${MDI_GLYPH_8}" + - "${MDI_GLYPH_9}" + - "${MDI_GLYPH_10}" + - "\U000F012C" # check + # Geheugen zichtbaar maken: dit bord heeft geen PSRAM, dus de vrije heap # bepaalt hoeveel UI erbij kan. Verschijnt ook als entity in Home Assistant. debug: diff --git a/packages/guition.yaml b/packages/guition.yaml index 5d38639..6b9d475 100644 --- a/packages/guition.yaml +++ b/packages/guition.yaml @@ -2,7 +2,7 @@ # Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911. # Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile. substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.12" + SCREEN_FIRMWARE_VERSION: "0.2.13" DYNAMIC_TILES: "true" DASHBOARD_INBOX_INTERNAL: "false" DEVICE_NAME: "guition-new" @@ -596,6 +596,7 @@ esphome: - - - + - name: ${DEVICE_NAME} friendly_name: ${DEVICE_FRIENDLY_NAME} on_boot: @@ -620,6 +621,8 @@ esphome: settings.night_brightness = atoi("${NIGHT_DIM_BRIGHTNESS}"); runtime_tiles::light_theme = true; runtime_tiles::watch_font = id(headline)->get_lv_font(); + runtime_tiles::watch_value_font = id(watch_value)->get_lv_font(); + runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font(); runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font(); runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true"; if (!runtime_tiles::enabled) return; @@ -998,6 +1001,16 @@ font: ' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', 'ä','ö','ü','Ä','Ö','Ü','ß','/'] + - file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/Roboto-500.ttf" + id: watch_value + size: 38 + bpp: 4 + glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°', + '0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + ' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', + 'ä','ö','ü','Ä','Ö','Ü','ß','/'] + - file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/Roboto-700.ttf" id: label size: 18 @@ -1070,6 +1083,26 @@ font: - "${MDI_GLYPH_10}" - "\U000F012C" # check + - file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/materialdesignicons-webfont.ttf" + id: watch_icon + size: 18 + glyphs: + - "\U000F0335" + - "\U000F0210" + - "\U000F111C" + - "\U000F029A" + - "${MDI_GLYPH_1}" + - "${MDI_GLYPH_2}" + - "${MDI_GLYPH_3}" + - "${MDI_GLYPH_4}" + - "${MDI_GLYPH_5}" + - "${MDI_GLYPH_6}" + - "${MDI_GLYPH_7}" + - "${MDI_GLYPH_8}" + - "${MDI_GLYPH_9}" + - "${MDI_GLYPH_10}" + - "\U000F012C" # check + # Geheugen zichtbaar maken: dit bord heeft geen PSRAM, dus de vrije heap # bepaalt hoeveel UI erbij kan. Verschijnt ook als entity in Home Assistant. debug: @@ -4520,7 +4553,8 @@ script: 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(); + // Standby dims slowly; the tile redraws below no longer interrupt the hardware fade. + id(set_backlight).execute(level, id(display_dimmed) ? 1500 : 300); if (id(display_dimmed)) { lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); lv_obj_move_foreground(id(dim_wake_overlay)); } 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); @@ -4533,18 +4567,30 @@ script: - lambda: |- id(last_touch_ms) = millis(); id(display_dimmed) = false; - - light.turn_on: - id: back_light - # 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 screen_settings::current.brightness / 100.0f;" + // Bijna instant omhoog. De ESPHome-standaard is 1s, en dat voelt traag + // als je het scherm aanraakt en op licht staat te wachten. + id(set_backlight).execute(screen_settings::current.brightness, 80); - lvgl.widget.hide: dim_wake_overlay - lvgl.widget.hide: color_detail_overlay - lvgl.widget.hide: climate_detail_overlay - lvgl.widget.hide: climate_mode_overlay - lvgl.widget.hide: vacuum_overlay + # One owner for the backlight level: the LEDC fader steps the duty itself, so + # LVGL redraws cannot make the transition stutter. A new call interrupts the + # running fade from its current duty; afterwards ESPHome's light state is synced. + - id: set_backlight + mode: restart + parameters: + level: int + ms: int + then: + - lambda: |- + if (!backlight_fade::start(*id(gpio_backlight_pwm), *id(back_light), level / 100.0f, ms)) + id(back_light).turn_on().set_transition_length(ms).set_brightness(level / 100.0f).perform(); + - delay: !lambda "return ms + 40;" + - lambda: backlight_fade::sync(*id(gpio_backlight_pwm), *id(back_light), level / 100.0f); + - id: dim_display mode: restart then: diff --git a/screen_manager/CHANGELOG.md b/screen_manager/CHANGELOG.md index 9aa4fda..9330bdc 100644 --- a/screen_manager/CHANGELOG.md +++ b/screen_manager/CHANGELOG.md @@ -1,3 +1,9 @@ +## Firmware 0.2.13 (app 0.2.11 blijft bruikbaar) + +- Grote waarde: klein domeinicoon naast de titel, titel en waarde verticaal gecentreerd, groter cijferfont (38 px Guition, 22 px CYD). Een te lange waarde krijgt puntjes; de eenheid staat altijd rechts naast het getal. +- Guition: de backlight dimt naar standby via de LEDC-hardwarefader in 1,5 s en wordt in 80 ms wakker. Schermherteken onderbreekt de overgang niet meer, dus geen schokkerig uitfaden. +- Installeer de nieuwe schermfirmware via ESP Screens; geen appupdate of gewijzigde configuratie nodig. + ## Firmware 0.2.12 (app 0.2.11 blijft bruikbaar) - Switches herkennen nu een aan/uit-terugmelding met ongewijzigde attributen. Daarmee verdwijnt de onterechte wachttijd van zes seconden. Na HA-bevestiging geldt voor switches slechts 150 ms minimale feedback. diff --git a/tools/generate_packages.py b/tools/generate_packages.py index ef2af6b..98dcdc9 100644 --- a/tools/generate_packages.py +++ b/tools/generate_packages.py @@ -21,7 +21,7 @@ def generate(board): s = re.sub(r'^ota:\n.*?(?=^\w)', '', s, flags=re.M | re.S) s = re.sub(r'^wifi:\n.*?(?=^\w)', '', s, flags=re.M | re.S) # Referenced files are fetched as an external component or over HTTPS. - s = re.sub(r'^ - (cyd_ui.h|light_controls.h|guition_diagnostics.h)$', r' - ', s, flags=re.M) + s = re.sub(r'^ - (cyd_ui.h|light_controls.h|guition_diagnostics.h|backlight_fade.h)$', r' - ', s, flags=re.M) components = '[xpt2046, smart_display]' if board == 'cyd' else '[smart_display]' external = f'''external_components: - source: