Lighter tile model, mini-slider handle, CYD light cards, wifi without modem sleep (0.2.24)

App 0.2.24 / firmware 0.2.20.

- Forecast and hourly data live in vectors that only weather tiles fill:
  Tile 2016 -> 1216 bytes, CYD free heap 20-26 KB -> 34-37 KB in the self-test.
- The bottom mini slider shows the same white handle as the control sliders.
- CYD light/fan/cover card uses the Guition light palette (last dark-theme remnant).
- power_save_mode: none in the wizard YAML and board profiles; docs explain
  adding it to existing YAMLs. Rendering review in docs/TEST_RESULTS_0224.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-13 15:11:40 +02:00
parent 2667f41f6f
commit 5a05f52dbc
13 changed files with 80 additions and 27 deletions
Executable
BIN
View File
Binary file not shown.
+3 -5
View File
@@ -52,11 +52,9 @@ struct Tile {
unsigned option_count = 0, history_hours = 24;
std::array<float,24> history{};
bool has_history = false;
std::array<Forecast, 5> forecast;
unsigned forecast_count = 0;
// Weather card details (0.2.19+): the next hours, wind and feels-like temperature.
std::array<Hour, 8> hours;
unsigned hour_count = 0;
// Weather only: up to five days and eight hours; empty vectors cost nothing on the other tiles.
std::vector<Forecast> forecast;
std::vector<Hour> hours;
float wind = NAN, feels = NAN;
std::string wind_unit;
// When a scene, script or button last ran (unix time), pre-computed by the manager.
+18 -14
View File
@@ -195,17 +195,17 @@ inline std::string receive(const std::string &payload) {
bool repack = was_wide != tile.wide;
// Pre-computed extras: the manager converts time zones and fetches forecasts.
auto extra = root["x"];
tile.forecast_count = 0;
tile.forecast.clear();
if (extra["days"].is<JsonArray>()) for (JsonVariant day : extra["days"].as<JsonArray>()) {
if (tile.forecast_count == tile.forecast.size()) break;
auto &f = tile.forecast[tile.forecast_count++];
if (tile.forecast.size() == 5) break;
tile.forecast.emplace_back(); auto &f = tile.forecast.back();
f.day = string(day["d"], 3); f.condition = string(day["c"], 20); f.high = number(day["h"]); f.low = number(day["l"]);
f.rain = number(day["p"]); f.mm = number(day["r"]);
}
tile.hour_count = 0;
tile.hours.clear();
if (extra["hours"].is<JsonArray>()) for (JsonVariant hour : extra["hours"].as<JsonArray>()) {
if (tile.hour_count == tile.hours.size()) break;
auto &h = tile.hours[tile.hour_count++];
if (tile.hours.size() == 8) break;
tile.hours.emplace_back(); auto &h = tile.hours.back();
h.time = string(hour["t"], 5); h.condition = string(hour["c"], 20); h.temp = number(hour["h"]); h.rain = number(hour["p"]); h.mm = number(hour["r"]);
}
tile.last_run = extra["last"].is<unsigned>() ? extra["last"].as<uint32_t>() : 0;
@@ -439,7 +439,7 @@ inline void render_weather_detail(const Tile &t,bool large,int width,int height,
int text_h=lv_font_get_line_height(detail_font),small_h=lv_font_get_line_height(small),mini_h=lv_font_get_line_height(mini),tiny_h=lv_font_get_line_height(tiny);
int icon_h=lv_font_get_line_height(icon_font),big_h=lv_font_get_line_height(big),hero=std::max(icon_h,big_h);
int card_pad=large?14:7,inner=width-2*pad-2*card_pad;
unsigned columns=std::min<unsigned>(t.hour_count,6);
unsigned columns=std::min<unsigned>(t.hours.size(),6);
int hours_h=columns?small_h+mini_h+text_h+small_h+(large?16:6):0;
int card_a_h=card_pad+hero+(columns?(large?14:8)+hours_h:0)+card_pad;
int y=large?62:38;
@@ -470,15 +470,15 @@ inline void render_weather_detail(const Tile &t,bool large,int width,int height,
}
y+=card_a_h+(large?12:6);
// Coming days: a heading and a card with one row per day.
if(!t.forecast_count){detail_text(detail_root,"Geen dagvoorspelling van Home Assistant",pad,y,width-2*pad,small,LV_TEXT_ALIGN_LEFT,muted);return;}
if(!t.forecast.size()){detail_text(detail_root,"Geen dagvoorspelling van Home Assistant",pad,y,width-2*pad,small,LV_TEXT_ALIGN_LEFT,muted);return;}
if(large){detail_text(detail_root,"Komende dagen",pad+4,y,width-2*pad,detail_font,LV_TEXT_ALIGN_LEFT,muted);y+=text_h+8;}
int card_b_h=height-y-(large?10:4);
auto *days=detail_card(pad,y,width-2*pad,card_b_h);
int row_pad=large?8:4,row=(card_b_h-2*row_pad)/(int)t.forecast_count;
int row_pad=large?8:4,row=(card_b_h-2*row_pad)/(int)t.forecast.size();
int day_w=large?46:26,icon_x=card_pad+day_w,cond_x=icon_x+mini_h+(large?12:5);
int high_w=large?52:30,low_w=large?46:28,rain_w=large?120:60,drop_w=tiny_h+(large?4:2);
int temps_x=width-2*pad-card_pad-high_w-low_w,rain_x=temps_x-(large?14:6)-rain_w;
for(unsigned i=0;i<t.forecast_count;++i){
for(unsigned i=0;i<t.forecast.size();++i){
const auto &f=t.forecast[i];int ry=row_pad+i*row,tcy=ry+(row-text_h)/2,scy=ry+(row-small_h)/2;
detail_text(days,f.day,card_pad,tcy,day_w,detail_font,LV_TEXT_ALIGN_LEFT,ink);
detail_text(days,weather_icon(f.condition),icon_x,ry+(row-mini_h)/2,mini_h+6,mini,LV_TEXT_ALIGN_LEFT,weather_accent(f.condition));
@@ -753,8 +753,12 @@ inline void bind(size_t index, lv_obj_t *tile, lv_obj_t *title, lv_obj_t *value,
lv_label_set_long_mode(title,LV_LABEL_LONG_DOT);lv_label_set_long_mode(value,LV_LABEL_LONG_DOT);
w.progress=lv_obj_create(tile);lv_obj_remove_style_all(w.progress);lv_obj_set_size(w.progress,0,3);lv_obj_align(w.progress,LV_ALIGN_BOTTOM_LEFT,0,0);lv_obj_add_flag(w.progress,LV_OBJ_FLAG_HIDDEN);
w.slider=lv_slider_create(tile);lv_obj_set_size(w.slider,lv_obj_get_width(tile)-24,lv_obj_get_height(tile)>80?28:10);lv_obj_align(w.slider,LV_ALIGN_BOTTOM_MID,0,0);lv_slider_set_range(w.slider,0,1000);
lv_obj_set_style_bg_color(w.slider,lv_color_hex(0x111111),LV_PART_KNOB);lv_obj_set_style_pad_hor(w.slider,lv_obj_get_height(tile)>80?-10:0,LV_PART_KNOB);lv_obj_set_style_pad_ver(w.slider,lv_obj_get_height(tile)>80?-5:1,LV_PART_KNOB);lv_obj_set_style_radius(w.slider,14,LV_PART_MAIN);lv_obj_set_style_radius(w.slider,14,LV_PART_INDICATOR);lv_obj_set_style_radius(w.slider,3,LV_PART_KNOB);lv_obj_set_style_bg_color(w.slider,lv_color_hex(0xFCE5B4),LV_PART_MAIN);lv_obj_set_style_bg_color(w.slider,lv_color_hex(0xFFB900),LV_PART_INDICATOR);
lv_obj_set_style_opa(w.slider,LV_OPA_TRANSP,LV_PART_KNOB);
// A short white bar inside the fill as handle, like the control sliders (invisible before 0.2.20).
int strip=lv_obj_get_height(tile)>80?28:10;
lv_obj_set_style_bg_color(w.slider,lv_color_hex(0xFFFFFF),LV_PART_KNOB);lv_obj_set_style_bg_opa(w.slider,LV_OPA_COVER,LV_PART_KNOB);
lv_obj_set_style_pad_hor(w.slider,-(strip/2-(strip>20?3:2)),LV_PART_KNOB);lv_obj_set_style_pad_ver(w.slider,-(strip/4),LV_PART_KNOB);
lv_obj_set_style_border_width(w.slider,0,LV_PART_KNOB);lv_obj_set_style_shadow_width(w.slider,0,LV_PART_KNOB);
lv_obj_set_style_radius(w.slider,14,LV_PART_MAIN);lv_obj_set_style_radius(w.slider,14,LV_PART_INDICATOR);lv_obj_set_style_radius(w.slider,2,LV_PART_KNOB);lv_obj_set_style_bg_color(w.slider,lv_color_hex(0xFCE5B4),LV_PART_MAIN);lv_obj_set_style_bg_color(w.slider,lv_color_hex(0xFFB900),LV_PART_INDICATOR);
lv_obj_add_flag(w.slider,LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(w.slider,LV_OBJ_FLAG_GESTURE_BUBBLE);
lv_obj_add_event_cb(w.slider,slider_event,LV_EVENT_ALL,(void*)(uintptr_t)index);
@@ -953,7 +957,7 @@ inline void render_forecast(Widgets &w,const Tile &t,bool large,int width,int he
part_label(w,2,w.value_font,0,y+std::max(icon_h,temp_h)+2,left-4,LV_TEXT_ALIGN_LEFT,weather_text(t.state));
int column=(width-left)/5,day_h=lv_font_get_line_height(title_font),icon_col=lv_font_get_line_height(day_icon);
for(unsigned k=0;k<5;++k){
int x=left+k*column;bool has=k<t.forecast_count;const auto &f=t.forecast[k];
int x=left+k*column;bool has=k<t.forecast.size();const auto &f=t.forecast[k];
char temps[24];if(has && std::isfinite(f.high))snprintf(temps,sizeof(temps),std::isfinite(f.low)?"%.0f/%.0f":"%.0f",f.high,f.low);else temps[0]=0;
if(large){
int rows=day_h+icon_col+text_h,top=std::max(0,(height-rows)/2);
@@ -1279,7 +1283,7 @@ inline void render(lv_obj_t *room) {
bool large_tile=lv_obj_get_height(w.tile)>80;
set_busy(w,pending && !t.builtin(),large_tile);
// Cards that replace the name/status layout entirely.
bool clock=t.builtin(), forecast=d=="weather" && t.display=="forecast" && w.wide && t.forecast_count>0 && fresh() && t.available();
bool clock=t.builtin(), forecast=d=="weather" && t.display=="forecast" && w.wide && t.forecast.size()>0 && fresh() && t.available();
bool sunpath=d=="sun" && t.display=="sunpath" && w.wide && !t.sunrise.empty() && !t.sunset.empty() && fresh() && t.available();
bool graph=d=="sensor" && t.display=="graph" && t.has_history && !clock;
bool custom=clock||forecast||sunpath;
+4
View File
@@ -94,6 +94,10 @@ je later opnieuw meten. Voor diagnose en de oudere handmatige installatie:
## 5. Kies en wijzig je tegels
Tip: zet in je ESPHome-YAML onder `wifi:` de regel `power_save_mode: none` (nieuwe
wizard-YAML's hebben hem al); het scherm hangt aan het lichtnet en reageert dan zonder
wifi-slaapvertraging op Home Assistant.
Selecteer je scherm, vul de titel in en zoek entiteiten. De kiezer heeft
domeinfilters met gekleurde iconen. Je kunt maximaal twintig tegels toevoegen.
Het schermvoorbeeld toont hun plaats: twee kolommen, zes tegels per pagina.
+7
View File
@@ -177,6 +177,13 @@ staan iconen in de browser uit het midden); draai daarna `generate_packages.py`.
De substituties `MDI_GLYPH_*` zijn vervallen: `TILEn_ICON` in handmatige
profielen moet uit de set komen. Geen gewijzigde preferences of sleutels.
### Compatibiliteit 0.2.24 / firmware 0.2.20
Alleen intern: `Tile::forecast` en `Tile::hours` zijn vectoren (voorheen vaste arrays in
elke tegel); protocol en opslag ongewijzigd. De wizard-YAML krijgt `power_save_mode: none`
onder `wifi:`; de gedeelde pakketten bevatten geen wifi-blok, dus bestaande schermen
veranderen pas als de gebruiker de regel zelf toevoegt.
### Compatibiliteit 0.2.23 / firmware 0.2.19
Opslagversie en tegelprotocol blijven 1. `tiles[].options.controls` is additief:
+29
View File
@@ -0,0 +1,29 @@
# Testresultaten app 0.2.24 / firmware 0.2.20 (2026-09-13)
Vervolg op 0.2.23: doorlichting van de rendering, minder geheugen per tegel, zichtbare
greep op de mini-slider, CYD-kaarten in het lichte palet, wifi zonder modem-slaap.
## Doorlichting (wat is gemeten, wat is veranderd, wat bewust niet)
- LVGL 9.5 via ESPHome 2026.6.2 tekent al partieel (`LV_DEF_REFR_PERIOD 16`, kernloop 16 ms,
alleen ongeldig gemaakte gebieden gaan naar het paneel; `auto_clear_enabled: false`).
`lv_obj_set_x/y/width/height` slaan een ongewijzigde waarde over, maar elke
`lv_obj_set_style_*` maakt het object ongeldig. Daarom (0.2.23) alleen nog zetten bij een
echte wijziging en geen herrender meer elke 250 ms tijdens *bezig*.
- `Tile` was 2016 bytes × 20 = 40 KB statisch; de dag- en uurvoorspelling zitten nu in
vectoren die alleen weertegels vullen: 1216 bytes × 20 = 24 KB. CYD vrije heap tijdens de
selftest met de 18-tegels demo: 20–26 KB (0.2.19) → 34–37 KB (0.2.20). Guition 6,33 → 6,35 MB.
- Niet veranderd, bewust: de paginawissel tekent twee keer (skelet, dan inhoud; gewenste
directe feedback); GT911 op 20 ms; `buffer_size` 25% (PSRAM) en 12% (CYD).
- `power_save_mode: none` in wizard-YAML en bordprofielen (hangt aan het lichtnet).
## Geautomatiseerd
- Python: 82 tests OK. C++: 11 tests PASS. `generate_icons.py --check`, `generate_packages.py --check` groen.
- Beide profielen compileren; OTA naar beide borden; `device_info` meldt 0.2.20.
## Op de borden
- Guition en CYD `ui_self_test` met `send_layout.py --controls` (18 tegels): 10/10 PASS,
geen geometrie-, paneel- of paletfouten.
- Nog fysiek te bekijken door Max: greep op de mini-slider, CYD-lichtkaart in het lichte palet.
+3 -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.19"
SCREEN_FIRMWARE_VERSION: "0.2.20"
DYNAMIC_TILES: "false"
DASHBOARD_INBOX_INTERNAL: "true"
DEVICE_NAME: "guition-wallbox"
@@ -834,6 +834,8 @@ ota:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Mains-powered panel: no modem sleep, so HA updates and OTA arrive without beacon latency.
power_save_mode: none
ap:
ssid: "Smartdisplay Fallback Hotspot"
password: !secret ap_password
+4 -2
View File
@@ -58,7 +58,7 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.19"
SCREEN_FIRMWARE_VERSION: "0.2.20"
DYNAMIC_TILES: "false"
DASHBOARD_INBOX_INTERNAL: "true"
DEVICE_NAME: "cyd-new"
@@ -861,7 +861,7 @@ esphome:
id(active_climate_swing_modes) = t.swing_modes; id(active_climate_swing_mode) = t.swing_mode;
id(open_value_overlay).execute(t.name, t.entity, t.domain(), "auto", t.active(), 0,
t.brightness, t.percentage, t.position, runtime_tiles::icon_for(t),
0x565C66, 0xE4B23C, 0xFFFFFF, "Uit", "Aan", t.modes,
0xD5EEFC, 0x00A6ED, 0x1B1B1B, "Uit", "Aan", t.modes,
t.current, t.target, t.humidity, t.state, t.hvac_modes,
std::to_string(t.minimum), std::to_string(t.maximum), std::to_string(t.step));
};
@@ -1029,6 +1029,8 @@ ota:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Mains-powered panel: no modem sleep, so HA updates and OTA arrive without beacon latency.
power_save_mode: none
ap:
ssid: "Smartdisplay Fallback Hotspot"
password: !secret ap_password
+2 -2
View File
@@ -59,7 +59,7 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.19"
SCREEN_FIRMWARE_VERSION: "0.2.20"
DYNAMIC_TILES: "true"
DASHBOARD_INBOX_INTERNAL: "false"
DEVICE_NAME: "cyd-new"
@@ -865,7 +865,7 @@ esphome:
id(active_climate_swing_modes) = t.swing_modes; id(active_climate_swing_mode) = t.swing_mode;
id(open_value_overlay).execute(t.name, t.entity, t.domain(), "auto", t.active(), 0,
t.brightness, t.percentage, t.position, runtime_tiles::icon_for(t),
0x565C66, 0xE4B23C, 0xFFFFFF, "Uit", "Aan", t.modes,
0xD5EEFC, 0x00A6ED, 0x1B1B1B, "Uit", "Aan", t.modes,
t.current, t.target, t.humidity, t.state, t.hvac_modes,
std::to_string(t.minimum), std::to_string(t.maximum), std::to_string(t.step));
};
+1 -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.19"
SCREEN_FIRMWARE_VERSION: "0.2.20"
DYNAMIC_TILES: "true"
DASHBOARD_INBOX_INTERNAL: "false"
DEVICE_NAME: "guition-new"
+6
View File
@@ -1,3 +1,9 @@
## 0.2.24 (firmware 0.2.20)
- **Lichter en zuiniger**: de dag- en uurvoorspelling zitten alleen nog in het geheugen van weertegels (was 832 bytes in elk van de twintig tegels, ruim 16 KB op de CYD zonder PSRAM). De mini-slider onderin een tegel (**Kleine slider**) heeft nu dezelfde zichtbare witte greep als de nieuwe schuiven. De licht-, ventilator- en zonweringkaart op de CYD gebruiken het lichte palet van de Guition (de laatste rest van het donkere thema).
- **Wifi zonder modem-slaap** (`power_save_mode: none`) in de installatie-YAML van de wizard en de bordprofielen: statusupdates, tikacties en OTA wachten niet meer op een wifi-beacon. Bestaande schermen: voeg de regel zelf toe onder `wifi:` in je ESPHome-YAML.
- Doorlichting van de rendering: LVGL 9.5 tekent partieel en slaat ongewijzigde posities en maten al over; de winst zat in de stijlzetters (0.2.23) en in niet meer herrenderen tijdens *bezig*. Een paginawissel tekent bewust twee keer (skelet, dan inhoud). Verder geen structurele last gevonden; zie docs/TEST_RESULTS_0224.md.
## 0.2.23 (firmware 0.2.19)
- **Directe bediening op dubbelbrede tegels**, zoals de entiteitsrijen in Home Assistant: rechts op de kaart staan knoppen of een schuif, links blijven icoon, naam en status. Per domein kies je in het tegelpaneel onder **Directe bediening op de tegel** welke set de tegel toont:
+2 -1
View File
@@ -16,7 +16,7 @@ WEEKDAYS = ['ma', 'di', 'wo', 'do', 'vr', 'za', 'zo']
REPO = 'https://github.com/MaxGramser/homeassistant_espscreen'
REFS = {'cyd': 'main', 'guition': 'main'}
# Firmware shipped with this app release; screens below it get an update offer.
FIRMWARE_VERSION = '0.2.19'
FIRMWARE_VERSION = '0.2.20'
ATTRS = frozenset('brightness percentage current_position current_temperature temperature current_humidity min_temp max_temp target_temp_step supported_color_modes hvac_modes hvac_action hs_color color_temp_kelvin min_color_temp_kelvin max_color_temp_kelvin fan_speed_list unit_of_measurement battery_level fan_speed volume_level is_volume_muted media_title options min max step temperature_unit supported_features device_class next_rising next_setting finishes_at duration remaining humidity wind_speed wind_speed_unit apparent_temperature fan_modes swing_modes fan_mode swing_mode'.split())
# Attributes whose boolean value the screen needs; every other bool stays behind.
BOOL_ATTRS = frozenset(['is_volume_muted'])
@@ -417,6 +417,7 @@ ota:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
power_save_mode: none
ap:
ssid: {quote(name + ' Setup')}
password: {quote(ap)}
+1 -1
View File
@@ -1,5 +1,5 @@
name: ESP Screen Manager
version: "0.2.23"
version: "0.2.24"
slug: esp_screen_manager
description: Kies tegels voor je CYD en Guition touchscreen, zonder opnieuw flashen.
url: https://github.com/MaxGramser/homeassistant_espscreen