Files
k-frame/README.md

165 lines
6.9 KiB
Markdown

# K-Frame
IoT dashboard system. A server aggregates data from external sources (weather APIs, media players, RSS feeds, webhooks) and pushes rendered updates to ESP32 display clients over TCP.
**Server** polls data sources, projects values through configurable widget mappings, computes layouts, and broadcasts binary frames to connected clients. Configuration via REST API + web UI.
**Clients** receive structured data + layout trees, compute bounding boxes, format text per display hint rules, and render to hardware. The client domain is chip-agnostic — the same rendering engine runs on ESP32 (ILI9341 TFT) and desktop (terminal).
**Web UI** (SPA) configures data sources, widgets, layouts, themes, and presets. Includes a live layout preview that mirrors what the physical display shows.
## Architecture
Hexagonal / ports-and-adapters with full CQRS. Domain logic has zero framework dependencies. Static dispatch everywhere (no `Box<dyn>`, no trait objects).
```
┌─────────────────── Server ───────────────────┐
│ domain/ entities, value objects, │
│ ports (traits) │
│ application/ use cases, data projection │
│ bootstrap/ composition root, polling │
│ │
│ adapters/ │
│ config-sqlite persistence │
│ http-api REST API (Axum) │
│ tcp-server binary protocol broadcast │
│ http-json external API polling │
│ media, rss source-specific adapters │
│ data-generators clock, static text │
│ auth argon2 + JWT │
├─────────────────── Shared ───────────────────┤
│ protocol/ wire types, postcard serde │
│ api-types/ REST DTOs │
├─────────────────── Client ───────────────────┤
│ client-domain/ layout engine, render engine │
│ markup parser, scroll state │
│ client-application/ message handling │
│ client-esp32/ ESP32 firmware (ILI9341) │
│ client-desktop/ terminal debug client │
├──────────────────── SPA ─────────────────────┤
│ spa/ React + TanStack Router │
│ shadcn/ui + Tailwind │
└──────────────────────────────────────────────┘
```
### Key design decisions
- **postcard + serde** for the wire protocol — compact varint encoding, `no_std` compatible, no codegen
- **Static dispatch** — port traits used as generic bounds, not trait objects. Composition root resolves all types
- **Event-driven CQRS** — config mutations emit domain events, read model projected in memory
- **Domain-owned rendering** — client-domain owns text wrapping, alignment, color markup, scroll. DisplayPort is a thin pixel-pusher (`draw_text_span`, `fill_rect`, `flush`)
- **WiFi provisioning** — ESP32 boots into AP captive portal if no config in NVS, auto-falls back on WiFi failure
See `docs/adr/` for architectural decision records and `CONTEXT.md` for the domain glossary.
## Features
- **Data sources**: HTTP/JSON, weather, media (Subsonic/Navidrome), RSS, webhooks, clock, static text
- **Layout engine**: flexbox-like containers (row/column, fixed/flex sizing, gap, padding, justify-content, align-items)
- **Theming**: 5 configurable colors (primary, secondary, accent, text, background), live push to clients
- **Rich text**: inline color markup (`{primary}text{/}`, `{#FF0000}hex{/}`)
- **Widget alignment**: per-widget horizontal/vertical text alignment (left/center/right, top/middle/bottom), reflected in layout preview
- **Connection indicator**: green/red dot on ESP32 display showing server connectivity
- **Overflow scroll**: bounce animation when content exceeds widget bounds, speed auto-derived from overflow
- **Captive portal**: ESP32 AP mode with DNS + HTTP config form for WiFi provisioning
- **Auth**: argon2 password hashing, JWT tokens, protected API routes
## Prerequisites
- **Rust** (stable) — server, desktop client, shared crates
- **Rust ESP32 toolchain** — `espup`, xtensa target. See [esp-rs book](https://docs.esp-rs.org/book/)
- **Node.js / Bun** — SPA development
- **SQLite** — server persistence (created automatically)
## Quick start
```bash
# 1. Clone and configure
cp .env.example .env
# Edit .env — set JWT_SECRET and KFRAME_ENCRYPTION_KEY
# 2. Start the server
make server
# 3. Start the SPA dev server (separate terminal)
cd spa && bun install && bun run dev
# Open http://localhost:5173
# 4. Register an account (first launch = setup mode)
# 5. Add a data source, create widgets, build a layout
```
### ESP32 client
```bash
# Build firmware
make esp-build
# Flash (default port: /dev/ttyACM0)
make esp-flash
# Flash with custom port
make esp-flash ESP_PORT=/dev/ttyUSB0
# Flash and monitor serial output
make esp-run
# On first boot: connect to "KFrame-Setup" WiFi, enter credentials in captive portal
```
### Desktop client
```bash
make desktop
# Connects to localhost:2699, prints render commands to terminal
```
## Development
```bash
# Full check suite (fmt + clippy + test)
make check
# Auto-fix formatting and clippy warnings
make fix
# Run tests only
make test
# SPA type checking
cd spa && bun run typecheck
```
### Project structure
| Path | Description |
|------|-------------|
| `crates/domain/` | Entities, value objects, port traits |
| `crates/application/` | Use cases (ConfigService, DataProjection) |
| `crates/protocol/` | Wire types, encode/decode (`no_std`) |
| `crates/bootstrap/` | Server composition root |
| `crates/adapters/` | All port implementations |
| `crates/client-domain/` | Display-agnostic rendering engine |
| `crates/client-application/` | Client message handling |
| `crates/client-esp32/` | ESP32 firmware |
| `crates/client-desktop/` | Terminal debug client |
| `crates/api-types/` | REST API DTOs |
| `spa/` | React SPA |
| `docs/adr/` | Architecture decision records |
| `CONTEXT.md` | Domain glossary |
## Contributing
1. Fork the repo
2. Create a feature branch
3. Run `make check` before pushing — CI runs the same checks
4. Open a PR with a clear description of what changed and why
The domain glossary in `CONTEXT.md` defines the canonical language. Use it in code, commits, and PRs. If you're adding a new concept, update the glossary.
Architecture decisions are documented in `docs/adr/`. If your change involves a hard-to-reverse design choice, write an ADR.
## License
[MIT](LICENSE)