Skip to main content

ryu_hardware/
feed.rs

1//! The dashboard *feed* seam: the minimal contract the hardware device-dashboard
2//! renderer + the display-nudge loop need from the Home-dashboards capability,
3//! inverted so this kernel crate has ZERO compile-time dependency on
4//! `ryu_dashboards`.
5//!
6//! ## Why this exists
7//!
8//! A hardware device (TRMNL model) renders a Home dashboard onto its e-ink / LCD
9//! panel: the device polls Core, Core renders the device's bound dashboard to a
10//! panel image. That render reads dashboard widgets + rasterizes them — a
11//! *dashboards* concern that had been welded into `ryu_hardware::api` as a direct
12//! `ryu_dashboards::DashboardEngine` field. Dashboards is now a swappable,
13//! out-of-process app; a kernel crate cannot hard-link it.
14//!
15//! [`DashboardFeed`] is the inversion. It exposes ONLY what the renderer + nudge
16//! loop need (render a device's dashboard, read/write its config + binding,
17//! subscribe to change events), in terms of plain owned types — never a
18//! `ryu_dashboards` type. Core provides the impl:
19//!
20//! - in-process (`InProcDashboardFeed`) — wraps the in-process engine;
21//! - out-of-process (`dashboards_client::DashboardsClient`) — proxies to the
22//!   `ryu-dashboards` sidecar over loopback (+ its SSE stream for change events).
23//!
24//! The device *auth* (per-device Bearer verification against the registry) stays
25//! Core-side; only the render + data cross this seam.
26
27use serde_json::Value;
28
29/// A device's panel geometry echoed in the display manifest + config `screen`
30/// object. Computed by the feed impl from the device class + prefs (so the panel
31/// constants live with the renderer, not here), and carried back as plain data.
32#[derive(Clone, Debug)]
33pub struct ScreenProfile {
34    pub w: u32,
35    pub h: u32,
36    pub bit_depth: u8,
37    /// Wire palette string (`"mono"` / `"rgba"` / `"rgb565"`).
38    pub palette: String,
39    pub rotation: u16,
40}
41
42/// The display-manifest facts for a device: the content revision (so the device
43/// can skip an unchanged re-download), its poll interval, and its panel geometry.
44#[derive(Clone, Debug)]
45pub struct DeviceManifest {
46    pub rev: String,
47    pub refresh_rate: u32,
48    pub screen: ScreenProfile,
49}
50
51/// A rendered device image plus the metadata the display endpoint returns.
52#[derive(Clone, Debug)]
53pub struct RenderedImage {
54    pub bytes: Vec<u8>,
55    /// `image/png` or `application/octet-stream` (packed mono / rgb565).
56    pub content_type: String,
57    /// Content hash the device caches against (`?rev=`).
58    pub rev: String,
59}
60
61/// The outcome of a device-dashboard write.
62#[derive(Clone, Debug)]
63pub struct SetDeviceResult {
64    pub dashboard_id: String,
65    pub refresh_rate: u32,
66}
67
68/// A device → dashboard binding (the nudge loop's work list).
69#[derive(Clone, Debug)]
70pub struct DeviceBinding {
71    pub device_id: String,
72    pub dashboard_id: String,
73}
74
75/// The dashboards capability, seen through the narrow hole the hardware surface
76/// needs. Implemented by Core (in-process or sidecar-backed).
77#[async_trait::async_trait]
78pub trait DashboardFeed: Send + Sync {
79    /// The display manifest facts for a device (renders internally to compute the
80    /// current `rev`). `device_type` is the RHP wire string; `prefs` the device's
81    /// saved prefs (may carry a `screen` override).
82    async fn device_manifest(
83        &self,
84        device_id: &str,
85        device_name: &str,
86        device_type: &str,
87        prefs: &Value,
88    ) -> Result<DeviceManifest, String>;
89
90    /// Render a device's dashboard image. Returns `None` when `known_rev` still
91    /// matches the freshly-rendered content (the caller answers `304`).
92    async fn device_image(
93        &self,
94        device_id: &str,
95        device_name: &str,
96        device_type: &str,
97        prefs: &Value,
98        known_rev: Option<&str>,
99    ) -> Result<Option<RenderedImage>, String>;
100
101    /// The device-dashboard config JSON (binding + widgets + screen).
102    async fn device_config(
103        &self,
104        device_id: &str,
105        device_name: &str,
106        device_type: &str,
107        prefs: &Value,
108    ) -> Result<Value, String>;
109
110    /// Set a device's poll interval and/or replace its widget selection.
111    async fn set_device_config(
112        &self,
113        device_id: &str,
114        device_name: &str,
115        refresh_rate: Option<u32>,
116        widgets: Option<Value>,
117    ) -> Result<SetDeviceResult, String>;
118
119    /// Drop a device's dashboard binding (on device revoke). Best-effort.
120    async fn delete_device(&self, device_id: &str);
121
122    /// Every device → dashboard binding (the nudge loop resolves which device(s)
123    /// bind a changed dashboard).
124    async fn list_bindings(&self) -> Result<Vec<DeviceBinding>, String>;
125
126    /// Subscribe to dashboard change events, yielding the changed `dashboard_id`.
127    /// The impl owns any reconnect/backoff (a loopback SSE can drop); the nudge
128    /// loop just drains the channel. A dropped subscription is latency-only — the
129    /// device still re-polls on its own cadence.
130    async fn subscribe_changes(&self) -> tokio::sync::mpsc::Receiver<String>;
131}