05b417a29b27e1247821794dc912370875113e00
DEFAULT_PALETTE_RGB was a guessed approximation of the panel's ink colors (pure sRGB primaries); swap in epdoptimize's measured spectra6 palette instead, which is far more muted/darker, matching how these inks actually look. _quantize now matches against the palette in OKLab space (perceptual distance) instead of PIL's raw-RGB quantize(), with lightness weighted down relative to hue/chroma when selecting the nearest color -- this palette's inks are lit so differently from their sRGB namesakes (muted dark red, bright yellow) that unweighted distance let lightness dominate and mismatch hue (pure red nearest "yellow"). Dithering switched from Floyd-Steinberg error diffusion to a Bayer ordered dither: true error diffusion is an inherently serial per-pixel loop, and doing that in pure Python for a full 800x480 panel took ~1s, blowing past the render-latency budget the "render widgets concurrently" fix (previous commit) exists to protect. The ordered dither finds each pixel's true nearest and second-nearest palette color and mixes between them (via projection onto that segment, not distance ratio) using a tiled Bayer threshold -- fully vectorized, no Python-level pixel loop.
ESPresso Frame
A DIY e-ink photo frame: an ESP32-C6 pulls photos from your Immich library and displays them on a 7.3" full-color e-paper panel, waking on a timer to refresh and spending the rest of its time in deep sleep.
- No cables to a computer, no SD card shuffling. Provisioning is a captive portal with a QR code drawn on the panel itself -- scan, join, fill in your WiFi and server address, done.
- The frame never decodes an image. A small self-hosted server does all the work (pulling from Immich, cropping, dithering, packing into the panel's exact pixel format) and hands the device a stream it can write straight to SPI. The ESP32-C6 has no PSRAM and not much SRAM to spare -- keeping it a dumb display client is what makes that workable.
- Crops toward faces, not just the center, using face bounding boxes Immich already computed for its own People feature -- no bundled face detector.
- Refresh interval and album are configurable from a web UI, no reflashing needed to change them.
Hardware
- ESP32-C6 dev board (8MB flash)
- Waveshare 7.3" E Ink Spectra 6 (E6) panel -- 800x480, 6-color, SPI
See docs/hardware.md for wiring,
docs/architecture.md for how the two halves talk
to each other, and docs/widgets.md for the server's
placeable photos/calendar/whiteboard widget system.
Getting started
server/-- run the FastAPI server first (Docker Compose, points at your Immich instance). Seeserver/README.md.firmware/-- build and flash the ESP32-C6, then scan the QR codes it draws on first boot to provision it. Seefirmware/README.md.
Repo layout
firmware/ ESP-IDF project for the ESP32-C6
server/ FastAPI server: Immich -> crop/dither/pack -> the frame
docs/ Wiring and architecture notes
License
MIT -- see LICENSE. A few small pieces of vendored
third-party code (a QR code generator, a bitmap font table) keep their
own permissive licenses; see LICENSE for details.
Built with substantial assistance from Claude Code.