Show a live picture on a camera tile, run an action from the alert button, roll long media titles, and keep full-page tiles in their own colour (0.2.91)

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>
This commit is contained in:
Max Gramser
2026-09-20 12:47:44 +02:00
parent 8a81ff4d93
commit 306636434b
55 changed files with 1133 additions and 126 deletions
+35 -5
View File
@@ -35,6 +35,34 @@ what plays, with the title, the artist and the album, a progress bar and the key
size *Full page* the alert's picture goes first: the tile's cover waits until the alert's picture
is there, and a cover already on its way finishes before the alert's picture starts.
## A live picture on a camera tile
App 0.2.91 with firmware 0.2.77 puts the camera on the tile itself: **Display → Live picture** in the
tile's settings, with a pace of 15 or 30 seconds (`refresh`). The tile shows a small square of the
camera's view in the icon's place, the middle of the snapshot cut square with the tile's rounded
corners, and refreshes it while that page is on the screen. A tap still opens the camera full screen.
- **One download per page.** The screen asks ESP Screen Manager for all the live tiles of the page at
once (the event `esphome.screen_camera` with `tiles`, the entities in slot order, `size`, the side of
the icon's circle, and `bg`, the colour of each tile behind the corners). The app answers with one
BMP: a strip of squares, top to bottom in that order, and every tile draws its own square out of it
(LVGL's image offset). Six live tiles cost the screen one download of about 50 KB; a tile over the
whole page gets one square of 128 px.
- **At the pace of the fastest tile.** The page loads its strip every 15 s when any of its tiles says
15 s. The app fetches a camera again only when that camera's own pace has passed, so a 30 s camera
on a 15 s page is fetched every other load. The strip comes whole every time, never as a 304:
ESPHome's `http_request` logs a 304 as a failed request and raises its error flag, which a page of
slow cameras would do every 15 s. Nobody loading means nothing fetched, as with the camera full
screen.
- **After the other pictures.** The strip waits for the alert's picture, a cover on its way and the
camera full screen (one picture loads at a time), and does not load under an open card, in standby
or under a finger. A page turn, dark mode (other colours behind the corners) or a changed tile drops
the strip and asks for a new one.
- **A camera without a picture** keeps its icon: the app names it with an empty entry in its answer
and paints a plain square of the tile's colour in the strip.
- The strip lives in a third `online_image` of the Guition profile (`tile_image`, PSRAM); the CYD has
none and the editor does not offer the live picture there.
## A doorbell
```yaml
@@ -105,11 +133,13 @@ Guition it comes in about 1.8 s (2.8 s with 4 KB).
## For developers
- `screen_manager/app/camera_feed.py`: fetching, sizing, links and the port.
- `screen_manager/app/camera_feed.py`: fetching, sizing, links and the port; `encode_live` and `CameraFeed.live`
make the strip for a page's live tiles (`Manager.answer_live` in `server.py` checks the tiles against the layout).
- `components/smart_display/camera_view.h`: when to ask for a link and when to load again
(`tests/test_camera_view.cpp`).
- `components/smart_display/runtime_tiles.h`: the full-screen view, the alert picture and the
`camera` message.
- `packages/boards/guition-4848s040.yaml`: the two `online_image` components, the alert frame, and the diagnostic
action `preview_camera` (an entity opens it, an empty entity closes it).
- `components/smart_display/runtime_tiles.h`: the full-screen view, the alert picture, the live tiles
(`live_tick`, `live_place`) and the `camera` message.
- `packages/boards/guition-4848s040.yaml`: the three `online_image` components (the camera full screen and the
cover, the alert's picture, the live tiles' strip), the alert frame, and the diagnostic action `preview_camera`
(an entity opens it, an empty entity closes it).
- `tests/test_camera.py`: the app side and the words both sides share.
+42
View File
@@ -219,6 +219,48 @@ icons sit off-center in the browser).
The `MDI_GLYPH_*` substitutions are retired: `TILEn_ICON` in manual
profiles must come from the set. No changed preferences or keys.
### Compatibility 0.2.91 / firmware 0.2.77
Four things: a live picture on a camera tile, an action behind an alert's button, media texts that roll by, and a
full-page tile without a colour of its own. Storage version and preferences unchanged; new tile settings and one new
camera view.
- **Live picture** (`display: live` with `refresh: 15|30` on a `camera.*` or `image.*` tile; `DISPLAYS`,
`LIVE_REFRESH`, `LIVE_MIN_FIRMWARE` in `core.py`, `validate_layout` drops `refresh` from any other display). The
state message's `o` carries both keys verbatim; firmware before 0.2.77 ignores them (`min_firmware` asks for 0.2.77).
The screen sends `esphome.screen_camera` with `tiles` (comma-separated entities in slot order), `size` (the icon
circle's side, 24-160) and `bg` (one `RRGGBB` per tile); `Manager.answer_live` refuses a tile that is not a live
camera tile of that layout and answers op `camera`, `t: "live"`, `e` the same list with `""` where the app has no
picture, `u` a link to a BMP of `size` by `size` times the tiles (`camera_feed.encode_live`, `CameraFeed.live`,
`Link.live`). The screen validates `e` with `valid_entity_list` (runtime_model.h, 400 bytes) and matches it to what
it asked (`same_list`). The strip is always served whole (200, never 304, whatever `If-None-Match` says): ESPHome's
`http_request` logs a 304 as a failed request and raises its error flag (seen on the bench Guition with a radar
camera), which a page of slow cameras would do every 15 s; the ETag still goes out, and every picture now carries a
`Last-Modified`, because `online_image` warns at each load about either header missing. `camera_view::Feed` got
`every` (the pace per feed; the full screen keeps `REFRESH_MS`) and
asks again at that pace after an empty answer. The Guition profile has a third `online_image`, `tile_image`, bound
as `runtime_tiles::camera_live` with `live_loaded(cached)` / `live_failed()`; `live_tick()` runs from
`camera_tick()` after `cover_tick()`. Each tile's square is an `lv_image` in the icon circle's place with
`LV_IMAGE_ALIGN_TOP_LEFT` and `offset_y = -n * size` (the default inner alignment centres a taller source, which
showed the neighbour's square); the circle hides under it (`live_place`, called wherever `render_slot` and
`render_full` place the circle). The strip is released (pictures hidden, `src` cleared first) when the page's wish
changes: entities, grounds (dark mode) or size. Loads wait for `alert_image_due()`, a cover or camera on its way,
the camera full screen, an open card, standby and a finger. `tests/test_camera.py` (LiveTiles, LiveApp),
`tests/test_camera_view.cpp`, `web/tests/components.spec.ts`.
- **Alert action** (`action` and `data` in `esp_screens_show_alert`; `ALERT_ACTION_FIELD`, `alert_action` in
`core.py`). `show_alert` keeps its seven fields: the app remembers `(key, action, data)` per node of every screen
that got the alert (`Manager.alert_actions`), subscribes to `esphome.screen_alert` and queues it with the broadcasts
(`alert_loop` → `alert_ended`); `action: ok` performs it once through `HomeAssistant.call` and forgets it on every
screen of that alert, any other ending forgets it on that screen, a new alert or a dismissal forgets it too. Works
with every firmware from 0.2.31. `tests/test_alert_broadcast.py`; the cheatsheet, the Claude skill and
README_EXTENDED document the field.
- **Full-page tiles are no longer tinted**: `render_slot` paints every card `theme::surface(t.background)` with
`theme::outline` (the `lit` tint of 0.2.62-0.2.76 and `Tile::lights_up()` are gone; `tests/test_state_colours.py`
guards it). The editor's mockup never painted the tint, so nothing changes there.
- **Media texts roll by**: the title and the artist line of the media card and of the full-page media tile are
`LV_LABEL_LONG_SCROLL_CIRCULAR` (`marquee()`, LVGL's default 40 px/s and 300 ms wait; a text that fits stands
still). The single and wide tiles keep `LV_LABEL_LONG_DOT` on their one status line.
### Compatibility 0.2.89 / firmware 0.2.75
ESPHome 2026.9.0 in the add-on (`screen_manager/Dockerfile`, `requirements.txt`); the packages' `min_version` stays
+69
View File
@@ -0,0 +1,69 @@
# Test results app 0.2.91 / firmware 0.2.77 (2026-09-20)
Four asks in one round: a live picture on a camera tile (issue #4, "show a camera preview on the tile as an
option"), an action behind the alert's button, media titles that roll by, and full-page tiles without a colour of
their own. The live picture is the one with design in it; the rest is small.
## Live pictures: the design and why
- **One picture per page, not per tile.** The screen already has two `online_image`s and a rule that one picture
loads at a time (a download shares ESPHome's main loop with touch and drawing). Six tiles each loading their own
picture would mean six downloads and six buffers. Instead the screen asks for all the live tiles of the page at
once and the app serves one BMP: a strip of squares, top to bottom in slot order. Every tile draws its own square
out of that one image with LVGL's image offset. A page with six live tiles costs one download of about 50 KB.
- **The corners come from the app.** LVGL's image radius (`clip_radius`) rounds the whole source image, not each
square of it, and `clip_corner` would draw into a layer (`tests/test_layer_free.py`). So the app bakes the rounded
corners into each square over that tile's own colour, as it does for a media cover; the screen sends the colour of
every tile with the request (`bg`), so dark mode and pastel tiles come out right.
- **A third `online_image`** (`tile_image`) holds the strip: it lives beside a cover or a camera full screen instead
of sharing their buffer, and PSRAM has room. Loading still waits its turn: after the alert's picture, a cover or
the camera full screen, never under an open card, in standby or under a finger.
- **The pace is the tile's.** 15 or 30 s per tile; the page loads at the pace of its fastest tile, and the app fetches
a camera again only when that camera's own pace has passed. Nobody loading means nothing fetched.
- **The size is the icon's.** The square takes the icon circle's place and size (54 px on a Guition tile, 128 px on a
tile over the whole page), so the tile's text does not move.
## Host harness (Mac, host build of the Guition package, real add-on code)
`live_host.py` builds the Guition package as a host program with probe actions and drives it over the ESPHome API
against a demo home with five cameras (`live_demo_home.py`, the real `Manager.answer_camera` behind `/dev/camera`);
git-ignored tooling under `.esphome/readme-render/live/`. 27 checks, all passing:
- page 1 asks for its three live tiles (two single tiles and a wide one, one of them on a pastel) with size 54 and
the three colours; the strip loads and every tile shows its own square (offsets 0, −54, −108), the circle hidden
under it, the plain camera tile keeping its icon;
- the page loads again after 15 s; the 15 s camera is fetched again while the 30 s camera is not, and after 30 s
both are;
- the camera full screen pauses the strip (no load for 17 s) and it loads again once closed, the pictures back;
- a full-page live tile alone on page 2 asks at 128 px and shows its picture at 128;
- a page without live tiles wants nothing and holds no picture; a media tile over the whole page still asks for its
cover; a camera without a picture keeps its icon (the app answers with an empty entry and no link);
- back on page 1 the strip is asked for again; a card over the page pauses the loads and closing it resumes them;
dark mode asks again with the dark grounds (`1A1A1A,4A2422,1A1A1A`) and shows the dark strip.
Found on the host, fixed before the bench: LVGL's default inner alignment centres a source that is taller than its
object, so every tile showed the square of its neighbour (and the corners of the wrong colour); the picture now sets
`LV_IMAGE_ALIGN_TOP_LEFT`. The host's `http_request` fails a 304 outright where the ESP32's `online_image` takes it as
"unchanged"; the harness counts both as a load.
The rolling title was checked the same way: two renders of a full-page media tile 1.5 s apart differ only in the rows
of the title and the artist line.
## Bench Guition (192.168.146.136, firmware 0.2.77 from this tree over OTA, local add-on 0.2.91 on the Yellow)
- `camera.buienradar` live on page 1: the screen asked for the strip at once, the radar showed as a rounded square
in the icon's place on the dark tile (`capture_ui.py`), and every 15 s the screen loaded again. The radar changes
every five minutes, so the app answered 304 in between, and ESPHome's `http_request` logged every one of those as
`HTTP Request failed; Code: 304` and raised its error flag (`online_image` then said "Download skipped"). The app
now serves a live strip whole every time, never a 304 (about 9 KB for one tile, 50 KB for six, on the LAN), and
without the ETag `online_image` warned "No header with name etag found" and "... last-modified ..." at every load,
so every picture now carries both headers. No "took a long time" warning during the loads.
- The alert with `action: persistent_notification.create` reached both bench screens; the button's report
(`esphome.screen_alert`, `action: ok`, sent the way the screen sends it) made the notification once, and the add-on
logged `Alert button on guition-wallbox: persistent_notification.create performed`.
## Builds
`tools/check.sh`: Python 516 tests, C++ tests, packages, icons, translations (9 languages, 1431 texts), editor tests
(106), types and build. `tools/check.sh --firmware` with ESPHome 2026.9.0: CYD 1,633,728 B, 89.0 % of the update slot
(832 B more than 0.2.90); Guition 2,058,368 B.