- Python 98.4%
- Dockerfile 1.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| src/retheia | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
reTheia
Async daemon that drives a Seeed reTerminal E1001 ePaper display running OpenDisplay firmware. The device deep-sleeps and wakes every ~6 h to broadcast a BLE advertisement; reTheia runs on a home server, scans for that advertisement, renders one of three screens, and pushes the image over BLE before the device sleeps again.
Screens
- dashboard (default) — current weather + 3-day forecast (Open-Meteo) + EUR/PLN and USD/PLN (NBP) on the left; next 6 CalDAV events on the right.
- calendar — full-width week view.
- http-cat — random http.cat image.
All three carry a 20 px status bar: screen name on the left, estimated battery percent (computed from the BLE advertisement's battery_mv) on the right.
Install
You need uv and libcairo installed.
uv sync
Configure
Either set env vars or pass CLI flags (CLI takes precedence). Env vars:
| Variable | Purpose |
|---|---|
RETHEIA_CITY / RETHEIA_COUNTRY |
City + ISO country code for weather. |
RETHEIA_LAT / RETHEIA_LON |
Skip geocoding by passing coordinates directly. |
RETHEIA_DEVICE_MAC |
Optional MAC filter — otherwise the daemon listens for any OpenDisplay advertisement. |
RETHEIA_DEVICE_NAME |
Optional name filter. |
RETHEIA_CALDAV_URL |
CalDAV calendar collection URL (e.g. https://dav.example.com/calendars/me/personal/). |
RETHEIA_CALDAV_USERNAME / RETHEIA_CALDAV_PASSWORD |
CalDAV credentials (env only). |
RETHEIA_TIMEZONE |
IANA tz name for rendering event times (default: $TZ or UTC). |
RETHEIA_STATE_FILE |
Override the active-screen state file. |
CalDAV
Point RETHEIA_CALDAV_URL at a single calendar collection (not the server root). Recurring events are expanded client-side. If the server is unreachable or not configured, the daemon does not crash — the calendar areas render an inline Calendar unavailable message.
Run
# pick a screen the next wake should render
uv run retheia screen dashboard # or calendar | http-cat
# start the BLE scan daemon
uv run retheia run --city Warsaw --country PL
# render a screen to a PNG and open it in the desktop image viewer
uv run retheia preview dashboard --city Warsaw --country PL
# render without opening (CI / iteration)
uv run retheia preview calendar --no-open
The preview pipeline runs the same render + dither chain as the daemon (via opendisplay.prepare_image), so the PNG matches what the panel will actually show.
BLE / BlueZ on Linux
py-opendisplay uses bleak on top of BlueZ over D-Bus. The user running the daemon must be able to talk to BlueZ — easiest is to add them to the bluetooth group:
sudo usermod -aG bluetooth $USER
# log out / back in
Sanity check the adapter with bluetoothctl scan on. If it returns No default controller available, check the controller status using hciconfig -a hci0. If it's DOWN, reload the Bluetooth driver:
sudo modprobe -r btusb
sudo modprobe btusb
How a wake is processed
BleakScannerruns continuously withmanufacturer_datafiltering. When the reTerminal advertises, the callback parses it withopendisplay.parse_advertisementand pushes the latest advertisement into a 1-slot async queue (older queued items are dropped — only the most recent wake is processed).- A worker reads the active screen from the state file, fetches the necessary data concurrently (weather + NBP + CalDAV), composes a 800×460 body image, overlays the 20 px status bar, and uploads via
OpenDisplayDevice.upload_image(..., fit=FitMode.CONTAIN). The library handles 4-grayscale dithering. - Duplicate advertisements within the same wake (matching
mac+loop_counter) are ignored. - If an upload takes longer than 15s, a warning is logged — the device's awake window is short and a failed upload costs 6 h of staleness.
Installing the OpenDisplay firmware
To install the OpenDisplay firmware, make sure you use a Chromium-based browser. Firefox does not support the required WebUSB and Bluetooth APIs.
If you get a failed to execute 'open' on 'SerialPort': Failed to open serial port. error on Linux, you may need to run the following command to set the correct ACL on the serial port:
sudo setfacl -m u:$USER:rw /dev/ttyUSB0