4f4b2844e64aa92143846780bd245df28ed5c973
After a successful home-WiFi connection, caches BSSID/channel and IP/netmask/gateway/DNS in NVS. The next wake's first connect attempt uses the cached BSSID/channel (skips the all-channel scan) and applies the cached IP directly once the link comes up (skips DHCP) -- a couple fewer seconds of radio-on time per wake, free every wake since nothing about the network actually needs renegotiating most of the time. Falls back to a normal scan+DHCP attempt, and clears the cache, if: the fast attempt itself fails, or it "succeeds" at the WiFi layer but the full fetch cycle then fails anyway (a stale cached IP/DNS/gateway that associates but can't actually reach the server). Also cleared on (re)provisioning and factory reset, since a new network shouldn't try to reuse the old one's cache. The static-IP path needed care to get right without touching untested territory: esp_netif_set_ip_info() only posts IP_EVENT_STA_GOT_IP (what the existing connect-wait logic blocks on) once the netif is already up, which the internal netif-glue's own WIFI_EVENT_STA_CONNECTED handler guarantees by running first (registered earlier, in esp_netif_create_default_wifi_sta()) -- confirmed against ESP-IDF's own static_ip example and esp_netif_handlers.c source rather than assumed. Falling back after a failed fast attempt also needed an explicit esp_netif_dhcpc_start() first: esp_netif_dhcpc_stop() leaves the netif's DHCP status STOPPED rather than resetting to INIT, and left alone the glue would silently re-post the stale cached IP on the next connect instead of actually running DHCP (esp_netif_action_connected). Version bumped to 1.1.0 (real feature, not just a fix); build-verified clean on both board configs (devkit 8MB, XIAO 4MB), no new warnings.
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 and
docs/architecture.md for how the two halves talk
to each other.
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.