# K-Frame — Design Spec IoT dashboard system. Server aggregates data from configurable sources, pushes to connected display clients via FlatBuffers/TCP. Clients own all rendering. ## Hardware - **Server**: Intel N100 homeserver - **Client**: LOLIN Mini Kit ESP32 (ESP-WROOM-32) + 2.4" ILI9341 LCD (240×320, SPI) ## Domain Model - **Widget** — generic display primitive. Has an ID, type hint (text, icon+text, etc.), and key-value data payload. Purely semantic, no visual info. - **LayoutNode** — recursive tree. Either a Container (row/column with children) or a Leaf (references widget by ID). - **Screen** — named layout tree + its widget set. The unit sent to clients. - **DataSource** — configured external data feed. Has a type, config (URL, API key, JSON path, etc.), poll interval. Maps output to one or more widget IDs. - **Client** — connected consumer (ESP32, future KDE Plasma widget, etc.). Receives screen updates. ## Server Architecture Hexagonal (ports & adapters) with DDD. Same patterns as thoughts/ and movies-diary/ projects. ### Crate structure ``` crates/ domain/ — models, value objects, port traits. Zero deps. application/ — use cases: manage config, poll data sources, push to clients api-types/ — DTOs for web UI API presentation/ — Axum HTTP (config web UI) + TCP listener (client connections) bootstrap/ — composition root, wires adapters into ports, starts everything adapters/ config-store/ — ConfigRepository impl (TOML or SQLite, behind the port) weather/ — weather API adapter media/ — Navidrome/Subsonic adapter xtb/ — XTB portfolio adapter rss/ — RSS feed adapter http-json/ — generic HTTP + JSON path extraction adapter webhook/ — webhook receiver (incoming HTTP → widget data) ``` A shared `protocol` crate lives at workspace root level, used by both server and client: ``` crates/ protocol/ — FlatBuffers .fbs schema + generated Rust code, no_std compatible ``` ### Dependency rule ``` bootstrap ──► presentation ──► application ──► domain ◄── adapters ``` - **domain** — zero framework deps, pure business logic, defines all port traits - **application** — orchestrates use cases, depends only on domain - **presentation** — HTTP handlers (Axum) + TCP server, depends on domain + application - **adapters** — implement domain ports, depend inward on domain only - **bootstrap** — composition root, wires adapters into ports ### Domain ports - **ConfigRepository** — CRUD for screens, widgets, data sources, layouts - **DataSourcePort** — trait each data source adapter implements: `async fn poll(&self, config) -> WidgetData` - **ClientBroadcaster** — push layout/data updates to connected clients - **DataSourceRegistry** — manages polling loops per data source, detects changes, only pushes diffs ### Data flow 1. Bootstrap starts polling loops per configured DataSource (each with its own configurable interval) 2. Adapter polls external API → returns new widget data 3. Application compares with previous value → if changed, pushes DataUpdate to all clients via ClientBroadcaster 4. User changes layout in web UI → Application pushes LayoutUpdate to all clients 5. User changes config → Application restarts/adds/removes polling loops as needed ## Protocol FlatBuffers over TCP. Length-prefixed framing: 4 bytes (u32 big-endian) message length, then FlatBuffers payload. ### Server → Client - **ScreenUpdate** — full layout tree + all widget data. Sent on initial connection and when user changes layout. - **DataUpdate** — one or more (widget ID + new data) pairs. Sent only when polled data actually changes. Only changed widgets included. ### Client → Server - **Heartbeat** — keepalive so server can detect dead clients. ### Widget data format Generic key-value map in FlatBuffers. E.g. weather widget: `{"icon": "cloud_rain", "temperature": "5.4", "unit": "°C"}`. Client interprets keys however it wants. Server doesn't prescribe rendering. ### Bandwidth - Only changed widgets sent (diff against previous state) - FlatBuffers is zero-copy and compact - Layout tree not resent unless it actually changes - Heartbeat is minimal ### Extensibility Protocol should be designed so auth handshake can be added later without breaking existing message types. ## Client (ESP32 Firmware) Rust firmware using `esp-idf-hal` / `esp-idf-svc`. ### Boot sequence 1. Show K logo splash (baked into firmware as RGB565 bitmap) 2. Connect WiFi (credentials in firmware config / NVS) 3. Connect TCP to server 4. Receive ScreenUpdate → render layout 5. Listen for DataUpdates, re-render changed widgets only ### Rendering - `embedded-graphics` for drawing primitives (text, rectangles, bitmaps) - Custom layout engine: walks LayoutNode tree, computes bounding boxes, assigns positions - Container nodes split available space among children (row = horizontal, column = vertical) - Leaf nodes render their widget using generic key-value data - Client decides: fonts, sizes, overflow behavior, marquee, truncation, padding - Small icon set baked into firmware as bitmaps ### Composable layouts User composes layout tree freely. If it doesn't fit or renders poorly, user's responsibility to fix. Server may provide hints/warnings but does not enforce. ### Marquee If text overflows its computed bounding box, client auto-scrolls horizontally. Smooth pixel-by-pixel animation. ### Offline behavior - Keep last received data in memory, keep rendering - Show small indicator (dot/icon in corner) when TCP connection lost - Reconnect loop with backoff ### Display-agnostic server Server sends semantic data only. All rendering decisions (overflow, marquee, truncation, font, color, layout computation) are client-side. Future clients (KDE Plasma widget, web dashboard, etc.) interpret the same protocol differently. ## Web UI (Config) Served by same binary via Axum. Simple HTML (static with forms or lightweight SPA). ### Features - **Manage data sources** — add/edit/remove. Configure type, URL, API keys, poll interval, JSON path. - **Manage widgets** — create widgets, assign to data source, define key mappings. - **Compose layouts** — build layout tree: add containers (row/column), nest them, place widgets as leaves. - **Push changes** — saving config triggers immediate LayoutUpdate to all connected clients. - **View connected clients** — see which clients are online (nice-to-have). No auth for now. Presentation crate can add middleware later. ## Initial Data Source Adapters | Adapter | Direction | Description | |------------|-----------|--------------------------------------------------| | weather | poll | OpenWeather or similar, configurable interval | | media | poll | Navidrome/Subsonic API (now playing, etc.) | | xtb | poll/ws | XTB portfolio value | | rss | poll | RSS feed, configurable interval | | http-json | poll | Generic: poll any URL, extract via JSON path | | webhook | push | Server exposes endpoint, external services POST | ## Multi-client Support Multiple clients (ESP32 devices, future Plasma widgets) connect to same server. All receive same layout tree and data updates via broadcast. ## Config Storage Abstracted behind ConfigRepository port. Initial adapter can be TOML file or SQLite — implementation detail hidden from domain. Web UI reads/writes through the same port.