Was ist das?

Eine 64x64 RGB-LED-Matrix, die über einen Raspberry Pi 4B gesteuert wird und das Album-Cover des aktuell auf Spotify gespielten Songs anzeigt. Eine Web-App auf dem Pi ermöglicht die Steuerung über den Browser — von überall, dank VPN-Routing und OIDC -Absicherung.

Neben dem Spotify-Modus stehen 13 verschiedene C++-Demos zur Verfügung (Game of Life, 3D-Würfel, Farbevolution und mehr), die direkt über das Dashboard gestartet werden können.

Raspberry Pi 4B mit Adafruit RGB Matrix Bonnet
Moby — Play auf 4096 Pixeln

Die Hardware

Das Panel besteht aus drei Komponenten: einem Raspberry Pi 4B, einem Adafruit RGB Matrix Bonnet und einem 64x64 HUB75 -LED-Panel mit 4096 RGB-Pixeln.

Der Bonnet sitzt direkt auf den GPIO -Pins des Pi und übersetzt die GPIO-Signale in das HUB75-Protokoll. Die Ansteuerung läuft über die C++-Library rpi-rgb-led-matrix, die das Panel mit hoher Refresh-Rate und minimaler Latenz betreibt.

Lötarbeit für 64x64

Der Bonnet funktioniert nicht direkt out of the box mit einem 64x64-Panel. Das liegt daran, dass solche Panels mit einem 1/32-Scan arbeiten und eine fünfte Adressleitung (“E”) benötigen, um alle 64 Zeilen ansprechen zu können. Kleinere Panels wie 32x32 oder 64x32 kommen mit den Leitungen A bis D aus (1/16-Scan). Auf dem Bonnet gibt es dafür ein Lötpad, das GPIO 8 mit dem E-Pin verbindet — dieser Jumper muss manuell mit Lötzinn überbrückt werden. Ohne diese Brücke kann das Panel nur die Hälfte seiner Zeilen ansteuern, was zu einem verzerrten Bild führt.

Von Elektrotechnik habe ich praktisch keine Ahnung — genau deshalb habe ich dieses Projekt gewählt. Immer mal was Neues lernen. Dass der Lötjumper für 64x64-Panels nötig ist, habe ich erst durch eine Vielzahl von Tutorials und Forenbeiträgen herausgefunden. Alleine wäre ich da nicht drauf gekommen. Da ich selbst keinen Lötkolben besitze, hat das freundlicherweise mein Bekannter Sven übernommen — danke dafür!

Die Herausforderung bei HUB75 : Das Protokoll multiplext die 64 Zeilen — zu jedem Zeitpunkt sind nur wenige Zeilen aktiv. Die Library muss die Zeilen schnell genug durchschalten, damit das menschliche Auge ein stabiles Bild sieht. Auf dem Pi funktioniert das zuverlässig, solange kein anderer Prozess die CPU-Kerne blockiert.

Spotify-Integration

Der interessanteste Teil: Das Panel zeigt das Album-Cover des Songs, der gerade auf Spotify läuft.

Der OAuth-Flow

Spotify bietet keine einfachen API-Keys. Stattdessen nutzt die API OAuth 2.0 mit dem Authorization Code Flow:

  1. Der Nutzer klickt “Mit Spotify verbinden” und wird zu Spotify weitergeleitet
  2. Nach dem Login redirected Spotify zurück mit einem Authorization Code
  3. Das Backend tauscht den Code gegen ein Access Token und einen Refresh Token
  4. Die Tokens werden persistent in SQLite gespeichert

Ab diesem Punkt ist kein weiterer Login nötig. Das Access Token läuft nach einer Stunde ab, wird aber automatisch per Refresh Token erneuert. Die Spotify-App läuft im Development Mode — das reicht für einen einzelnen Account.

Vom API-Call zum LED-Pixel

Wenn ein neuer Song startet, passiert Folgendes:

  1. Das Backend pollt alle 5 Sekunden Spotifys /v1/me/player/currently-playing
  2. Bei Song-Wechsel wird das kleinste verfügbare Cover-Bild heruntergeladen (64x64 JPEG)
  3. led-image-viewer aus der rpi-rgb-led-matrix-Library zeigt das Bild auf dem Panel an

Der letzte Schritt nutzt child_process.spawn , um den Image Viewer als Root-Prozess zu starten — GPIO-Zugriff erfordert Root-Rechte. Ein Process Manager stellt sicher, dass immer nur ein Prozess die Matrix ansteuert: Bevor ein neues Cover angezeigt wird, wird der vorherige Prozess sauber beendet.

Auto-Modus

Im Auto-Modus vergleicht das Backend bei jedem Poll den aktuellen Track mit dem zuletzt angezeigten. Nur bei tatsächlichem Song-Wechsel wird ein neues Cover geladen und angezeigt. Das spart API-Calls und vermeidet unnötige Prozess-Neustarts.

Der Auto-Modus läuft serverseitig — das Cover wechselt also auch bei geschlossenem Browser, solange Spotify auf irgendeinem Gerät spielt.

Der Weg zum Pi: Netzwerk-Routing

Der Pi hängt im lokalen Netzwerk und ist nur über WireGuard VPN erreichbar. Die Web-App muss aber öffentlich zugänglich sein (mit Login). Die Lösung ist eine dreistufige Routing-Kette:

Browser → Nginx (Hetzner-Server, SSL) → socat (VPN-Server) → Pi (172.30.0.4:3005)

