306636434b
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>
116 lines
5.3 KiB
C++
116 lines
5.3 KiB
C++
#include "screen_text_en.h"
|
|
// 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));
|
|
|
|
// The live pictures of a page's camera tiles (firmware 0.2.77+) load at the tiles' own pace instead of REFRESH_MS.
|
|
Feed tiles;
|
|
tiles.open("camera.front_door,camera.garden", false, 15000);
|
|
tiles.ask(0);
|
|
tiles.link("http://h/camera/tiles.bmp");
|
|
tiles.start(100);
|
|
tiles.finish(400, true);
|
|
assert(!tiles.should_load(100 + 15000 - 1) && tiles.should_load(100 + 15000));
|
|
assert(tiles.every == 15000 && feed.every == REFRESH_MS);
|
|
// Without a picture (an empty link) they ask again at that pace, not every ASK_AGAIN_MS.
|
|
tiles.open("camera.front_door", false, 30000);
|
|
tiles.ask(0);
|
|
tiles.link("");
|
|
assert(tiles.empty && !tiles.should_ask(ASK_AGAIN_MS) && !tiles.should_ask(30000 - 1) && tiles.should_ask(30000));
|
|
|
|
// An album cover (firmware 0.2.64+) loads once per link: it stays on screen, never refreshed on a clock.
|
|
Feed cover;
|
|
cover.open("media_player.office", true);
|
|
assert(cover.once && cover.should_ask(0));
|
|
cover.ask(0);
|
|
cover.link("http://h/camera/cover.bmp");
|
|
assert(cover.should_load(100));
|
|
cover.start(100);
|
|
cover.finish(400, true);
|
|
assert(cover.loaded && cover.shown && !cover.should_load(400 + GAP_MS) && !cover.should_load(400 + 10 * REFRESH_MS) && !cover.should_ask(400 + ASK_AGAIN_MS));
|
|
// A failed load is tried again after the gap; three failures forget the link so the card asks for a new one.
|
|
cover.link("http://h/camera/cover2.bmp");
|
|
assert(!cover.loaded && cover.should_load(1000));
|
|
cover.start(1000); cover.finish(1100, false);
|
|
assert(!cover.should_load(1100 + GAP_MS - 1) && cover.should_load(1100 + GAP_MS));
|
|
cover.start(2000); cover.finish(2100, false);
|
|
cover.start(3000); cover.finish(3100, false);
|
|
assert(cover.url.empty() && cover.should_ask(3100));
|
|
// A new link (a new picture in Home Assistant) loads again, once.
|
|
cover.ask(3100);
|
|
cover.link("http://h/camera/cover3.bmp");
|
|
cover.start(3200); cover.finish(3500, true);
|
|
assert(cover.loaded && !cover.should_load(3500 + 10 * REFRESH_MS));
|
|
// A player without a picture (or an app from before covers) answers an empty link: asked once, then left alone.
|
|
Feed bare;
|
|
bare.open("media_player.radio", true);
|
|
bare.ask(0);
|
|
bare.link("");
|
|
assert(bare.empty && !bare.should_ask(ASK_AGAIN_MS) && !bare.should_ask(100 * ASK_AGAIN_MS) && !bare.should_load(ASK_AGAIN_MS));
|
|
// A camera feed keeps its clock: open() without the flag.
|
|
Feed live;
|
|
live.open("camera.front_door");
|
|
assert(!live.once);
|
|
return 0;
|
|
}
|