Skip to content

How it works

Wells Riley edited this page Sep 27, 2026 · 4 revisions
 BirdNET-Pi, BirdNET-Go   ──▶  Featherframe server  ──▶  Frame (ESP32-S3)  ──▶  E-paper screen
 or BirdWeather                (FastAPI)                 GET /api/frame

The repository has two parts:

  • server/: a Python (FastAPI) service. It reads detections, chooses illustrations, renders a picture for each frame's screen, and runs the Featherframe webapp. All the logic is here.
  • firmware/: an ESP32-S3 program. It downloads its picture from the server and shows it. It makes no decisions.

Server

  • It only reads from your detector. It never writes to or locks BirdNET's database.
  • It renders a new picture only when a detection qualifies. By default it does nothing, to keep screen changes few.
  • It renders pictures in the background. A web request only sends a picture that's already rendered.

There are two pictures: Individual detections (one species at a time) and Collage. Each is composed once, then fitted, matted, dithered, and packed for each frame that shows it.

Firmware

  • On USB: the frame keeps a WebSocket open to the server. The server tells it as soon as its picture changes.
  • On battery: the frame wakes on a timer or a button press. It sends the ETag of its current picture. The server answers 304 if nothing changed, or sends the new picture.

The picture format (FFF) is a 16-byte header followed by 4-bit pixels. For the EE03 kit it's 1872 × 1404 landscape, rotated by the server.

Any other screen

Self-hosted only. Any device that can show an image from a URL can show the picture:

http://<your-server>/api/view.png?w=1072&h=1448&format=gray256
Parameter Values
w, h Width and height in pixels.
format color, gray256, gray16, gray2, or mono.

A landscape screen shows the picture rotated, so you can hang it in portrait.

Command line

Self-hosted only. You can change any frame's settings with curl:

# List frames and their IDs
curl http://<your-server>/api/status | jq '.frames.list[] | {id, title, summary}'

# Name a frame, show the collage, and rotate it
curl -X POST http://<your-server>/api/frames/<ID> \
     -H 'Content-Type: application/json' \
     -d '{"name": "Hall TRMNL", "shows": "collage", "rotation": 270}'

# Remove a frame
curl -X POST http://<your-server>/api/frames/<ID> \
     -H 'Content-Type: application/json' -d '{"forget": true}'

Featherframe ignores settings a frame doesn't support. For example, a tablet can't be rotated, and a TRMNL has no mat.

Other addresses for each frame:

  • Its current picture: /api/frames/<ID>/preview.png
  • Its battery readings: /api/battery?frame=<ID>

More

AGENTS.md describes the architecture in detail, for contributors and coding agents.

Clone this wiki locally