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:
Max Gramser
2026-09-17 15:31:11 +02:00
parent 9567ab591a
commit d60a0f122b
31 changed files with 1847 additions and 117 deletions
+65
View File
@@ -0,0 +1,65 @@
// clang++ -std=c++17 -Wall -Wextra -Werror -I. tests/test_camera_view.cpp -o /tmp/test_camera_view && /tmp/test_camera_view
#include "../components/smart_display/camera_view.h"
#include <cassert>
using namespace camera_view;
int main() {
Feed feed;
assert(!feed.open() && !feed.should_ask(0) && !feed.should_load(0));
// Opening asks at once; a link that does not come is asked for again after ASK_AGAIN_MS.
feed.open("camera.front_door");
assert(feed.should_ask(1000));
feed.ask(1000);
assert(!feed.should_ask(1000 + ASK_AGAIN_MS - 1));
assert(feed.should_ask(1000 + ASK_AGAIN_MS));
// A link loads at once, then every REFRESH_MS from the start of the last load.
feed.link("http://192.168.1.2:8098/camera/abcdefghijklmnopqrstuvwx.jpg");
assert(!feed.should_ask(2000) && feed.should_load(2000));
feed.start(2000);
assert(!feed.should_load(2100) && !feed.should_ask(2100));
feed.finish(2300, true);
assert(feed.shown && !feed.should_load(2000 + REFRESH_MS - 1) && feed.should_load(2000 + REFRESH_MS));
// A load that ends near the next start still leaves GAP_MS for touch and drawing before the next one.
const uint32_t slow_end = 5000 + REFRESH_MS - 100;
feed.start(5000);
feed.finish(slow_end, true);
assert(!feed.should_load(5000 + REFRESH_MS) && !feed.should_load(slow_end + GAP_MS - 1) && feed.should_load(slow_end + GAP_MS));
// An ordinary load keeps the rhythm: the next start is REFRESH_MS after this one, however quickly it ended.
feed.start(20000);
feed.finish(20000 + REFRESH_MS / 2, true);
assert(!feed.should_load(20000 + REFRESH_MS - 1) && feed.should_load(20000 + REFRESH_MS));
// Failures keep the link until MAX_FAILURES in a row; then a new link is asked for, the image stays shown.
uint32_t now = 10000;
for (uint8_t i = 0; i < MAX_FAILURES; ++i) {
assert(!feed.url.empty());
feed.start(now);
feed.finish(now + 100, false);
now += REFRESH_MS;
}
assert(feed.url.empty() && feed.shown && feed.should_ask(now));
// An app without an image answers an empty link: nothing loads, and the view asks again later.
feed.ask(now);
feed.link("");
assert(feed.empty && !feed.should_load(now) && !feed.should_ask(now + 1) && feed.should_ask(now + ASK_AGAIN_MS));
// A success after a failure starts the count again.
feed.link("http://192.168.1.2:8098/camera/abcdefghijklmnopqrstuvwx.jpg");
feed.start(now); feed.finish(now + 10, false);
feed.start(now + REFRESH_MS); feed.finish(now + REFRESH_MS + 10, true);
assert(feed.failures == 0 && !feed.url.empty());
// Opening another camera forgets everything of the last one; millis() 0 still counts as a start.
feed.open("image.doorbell_snapshot");
assert(feed.url.empty() && !feed.shown && feed.should_ask(0));
feed.link("http://h/camera/x.jpg");
feed.start(0);
feed.finish(10, true);
assert(!feed.should_load(REFRESH_MS - 1) && feed.should_load(REFRESH_MS + 1));
return 0;
}