Build and push server image / build-and-push (push) Failing after 11s
- pillow==11.1.0 has no prebuilt wheel for Python 3.14, so pip fell back to building from source and failed without libjpeg dev headers installed. Bumped to 12.3.0 (has wheels); re-ran the local test suite against it with no other changes needed. - The local-dev uvicorn command in the README was missing --host 0.0.0.0, so it defaulted to 127.0.0.1 -- unreachable from the ESP32 on the LAN. The Docker image already binds 0.0.0.0 correctly; only the doc'd local command was wrong.
73 lines
2.7 KiB
Markdown
73 lines
2.7 KiB
Markdown
# ESPresso Frame Server
|
|
|
|
Pulls photos from an [Immich](https://immich.app) 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. **Run the server**:
|
|
```
|
|
docker compose up -d
|
|
```
|
|
3. Open `http://<this-machine>:8420/` in a browser, enter your Immich URL
|
|
and API key, click **Load Albums**, pick one, and **Save**.
|
|
4. On the ESP32's captive portal setup form, set the **Tools Server** field
|
|
to `<this-machine>:8420`.
|
|
|
|
## Endpoints
|
|
|
|
- `GET /` -- config UI
|
|
- `GET /api/albums` -- lists Immich albums (used by the config UI)
|
|
- `POST /api/config` -- saves Immich URL/API key/album/order
|
|
- `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 /health` -- liveness check
|
|
|
|
## Notes
|
|
|
|
- Config (including the Immich API key) is stored in `./data/config.json`
|
|
on the host via the compose volume mount.
|
|
- `/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`
|
|
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.
|