Files
homeassistant_espscreen/components/smart_display/camera_view.h
T
Max Gramser f13d8ad0ed A media card like a phone's now playing, with the album cover on a Guition (0.2.77)
The card behind a media tile shows the album cover, the title, the artist and
the album, a progress bar that runs while the track plays with the times at its
ends, round keys for previous, play or pause and next, and a volume row with a
mute key, a wide slider and the percentage. Keys the player lacks stay faded,
an off player shows one power key, a stream without a length shows no bar, and
the CYD shows the player's icon in the cover's place. A media tile over the
whole page is the same card under the tile's head. media_card.h holds the
geometry, tests/test_media_card.cpp checks every shape.

The cover comes the way camera images do: the screen asks with the size and
the colour behind the corners, the app fetches the picture from Home Assistant,
cuts it square, rounds the corners over that colour and serves it on port 8098;
the Guition loads it once and asks again only when the picture's mark in the
state changes. Firmware 0.2.64.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 17:04:44 +02:00

60 lines
3.3 KiB
C++

#pragma once
// A camera image full screen (firmware 0.2.57+, Guition): when to ask ESP Screen Manager for a link and when to load it
// again. Pure bookkeeping, free of LVGL and ESPHome, so tests/test_camera_view.cpp can check it on a PC; runtime_tiles.h
// draws the view and the board's online_image loads the link.
//
// The app answers `esphome.screen_camera` with a link to a BMP it keeps fresh (screen_manager/app/camera_feed.py).
// Loading shares the main loop with touch and drawing, so the view loads one image at a time and leaves time free after
// each. The rhythm is fixed rather than as fast as the Wi-Fi allows: a picture every four seconds, every time, reads as
// smoother than one after three seconds and the next after five. Never a queue: a slow load makes the next one later.
#include <cstdint>
#include <string>
namespace camera_view {
constexpr uint32_t REFRESH_MS = 4000; // from the start of one load to the start of the next: a steady rhythm
constexpr uint32_t GAP_MS = 800; // at least this long between the end of one load and the next
constexpr uint32_t ASK_AGAIN_MS = 10000; // a link that does not come (or an app without an image) is asked for again
constexpr uint8_t MAX_FAILURES = 3; // a link that fails this often in a row is old: ask for a new one
constexpr uint32_t PENDING_MS = 20000; // an alert's camera announced this long before the alert still belongs to it
struct Feed {
std::string entity, url;
uint32_t asked_at = 0, started_at = 0, finished_at = 0;
bool asked = false, loading = false, shown = false, empty = false; // empty: the app said it has no image
// once (firmware 0.2.64+): an album cover on the media card. It loads one time per link and stays; a new cover comes
// with a new link (the card asks again when Home Assistant's picture changes), never on a clock.
bool once = false, loaded = false; // loaded: this link's image is on screen
uint8_t failures = 0;
void open(const std::string &camera, bool one_load = false) { *this = Feed{}; entity = camera; once = one_load; }
bool open() const { return !entity.empty(); }
// A cover the app has none of (or an app that knows no covers) is asked for once: the card keeps its placeholder.
bool should_ask(uint32_t now) const {
if (once && empty) return false;
return open() && !loading && url.empty() && (!asked || now - asked_at >= ASK_AGAIN_MS);
}
void ask(uint32_t now) { asked = true; asked_at = now; }
// The app's answer: a link, or none (no image from Home Assistant, or a camera this screen may not show).
void link(const std::string &address) {
url = address;
empty = address.empty();
failures = 0;
started_at = finished_at = 0;
loaded = false;
}
bool should_load(uint32_t now) const {
if (!open() || loading || url.empty()) return false;
if (!started_at) return true;
if (once) return !loaded && now - finished_at >= GAP_MS; // a failed load is tried again, a loaded one stays
return now - started_at >= REFRESH_MS && now - finished_at >= GAP_MS;
}
void start(uint32_t now) { loading = true; started_at = now ? now : 1; }
void finish(uint32_t now, bool ok) {
loading = false;
finished_at = now;
if (ok) { shown = true; loaded = true; failures = 0; return; }
if (++failures >= MAX_FAILURES) { url.clear(); asked = false; failures = 0; }
}
};
} // namespace camera_view