- Python 97.2%
- Dockerfile 1.4%
- Shell 1.4%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| scripts | ||
| src/retheia | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.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 Google Calendar 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_CALENDAR_ID |
Google Calendar id (default primary). |
RETHEIA_TIMEZONE |
IANA tz name for rendering event times (default: $TZ or UTC). |
RETHEIA_STATE_FILE |
Override the active-screen state file. |
RETHEIA_GOOGLE_CREDENTIALS / RETHEIA_GOOGLE_TOKEN |
Override OAuth file paths. |
Google Calendar auth
- In Google Cloud Console: enable the Calendar API, create an OAuth client of type "Desktop app", download the JSON.
- Save it to
~/.config/retheia/google/credentials.json(or setRETHEIA_GOOGLE_CREDENTIALS). - Run
uv run retheia auth. A browser window opens; consent. A token is cached at~/.config/retheia/google/token.json(mode 600).
Notes:
- The flow needs a desktop browser. On a headless server, run
retheia authon your primary device and copytoken.jsonto the server. - In Google Cloud Console "Testing" mode the refresh token expires after 7 days. Either move the project to "Production" (no review needed for the
calendar.readonlyscope on a personal account) or re-auth weekly. - If the token can no longer refresh, the daemon does not crash — the dashboard's calendar column renders a
Calendar auth expired — run \retheia auth`` message until you re-run auth.
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.
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 + GCal), 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