Public transit arrivals on a 128×32 HUB75 LED display, driven by a Raspberry Pi. This is the Python-side renderer that calls the arrivals-kmp CLI for data.
Common setup:
- Adafruit RGB Matrix Bonnet
- 2× 64×32 HUB75 RGB LED panels (2.5mm pitch), chained horizontally → 128×32
- 5V power supply (10A recommended to power both panels)
Supported Pi boards:
- Raspberry Pi 5 — uses the Piomatter driver (PIO-based)
- Raspberry Pi Zero 2 W — uses hzeller's rpi-rgb-led-matrix driver via the Adafruit install script
- The Bonnet's HUB75 socket feeds panel 1's IN
- Run a HUB75 ribbon from panel 1's OUT to panel 2's IN
- Power both panels from the same 5V rail
- The library treats the pair as a single 128×32 display via
Geometry(width=128, height=32, ...)
If panel 2 appears flipped or mirrored, either flip it physically or change Orientation.Normal to Orientation.R180 in led_matrix.py.
Follow the Adafruit Pi 5 RGB Matrix Panel guide first to install the Piomatter library system deps and udev rules (so /dev/pio0 is accessible without sudo).
Follow the Adafruit RGB Matrix Bonnet guide and run the install script to compile and install the rgbmatrix Python library.
Clone this repo on the Pi and run:
./install.shThe script detects your Pi model and installs the correct driver. On Pi 5 it installs Piomatter via pip; on Pi Zero 2 it builds the rgbmatrix Python bindings from ~/rpi-rgb-led-matrix into the venv.
The Python script calls the arrivals binary from jdamcd/arrivals-kmp. Cross-compile the native CLI for ARM Linux from a macOS or x86 Linux machine:
./gradlew :cli:linkReleaseExecutableLinuxArm64Then copy the binary to the Pi and put it on your PATH:
scp cli/build/bin/linuxArm64/releaseExecutable/cli.kexe <user>@<host>:/tmp/arrivals
ssh <user>@<host> 'sudo mv /tmp/arrivals /usr/local/bin/arrivals'Verify on the Pi:
arrivals --json tfl --station 910GSHRDHST --platform 2You should get a JSON object with station and arrivals fields.
source venv/bin/activate
python arrivals.py "arrivals --json tfl --station 910GSHRDHST --platform 2"The rgbmatrix driver needs root for GPIO access, so on the Pi Zero 2:
sudo venv/bin/python arrivals.py "arrivals --json tfl --station 910GSHRDHST --platform 2"There's a template in systemd/arrivals-led.service. To install:
# Edit the ExecStart line to point at your preferred station
mkdir -p ~/.config/systemd/user
cp systemd/arrivals-led.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now arrivals-led
loginctl enable-linger $USER # So it runs when you're not logged inThere's a template in systemd/arrivals-led-root.service. To install:
# Edit the ExecStart line to point at your preferred station and user home
sudo cp systemd/arrivals-led-root.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now arrivals-led-rootI've included a couple of models for 3D-printed parts that might be useful:
- Bracket to connect the 2x LED panels horizontally (with M3 screws)
- Riser to attach a Pi 5 or Pi Zero 2 (with M2.5 & M3 screws)
/dev/pio0: permission denied(Pi 5): The Adafruit udev rule isn't in place. Check the Pi 5 guide's udev section.- Flicker during data refresh: The LED refresh thread can be preempted by the Linux scheduler, especially when a subprocess is running. Isolating a CPU core helps on both boards:
- Append
isolcpus=3to the existing line in/boot/firmware/cmdline.txt, then reboot. - Pi 5 only: Install the patched Piomatter from this branch which pins the blit thread to the isolated core:
See Piomatter PR #79 for details. The rgbmatrix driver picks up the isolated core automatically.
pip install git+https://github.com/lehni/Adafruit_Blinka_Raspberry_Pi5_Piomatter.git@pin-blit-thread-to-isolated-cpu
- Append
- Colours look wrong: The panels are assumed to be wired in RBG order. If your panels use standard RGB wiring, change
RGB_SEQUENCE = "RBG"to"RGB"inarrivals.py. - Power: If you have any power issues, try powering the Pi separately via its standard USB adapter.
The bundled bitmap font was generated based on London Underground Dot Matrix Regular.

