Installing and running the server

The server is a Python package, todays_paper_web, managed with uv.

Local development

uv sync
uv run uvicorn todays_paper_web.paper:app --reload --host 0.0.0.0

This starts the FastAPI app from todays_paper_web/paper.py on port 8000 by default, reloading on code changes. A device (or curl) fetches its current screen with:

GET /paper?uuid=<config-name>&format=png

uuid is the config name (see Config format) — for a real device this is the UUID it generated and persisted in its own NVS storage; for local testing it’s just the filename (minus .yml) of one of the files under configs/. Omit format=png to get the raw e-paper epdiy image the device itself consumes. POST /paper is what the firmware calls in practice, additionally reporting battery voltage, temperature and firmware version.

Docker

docker build -t todays-paper-web .
docker run -p 8000:8000 -v "$(pwd):/app" todays-paper-web

The container runs the same uvicorn command, listening on $PORT (default 8000).

Configuration

The server has no config file of its own — behavior is controlled by environment variables and by the per-device YAML configs it loads (see Config format):

Variable

Default

Purpose

CONFIG_DIR

configs

Directory (or S3 prefix) the server loads device configs and screen templates from.

CACHE_DIR

cache

Where rendered screens (<name>.png / <name>.epdiy) and per-device request logs are written.

TP_STORAGE

local

Storage backend: local (reads CONFIG_DIR/CACHE_DIR from disk) or s3.

TP_S3_BUCKET

Bucket name, required when TP_STORAGE=s3.

PORT

8000

Listen port (Docker only).

With TP_STORAGE=local, every config’s files — templates, fonts, images — are resolved directly under CONFIG_DIR, shared across all configs. With TP_STORAGE=s3, each config is namespaced under configs/<config-name>/ in the bucket instead. Keep this in mind when deciding whether an asset (a font, a shared partial template) belongs at the top of CONFIG_DIR or inside a specific config’s own folder.

Rendering a screen without the server

Useful while designing a screen, since it doesn’t require running the FastAPI app or waiting for the scheduler:

# from web/
uv run todays_paper <config> -o output.png

<config> is the config name without the .yml extension (dev, not dev.yml). Useful flags:

  • -s <screen> — render a specific screen by name instead of whatever the scheduler would currently pick.

  • -t "2024-12-25 08:00" — render as if it were this date/time, instead of now. Handy for testing time-of-day or day-of-week logic in a template without waiting for it to actually happen.

  • -d — dump the intermediate rendered XML (after Jinja, before the widget tree walks it) to the log, which is the fastest way to debug a template that isn’t producing the layout you expect.

  • -f {png,waveshare,epdiy} — output format; inferred from -o’s extension if omitted.

See also tpconvert (cli/convert.py) for converting a rendered image between png/waveshare/epdiy after the fact.