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 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-13 01:14:39 +02:00
parent c316d99fd7
commit 53bf1bc228
14 changed files with 342 additions and 29 deletions
+4
View File
@@ -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/<naam>`. 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.
+2
View File
@@ -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
+6
View File
@@ -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
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <driver/ledc.h>
#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<typename T> 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<esphome::ledc::LEDCOutput>;
const auto channel = static_cast<ledc_channel_t>(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(&current);
const uint32_t from = hardware_owns ? ledc_get_duty(speed, channel) : static_cast<uint32_t>(std::lround(current * max_duty));
ledc_set_duty(speed, channel, from);
ledc_update_duty(speed, channel);
hardware_owns = true;
const auto target = static_cast<uint32_t>(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<esphome::ledc::LEDCOutput>::channel(output)), static_cast<ledc_channel_t>(Peek<esphome::ledc::LEDCOutput>::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
+36 -9
View File
@@ -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<void(Tile &)> 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, 10> 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<int>(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.y2<track.y1;
else fits=fits && circle.y2<=content.y2;
if(!fits)ESP_LOGE("ui_test","Icon bounds slot=%u circle=%d,%d..%d,%d title_x=%d content=%d,%d..%d,%d",(unsigned)w.index,circle.x1,circle.y1,circle.x2,circle.y2,title.x1,content.x1,content.y1,content.x2,content.y2);
if(mini || lv_obj_get_height(w.tile)<=80){
bool watch=w.index<model.count && model.tiles[w.index].display=="watch";
if(!watch && (mini || lv_obj_get_height(w.tile)<=80)){
int header_bottom=mini?track.y1-(lv_obj_get_height(w.tile)>80?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.x2<unit.x1 && unit.x2<=content.x2 && unit.y2<=content.y2 && unit.y1>title.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.index<model.count && model.tiles[w.index].background){
bool palette_ok=lv_color_eq(lv_obj_get_style_bg_color(w.tile,LV_PART_MAIN),lv_color_hex(model.tiles[w.index].background)) &&
+4
View File
@@ -44,6 +44,10 @@ vier pagina’s; tot zes verdwijnt de navigatie. De huidige interface heeft een
lichtgrijze achtergrond, witte kaarten en gekleurde domeiniconen. Vanaf 0.2.10 kun
je per tegel een pastel achtergrond kiezen met donkere tekst. Standby begint
standaard na tien minuten zonder aanraking en is instelbaar in de beheerpagina.
Vanaf 0.2.13 dimt de backlight via de LEDC-hardwarefader (`backlight_fade.h`,
1,5 s naar standby, 80 ms wakker): de volledige LVGL-herteken bij standby
onderbreekt de overgang dan niet meer. ESPHome's lichtstatus wordt na de fade
gesynchroniseerd, zodat de entity in HA en latere overgangen kloppen.
De bestaande tegelacties, climatebediening en vacuumkaart zijn behouden.
Alle kaarttypen zijn beschikbaar op alle twintig runtime-posities. Alleen in
+3 -1
View File
@@ -23,7 +23,9 @@
`python3 tools/generate_packages.py`; bewerk `packages/*.yaml` nooit rechtstreeks.
2. Draai alle Python-tests met aiohttp geïnstalleerd, alle `tests/*.cpp`, de
generator met `--check`, en compileer beide Easy Setup-profielen plus de
bestaande handmatige profielen. Controleer dat er geen secrets in Git staan.
bestaande handmatige profielen. Doe dat na elkaar: profielen met dezelfde
`DEVICE_NAME` delen één build-map, en een parallelle build laat een upload
het verkeerde `firmware.bin` kiezen. Controleer dat er geen secrets in Git staan.
3. Test appstart, opslaan, opnieuw starten/updaten met bestaande indelingen,
opnieuw verbinden met HA en een ESP-herstart. Test een nieuwe kaart op echte
hardware. Een goede build vervangt fysieke touchacceptatie niet.
+46
View File
@@ -0,0 +1,46 @@
# Firmware 0.2.13 — grote waarde en Guition-backlightfade
## Wijzigingen
- **Grote waarde** (`display: watch`): een klein domeinicoon in een cirkel
(18 px Guition, 12 px CYD) naast de titel; titel en waarde staan als groep
verticaal gecentreerd. Het getal gebruikt een eigen groter font
(Roboto 500, 38 px Guition / 22 px CYD). Een te lange waarde wordt met
puntjes afgekapt; de eenheid staat als apart label rechts onderaan de regel
en blijft altijd zichtbaar. De geometriecontrole test ook het eenheidslabel.
- **Guition-backlight**: `components/smart_display/backlight_fade.h` laat de
LEDC-hardwarefader de duty stappen. ESPHome's software-overgang loopt op de
hoofdloop, en de volledige LVGL-herteken bij standby onderbrak die zichtbaar.
Standby dimt nu in 1,5 s, wakker worden duurt 80 ms. Eén script
(`set_backlight`) is eigenaar van het niveau; een nieuwe aanroep onderbreekt
de lopende fade vanaf de huidige duty en synchroniseert daarna ESPHome's
lichtstatus (entity in HA, latere overgangen). Zonder fader (klassieke ESP32)
valt het script terug op de ESPHome-overgang; de CYD gebruikt dit niet.
- Protocol, opslagversie, preferences en app 0.2.11 zijn ongewijzigd.
## Tests
- 51 Python-tests en alle negen C++-testprogramma's geslaagd.
- `tools/generate_packages.py --check` geslaagd; beide Easy Setup-profielen en
beide handmatige profielen compileren met ESPHome 2026.6.2.
- USB-flash op de vooraf via `esptool chip_id` geïdentificeerde poorten
(ESP32 = CYD, ESP32-S3 = Guition). Beide melden daarna firmware 0.2.13 met
de verwachte compileertijd via `device_info`.
- Renderdiagnose op beide apparaten: tien paginacontroles en vijftig
overlaycycli geslaagd, inclusief tekst-, icoon-, eenheid- en sliderbegrenzing.
Guition heap ~6,49 MB, CYD ~57 kB vrij tijdens de proef.
- De eigenaar bevestigt de nieuwe grote waarde en het rustige uitfaden op de
Guition: "super ik ben helemaal blij". Er is geen langdurige stabiliteitsproef
uitgevoerd.
## Incident tijdens het flashen
De eerste Guition-flash schreef per ongeluk het handmatige profiel
(`guition-device.yaml`, vaste tegels) in plaats van de Easy Setup-firmware.
Beide profielen heten `guition-wallbox` en delen daardoor
`.esphome/build/guition-wallbox`; een gelijktijdig gestarte regressiebuild van
het handmatige profiel liet de upload het verkeerde bestand kiezen. De Guition
verdween daardoor tijdelijk uit ESP Screen Manager. Herstel: sequentieel opnieuw
bouwen, uploadlog op het pad van `firmware.bin` controleren en de compileertijd
via `device_info` vergelijken. Bewaarde tegels, instellingen en sleutels waren
niet geraakt. Zie de nieuwe regel in AGENTS.md en docs/RELEASING.md.
+54 -8
View File
@@ -1,7 +1,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: "false"
DASHBOARD_INBOX_INTERNAL: "true"
DEVICE_NAME: "guition-wallbox"
@@ -592,6 +592,7 @@ esphome:
- cyd_ui.h
- light_controls.h
- guition_diagnostics.h
- backlight_fade.h
name: ${DEVICE_NAME}
friendly_name: ${DEVICE_FRIENDLY_NAME}
on_boot:
@@ -616,6 +617,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;
@@ -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:
+33 -1
View File
@@ -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:
+33 -1
View File
@@ -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:
+54 -8
View File
@@ -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:
- <esphome/components/smart_display/cyd_ui.h>
- <esphome/components/smart_display/light_controls.h>
- <esphome/components/smart_display/guition_diagnostics.h>
- <esphome/components/smart_display/backlight_fade.h>
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:
+6
View File
@@ -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.
+1 -1
View File
@@ -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' - <esphome/components/smart_display/\1>', s, flags=re.M)
s = re.sub(r'^ - (cyd_ui.h|light_controls.h|guition_diagnostics.h|backlight_fade.h)$', r' - <esphome/components/smart_display/\1>', s, flags=re.M)
components = '[xpt2046, smart_display]' if board == 'cyd' else '[smart_display]'
external = f'''external_components:
- source: