Files
espresso_frame/server/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.6 KiB

ESPresso Frame Server

Pulls photos from an Immich album, resizes/dithers/quantizes them to the E Ink Spectra 6 panel's exact 6-color format, and serves the frame a ready-to-display image once an hour. All the image processing happens here so the ESP32 never has to decode a JPEG or run a dithering algorithm itself -- it just streams the response straight to the panel.

Setup

  1. Get an Immich API key: in Immich, go to Account Settings -> API Keys -> New API Key. Read-only access to albums/assets is enough.
  2. Copy the compose file and fill in your Immich details:
    cp docker-compose.yml.example docker-compose.yml
    
    Edit docker-compose.yml and set IMMICH_URL/IMMICH_API_KEY under environment:. docker-compose.yml is gitignored (it'll hold your real API key) -- docker-compose.yml.example is the one that's committed.
  3. Run the server:
    docker compose up -d
    
  4. Open http://<this-machine>:8420/ in a browser, click Load Albums, pick one, and Save. (The Immich URL/API key fields will already be populated from the environment; changing them in the UI has no effect as long as the env vars are set -- they win on every load.)
  5. On the ESP32's captive portal setup form, set the Tools Server field to <this-machine>:8420.

Endpoints

  • GET / -- config UI (album, order, refresh interval, face-aware crop toggle -- not Immich URL/API key, see Setup above)
  • GET /api/albums -- lists Immich albums (used by the config UI)
  • POST /api/config -- saves album/order/refresh_interval_s/smart_crop_faces
  • GET /frame/image -- returns the current photo pre-processed into the panel's raw 800x480, 4-bit-per-pixel, 2-pixels-per-byte format (application/octet-stream, exactly 192,000 bytes)
  • GET /frame/config -- {"refresh_interval_s": ...}, polled by the frame each wake alongside its reachability check
  • GET /health -- liveness check

Notes

  • Album/order/refresh-interval/etc. are stored in ./data/config.json on the host via the compose volume mount. Immich URL/API key are too if set via the web UI, but IMMICH_URL/IMMICH_API_KEY env vars (see Setup above) always take precedence when present.
  • /frame/image isn't authenticated yet. That's fine on a trusted home LAN for now, but worth revisiting once the ESP32 side is wired up to send a shared device token.
  • The 6-color palette RGB values in app/image_pipeline.py are approximations, not measured values (Waveshare doesn't publish exact color primaries for this panel) -- tune them once you can compare a rendered test image against the real panel.

Deploying a pre-built image

Every push to main that touches server/ triggers a Gitea Actions workflow (.gitea/workflows/server-docker-build.yml) that builds this image and pushes it to this repo's Gitea Container Registry at git.thumeit.com/tfaour/espresso-frame-server. docker-compose.yml (copied from docker-compose.yml.example, see Setup above) already points at that image, so a deploy host doesn't need this repo's build context at all -- just the compose file:

docker compose pull
docker compose up -d

docker compose build (or up --build) still works too, for local iteration against your own Dockerfile changes.

Local development (without Docker)

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
CONFIG_PATH=./data/config.json uvicorn app.main:app --reload --host 0.0.0.0 --port 8420

--host 0.0.0.0 matters here: without it, uvicorn defaults to 127.0.0.1 (localhost-only), which the ESP32 can't reach over the LAN. The Docker image already binds 0.0.0.0 by default.