Files
espresso_frame/firmware/README.md
T
tfaour 7013311249
Build and push server image / build-and-push (push) Successful in 32s
Add READMEs, docs, and LICENSE for publishing
- LICENSE: MIT, with attribution notes for the vendored qrcode/epaper_fonts/
  dns_server code and the epd7in3e driver's transcription of Waveshare's
  register sequence.
- Top-level README.md: project overview, hardware list, quick-start
  pointing at firmware/ and server/, repo layout, license, Claude Code
  attribution.
- firmware/README.md: full rewrite (was still the stock ESP-IDF captive
  portal example's README) -- build/flash instructions, Kconfig reference
  table, first-boot walkthrough, and how to reset to provisioning mode via
  NVS erase (the only way in right now; a proper reconfigure trigger is a
  future addition).
- docs/hardware.md: wiring table + parts list + strapping-pin/SPI-speed notes.
- docs/architecture.md: sequence diagram and walkthrough of the full
  provision -> connect -> fetch -> display -> sleep cycle, plus the
  reasoning behind doing image processing server-side and reusing Immich's
  face detection instead of bundling a detector.
- server/README.md: fixed stale endpoint docs (missing GET /frame/config,
  POST /api/config still describing removed immich_url/api_key fields).
2026-07-18 16:47:30 -04:00

3.9 KiB

ESPresso Frame Firmware

ESP-IDF firmware for the ESP32-C6. On first boot it provisions itself over a WiFi captive portal; after that it wakes on a timer, fetches an already-processed frame from the server, streams it straight to the panel over SPI, and goes back to deep sleep.

See docs/architecture.md for the full boot/fetch cycle and docs/hardware.md for wiring.

Build and flash

Requires ESP-IDF (developed against v5.x/v6.x) with the environment sourced (. $IDF_PATH/export.sh or your distro's equivalent).

idf.py set-target esp32c6
idf.py build
idf.py -p PORT flash monitor

(Ctrl-] exits the monitor.)

The committed sdkconfig.defaults pins an 8MB flash size and a custom partitions.csv (2MB app partition -- the default "single app" ~1MB partition runs out of room once the HTTP client, TLS, and vendored fonts/QR library are linked in). If your board has less flash, you'll need to shrink the app partition and drop features to fit.

Configuration (idf.py menuconfig)

Under ESPresso Frame Configuration:

Option Default What it does
ESP_AP_SSID ESPRESSO Provisioning softAP SSID prefix (device appends _XXXXXX from its MAC)
ESP_MAX_STA_CONN 4 Max clients on the provisioning softAP
ESP_ENABLE_DHCP_CAPTIVEPORTAL on DHCP Option 114 captive portal detection
FRAME_STA_CONNECT_MAX_RETRIES 3 Home WiFi connect attempts before falling back to provisioning
FRAME_STA_CONNECT_TIMEOUT_MS 15000 Per-attempt WiFi connect timeout
FRAME_SERVER_CHECK_TIMEOUT_MS 3000 Timeout for GET /frame/config (reachability check + refresh interval)
FRAME_FETCH_TIMEOUT_MS 15000 Timeout for GET /frame/image
FRAME_SLEEP_INTERVAL_S 3600 Fallback only -- the refresh interval is normally set server-side; see below
FRAME_RETRY_INTERVAL_S 300 Sleep duration after a failed cycle, before retrying

Under E-Paper Display (epd7in3e) Configuration: SPI/GPIO pin assignments and SPI clock speed -- see docs/hardware.md for the wiring these correspond to.

Why FRAME_SLEEP_INTERVAL_S says "fallback"

The actual refresh interval is set from the server's web UI (see server/README.md) and delivered to the device on every wake via GET /frame/config, so it can be changed without reflashing. The Kconfig value only applies before the device has ever successfully reached a configured server, or if the response doesn't include a valid interval.

First boot

With no stored WiFi config (a fresh device, or after erasing NVS -- see below), the device brings up the display before anything else and shows a two-step setup screen:

  1. Connect to WiFi -- a QR code encoding WIFI:T:WPA;S:...;P:...;; for the device's own ESPRESSO_XXXXXX softAP, with the SSID and password also printed underneath for anyone provisioning from a desktop/laptop that can't scan a QR code.
  2. Configure device -- a QR code linking straight to the captive portal's config page (http://192.168.4.1/ by default), for a one-scan shortcut once you've joined the AP.

The config page asks for your home WiFi SSID/password and the "Tools Server" address (host:port of the server -- not your Immich server). Saving reboots the device, which then connects to your home network and starts its normal fetch/sleep cycle.

Resetting to provisioning mode

There's currently no in-field way to force the device back into provisioning (a future addition) -- reconfiguring means erasing its NVS partition over USB:

python -m esptool --chip esp32c6 -p PORT erase-region 0x9000 0x6000

(Offset/size match the nvs entry in partitions.csv; this only wipes the config, not the app itself.)