Show a spinner while a screen starts and while a camera loads, bring a camera's first picture sooner, and round the alert's camera picture (0.2.87)
Starting screen: until the first layout, render() keeps the status text
("Connecting to Home Assistant", "Waiting for ESP Screens") and a spinner
as one block in the middle of the page; the first layout deletes both.
The room label stays empty meanwhile, and ROOM_NAME defaults to "".
Camera: the full screen shows a spinner instead of "Loading image". The
screen asks when the camera opens and loads a link as soon as it comes
(camera_load, never under a finger) instead of waiting for the 250 ms
tick twice, and online_image reads 16 KB per loop instead of 4 KB. On the
bench Guition with the EZVIZ camera: cold 5.8 -> 4.55 s, warm 3.4 -> 2.06 s.
Alert picture: the image takes the frame's radius (18, the alert card's);
LVGL's software renderer clips it row by row, without a layer.
One spinner_create() makes the busy sheet's, the starting screen's and the
camera's spinner; theme role CAMERA_TRACK colours the camera's ring. The
README gets a row of camera pictures; docs/CAMERA.md, RELEASING.md and
TEST_RESULTS_0287.md describe the measurements.
Checked with tools/check.sh (473 Python tests, 19/19 C++, editor) and
--firmware: CYD 1,678,816 B (91.5 %, +1,312 B), Guition 2,055,280 B
(25.3 %). Host renders of both boards in both looks; four OTA builds on
the bench Guition Wallbox, which runs this firmware.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+17
-7
@@ -3,11 +3,13 @@
|
||||
App 0.2.66 with firmware 0.2.57 shows camera images on a Guition 4848S040:
|
||||
|
||||
- **A camera tile.** Add a `camera.*` or `image.*` entity as a tile. A tap opens the image full
|
||||
screen, with the round back key at the top left like every card. The image is refreshed every
|
||||
four seconds while it is open. It is not video: ESPHome has no video decoder.
|
||||
screen, with the round back key at the top left like every card, and a spinner until the first
|
||||
image is there (firmware 0.2.73). The image is refreshed every four seconds while it is open. It
|
||||
is not video: ESPHome has no video decoder.
|
||||
- **An alert with a picture.** Add `camera: camera.front_door` to the `esp_screens_show_alert`
|
||||
event. The card shows the picture of that moment across its top (it stays that picture); a tap
|
||||
on it opens the camera full screen over the alert, and Back returns to the alert.
|
||||
event. The card shows the picture of that moment across its top (it stays that picture), with
|
||||
the card's round corners (firmware 0.2.73); a tap on it opens the camera full screen over the
|
||||
alert, and Back returns to the alert.
|
||||
|
||||
The CYD has no memory for images (a 320×180 image needs 115 KB in one piece, the CYD's largest
|
||||
free block is about 45 KB). It shows the alert without the picture, and the editor doesn't offer
|
||||
@@ -70,9 +72,10 @@ screen's made the picture change after 1.5 s one time and 6 s the next.) A slow
|
||||
images older, never the screen slower, and nothing queues up. Nobody loading means nothing fetched.
|
||||
A link that nobody loads for two minutes stops working; an alert's picture stays for half an hour.
|
||||
|
||||
**Why BMP.** ESPHome decodes a BMP piece by piece while it downloads (4 KB per round of its main
|
||||
loop). A JPEG of the full screen took 0.6 s in one piece on the Guition, during which the screen
|
||||
missed taps. A full-screen BMP is about 390 KB; on the LAN that takes one to three seconds.
|
||||
**Why BMP.** ESPHome decodes a BMP piece by piece while it downloads (16 KB per round of its main
|
||||
loop since firmware 0.2.73, 4 KB before). A JPEG of the full screen took 0.6 s in one piece on the
|
||||
Guition, during which the screen missed taps. A full-screen BMP is about 390 KB; on the bench
|
||||
Guition it comes in about 1.8 s (2.8 s with 4 KB).
|
||||
|
||||
## Network
|
||||
|
||||
@@ -91,6 +94,13 @@ missed taps. A full-screen BMP is about 390 KB; on the LAN that takes one to thr
|
||||
- The internal heap stays level while a camera refreshes: 77.2 KB free after 91 images in six minutes,
|
||||
and PSRAM unchanged.
|
||||
- Standby, **Back to page 1** and a new layout close the camera; nothing loads in standby.
|
||||
- The first image (firmware 0.2.73, measured 2026-09-19 with an EZVIZ camera): the screen asks when
|
||||
the camera opens and loads the link as soon as it comes. About 2 s when the app still has the
|
||||
camera's last snapshot (it keeps one for 30 s after the last load), 4.5 s when it must ask Home
|
||||
Assistant first, of which 2.4 s is the camera's own snapshot. Firmware 0.2.72 took 3.4 s and
|
||||
5.8 s. Details in docs/TEST_RESULTS_0287.md.
|
||||
- `power_save_mode: none` under `wifi:` (the YAML ESP Screens writes has it since app 0.2.24) keeps
|
||||
the screen from waiting on its Wi-Fi now and then while an image comes in.
|
||||
|
||||
## For developers
|
||||
|
||||
|
||||
@@ -219,6 +219,32 @@ 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.87 / firmware 0.2.73
|
||||
|
||||
Firmware only, plus README pictures and docs/CAMERA.md. No protocol, storage, preference or key change: an older app
|
||||
drives this firmware as before, and the camera messages are the same; only when the screen sends and loads changed.
|
||||
|
||||
- Starting screen: while `!model.configured`, `render()` calls `boot_status()`, which keeps a text in `watch_font`
|
||||
(paint `ink`) and a spinner as one block in the middle of the page, at the room label's index in the drawing order;
|
||||
the room label is empty meanwhile. The first layout deletes the block (`boot_panel`), spinner and all.
|
||||
`ROOM_NAME` defaults to "" (was "Choose tiles in HA").
|
||||
- `spinner_create(parent, size, arc)` makes every spinner: the busy sheet (30/20 px, unchanged), the starting screen
|
||||
and the camera (48/32 px).
|
||||
- Camera full screen: `camera_open()` asks at once (awake and fresh); `camera_answer()` loads a new link at once
|
||||
through `camera_load()`, which like `camera_tick()` never starts under a finger. `camera_tick()` still does the
|
||||
4 s refresh and the retries. `camera_spinner` turns until the first picture or a note; its ring is the new theme
|
||||
role `CAMERA_TRACK` (0x393D42 in both looks). "Loading image" is gone.
|
||||
- Guition `online_image` `buffer_size` 16384 (was 4096) for `camera_image` and `alert_image`: the full picture in
|
||||
about 1.8 s instead of 2.8 s on the bench; without Wi-Fi power save no loop over 50 ms. 32 KB held a loop 0.1 s.
|
||||
- Alert picture: `alert_image_frame` has the alert card's radius (18) and `camera_show()` gives the alert's picture the
|
||||
frame's radius. LVGL 9.5 clips an image to its own radius row by row (`radius_only` in `lv_draw_sw_img.c`) without a
|
||||
layer, so `clip_corner` stays out (`tests/test_layer_free.py`). About 5 % more render time for the picture on the
|
||||
host, only when it is drawn.
|
||||
- Tests: `test_camera` (the first picture waits for no tick), `test_header_bar` (the starting screen;
|
||||
`render_header()` anywhere in `render()`), `test_theme` (the spinner paint in `spinner_create()`).
|
||||
|
||||
Details: docs/TEST_RESULTS_0287.md.
|
||||
|
||||
### Compatibility 0.2.85 / firmware 0.2.71
|
||||
|
||||
Tile colours as Home Assistant draws them. No protocol, storage or editor change: the state messages are the same as
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
# Test results app 0.2.87 / firmware 0.2.73 (2026-09-19)
|
||||
|
||||
After the README picture refresh Max asked for four things:
|
||||
|
||||
- cameras in the README as tiles of their own ("zodat mensen snappen dat camera niet alleen tijdens alert is");
|
||||
- the alert card's rounded corners on its camera picture, if that stays light;
|
||||
- the spinner instead of "Loading image" on a camera that opens, and a look at why its first picture takes so long;
|
||||
- a nicer starting screen: the text in the middle, a spinner under it.
|
||||
|
||||
Performance comes first for him, and the add-on often runs on a Raspberry Pi 4 class machine (his Home Assistant
|
||||
Yellow is one), so work moved to the add-on is not free either. Everything below stays on ESPHome's and LVGL's own
|
||||
means: `online_image`'s `buffer_size`, LVGL's spinner and image radius, no ESP-IDF or lwIP settings.
|
||||
|
||||
## Round corners on the alert's picture
|
||||
|
||||
### Three ways to round the picture
|
||||
|
||||
- **`clip_corner` on the frame.** LVGL 9.5 then draws the frame and its picture into a layer of the frame's size to
|
||||
clip them (392 × 220 ARGB8888, about 345 KB, on every redraw). The project keeps layers out:
|
||||
`tests/test_layer_free.py` refuses `clip_corner` (see the CYD memory research of 0.2.50).
|
||||
- **Corners baked in by the add-on**, as for the media covers. The screen would copy a square picture as it does now,
|
||||
but the add-on would have to know the exact colour behind each corner: the alert's swatch in the light or the dark
|
||||
look, which only `theme.h` on the screen knows, and it would have to make the picture again (Pillow, on a Pi) when
|
||||
the look changes while the alert is up. A wrong guess shows as coloured corners.
|
||||
- **LVGL's own image radius (chosen).** `lv_image` passes its `radius` style to the draw as `clip_radius`, and the
|
||||
software renderer's `radius_only()` (`lv_draw_sw_img.c`) blends the picture row by row with a mask of one row
|
||||
(392 bytes). Rows between the corners come back from the mask as fully covered and are copied as before; only the
|
||||
rows of the corners are blended through the mask. No layer and no allocation beyond that row.
|
||||
`LV_DRAW_SW_COMPLEX` is 1 in ESPHome's `lv_conf.h`, so this path is compiled in; rounded rectangles already use it.
|
||||
|
||||
### What changed
|
||||
|
||||
- `packages/boards/guition-4848s040.yaml`: `alert_image_frame` has radius 18, the radius of the alert card
|
||||
(`packages/core.yaml`), instead of 0, so the frame behind a picture that doesn't fill it is rounded too.
|
||||
- `components/smart_display/runtime_tiles.h`: `camera_show(..., radius)` gives the picture that radius when it
|
||||
creates it; `camera_loaded()` passes the frame's radius for the alert's picture. The camera full screen and the
|
||||
media cover pass none and stay square.
|
||||
- README.md: a camera row under "On the screen" (`guition-camera-tiles.png` new: a page with a camera, a live camera
|
||||
and a doorbell's last ring; the camera full screen; the alert with its round picture). README_EXTENDED.md: a
|
||||
Cameras bullet under "What you can configure" and the tiles picture beside "With a camera picture".
|
||||
|
||||
### Measured (host build on the Mac, `.esphome/readme-render/readme-0919/time_radius.py`)
|
||||
|
||||
The render time of the picture's frame after invalidating it (LVGL's render events minus the flushes to the SDL
|
||||
window, which are the same for both), 40 redraws per run, four runs alternating between the two radii:
|
||||
|
||||
| Radius | Fastest | Median of the runs' medians |
|
||||
|---|---|---|
|
||||
| 0 (square, as before) | 1269 µs | 1340 µs |
|
||||
| 18 (the card's) | 1352 µs | 1413 µs |
|
||||
|
||||
About 5 % more, and only when the picture is drawn: when the alert appears, when the camera full screen closes over
|
||||
it, when something next to it is redrawn. A picture that stays up costs nothing afterwards. These are host numbers;
|
||||
the Guition is slower in absolute terms, and per row the extra work stays one 392-byte memset and one blend call.
|
||||
|
||||
## The starting screen
|
||||
|
||||
Until the first layout the top bar read "Connecting to Home Assistant..." and then "Waiting for ESP Screens..." where
|
||||
the screen's name goes (0.2.43). Now `render()` calls `boot_status()`, which puts the text in the middle of the page in
|
||||
the name's font (`watch_font`, 27 px Guition, 18 px CYD) with a spinner under it (48/32 px, 24/16 px apart), the two
|
||||
as one block in the middle; the name stays empty. The first layout deletes the block, spinner and all, so nothing
|
||||
turns behind the tiles and no animation runs for the rest of the day. The block sits in the name's place in the
|
||||
drawing order, under the tiles, the cards and an alert (an alert can come before the tiles). It is drawn in the
|
||||
shared paints (`ink`, `spinner`), so Dark mode colours it like the rest. `ROOM_NAME`, the name in the profile until the
|
||||
first `render()`, is empty instead of "Choose tiles in HA", which showed for a moment at every start. The text has no
|
||||
"..." any more; the spinner says it is busy.
|
||||
|
||||
The three spinners of the firmware (a busy card, the starting screen, a camera that loads) come from one
|
||||
`spinner_create()` in `runtime_tiles.h`; the busy card's looks the same as before.
|
||||
|
||||
## A camera that opens
|
||||
|
||||
### The spinner
|
||||
|
||||
The camera full screen shows the spinner in the middle until the first picture is there, instead of "Loading image".
|
||||
Its ring uses the new theme role `CAMERA_TRACK` (0x393D42, the dark look's spinner ring), since the camera page is
|
||||
black in both looks. A note ("No image from this camera") takes the spinner's place; the first picture ends both.
|
||||
|
||||
### Where the wait went
|
||||
|
||||
A camera opened full screen on the bench Guition (fw 0.2.72, 4 KB chunks), timed from the screen's own log
|
||||
(`.esphome/camera-speed/timeline.py`), with Max's EZVIZ camera `camera.max`:
|
||||
|
||||
1. 0.2-0.35 s until the screen asked: `camera_tick()`, every 250 ms, sent the request, not `camera_open()`.
|
||||
2. 2.6 s until the link came: Home Assistant's snapshot of the EZVIZ takes 2.4 s (the camera itself; measured the
|
||||
same in 0.2.66); the add-on's encoding is small next to it (a 1080p JPEG to the 480 × 270 BMP takes 10 ms on the
|
||||
Mac, several times that on a Pi 4 class machine).
|
||||
3. Up to 0.25 s until the load started: the next tick again.
|
||||
4. 2.8 s download: 388,854 bytes at 136 KB/s.
|
||||
|
||||
Step 4 is ESPHome's `online_image`: every round of the main loop it reads one `buffer_size` (then 4 KB) from the
|
||||
connection and decodes it, and `esp_http_client_read` waits until that chunk is complete. 4 KB a round came to about
|
||||
136 KB/s. A bigger chunk a round is faster (the table below), up to what one TCP connection with lwIP's 5,760-byte
|
||||
window gets over this Wi-Fi, roughly 300 KB/s. The same 2.7-3.3 s was measured in 0.2.66 from the Mac and from the
|
||||
Yellow: the limit was on the screen's side, not the sender's.
|
||||
|
||||
### What changed
|
||||
|
||||
- `camera_open()` asks right away; `camera_answer()` starts the load as soon as the link comes, as an alert's
|
||||
picture already did. Both through `camera_load()`, which, like the tick, never starts under a finger.
|
||||
`camera_tick()` keeps doing the refresh every 4 s and the retries.
|
||||
- `online_image` `buffer_size` 16384 instead of 4096, for both images.
|
||||
|
||||
### Measured on the bench Guition
|
||||
|
||||
`.esphome/camera-speed/speed.py`: per open, from the `preview_camera` call (the same path as a tap on the tile) until
|
||||
`online_image` reports the picture complete. Cold: 35 s after the last close, so the add-on has forgotten the snapshot
|
||||
and asks Home Assistant for a new one. Warm: 5 s after the last close, so the add-on answers with the snapshot it has
|
||||
and fetches the next one meanwhile. Three opens each (two cold for 32 KB); medians. The 0.2.73 builds went over the
|
||||
air from `.esphome/camera-speed/guition-wallbox*.yaml` (the Device Builder's API, OTA and Wi-Fi settings, the packages
|
||||
of this branch). One "cold" open of the power-save-off build found the snapshot still there and is left out.
|
||||
|
||||
| Build | Wi-Fi power save | Cold open | Warm open | Download | Longest loop wait during the opens |
|
||||
|---|---|---|---|---|---|
|
||||
| fw 0.2.72, 4 KB, tick | light (ESPHome's default) | 5.80 s | 3.40 s | 2.86 s (136 KB/s) | 131 ms (lvgl), 65 ms |
|
||||
| 0.2.73, 16 KB | light | 4.36 s | 2.09 s | 1.74-1.91 s (216-223 KB/s) | 58, 61, 77 ms |
|
||||
| 0.2.73, 16 KB | none | 4.24 s | 1.99 s | 1.73-1.80 s (223 KB/s) | none over 50 ms |
|
||||
| 0.2.73, 32 KB | none | 3.94 s | 1.62 s | 1.42-1.45 s (271 KB/s) | 101 ms |
|
||||
| 0.2.73, 16 KB (this release) | light | 4.55 s | 2.06 s | 1.75-1.90 s (208-218 KB/s) | 93, 107 ms |
|
||||
|
||||
The screen now asks 20-150 ms after the call (the API call and the log line's way back included) instead of
|
||||
210-345 ms, and loads about 150 ms after asking when the add-on has the snapshot. "Longest loop wait" is ESPHome's own
|
||||
"took a long time" warning (over 50 ms). With Wi-Fi power save, `online_image` now and then held a loop up to 107 ms
|
||||
(65 ms with 4 KB, next to a 131 ms LVGL frame in the same opens); without power save nothing went over 50 ms.
|
||||
|
||||
- **16 KB, not 32 KB.** 32 KB saves 0.3 s more, but a read of 32 KB waits for several TCP windows (lwIP's is 5,760
|
||||
bytes in ESPHome's build) and held a loop 101 ms even without power save: a tap in that moment waits as long.
|
||||
- **Wi-Fi power save.** The bench Guition's Device Builder YAML dates from before 0.2.24 and has no
|
||||
`power_save_mode: none`, so it runs ESPHome's default `light`: pings 103-191 ms (117 ms average) against 5-94 ms
|
||||
(21 ms) without it. With 16 KB chunks it made no difference to the download, but without it no loop waited over
|
||||
50 ms. The YAML the app writes has had `power_save_mode: none` since 0.2.24 (EASY_SETUP.md says why); the package
|
||||
doesn't set it, since it is the owner's Wi-Fi block.
|
||||
- **What remains** of a cold open is the camera: 2.4 s of the 4.5 s is Home Assistant's snapshot of the EZVIZ. Only
|
||||
fetching before the tap would hide it (when a camera's page shows, or when the screen wakes). That polls cameras
|
||||
nobody looks at, and a battery camera wakes for it; it is left for Max to decide.
|
||||
- Not changed: lwIP's TCP window and other ESP-IDF settings (`sdkconfig_options`). They would speed up the reads
|
||||
further but are memory and platform tuning outside ESPHome's defaults.
|
||||
|
||||
## Automated
|
||||
|
||||
- `tools/check.sh` (worktree, after the rebase on 0.2.86): Python 473 tests OK (two new: `test_camera` "the first
|
||||
picture waits for no tick", `test_header_bar` "a starting screen says what it waits for in the middle"; the
|
||||
position check of `render_header()` now looks in `render()` as a whole, and the spinner paint check in
|
||||
`spinner_create()`), C++ 19/19, packages, icons, editor tests, types, build and bundle PASS.
|
||||
- `tools/check.sh --firmware --baseline 1677504`: CYD 1,678,816 B of 1,835,008 B = 91.5 % (156,192 B free),
|
||||
+1,312 B against 0.2.86 (WARN: the 90-93 % band, under the 8 KB that needs Max's OK); Guition 2,055,280 B = 25.3 %.
|
||||
|
||||
## Renders
|
||||
|
||||
- Host builds from this branch (`.esphome/readme-render/readme-0919/boot_camera_host.py`, both boards, light and
|
||||
dark): the starting screen with both texts, page 1 after the layout without it, and on the Guition the camera page
|
||||
with the spinner, with the picture and with the note.
|
||||
- `guition-alert-camera.png` from the new firmware on the host: only the four corners of the picture changed
|
||||
(1,400 pixels of the 960 × 960 README image).
|
||||
- `guition-camera-tiles.png`: the camera tiles page of the README demo home (Home Assistant's icons and words), the
|
||||
front door camera idle and grey, the garden camera live and amber, since 0.2.85's colours.
|
||||
|
||||
## Hardware
|
||||
|
||||
- Bench Guition Wallbox (192.168.146.136): four builds of this branch over the air for the table above (its first
|
||||
row is the toggle-key session's 0.2.72 bench build that was on it); it runs this release's firmware (16 KB, its
|
||||
own `light` Wi-Fi). An LVGL snapshot during an open shows the spinner on the black camera page. After a
|
||||
restart the add-on's layout was there before my own API connection (6.8 s), and the snapshot showed "Loading tiles..."
|
||||
with the tiles in place: the starting screen had gone as it should.
|
||||
- The CYD: compiled only.
|
||||
|
||||
## Not tested
|
||||
|
||||
- The round alert picture on the panel: the add-on sends an alert picture only with the `esp_screens_show_alert`
|
||||
event, which goes to every screen, Studio 1 included. It shows with the next doorbell or camera alert.
|
||||
- The starting screen on the panels themselves: it is up for the few seconds between the start and ESP Screens'
|
||||
layout. Max sees it at the next update or restart.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Reference in New Issue
Block a user