Fix switch confirmation and details; retain compact slider icons

This commit is contained in:
Max Gramser
2026-09-13 00:47:49 +02:00
parent ba9ba414a5
commit c316d99fd7
10 changed files with 301 additions and 26 deletions
+6
View File
@@ -44,6 +44,12 @@ De firmware en ingebouwde CLI zijn getest met **ESPHome 2026.6.2**.
Functies hangen af van de mogelijkheden die Home Assistant voor een entiteit
meldt. De app moet blijven draaien om de schermen van actuele gegevens te voorzien.
Vanaf firmware **0.2.12** toont lang indrukken op een switch een grote
schakelaar. Een korte tik schakelt direct; de uitstand krijgt een grijs icoon.
De feedback stopt zodra Home Assistant de gewijzigde stand meldt, met een
minimum van 150 ms voor switches. Tegels met een mini-slider behouden hun
icoon; op CYD staan het icoon en tekstblok verticaal gecentreerd.
## Installeren vanuit Home Assistant
### Heb ik ESPHome nodig?
+5 -1
View File
@@ -19,6 +19,9 @@ inline bool valid_entity(const std::string &entity) {
if (domain == allowed) return true;
return false;
}
inline std::string state_revision(const std::string &state, const std::string &attributes) {
return state + "\n" + attributes;
}
struct Tile {
std::string entity, name, state, unit, modes, hvac_modes;
std::array<std::string, 4> fan_speeds;
@@ -40,7 +43,8 @@ struct Tile {
std::string revision, pending_revision;
uint32_t pending_since = 0;
bool pending = false, confirmed = false, local_feedback = false;
bool loading(uint32_t now) const { return pending && (now-pending_since < 1000 || (!confirmed && !local_feedback && now-pending_since < 6000)); }
bool is_switch() const { return domain()=="switch" || domain()=="input_boolean"; }
bool loading(uint32_t now) const { return pending && (now-pending_since < (is_switch()?150u:1000u) || (!confirmed && !local_feedback && now-pending_since < 6000)); }
bool awaiting_action(uint32_t now) const { return loading(now) && !local_feedback; }
void begin(uint32_t now, bool local=false) { pending=true; pending_since=now; confirmed=false; local_feedback=local; pending_revision=revision; }
void observe(const std::string &next) { revision=next; if (pending && revision!=pending_revision) confirmed=true; }
+81 -19
View File
@@ -23,6 +23,7 @@ inline esphome::ESPPreferenceObject swipe_preference;
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 void tick();
inline void refresh_detail(unsigned index);
inline int active_index = -1;
@@ -60,7 +61,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{}; };
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{}; };
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) {
@@ -134,8 +135,13 @@ inline std::string receive(const std::string &payload) {
if (!model.accepts(index, entity)) { result = "Fout: verouderde tegel"; return false; }
Tile &tile = model.tiles[index];
auto a = root["a"].as<JsonObject>();
std::string revision = string(root["state"]); serializeJson(a, revision);
// ArduinoJson clears its destination string: serialize attributes first, then add state.
std::string attributes; serializeJson(a, attributes);
std::string revision = state_revision(string(root["state"]), attributes);
bool was_confirmed=tile.confirmed;
tile.observe(revision);
if(tile.pending && !tile.local_feedback && !was_confirmed && tile.confirmed)
ESP_LOGI("runtime_action","HA state received entity=%s elapsed=%u ms",entity.c_str(),(unsigned)(esphome::millis()-tile.pending_since));
auto options = root["o"];
tile.background = tile_palette::color(string(options["background"],16));
tile.tap = string(options["tap"]); if (tile.tap.empty()) tile.tap="auto";
@@ -208,6 +214,8 @@ inline void action(const std::string &service, const std::string &entity, const
if(!key.empty()) {esphome::api::HomeassistantServiceMap param;param.key=esphome::StringRef(key);param.value=esphome::StringRef(value);request.data.push_back(param);}
for(auto &tile:model.tiles) if(tile.entity==entity)tile.begin(esphome::millis());
esphome::api::global_api_server->send_homeassistant_action(request);
ESP_LOGI("runtime_action","Sent service=%s entity=%s",service.c_str(),entity.c_str());
if(refresh)refresh();
}
inline void setting_event(const std::string &key, int value) {
if(inbox.empty())return;
@@ -227,12 +235,15 @@ inline lv_obj_t *detail_actions[16]{};
inline unsigned detail_action_count=0;
inline lv_obj_t *detail_status=nullptr;
inline lv_obj_t *detail_badge_status=nullptr;
inline lv_obj_t *detail_switch=nullptr;
inline lv_obj_t *detail_label(lv_obj_t *parent,const std::string &text,int x,int y,int width) {
auto *label=lv_label_create(parent);lv_label_set_text(label,text.c_str());lv_obj_set_pos(label,x,y);lv_obj_set_width(label,width);
lv_obj_set_style_text_font(label,detail_font,0);lv_obj_set_style_text_color(label,lv_color_hex(light_theme?0x202020:0xFFFFFF),0);
lv_label_set_long_mode(label,LV_LABEL_LONG_DOT);lv_obj_set_height(label,lv_font_get_line_height(detail_font));return label;
}
inline std::string detail_state(const Tile &t){
if(t.state=="on")return "Aan";
if(t.state=="off")return "Uit";
if(t.state=="docked")return "In dock";
if(t.state=="cleaning")return "Bezig met schoonmaken";
if(t.state=="paused")return "Gepauzeerd";
@@ -304,7 +315,7 @@ inline void show_detail(unsigned index){
if(index>=model.count)return;detail_index=index;auto &t=model.tiles[index];
if(!detail_font)detail_font=lv_obj_get_style_text_font(widgets[0].title,LV_PART_MAIN);
if(!detail_root){detail_root=lv_obj_create(lv_screen_active());lv_obj_remove_style_all(detail_root);lv_obj_set_size(detail_root,lv_pct(100),lv_pct(100));lv_obj_remove_flag(detail_root,LV_OBJ_FLAG_SCROLLABLE);}
detail_action_count=0;detail_status=nullptr;detail_badge_status=nullptr;lv_obj_clean(detail_root);lv_obj_remove_flag(detail_root,LV_OBJ_FLAG_HIDDEN);lv_obj_move_foreground(detail_root);
detail_action_count=0;detail_status=nullptr;detail_badge_status=nullptr;detail_switch=nullptr;lv_obj_clean(detail_root);lv_obj_remove_flag(detail_root,LV_OBJ_FLAG_HIDDEN);lv_obj_move_foreground(detail_root);
lv_obj_set_style_bg_color(detail_root,lv_color_hex(light_theme?0xE7E7E7:0x202A38),0);lv_obj_set_style_bg_opa(detail_root,LV_OPA_COVER,0);
int width=lv_display_get_horizontal_resolution(lv_display_get_default()), height=lv_display_get_vertical_resolution(lv_display_get_default());
bool large=width>=480;int pad=large?20:10, top=large?100:62, gap=large?12:6,bh=large?58:34,cw=(width-pad*2-gap)/2;
@@ -313,7 +324,28 @@ inline void show_detail(unsigned index){
std::string state=detail_state(t);
detail_status=detail_label(detail_root,state+(t.unit.empty()?"":" "+t.unit),pad,large?60:35,width-2*pad);
auto d=t.domain();
if(d=="vacuum"){
if(t.is_switch()){
detail_label(detail_root,"Tik om te schakelen",pad,top,width-2*pad);
detail_switch=lv_switch_create(detail_root);
lv_obj_set_size(detail_switch,large?240:140,large?112:64);
lv_obj_set_pos(detail_switch,(width-(large?240:140))/2,top+(large?65:30));
lv_obj_set_style_bg_color(detail_switch,lv_color_hex(0xB0B0B0),LV_PART_MAIN);
lv_obj_set_style_bg_color(detail_switch,lv_color_hex(0xFFB900),LV_PART_INDICATOR|LV_STATE_CHECKED);
lv_obj_set_style_bg_color(detail_switch,lv_color_hex(0xFFFFFF),LV_PART_KNOB);
lv_obj_set_style_opa(detail_switch,LV_OPA_50,LV_STATE_DISABLED);
if(t.state=="on")lv_obj_add_state(detail_switch,LV_STATE_CHECKED);
lv_obj_add_event_cb(detail_switch,[](lv_event_t *e){
if(detail_index>=model.count)return;
auto &tile=model.tiles[detail_index];auto *control=lv_event_get_target_obj(e);
bool allowed=fresh() && tile.available() && !tile.awaiting_action(esphome::millis()) &&
cyd::touch_guard.accept(esphome::millis(),350);
bool requested_on=lv_obj_has_state(control,LV_STATE_CHECKED);
// Only HA's reported state is authoritative, including a refused/failed command.
if(tile.state=="on")lv_obj_add_state(control,LV_STATE_CHECKED);else lv_obj_remove_state(control,LV_STATE_CHECKED);
if(allowed)action(tile.domain()+(requested_on?".turn_on":".turn_off"),tile.entity);
},LV_EVENT_VALUE_CHANGED,nullptr);
detail_actions[detail_action_count++]=detail_switch;
}else if(d=="vacuum"){
// Native shapes keep the robot crisp without image buffers or extra layers.
auto shape=[&](lv_obj_t *parent,int x,int y,int w,int h,uint32_t color,int radius){
auto *o=lv_obj_create(parent);lv_obj_remove_style_all(o);lv_obj_set_pos(o,x,y);lv_obj_set_size(o,w,h);
@@ -422,7 +454,7 @@ inline void event(lv_event_t *event) {
active_index = w.index;
tile.begin(esphome::millis(),true);
if (detail) detail(tile);
} else { active_index=w.index;tile.begin(esphome::millis(),true);show_detail(w.index); }
} else { active_index=w.index;show_detail(w.index); }
return;
}
if (d == "light" || d == "switch" || d == "input_boolean" || d == "fan") action(d + ".toggle", tile.entity);
@@ -432,9 +464,11 @@ inline void event(lv_event_t *event) {
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) {
// 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);
lv_obj_update_layout(tile);
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);
// 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));
@@ -505,22 +539,32 @@ inline void render(lv_obj_t *room) {
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";
lv_obj_set_style_text_font(w.value,watch && watch_font ? watch_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_set_pos(w.value,watch?0:w.value_x,watch?(lv_obj_get_height(w.tile)>80?42:19):w.value_y);
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_set_x(w.title,watch?0:w.title_x);
bool mini=t.inline_control=="slider" && !watch && t.available();
bool large_tile=lv_obj_get_height(w.tile)>80;
lv_obj_set_y(w.title,mini?0:w.title_y);
if(mini){lv_obj_set_y(w.value,large_tile?24:lv_obj_get_height(w.title)+1);lv_obj_add_flag(w.circle,LV_OBJ_FLAG_HIDDEN);lv_obj_set_x(w.title,0);lv_obj_set_x(w.value,0);}
// Labels default to content-sized widths. Explicit bounds are necessary for
// LVGL ellipsis, and must expand again when the icon is hidden for a slider.
lv_obj_set_style_text_font(w.value,watch && watch_font ? watch_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);
lv_obj_set_width(w.title,std::max(1,content_width-(int)lv_obj_get_x(w.title)));
lv_obj_set_width(w.value,std::max(1,content_width-(int)lv_obj_get_x(w.value)));
lv_obj_set_size(w.slider,content_width,large_tile?28:8);
int content_width=lv_obj_get_content_width(w.tile),content_height=lv_obj_get_content_height(w.tile);
int title_height=lv_obj_get_height(w.title),value_height=lv_obj_get_height(w.value);
int line_gap=large_tile?2:1,text_height=title_height+line_gap+value_height;
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);
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_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);
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,
watch?(large_tile?42:19):(mini || !large_tile)?text_y+title_height+line_gap:w.value_y);
lv_obj_set_pos(w.circle,0,(mini || !large_tile)?std::max(0,(header_height-circle_size)/2):12);
// Use the requested coordinates: LVGL getters still return the previous
// 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)));
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);}
else lv_obj_add_flag(w.slider,LV_OBJ_FLAG_HIDDEN);
@@ -530,7 +574,7 @@ inline void render(lv_obj_t *room) {
if (w.cached_active == palette_state) continue;
w.cached_active = palette_state;
uint32_t accent = domain_accent(t);
auto color=lv_color_hex(accent);
auto color=lv_color_hex(t.is_switch() && !on ? 0x9E9E9E : accent);
if(t.domain()=="light" && on && t.has_hs_color)
color=lv_color_hsv_to_rgb(t.hue%360,t.saturation,100);
bool dark_text=light_theme || t.background!=0;
@@ -565,6 +609,20 @@ inline bool check_tile_geometry() {
lv_obj_get_coords(w.slider,&track);
fits=fits && value.y2<track.y1 && track.y2<=content.y2;
}
if(!lv_obj_has_flag(w.circle,LV_OBJ_FLAG_HIDDEN)){
lv_area_t circle;lv_obj_get_coords(w.circle,&circle);
bool mini=!lv_obj_has_flag(w.slider,LV_OBJ_FLAG_HIDDEN);
fits=fits && circle.x1>=content.x1 && circle.x2<title.x1 && circle.y1>=content.y1;
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){
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(!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)) &&
@@ -603,6 +661,10 @@ inline void tick() {
std::string status=waiting?(t.confirmed?"Bevestigd door Home Assistant":"Opdracht verstuurd..."):detail_state(t);
if(detail_status)label(detail_status,status+(!waiting && !t.unit.empty()?" "+t.unit:""));
if(detail_badge_status)label(detail_badge_status,status);
if(detail_switch && !lv_obj_has_state(detail_switch,LV_STATE_PRESSED)){
if(t.state=="on")lv_obj_add_state(detail_switch,LV_STATE_CHECKED);
else lv_obj_remove_state(detail_switch,LV_STATE_CHECKED);
}
}
if(!enabled)return;
bool redraw=false;
+71
View File
@@ -0,0 +1,71 @@
# Firmware 0.2.12 — switches
## Oorzaak en wijziging
ArduinoJson 7 leegt de uitvoerstring bij `serializeJson`. De oude code zette
`state` eerst in die string en serialiseerde daarna de attributen eroverheen.
Een simpele switch met gelijke attributen kreeg daardoor bij aan/uit dezelfde
revision. De firmware bleef zes seconden op een bevestiging wachten, ook als
de nieuwe status al ontvangen was.
Attributen worden nu apart geserialiseerd, waarna de status wordt toegevoegd.
Switches/input_boolean hebben 150 ms minimale feedback in plaats van één seconde;
zonder bevestiging blijft de bestaande begrensde wachttijd behouden. Overige
kaarten houden hun minimale feedback. De opdracht wordt meteen verzonden en de
tegel wordt direct ververst. Logs meten de tijd tot een gewijzigde HA-status;
dit is een statusbevestiging, geen bewijs van een fysiek relaiscontact.
Lang indrukken opent een native LVGL-switch, zonder HA-opdracht bij openen.
De bediening toont de door HA gemelde stand en blokkeert tijdens een lopende
opdracht of onbeschikbaarheid. Uitgeschakelde switchiconen zijn grijs; eigen
pastel tegelachtergronden blijven behouden. Beide borden gebruiken dezelfde code.
## Metingen en tests
- Voor de correctie: HA-events tijdens bediening door de eigenaar: printer-
stroomschakelaar 56 ms van servicecall tot statuswijziging; de instelling
voor stroomuitvalgeheugen 142–151 ms. De oude schermwachttijd is daarmee
niet gelijk aan de verwerkingstijd van de HA-switch.
- 51 Python-tests geslaagd.
- Alle negen C++-testprogramma's geslaagd. De feedbacktest controleert een
aan/uit-wijziging met identieke attributen, een onveranderde status, timeout,
input_boolean en de millis-overloop.
- Pakketgenerator gecontroleerd; protocol, opgeslagen tegels en preferences
ongewijzigd. App 0.2.11 blijft bruikbaar; dit is een firmware-update.
## Controle op de schermen
- Beide runtimefirmwares gebouwd met ESPHome 2026.6.2 en via OTA geïnstalleerd
op de vooraf geïdentificeerde Guition en CYD. Beide melden firmware 0.2.12.
- Op beide apparaten: tien paginacontroles en vijftig overlaycycli geslaagd,
inclusief bestaande controles voor tekst, sliders en pastelkleuren.
- Guition: echte korte tikken geven voor de printer statusbevestiging op het
scherm na 305 en 434 ms. De grote switch geeft 204 en 365 ms. De switch voor
stroomuitvalgeheugen geeft 531 en 667 ms. Dit zijn volledige terugmeldtijden
gemeten door de firmware, inclusief HA en de manager-synchronisatie.
- Interne LVGL-schermopname gecontroleerd: naam, HA-stand en grote schakelaar
zichtbaar. De eigenaar bevestigt snelle bediening, goed detailmenu en de
grijze uitstand: “yes, allemaal top!”.
- De actuele indelingen en instellingen voor beide schermen zijn na OTA exact
gelijk aan de momentopname vóór deze update. Geen appdata of sleutels gewijzigd.
De fysieke bevestiging betreft Guition. CYD is gebouwd, geflasht en met de
renderdiagnose getest; de nieuwe switchbediening is daar niet apart fysiek
bevestigd. Er is geen langdurige stabiliteitsproef uitgevoerd voor deze patch.
## Aanvullende indeling
Mini-sliders behouden hun domeinicoon in een compacte cirkel naast titel en
waarde. Daarvoor zijn afzonderlijke kleine MDI-fonts toegevoegd (18 px CYD,
26 px Guition), zonder schaling via extra renderbuffers. CYD centreert zowel
het tekstblok als de icooncirkel; met een slider gebeurt dat in de ruimte boven
de slider. De geometriecontrole test nu ook icoonbegrenzing en centrering.
De eerste uitgebreide Guition-test vond te brede tekst bij wisselen van een
watch/slider-tegel naar een gewone tegel in hetzelfde vak. LVGL gaf nog de oude
x-positie terug. De berekening gebruikt nu de gevraagde nieuwe positie. Na die
correctie slagen op beide apparaten opnieuw tien paginacontroles en vijftig
overlaycycli, inclusief icoonbegrenzing, tekstbreedte, centrering en sliderafstand.
Alle vier definitieve firmwareprofielen bouwen succesvol. Onder de zware
Guition-renderproef zijn er nog waarschuwingen voor lange renderbewerkingen;
die proef is geen bewijs van constante frame latency in alle situaties.
+23 -1
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.10"
SCREEN_FIRMWARE_VERSION: "0.2.12"
DYNAMIC_TILES: "false"
DASHBOARD_INBOX_INTERNAL: "true"
DEVICE_NAME: "guition-wallbox"
@@ -587,6 +587,7 @@ esphome:
platformio_options:
build_flags:
- -DLV_USE_SNAPSHOT=1
- -DLV_USE_SWITCH=1
includes:
- cyd_ui.h
- light_controls.h
@@ -615,6 +616,7 @@ esphome:
settings.night_brightness = atoi("${NIGHT_DIM_BRIGHTNESS}");
runtime_tiles::light_theme = true;
runtime_tiles::watch_font = id(headline)->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;
runtime_tiles::rotation_supported = true;
@@ -1057,6 +1059,26 @@ font:
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- file: 'fonts/materialdesignicons-webfont.ttf'
id: materialdesign_icons_mini
size: 26
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:
+25 -1
View File
@@ -58,7 +58,7 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.10"
SCREEN_FIRMWARE_VERSION: "0.2.12"
DYNAMIC_TILES: "false"
DASHBOARD_INBOX_INTERNAL: "true"
DEVICE_NAME: "cyd-new"
@@ -779,6 +779,9 @@ esphome:
project:
name: maxgramser.esp_screen
version: "${SCREEN_FIRMWARE_VERSION}"
platformio_options:
build_flags:
- -DLV_USE_SWITCH=1
includes:
- cyd_ui.h
- light_controls.h
@@ -807,6 +810,7 @@ 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::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font();
runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true";
if (!runtime_tiles::enabled) return;
runtime_tiles::load_settings();
@@ -1245,6 +1249,26 @@ font:
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- file: 'fonts/materialdesignicons-webfont.ttf'
id: materialdesign_icons_mini
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:
+25 -1
View File
@@ -59,7 +59,7 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.10"
SCREEN_FIRMWARE_VERSION: "0.2.12"
DYNAMIC_TILES: "true"
DASHBOARD_INBOX_INTERNAL: "false"
DEVICE_NAME: "cyd-new"
@@ -783,6 +783,9 @@ esphome:
project:
name: maxgramser.esp_screen
version: "${SCREEN_FIRMWARE_VERSION}"
platformio_options:
build_flags:
- -DLV_USE_SWITCH=1
includes:
- <esphome/components/smart_display/cyd_ui.h>
- <esphome/components/smart_display/light_controls.h>
@@ -811,6 +814,7 @@ 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::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font();
runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true";
if (!runtime_tiles::enabled) return;
runtime_tiles::load_settings();
@@ -1236,6 +1240,26 @@ font:
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/materialdesignicons-webfont.ttf"
id: materialdesign_icons_mini
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:
+23 -1
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.10"
SCREEN_FIRMWARE_VERSION: "0.2.12"
DYNAMIC_TILES: "true"
DASHBOARD_INBOX_INTERNAL: "false"
DEVICE_NAME: "guition-new"
@@ -591,6 +591,7 @@ esphome:
platformio_options:
build_flags:
- -DLV_USE_SNAPSHOT=1
- -DLV_USE_SWITCH=1
includes:
- <esphome/components/smart_display/cyd_ui.h>
- <esphome/components/smart_display/light_controls.h>
@@ -619,6 +620,7 @@ esphome:
settings.night_brightness = atoi("${NIGHT_DIM_BRIGHTNESS}");
runtime_tiles::light_theme = true;
runtime_tiles::watch_font = id(headline)->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;
runtime_tiles::rotation_supported = true;
@@ -1048,6 +1050,26 @@ font:
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/materialdesignicons-webfont.ttf"
id: materialdesign_icons_mini
size: 26
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:
+8
View File
@@ -1,3 +1,11 @@
## 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.
- Uitgeschakelde switches hebben een grijs icoon en een grijze icoonachtergrond. Een zelfgekozen pastel tegelachtergrond blijft behouden.
- Mini-sliders behouden een compact domeinicoon naast titel en waarde. CYD centreert iconen en tekst verticaal; de renderdiagnose controleert centrering en vrije ruimte boven de slider.
- Lang indrukken op een switch of input_boolean opent een grote native LVGL-schakelaar met de echte HA-status, ook op CYD. Openen verstuurt geen opdracht.
- Installeer de nieuwe schermfirmware via ESP Screens; geen appupdate of gewijzigde configuratie nodig.
## 0.2.11
- Compact raster voor de pastelkleuren in de tegeleditor; voorkomt dat algemene formulierstijlen het palet in een lange kolom zetten. Alleen de app verandert; schermfirmware 0.2.10 blijft actueel.
+34 -2
View File
@@ -1,4 +1,36 @@
#include "components/smart_display/runtime_model.h"
#include <cassert>
#include "components/smart_display/cyd_ui.h"
int main(){cyd::TouchGuard guard;guard.begin(0);guard.consume();assert(!guard.accept(100,1));assert(!guard.accept_slider(100,1));guard.begin(200);assert(guard.accept(300,1));runtime_tiles::Tile t;t.revision="off";t.begin(100);assert(t.loading(200));assert(t.awaiting_action(200));t.observe("off");assert(!t.confirmed);t.observe("on");assert(t.confirmed);assert(t.loading(999));assert(!t.loading(1100));t.begin(2000);t.observe("on");assert(t.loading(7999));assert(!t.loading(8000));assert(!t.awaiting_action(8000));t.begin(0xFFFFFFF0);assert(t.loading(20));t.begin(100,true);assert(t.loading(1099));assert(!t.awaiting_action(1099));assert(!t.loading(1100));}
#include <cassert>
int main() {
cyd::TouchGuard guard;
guard.begin(0); guard.consume();
assert(!guard.accept(100,1)); assert(!guard.accept_slider(100,1));
guard.begin(200); assert(guard.accept(300,1));
runtime_tiles::Tile t;
t.revision="off"; t.begin(100);
assert(t.loading(200)); assert(t.awaiting_action(200));
t.observe("off"); assert(!t.confirmed);
t.observe("on"); assert(t.confirmed);
assert(t.loading(999)); assert(!t.loading(1100));
t.begin(2000); t.observe("on");
assert(t.loading(7999)); assert(!t.loading(8000)); assert(!t.awaiting_action(8000));
t.begin(0xFFFFFFF0); assert(t.loading(20));
t.begin(100,true);
assert(t.loading(1099)); assert(!t.awaiting_action(1099)); assert(!t.loading(1100));
// A plain switch changes state with identical attributes. This must confirm
// immediately instead of leaving the tile blocked for the six-second timeout.
runtime_tiles::Tile sw;
sw.entity="switch.printer";
sw.revision=runtime_tiles::state_revision("off","{}"); sw.begin(100);
assert(sw.loading(249));
sw.observe(runtime_tiles::state_revision("on","{}"));
assert(!sw.loading(250));
sw.begin(300); sw.observe(runtime_tiles::state_revision("on","{}"));
assert(sw.loading(1000)); // Repeated identical data is not confirmation.
assert(!sw.loading(6300));
sw.entity="input_boolean.test"; sw.begin(0xFFFFFFF0); sw.observe("off");
assert(sw.loading(20)); assert(!sw.loading(200));
}