Das Problem: Der Hetzner-Server und der Pi sind in verschiedenen Netzen. Der Hetzner-Server kennt das VPN-Subnet nicht. Ein socat-Port-Forward auf dem VPN-Server überbrückt das — er lauscht auf dem Hetzner-Privatnetz und leitet Traffic durch den WireGuard-Tunnel zum Pi.

Process Manager: Ein Prozess, eine Matrix

Die zentrale Einschränkung der Hardware: Nur ein Prozess kann die GPIO-Pins gleichzeitig ansteuern. Der Process Manager kapselt diese Logik:

  • Start — Prüft, ob bereits ein Prozess läuft, beendet ihn falls ja, startet dann den neuen
  • Stop — Sendet SIGTERM, wartet bis zu 5 Sekunden, dann SIGKILL
  • Status — Liefert den aktuellen Modus (demo oder image), die PID und die Laufzeit

Jeder Aufruf — ob Demo-Start, Cover-Anzeige oder Stop — geht durch den Process Manager. Das verhindert Zombie-Prozesse und stellt sicher, dass die Matrix immer in einem definierten Zustand ist.

Deployment: GitHub Actions → SSH-Jump → Pi

Das Deployment ist ungewöhnlich, weil das Ziel kein Server in der Cloud ist, sondern ein Pi hinter einem VPN. Die GitHub Action nutzt einen SSH-Jump über den VPN-Server:

GitHub Actions → SSH → VPN-Server (Jump Host) → SSH → Pi (172.30.0.4)

Auf dem Pi wird gebaut und neu gestartet — kein Docker, direkt Node.js mit einem systemd-Service. Das spart die Build-Zeit für Docker-Images, die auf einem Pi deutlich länger dauern würde.

Was ich gelernt habe

  • GPIO-Ansteuerung braucht Root — und damit einen sauberen Process Manager, der Prozesse trackt und aufräumt. Verwaiste Root-Prozesse auf einem Pi ohne Monitor sind schwer zu debuggen.
  • Spotify hat keine API-Keys — nur OAuth 2.0. Für einen persönlichen Use Case ist das Overhead im Setup, aber der Refresh Token macht es danach wartungsfrei.
  • 64x64 Pixel sind überraschend ausdrucksstark — Album-Cover sind für kleine Auflösungen designt und sehen auch auf einer LED-Matrix erstaunlich gut aus.
  • Multi-Hop SSH-Deployments funktionieren zuverlässig mit GitHub Actions, erfordern aber sauberes Key-Management und einen stabilen VPN-Tunnel als Grundlage.

Glossar

GPIO
General Purpose Input/Output — frei programmierbare Pins auf einem Mikrocontroller oder Einplatinencomputer. Über GPIO steuert der Raspberry Pi die LED-Matrix direkt auf Hardware-Ebene, ohne zusätzliche Controller.
HUB75
Ein Standard-Interface für RGB-LED-Panels, das Zeilen- und Farbdaten über parallel geschaltete Pins überträgt. Der Raspberry Pi steuert das Panel über GPIO-Pins, die von einem HAT (wie dem Adafruit RGB Bonnet) auf den HUB75-Stecker gemappt werden.
OAuth 2.0
Ein Autorisierungsframework, das Anwendungen erlaubt, im Namen eines Nutzers auf Ressourcen zuzugreifen, ohne dessen Passwort zu kennen. Bildet die Grundlage für OpenID Connect und viele Login-mit-Drittanbieter-Flows.
Refresh Token
Ein langlebiger Token, der im Hintergrund neue Access Tokens anfordert, ohne dass der Nutzer sich erneut einloggen muss. Access Tokens laufen typischerweise nach einer Stunde ab — der Refresh Token erneuert sie automatisch.
child_process.spawn
Eine Node.js-Funktion, die einen externen Prozess als Kindprozess startet. Im Gegensatz zu exec streamt spawn stdout/stderr, statt auf das Ende zu warten — ideal für langlebige Prozesse wie die LED-Ansteuerung.
OpenID Connect (OIDC)
Ein Authentifizierungsprotokoll auf Basis von OAuth 2.0. Der Identity Provider bestätigt die Identität eines Nutzers und liefert Informationen wie Name, E-Mail und Rollen in einem signierten ID-Token zurück.
Single Sign-On (SSO)
Ein Authentifizierungsverfahren, bei dem sich ein Nutzer einmalig anmeldet und danach auf mehrere Anwendungen zugreifen kann, ohne sich erneut einloggen zu müssen. Der Identity Provider verwaltet die zentrale Session.
Reverse Proxy
Ein Server, der eingehende Anfragen entgegennimmt und an Backend-Dienste weiterleitet. Übernimmt dabei Aufgaben wie SSL-Terminierung, Rate Limiting und Zugriffskontrolle, sodass die eigentlichen Anwendungen nicht direkt aus dem Internet erreichbar sein müssen.
WireGuard
Ein modernes VPN-Protokoll, das auf minimalen Code und starke Kryptografie setzt. Im Vergleich zu OpenVPN oder IPSec deutlich schlanker (~4.000 Zeilen Kernel-Code), schneller im Verbindungsaufbau und einfacher zu konfigurieren.
CI/CD
Continuous Integration und Continuous Deployment — automatisierte Prozesse, die Code nach jedem Push testen, bauen und auf den Server deployen. Reduziert manuelle Fehlerquellen und ermöglicht schnelle, reproduzierbare Deployments.