- 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).
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:
- Connect to WiFi -- a QR code encoding
WIFI:T:WPA;S:...;P:...;;for the device's ownESPRESSO_XXXXXXsoftAP, with the SSID and password also printed underneath for anyone provisioning from a desktop/laptop that can't scan a QR code. - 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.)