Build the screens with ESPHome 2026.9 and keep its build memory in the app's own storage, give the CYD 94 KB more room, and stop a timer from counting one second too many (0.2.89)
The add-on ships ESPHome 2026.9.0: it builds an ESP32 with ESP-IDF itself and keeps a compiler cache. firmware.py gives the CLI its own storage in /data (ESPHOME_DATA_DIR /data/esphome, which the ESPHome Device Builder no longer wipes on start; ESP-IDF and ccache in /data/idf, at most 1 GB; compilers limited to the core count as PlatformIO ran them), removes the unused PlatformIO once before the first ESP-IDF build, and leaves the new folders out of backups. Firmware 0.2.75: assertion_level SILENT in every board's esp32 block, the core logs from INFO, and clock_parts() in runtime_model.h replaces sscanf. timer_left() caps a running timer at its duration (the screen's clock runs up to a second behind Home Assistant's). The packages keep min_version 2026.6.2, the top-level online_image and OTA passwords for new screens, so an add-on or Device Builder that isn't updated yet keeps building every screen; CI now builds both boards with the add-on's ESPHome and with min_version. ESP-IDF's error names stay on, and build_flags stay under platformio_options, because ESPHome 2026.9's LVGL reads only those. tools/check.sh: 478 Python tests, 19/19 C++, editor; --firmware PASS with ESPHome 2026.6.2 (CYD 87.7 %), 2026.8.1 (88.3 %) and 2026.9.0 (86.4 %, was 91.5 %). Both bench screens OTA'd and watched for half an hour; ESP Screens as a local add-on on the Yellow built both profiles (CYD 1,037 s first build, 41 s unchanged). Details: docs/TEST_RESULTS_0289.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -144,6 +144,44 @@ struct Extra {
|
||||
action_templates.empty() && state_word.empty() && effect.empty() && option_rows.empty() && number_rows.empty();
|
||||
}
|
||||
};
|
||||
// The numbers of a clock text ("0:05:00", "07:45"), at most `max` of them, each after optional white space, up to the
|
||||
// first character that is not a digit or a colon after one: what sscanf's "%u:%u:%u" reads. Read by hand (firmware
|
||||
// 0.2.75+): sscanf brought newlib's whole scanf into the firmware, 9.5 KB on the CYD.
|
||||
inline int clock_parts(const std::string &text, unsigned *out, int max) {
|
||||
int n = 0;
|
||||
const char *p = text.c_str();
|
||||
while (n < max) {
|
||||
while (*p == ' ' || (*p >= '\t' && *p <= '\r')) ++p;
|
||||
if (*p < '0' || *p > '9') break;
|
||||
unsigned value = 0;
|
||||
while (*p >= '0' && *p <= '9') value = value * 10 + unsigned(*p++ - '0');
|
||||
out[n++] = value;
|
||||
if (*p != ':') break;
|
||||
++p;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
// A timer's duration or remaining time as Home Assistant writes it ("0:05:00", or "5:00") in seconds; 0 when unusable.
|
||||
inline uint32_t duration_seconds(const std::string &text) {
|
||||
unsigned v[3] = {0, 0, 0};
|
||||
const int n = clock_parts(text, v, 3);
|
||||
if (n == 3) return v[0] * 3600 + v[1] * 60 + v[2];
|
||||
if (n == 2) return v[0] * 60 + v[1];
|
||||
return 0;
|
||||
}
|
||||
// Seconds a running timer has left, from Home Assistant's end time and the screen's clock. Both are whole seconds and
|
||||
// the clock runs up to a second behind Home Assistant's (it syncs in whole seconds), so the difference can be one more
|
||||
// than the timer holds: never more than its duration (firmware 0.2.75+; a 3 s timer started early in a second read 0:04).
|
||||
inline uint32_t timer_left(uint32_t end, uint32_t now, const std::string &duration) {
|
||||
const uint32_t left = end > now && now ? end - now : 0;
|
||||
const uint32_t full = duration_seconds(duration);
|
||||
return full && left > full ? full : left;
|
||||
}
|
||||
// A sun time from the add-on ("06:45") in minutes after midnight; -1 when unusable.
|
||||
inline int minutes_of(const std::string &clock) {
|
||||
unsigned v[2] = {0, 0};
|
||||
return clock_parts(clock, v, 2) == 2 && v[0] < 24 && v[1] < 60 ? int(v[0] * 60 + v[1]) : -1;
|
||||
}
|
||||
// The Extra of a tile on the heap, copied along with the tile like an ordinary member.
|
||||
struct ExtraBox {
|
||||
std::unique_ptr<Extra> ptr;
|
||||
|
||||
@@ -2144,13 +2144,6 @@ inline const char *icon_for(const Tile &tile) {
|
||||
if (d == "camera" || d == "image") return "\U000F07AE";
|
||||
return "\U000F0425";
|
||||
}
|
||||
// HA duration strings ("0:05:00") to seconds; 0 when unusable.
|
||||
inline uint32_t duration_seconds(const std::string &text) {
|
||||
unsigned h = 0, m = 0, s = 0;
|
||||
if (sscanf(text.c_str(), "%u:%u:%u", &h, &m, &s) == 3) return h * 3600 + m * 60 + s;
|
||||
if (sscanf(text.c_str(), "%u:%u", &m, &s) == 2) return m * 60 + s;
|
||||
return 0;
|
||||
}
|
||||
inline std::string countdown(uint32_t seconds) {
|
||||
char b[16];
|
||||
if (seconds >= 3600) snprintf(b, sizeof(b), "%u:%02u:%02u", seconds / 3600, seconds / 60 % 60, seconds % 60);
|
||||
@@ -2171,7 +2164,7 @@ inline std::string last_run_text(uint32_t epoch) {
|
||||
}
|
||||
inline std::string timer_text(const Tile &t) {
|
||||
const Extra &x = t.extra();
|
||||
if (t.state == "active") { uint32_t now = now_epoch(); return countdown(x.timer_end > now && now ? x.timer_end - now : 0); }
|
||||
if (t.state == "active") return countdown(timer_left(x.timer_end, now_epoch(), x.duration));
|
||||
if (t.state == "paused") return "Paused " + countdown(duration_seconds(x.remaining));
|
||||
return x.duration.empty() ? "Off" : countdown(duration_seconds(x.duration));
|
||||
}
|
||||
@@ -2617,9 +2610,6 @@ inline void render_graph(Widgets &w,const Tile &t,bool large,int x,int y,int wid
|
||||
}
|
||||
// Sun path: horizon, an arc from sunrise to sunset and the sun at the current
|
||||
// position (or below the horizon at night). Wide cards only.
|
||||
inline int minutes_of(const std::string &clock) {
|
||||
unsigned h=0,m=0;return sscanf(clock.c_str(),"%u:%u",&h,&m)==2 && h<24 && m<60 ? int(h*60+m) : -1;
|
||||
}
|
||||
inline void render_sunpath(Widgets &w,const Tile &t,bool large,int width,int height) {
|
||||
begin_extra(w,"sunpath",width,height);
|
||||
const lv_font_t *title_font=lv_obj_get_style_text_font(w.title,LV_PART_MAIN);
|
||||
|
||||
Reference in New Issue
Block a user