The 2 ms reset of ESPHome's expander support (0.2.81) left the chip silent: "Calibration error" with the PCA9557
answering at 0x18. A component of smart_display's (elecrow_touch_reset.h, setup priority 700: after the I2C bus,
before the GT911 driver) writes the expander as Elecrow's V3.0 ESPHome example does: every pin an output and low,
20 ms, the reset released, 100 ms, the interrupt line back to an input. The driver's reset and interrupt pins are gone.
A unit's touch never answered: "touchscreen is marked FAILED: Calibration error" at setup, the GT911 still in reset
behind the PCA9557 (I2C 0x18) of the V3.0 board. The board file declares the expander as ESPHome's pca9554 (the same
register map) and hands the GT911 driver its reset (IO0) and interrupt (IO1) pins on it, so the driver runs Elecrow's
own sequence: reset low with the interrupt line low, reset high, 56 ms, address 0x5D. A V2.0 without the expander boots
its GT911 by the pull-up as before. The bus is scanned at boot for the log.
The GT911 of the CrowPanel 7.0" sits on GPIO 19 and 20, the ESP32-S3's USB pins. Firmware 0.2.79 freed them once, in a
static constructor; the Wi-Fi PHY library then kept the USB PHY on (CONFIG_ESP_PHY_ENABLE_USB, on by default on an
ESP32-S3) and the touch went quiet once Wi-Fi came up. The board file turns that option off, frees the pins again in
BOOT_TOUCH after Wi-Fi started, and a one-second watchdog frees them whenever anything takes them, with a log line.
A board file for the Elecrow CrowPanel 7.0" (DIS08070H, ESP32-S3-WROOM-1-N4R8): 800x480 through mipi_rgb, GT911 on the
ESP32-S3's USB pins (elecrow_usb_phy.h frees them, behind a flag only this board sets), the Guition's vertical sizes
across a wider page, no rotation. Its 4 MB of flash take the CYD's budget: a partition table of the component's
(smart_display: partitions: 4mb_wide_slots, 1,984 KB slots), the icon fonts at 2 bits a pixel and the TLS client only
bring the firmware to 1,865,440 bytes, 91.8 % of a slot, with camera images kept. docs/ELECROW.md has the numbers and
the trade.
The add-on reads a large board's name from the type sensor's value, the editor offers the board under New screen and
draws the wide preview, tools/check.sh builds it with the budget, render_topbar and capture_ui take the panel's size.
Firmware 0.2.79; the CYD image is unchanged (1,633,760 bytes).
This fork builds from gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen: the packages, the fonts, the
component and the add-on repository point there instead of GitHub.
A single or double-width media player tile takes Display -> Album cover
on a Guition: the cover of what plays in the icon's place, like Home
Assistant's own tile. It rides in the page's strip of 0.2.91: the request
and the answer are the same, a media player is simply among the tiles.
The app fetches a player's picture only when its address changes; a page
with a camera loads at the camera's pace with the cover reused, a page of
media tiles alone loads once, and a new track (the picture's mark in the
state, part of the page's wish) asks again at once. A player without a
picture keeps its icon and is not asked for; the double-width tile keeps
its controls; the tile over the whole page keeps the card's big cover.
Firmware 0.2.78.
Host harness 31 checks (docs/TEST_RESULTS_0292.md). CYD firmware
1,633,760 B, 89.0 %.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Live picture (issue #4): a camera or image tile on a Guition takes
Display -> Live picture with a pace of 15 or 30 s. The camera tiles of a
page share one download: the screen asks ESP Screens for them together
(esphome.screen_camera with tiles, size and bg) and the app serves one
BMP, a strip of squares with the corners rounded over each tile's own
colour; every tile draws its square out of it with LVGL's image offset
(LV_IMAGE_ALIGN_TOP_LEFT: the default centres a taller source). A third
online_image on the Guition holds the strip; it loads after the alert's
picture, a cover and the camera full screen, never under a card, in
standby or under a finger. The strip is served whole every time: ESPHome's
http_request logs a 304 as a failed request. Firmware 0.2.77.
Alert button: esp_screens_show_alert takes action and data; the app
performs the action once when the button's report (esphome.screen_alert,
ok) comes from any screen. show_alert keeps its seven fields, so every
firmware from 0.2.31 has it.
Media card and full-page media tile: a title or artist line that is too
wide rolls by (LV_LABEL_LONG_SCROLL_CIRCULAR) instead of ending in dots.
Full-page tiles are white or their own pastel like every other tile; the
tint in the state colour of firmware 0.2.62 to 0.2.76 is gone.
Host harness (27 checks) and bench Guition + local add-on on the Yellow:
docs/TEST_RESULTS_0291.md. CYD firmware 1,633,728 B, 89.0 %.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A tile has one line under the name, and LVGL cuts what doesn't fit off the end, which is where that line
often carries what you want to read. Two lines lost it:
- "Yesterday 9:15 PM" (English with a 12-hour clock) read "Yesterday 9:1…" on both boards. The tile now takes
a shorter wording of the same line when the full one doesn't fit, screen.script.yesterday_time_short; every
language whose word is short enough keeps the same text there.
- A robot's battery stood behind Home Assistant's own word for its state, so German, French and Polish showed
"Angedockt / 10…". The battery keeps its room now and the word takes the dots: "Angedo… / 10 %".
`tools/i18n.py check` measures a tile line with a 12-hour clock in its placeholder, so a translation that is
too long for the line fails there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The climate card's fan and swing rows showed the raw mode names (Auto, Low, Medium, High) in every language.
Home Assistant names the common ones itself (Laag, Mittel, Élevée): ha-words now copies those into screen.ha, and
the card looks a mode up there; an integration's own mode still reads as its name. ha-words also says how many
words it really changed. Found in the second test pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Following docs/TRANSLATING.md for Swedish: `i18n.py new` guessed three letters (sön mån tis) and `check` then failed
the new file on it. The guess is now two letters from the abbreviation (sö må ti, the CLDR's short width here).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The app fetched Home Assistant's state words in the screens' language only, so a screen still on 0.2.75 showed
"Speelt" among its own English words (and would get letters it can't draw). It now keeps them per language, the
screens' and English, and picks the set of the screen it writes for. Found in the second test pass on the boards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The standard tile still joined a unit with a space, and the climate status, cover and light values and the slider
overlay wrote their own "%": all go through screen_text::with_unit / percent now (54% in Spanish, 54 % in German).
Found on the Guition in the end-to-end test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- The CYD's weather columns hold two letters beside the icon: every language has its own two-letter days (the CLDR's
short width, Polish nd pn wt); French mar became nar there. check fails on a longer one.
- The top bar takes the language's abbreviation (sam. 19 sept., Sa., 19. Sept.), English keeps Sa 19 Sep.
- The editor's clock and date follow the screens: 7:05 PM in the top bar, 7:05 on the clock card.
- A long weather word ends in an ellipsis before the days.
- Tile lines that were cut on a screen are shorter (Zum Öffnen, Ouvrir, Otwórz, Sin imagen, ...); check measures
them with the screens' font against the tile width of both boards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Numbers in the mockup follow what the app sends the screens (style, grouping from five digits, the space before
%), a state without a unit stays as it is, and built-in cards carry the screens' names (Instellingen, not the
editor's Einstellungen).
- Automatic in Language & region is labelled with what it means now (Home Assistant's language decides).
- A screen's status (Synced, Loading tiles, ...) shows in the editor's language; the screens keep reporting it in
English, which the app reads.
- The doorbell example uses the alert examples of the editor's language; control labels keep their capitals.
- Editor bundle rebuilt with every language's texts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
From the end-to-end test on the bench boards and an independent review:
- Units spaced as Home Assistant does (54% / 54 % in German and French, 18°, 21.5 °C), in the firmware and the app.
- Grouping from five digits where the language does (1234 but 12.345 in Spanish, Polish, Portuguese): the layout
message carries group_min and percent_space; an unknown number style no longer refuses a layout.
- Automatic follows Home Assistant's own language for the clock and numbers, also without a translation (Swedish:
24 hours, 1 234,5; pt-BR: 1.234,5): regions.json from the CLDR for all 63 languages Home Assistant offers.
- 12-hour clock: the top bar says 3:48 PM, the clock card 3:48 (not 03:48), Last runs follow the clock.
- The climate setpoint font has the decimal comma; the value fonts have ², ³ and µ (m³ was a box).
- Weather days: at most three letters without the dot (mié, sáb, czw); the screen took three bytes.
- Texts fall back per key through the base language (pt-BR -> pt -> en) in the firmware and the app.
- Each screen gets the app's words in the language its firmware speaks; firmware from before the languages gets
English and its own letters, not boxes.
- set_language re-reads what it writes (indentation, comments, quoted keys, CRLF, !include) and every build writes
the language first; the 24-hour pin no longer hits a fresh install and waits for the old clock switches.
- Tests: region and notation, plural parity with the C++ rules, set_language edge cases, the pin, PUT /api/language.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The cached screen list only read the entities named in SCREEN_ENTITY_NAMES, which lacked the new sensor: the
updater waited in vain after a language update, the live page kept offering it, and the nightly round would have
rebuilt the screen every night. Found in the end-to-end test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every error, delivery status, update result and label the editor shows is an `addon` text of en.json now, in the language
of the editor's request: its X-ESP-Screens-Language header, or ?language= for the live updates, which an EventSource sends
without headers. A text made in the background and read later (a delivery status, an update result in updates.json, why a
screen can't show an alert) is kept in English with its key (i18n.Text, i18n.english) and written in the editor's language
when it is shown (i18n.shown); updates.json keeps the English message next to the key, for an older app.
The words the app sends to the screens come in the screens' language (Settings -> Language & region): the names of the
built-in tiles, the top bar's words, dates and numbers, the history card's words, the vacuum chips, the forecast's day
names and the Identify card, and so does the notification of a stopped update round. The top bar and the history card
take a binary sensor's words from screen.ha.binary, as the tiles do, which makes test_binary_words pass again; three tests
follow Home Assistant's new words there (Detected, Clear).
What stays English: what Home Assistant gets as data (a tile event's answer, the layout sensors, entity names), the logs
and the firmware jobs' logs, the screens' protocol words, and the Claude skill, which is built from the English tables in
core.py and tile_icons.py; en.json repeats those and tests/test_addon_translations.py keeps both equal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- The page takes the screens' words its mockup draws from their own texts: screen.ha (Home Assistant's words, filled
from Home Assistant), screen.time, screen.date, screen.number and screen.tile.page, per language next to _meta and
editor. The copies under editor.mockup go; the rest of `screen` and all of `addon` stay out of the build.
- Numbers in the mockup follow the screens' language (screen.number, with its minimum grouping) or the chosen style,
as the firmware writes them.
- @intlify/unplugin-vue-i18n compiles every text at build time, so the page runs vue-i18n's runtime-only build
without the message compiler; every language's _meta comes with the page. Empty texts stay out while building.
- The live updates carry the editor's language (api/events?language=), as an EventSource sends no headers.
- web/ needs Node 22.13 or newer: vue-i18n 11.4, Vite 7 and the unplugin ask for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- vue-i18n 11 with the Composition API: English comes with the page, any other language loads the first time it is
needed from screen_manager/translations, and a text a language lacks shows in English. The build keeps only _meta
and editor of each file (vite.config.ts), never the screens' or the add-on's texts.
- The editor follows the language of the user's Home Assistant profile (<html lang> of Home Assistant's page), else
the browser's; plural forms follow the file's _meta.plural, with the screens' rules.
- Every request tells the add-on the editor's language (X-ESP-Screens-Language), so its own words can follow it.
- The mockup draws the screens' words (Unavailable, Page 2 ›, the date, "5 min ago") in the screens' language, with
their clock and number format.
- Settings → Language & region: the screen language, time format and number format for every screen, with the user's
own profile offered when it differs. The per-screen clock choice goes; the top bar points there. A screen that
still needs the new language says so in its update badge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tools/i18n.py gets cldr (day and month names, date orders, numbers and the clock from Node's Intl), ha-words (the
words for states from Home Assistant's own translations, English included) and lint (English left in the firmware's
code). The screens write numbers from Home Assistant in the chosen format, and every text font carries the apostrophe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The firmware takes its texts from screen_manager/translations/<code>.json at build time (components/smart_display:
one table in main.cpp, keys in screen_text_keys.h), and the fonts carry every letter European languages write with.
The app keeps Settings -> Language & region (language, clock, numbers) for every screen, writes LANGUAGE into the
screens' YAML and sends the clock and the number format in the layout message; the per-screen 24-hour clock goes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>