Show who is at the door: camera images on the Guition (0.2.66)
An alert from esp_screens_show_alert takes a `camera` (a camera.* or image.* entity): a Guition with firmware 0.2.57 shows that moment's picture across the top of the card, and a tap opens the camera full screen. A camera or image tile opens the same full-screen view, refreshed every four seconds at a steady pace. ESP Screen Manager fetches the snapshot with its own access, sizes it for the screen and serves a 24-bit BMP under a random, short-lived link on port 8098; the screen loads it with ESPHome's online_image and holds no Home Assistant token. BMP because ESPHome decodes it per chunk, where a full-screen JPEG held the loop 0.6 s. The CYD keeps its alerts without the picture and gets no camera tiles. The Guition also builds with ESPHome 2026.8+ again: backlight_fade.h derived from ESPHome's LEDC output, which 2026.8 made final, so the backlight now dims through ESPHome's own light transition. Tested on the bench boards and a local add-on on the Home Assistant Yellow; docs/TEST_RESULTS_0266.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# Camera images on a Guition
|
||||
|
||||
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.
|
||||
- **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.
|
||||
|
||||
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
|
||||
camera tiles for it.
|
||||
|
||||
## A doorbell
|
||||
|
||||
```yaml
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: event.front_door_ding
|
||||
actions:
|
||||
- event: esp_screens_show_alert
|
||||
event_data:
|
||||
title: Someone is at the door
|
||||
icon: doorbell
|
||||
camera: camera.front_door
|
||||
timeout: 60
|
||||
```
|
||||
|
||||
Any `camera.*` entity works, and so does an `image.*` entity, such as the snapshot a doorbell or
|
||||
motion integration keeps of its last event.
|
||||
|
||||
## How the image gets to the screen
|
||||
|
||||
The screen never talks to Home Assistant about images, and it never holds a Home Assistant token.
|
||||
|
||||
1. The screen asks ESP Screen Manager for a camera (the event `esphome.screen_camera`), or the app
|
||||
sends the picture of an alert by itself.
|
||||
2. The app fetches the snapshot from Home Assistant with its own access (the same pictures the
|
||||
Home Assistant frontend shows), and makes it exactly as large as the screen draws it: at most
|
||||
480×480 full screen, 392×220 on an alert card, proportions kept.
|
||||
3. It serves the result as an uncompressed 24-bit BMP on **port 8098** under a random link, and
|
||||
sends the link to the screen. ESPHome's `online_image` loads it.
|
||||
|
||||
While a camera is open, the screen loads its link every four seconds, one image at a time. The app
|
||||
serves the last snapshot at once and starts fetching the next one, so each load gets a picture one
|
||||
load younger: the picture changes at the screen's steady pace. (A fetch on its own clock next to the
|
||||
screen's made the picture change after 1.5 s one time and 6 s the next.) A slow camera makes the
|
||||
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.
|
||||
|
||||
## Network
|
||||
|
||||
- **Home Assistant OS:** the app publishes port 8098 on the Home Assistant host. Keep it at 8098 in
|
||||
the app's network settings; the screens need to reach Home Assistant's address on that port.
|
||||
- **Docker** (docs/DOCKER.md): the container uses the host network, so 8098 is open as is. When
|
||||
the screens reach the host under another address, set `SCREEN_CAMERA_URL`, for example
|
||||
`http://192.168.1.20:8098`.
|
||||
- The images travel unencrypted over the LAN, like the screens' other HTTP traffic. Links are
|
||||
random, short-lived, and only issued for a camera on that screen's tiles or in a recent alert.
|
||||
|
||||
## Memory and speed (Guition, measured 2026-09-17)
|
||||
|
||||
- Full screen: 480×270 RGB565 is 259 KB of PSRAM; the alert picture 172 KB. Both are freed when
|
||||
the camera or the alert closes. A new alert closes an open camera first.
|
||||
- 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.
|
||||
|
||||
## For developers
|
||||
|
||||
- `screen_manager/app/camera_feed.py`: fetching, sizing, links and the port.
|
||||
- `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.
|
||||
- `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).
|
||||
- `tests/test_camera.py`: the app side and the words both sides share.
|
||||
+7
-1
@@ -80,6 +80,10 @@ under **Settings → Devices & services** in Home Assistant, and then choose the
|
||||
ESPHome folder. `data/build` and `data/platformio` are caches.
|
||||
- **Claude:** **Install for Claude Code** is meant for the Claude Code app on Home Assistant
|
||||
OS. Use **Download for claude.ai** instead.
|
||||
- **Camera images (Guition, app 0.2.66+):** the screens load camera pictures from port **8098**
|
||||
of this host, on all its addresses, so keep that port open to the screens. The app uses Home
|
||||
Assistant's own LAN address; when the screens reach this host under another one, set
|
||||
`SCREEN_CAMERA_URL` (see below). The links are random and short-lived. [docs/CAMERA.md](CAMERA.md)
|
||||
|
||||
## Updating
|
||||
|
||||
@@ -96,8 +100,10 @@ After an update, ESP Screens shows per screen whether newer firmware is availabl
|
||||
|
||||
| Setting | What it does |
|
||||
| --- | --- |
|
||||
| `SCREEN_DEV: "1"` | Runs the app outside the Supervisor: it reads the token from `HA_TOKEN_FILE` and only listens on 127.0.0.1 |
|
||||
| `SCREEN_DEV: "1"` | Runs the app outside the Supervisor: it reads the token from `HA_TOKEN_FILE` and serves its page on 127.0.0.1 only (camera images still on port 8098 for the screens) |
|
||||
| `HA_API` | Home Assistant's address, ending in `/api` |
|
||||
| `HA_TOKEN_FILE` | The token file inside the container (the `ha_token` secret) |
|
||||
| `ESPHOME_CONFIG` | The ESPHome folder inside the container |
|
||||
| `SCREEN_DATA` | Layouts, update settings, and build caches |
|
||||
| `SCREEN_CAMERA_URL` | Optional: where screens load camera images, such as `http://192.168.1.20:8098`; by default Home Assistant's own LAN address |
|
||||
| `SCREEN_CAMERA_PORT` | Optional: the camera image port instead of 8098 |
|
||||
|
||||
+4
-4
@@ -44,10 +44,10 @@ four pages; navigation disappears at six or fewer. The current interface has a
|
||||
light gray background, white cards, and colored domain icons. From 0.2.10, you
|
||||
can choose a pastel background with dark text per tile. Standby starts
|
||||
after ten minutes without touch by default and is adjustable in the management page.
|
||||
From 0.2.13, the backlight dims via the LEDC hardware fader (`backlight_fade.h`,
|
||||
1.5 s to standby, 80 ms to wake): the full LVGL redraw on standby no longer
|
||||
interrupts the transition. ESPHome's light state is synced after the fade,
|
||||
so the entity in HA and later transitions stay correct.
|
||||
The backlight dims through ESPHome's own light transition (1.5 s to standby, 80 ms to
|
||||
wake). Firmware 0.2.13-0.2.56 used the LEDC hardware fader (`backlight_fade.h`) instead;
|
||||
0.2.57 went back to ESPHome's transition, because that fader reached into ESPHome's LEDC
|
||||
output, which ESPHome 2026.8 no longer allows.
|
||||
|
||||
The existing tile actions, climate control, and vacuum card are preserved.
|
||||
Every card type works in all twenty positions.
|
||||
|
||||
@@ -176,6 +176,51 @@ icons sit off-center in the browser); run `generate_packages.py` afterward.
|
||||
The `MDI_GLYPH_*` substitutions are retired: `TILEn_ICON` in manual
|
||||
profiles must come from the set. No changed preferences or keys.
|
||||
|
||||
### Compatibility 0.2.66 / firmware 0.2.57
|
||||
|
||||
Camera images on the Guition (docs/CAMERA.md). Storage, keys, preferences and the settings block are unchanged; the
|
||||
layout message is unchanged apart from two tile domains, and the `show_alert` action keeps its seven fields.
|
||||
|
||||
- New tile domains `camera` and `image` in `core.DOMAINS` and the firmware's `valid_entity()`. `min_firmware()` asks
|
||||
0.2.57 for them, and `Manager.save` refuses them for a screen whose board is not in `camera_feed.BOXES` (the CYD)
|
||||
before it asks for firmware, so a CYD never hears "update first". The editor hides them for other boards; the top
|
||||
bar refuses them (`header_entity`). An `image` tile carries `x.last` like a scene. Older firmware never gets these
|
||||
tiles because of `min_firmware`.
|
||||
- New protocol, all optional: the screen fires `esphome.screen_camera` {inbox, entity} when a camera opens; the app
|
||||
answers through `screen_message` with op `camera` {t: `full`|`alert`, e, u}. An empty `u` means no image. For an
|
||||
alert the app sends op `camera` with `t: alert` and an empty `u` to screens that can show it (`camera_feed.can_show`:
|
||||
a Guition on 0.2.57+) BEFORE `show_alert`, so the card opens with room, and the link after it. `show_alert` has no
|
||||
camera field on purpose: Home Assistant requires every field of an ESPHome action, so a new field would break every
|
||||
automation calling `esphome.<screen>_show_alert` directly. The camera is only a field of the
|
||||
`esp_screens_show_alert` event (`core.alert_camera`, `ALERT_CAMERA_FIELD`).
|
||||
- The app publishes port 8098 (`config.yaml` `ports`) and serves `GET /camera/<token>.bmp` without the ingress guard.
|
||||
Tokens are 24 random URL-safe characters, per camera and size; live links expire 120 s after their last use, an
|
||||
alert's still after 30 min; at most 64 links. Only a camera on the screen's layout or in an alert of the last
|
||||
30 min gets one. The address is Home Assistant's default adapter from the `network` websocket command, else the
|
||||
host of `network/url`; `SCREEN_CAMERA_URL` / `SCREEN_CAMERA_PORT` override it (Docker). A port that cannot be
|
||||
bound logs an error and leaves the rest of the app running.
|
||||
- Images are 24-bit BMP, not JPEG: ESPHome 2026.6.2 decodes a JPEG in one loop call (623 ms for 480x270 on the
|
||||
Guition) and a BMP per downloaded chunk (`buffer_size: 4096`). Boxes: full 480x480, alert 392x220, proportions kept.
|
||||
- Fetching follows the screen: serving a live link starts the next snapshot fetch (`CameraFeed.frame(fresh=True)`),
|
||||
one at a time per camera, so the picture changes at the screen's pace. The screen loads every 4 s start to start with
|
||||
at least 800 ms free after a load (`camera_view.h`), never under a finger and never in standby. An alert fetches its
|
||||
own snapshot (`now=True`), never one kept from an earlier view. A failing camera is asked again after 5-30 s.
|
||||
- Firmware (Guition profile only): `http_request` (4 s timeout, no TLS), two top-level `online_image`s
|
||||
(`camera_image`, `alert_image`, the form 2026.6.2 knows; 2026.7+ reads it with a deprecation note), a hidden LVGL
|
||||
`image` seed so `LV_USE_IMAGE` is compiled, the `alert_image_frame` in the alert card, `preview_camera` for
|
||||
diagnostics and renders, theme roles `CAMERA_PAGE/INK/NOTE` and paint `camera`. The load hooks only call `set_url()`
|
||||
for a new link, since it forgets the ETag. `runtime_tiles.h` builds the full view on `lv_layer_top()`; closing
|
||||
frees the image on the next tick. `LV_USE_IMAGE` guards the drawing, so the CYD (no image widget) still compiles;
|
||||
the CYD profile binds no hooks and never opens a camera.
|
||||
- Swipes, the settings hold and Back to page 1 treat an open camera like an open card; `close_cards` (standby,
|
||||
Back to page 1, a new layout) closes it, and a new alert closes it first.
|
||||
- `guition_diagnostics.h` snapshots composite the top layer, so `capture_ui.py` shows alerts and the camera.
|
||||
- `backlight_fade.h` is gone. It derived from `esphome::ledc::LEDCOutput` to read its channel and bit depth, and
|
||||
ESPHome 2026.8 declared that class `final`, so the Guition package no longer compiled in ESPHome Device Builder
|
||||
2026.8+ (the app's own ESPHome 2026.6.2 still built it). `set_backlight` now only calls ESPHome's light transition
|
||||
(1.5 s to standby, 80 ms to wake); a new call interrupts a running transition as ESPHome does. Both profiles and the
|
||||
camera code were compiled with ESPHome 2026.6.2 and 2026.8.1 for this release.
|
||||
|
||||
### Compatibility 0.2.65 / firmware 0.2.56
|
||||
|
||||
Both board profiles and the editor page; the app raises `FIRMWARE_VERSION`. Storage, tile protocol, preferences, keys
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# Test results app 0.2.66 / firmware 0.2.57 (2026-09-17)
|
||||
|
||||
Camera images on the Guition, asked for by Max: an alert that shows who is at the door, and a camera tile that opens a
|
||||
refreshed image full screen. Plus the Guition building again with ESPHome 2026.8+. See CHANGELOG 0.2.66, docs/CAMERA.md
|
||||
and the compatibility note in docs/RELEASING.md. Built on 0.2.65 (`9567ab5`).
|
||||
|
||||
## Design decisions, with the measurement behind each
|
||||
|
||||
- **The add-on serves the image, the screen holds no token.** Home Assistant's camera access token changes every five
|
||||
minutes and a long-lived token on every screen opens all of Home Assistant. The app fetches with its own access and
|
||||
serves a random, short-lived link on port 8098 (Max chose a LAN port over a file in `/config/www`).
|
||||
- **BMP, not JPEG.** On the bench Guition (Wi-Fi -70 dBm) ESPHome 2026.6.2 decoded a 480x270 JPEG in one loop call:
|
||||
`online_image took a long time for an operation (623 ms)`, long enough to lose a tap. A 24-bit BMP is decoded per
|
||||
4 KB as it downloads; the longest single loop operation over a six-minute soak was 353 ms, typically under 170 ms.
|
||||
The full-screen BMP is 388,854 bytes and downloads in 2.7-3.3 s over that Wi-Fi (the same from the Mac and from the
|
||||
Home Assistant Yellow, so the screen's Wi-Fi is the limit).
|
||||
- **A steady rhythm.** Max saw the picture change after 3, 5, 7 s. The EZVIZ camera answered every snapshot in a
|
||||
steady 2.4 s (10 fetches: 2.36-2.56 s), so the cause was the app fetching on its own 2.6 s clock next to the screen's
|
||||
3.75 s. Now each load starts the next fetch and the screen loads every 4 s start to start: pulling every 4 s against
|
||||
the real camera gave camera timestamps 5, 4, 4, 4, 4, 3, 4, 5, 3, 5 s apart (the camera stamps whole seconds).
|
||||
- **The alert keeps the picture of its moment** (Max's choice); a tap opens the camera now. An alert fetches its own
|
||||
snapshot, never one kept from a view in the last 30 s (found in review).
|
||||
- **Alert design** (Max): the picture across the top of the card, 392x220, title and subtitle under it.
|
||||
- **CYD: no images.** 320x180 RGB565 needs 115 KB in one piece; the CYD's largest free block is 41-53 KB.
|
||||
|
||||
## Automated
|
||||
|
||||
- Python (`.venv-portal`, with Pillow 12.2.0 as in the ESPHome image): 337 tests OK (334 before the review fixes).
|
||||
New `tests/test_camera.py` (23): the rules (domains, firmware, board, header, alert field), BMP encoding of JPEG, PNG
|
||||
with alpha, GIF and portrait snapshots (24 bits, no compression, exact size), links (304, expiry, bounds, stills),
|
||||
fetching per load, a failing camera's pause, an alert's own snapshot, the HTTP port, the broadcast order (announce,
|
||||
alert, link) with and without an image, requests a screen may and may not make, camera tiles only on a Guition, the
|
||||
words firmware and app share, and the profile's `set_url` guard. `test_wake_sleep.py` counts `preview_camera` among
|
||||
the writers of `last_use_ms`; `tile_icons` knows the cctv default.
|
||||
- C++ (`clang++ -std=c++17 -Wall -Wextra -Werror -I.`): 17/17 PASS, new `tests/test_camera_view.cpp` (asking, loading
|
||||
rhythm, the gap after a slow load, failures, an empty link).
|
||||
- `generate_packages.py --check` green. No token or Home Assistant URL in tracked files.
|
||||
- Code review (high) found three issues, all fixed with tests: the alert picture could be a snapshot kept from a view
|
||||
up to 30 s earlier; a busy camera port stopped the whole app; `set_url()` on every load threw away the ETag, so an
|
||||
unchanged picture was downloaded again instead of answering 304.
|
||||
|
||||
## Builds
|
||||
|
||||
Easy profiles with the generated packages and local components (ESPHome 2026.6.2, the app's own):
|
||||
|
||||
| Profile | RAM | Flash | vs 0.2.65 |
|
||||
|---|---|---|---|
|
||||
| CYD | 23.8% (77,940 B) | 77.6% (1,423,815 B) | +368 B RAM, +2.6 KB flash |
|
||||
| Guition | 25.5% (83,516 B) | 20.5% (1,664,915 B) | +2.6 KB RAM, +140 KB flash (http_request, online_image, BMP decoder) |
|
||||
|
||||
(0.2.65's numbers came from check profiles with other device names, a few bytes apart.)
|
||||
|
||||
ESPHome 2026.8.1 (ESPHome Device Builder on Max's Home Assistant), separate build folders: both packages compile.
|
||||
Before this release the Guition package failed there: `backlight_fade.h` derived from `esphome::ledc::LEDCOutput`,
|
||||
which ESPHome 2026.8 made `final`. With Max's agreement the hardware fader is gone and the backlight uses ESPHome's own
|
||||
transition. Warnings left for later: top-level `online_image:` is removed in ESPHome 2027.1.0 (use
|
||||
`image: - platform: online_image` once min_version is 2026.7+), and `platformio_options: build_flags` in 2026.12.0.
|
||||
|
||||
## Hardware (bench, Max's home Home Assistant, HA 2026.9.2 on a Home Assistant Yellow)
|
||||
|
||||
The app ran as a local add-on built from this tree on the Yellow (store add-on stopped meanwhile); both screens OTA.
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| Guition boots 0.2.57 (compiled 15:22:34), inbox Synced, `sensor.guition_wallbox_screen_firmware` 0.2.57 | yes |
|
||||
| `diagnostics/run_ui_test.py` on the Guition | PASS: 10 page checks and 50 overlay render cycles |
|
||||
| Sleep and Wake from Home Assistant twice, ESPHome transition | no long operations logged; Max watched: "dimmen gaat mooi" |
|
||||
| Alert with `camera: camera.max` through the real event | picture after 1.8-1.9 s (258,774 B, 391x220) |
|
||||
| An `image.*` entity as the alert camera | loads (392x98: that entity's own 400x100 picture) |
|
||||
| Alert without camera, Guition and CYD | the old card, unchanged |
|
||||
| Tap on the alert picture (Max) | camera full screen; Back returns to the alert |
|
||||
| Camera tile tapped (Max), refresh | seven images, 2.73-2.79 s downloads each, Back closes it |
|
||||
| Camera open, then an alert with a camera | camera closes (PSRAM back to 6.09 MB), alert picture loads |
|
||||
| Camera opened over an alert | both images held: PSRAM 5.66 MB free; close keeps the alert |
|
||||
| Six-minute soak, 91 images | internal heap level at 77.2 KB free, PSRAM unchanged, no errors |
|
||||
| Five real alerts in a row (Max testing) | all shown, pictures 0.9-1.9 s, no failed download |
|
||||
| First image after the add-on started | one `ESP_ERR_HTTP_CONNECT`; the alert picture now retries three times |
|
||||
| Home Assistant Yellow while a camera refreshes | add-on CPU 0-1.5 %, 51 MB |
|
||||
| CYD boots 0.2.57 (compiled 15:23:49), inbox Synced | yes |
|
||||
| Alert with a camera on the CYD | shown without the picture, OK closes it |
|
||||
| Camera tile saved for the CYD | refused: "Camera images need a Guition screen." |
|
||||
|
||||
Internal heap from the screens' own sensors, five minutes after the release build booted (the Guition's window held the
|
||||
UI self test, Sleep/Wake twice, a camera view and an alert with a picture; the CYD's a sync and an alert):
|
||||
|
||||
| Screen | Free | Largest block | Lowest since boot | Before (earlier today) |
|
||||
|---|---|---|---|---|
|
||||
| Guition 0.2.57 | 80,108 B | 34,816 B | 56,548 B | 0.2.55 after hours of use: 83,172 / 40,960 / 72,472 B |
|
||||
| CYD 0.2.57 | 114,024 B | 57,344 B | 104,884 B | 0.2.54 after hours of use: 104,424 / 49,152 / 85,600 B |
|
||||
|
||||
PSRAM on the Guition stays near 6.1 MB free with nothing open, 5.9 MB with a camera and 5.7 MB with a camera over an
|
||||
alert. The Guition's lowest point is lower than this morning's after a UI self test plus camera and alert in the same
|
||||
five minutes; the soak kept the heap level, so it is headroom used at once, not a leak.
|
||||
|
||||
Not tested: the Docker route (no Linux Docker host here); a doorbell integration (Max has none, `camera.max` stood in).
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 153 KiB |
Reference in New Issue
Block a user