Python application & daemon that integrates with a OpenDisplay screen to show custom screens.
  • Python 98.4%
  • Dockerfile 1.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Kamil Marut 4bcdcedb06
All checks were successful
CI / ruff (push) Successful in 2m46s
CI / push (push) Successful in 1m1s
ci: use cache in action
2026-10-24 20:31:10 +02:00
.forgejo/workflows ci: use cache in action 2026-10-24 20:31:10 +02:00
src/retheia feat(cal): replace Google Calendar integration with CalDAV 2026-10-24 20:25:03 +02:00
.dockerignore docker: Add .dockerignore file 2026-06-08 19:15:09 +02:00
.env.example feat(cal): replace Google Calendar integration with CalDAV 2026-10-24 20:25:03 +02:00
.gitignore build(docker): Add Dockerfile and Docker Compose file 2026-05-28 22:44:14 +02:00
AGENTS.md feat(cal): replace Google Calendar integration with CalDAV 2026-10-24 20:25:03 +02:00
CLAUDE.md feat(cal): replace Google Calendar integration with CalDAV 2026-10-24 20:25:03 +02:00
docker-compose.yml feat(cal): replace Google Calendar integration with CalDAV 2026-10-24 20:25:03 +02:00
Dockerfile build: Improve release process with release script and Dockerfile changes 2026-06-08 19:15:31 +02:00
pyproject.toml feat(cal): replace Google Calendar integration with CalDAV 2026-10-24 20:25:03 +02:00
README.md feat(cal): replace Google Calendar integration with CalDAV 2026-10-24 20:25:03 +02:00
uv.lock feat(cal): replace Google Calendar integration with CalDAV 2026-10-24 20:25:03 +02:00

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

  1. BleakScanner runs continuously with manufacturer_data filtering. When the reTerminal advertises, the callback parses it with opendisplay.parse_advertisement and pushes the latest advertisement into a 1-slot async queue (older queued items are dropped — only the most recent wake is processed).
  2. 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.
  3. Duplicate advertisements within the same wake (matching mac + loop_counter) are ignored.
  4. 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