Busy Lamp
- (Tech-)Alltag
- gepostet am 2.Juni 2026
Im HomeOffice kommt es öfters mal vor, dass man an der Tür signalisieren will, dass man gerade gar nicht gestört werden will. Es gibt Anbieter wie Luxafor, die für solche Zwecke tolle Angebote haben. Allerdings richten die sich in der Regel an Büros und sind auch recht teuer.
Zudem gibt es diverse Limitierungen, bzw. weiß ich nicht ob es eine Limitierung ist – ich habe nur nichts dazu gefunden. So möchte ich eben nicht, dass es vor der Tür permanent grün leuchtet. Ich möchte lediglich signalisieren, wenn ich gar nicht gestört werden möchte.
Bei der Recherche bin ich auf ein Projekt gestoßen, dass genau aus dieser Richtung kommt: https://www.eliostruyf.com/diy-building-busy-light-show-microsoft-teams-presence/
Dort wird auf Basis eine Raspberry Pi Zero W eine Busy Lamp gebaut. Und das baue ich nach.
Ich möchte allerdings kein Homebridge installieren. Ich möchte den Status der LEDs mit einem simplen REST-API-Call steuern. So lässt sich die Steuerung besser in alles mögliche integrieren.
Was braucht man
- Raspberry Pi Zero W
- Waveshare RGB LED HAT – bekommt man überall recht gut. Ich habe ihn bei Berrybase bestellt.
- pHAT Diffuser – damit sieht es deutlich gefälliger aus. Leider ist der bei Pimoroni nicht mehr erhältlich. Ich habe ihn nur noch bei Grobotronic gefunden.
- Pibow Zero W als Gehäuse.
Raspberry Pi OS Lite
Als OS nehme ich das Standard Pi OS Lite. Da ich keinen Monitor und nichts anschließen will, bereite ich es über den Raspberry Pi Imager so vor, dass WLAN und SSH direkt aktiviert sind.


Habt beim Starten ein wenig Geduld. Der Pi Zero ist wirklich nicht schnell.

Vorbereitung
Jetzt richten wir den Pi Zero ein. Zunächst Python:
sudo apt install -y python3-pip python3-venv git
Jetzt wird eine venv im Home-Ordner erstellt und die notwendigen Packages installiert.
cd ~
python3 -m venv venv --system-site-packages
source venv/bin/activate
pip install fastapi uvicorn rpi_ws281x
Beachtet, dass alleine das Anlegen der venv auf dem Zero 1-2 Minuten dauert.
Busylamp Skript
Ich habe mir von der KI ein Skript erstellen lassen, mit dem die Beleuchtung per API-Call ein- und ausgeschalten werden kann, hänge ich unten an.
Dieses Skript könnt ihr unter ~/busylamp.py speichern.
Mit folgendem Befehl startet ihr den Uvicorn-Server:
sudo venv/bin/uvicorn busylamp:app --host 0.0.0.0 --port 8080
Nun funktionieren die folgenden Befehle:
http://busylamp:8080/led/busy → sanftes Rot
http://busylamp:8080/led/available → sanftes Grün
http://busylamp:8080/led/off → alles aus
http://busylamp:8080/led/demo → Lichtshow
http://busylamp:8080/led/status → zeigt aktuellen Zustand
Anstelle von busylamp muss da natürlich die IP oder der Hostname eures Raspberry Pis stehen.
Und damit sollte es schon klappen.





Service einrichten
Nun wollen wir, dass dieses Skript bei jedem Start des Raspberry Pis automatisch startet. Dazu legen wir einen Service bzw. eine Datei unter /etc/systemd/system/busylamp.service mit folgendem Inhalt an:
[Unit]
Description=BusyLamp REST Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=/home/busylamp
ExecStart=/home/busylamp/venv/bin/uvicorn busylamp:app --host 0.0.0.0 --port 8080
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
Einmal Dienste neustarten und aktivieren:
sudo systemctl daemon-reload
sudo systemctl restart busylamp
sudo systemctl enable busylamp
busylamp.py
"""
BusyLamp – Waveshare RGB LED HAT Steuerung
============================================
REST-API für Raspberry Pi Zero W.
Steuert den Waveshare RGB LED HAT (4x8 = 32 WS2812B LEDs).
Endpunkte:
GET /led/busy Rot – beschaeftigt
GET /led/available Gruen – verfuegbar
GET /led/off LEDs aus
GET /led/demo 5-Sekunden Lichtshow
GET /led/status Aktuellen Zustand abfragen
Start: sudo ~/venv/bin/uvicorn busylamp:app --host 0.0.0.0 --port 8080
Docs: http://<ip>:8080/docs
sudo systemctl enable busylamp
sudo systemctl disable busylamp
sudo systemctl start DIENSTNAME
sudo systemctl stop DIENSTNAME
"""
import asyncio
import time
from contextlib import asynccontextmanager
from fastapi import FastAPI
from rpi_ws281x import PixelStrip, Color
# ── Konfiguration (Waveshare RGB LED HAT) ──────────────────────────
LED_COUNT = 32 # 4 Zeilen x 8 Spalten
LED_PIN = 18 # GPIO18 (PWM0) – Standard beim RGB LED HAT
LED_FREQ_HZ = 800_000
LED_DMA = 10
LED_BRIGHTNESS = 80 # gedaempft fuer sanfte Farben
LED_INVERT = False
LED_CHANNEL = 0
# Sanfte Farben
COLOR_BUSY = Color(180, 30, 20) # sanftes Rot
COLOR_AVAILABLE = Color(30, 160, 40) # sanftes Gruen
COLOR_OFF = Color(0, 0, 0)
# ── LED-Strip initialisieren ───────────────────────────────────────
strip = PixelStrip(
LED_COUNT, LED_PIN, LED_FREQ_HZ,
LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL,
)
state = {"status": "off"}
def set_all(color: Color) -> None:
for i in range(strip.numPixels()):
strip.setPixelColor(i, color)
strip.show()
# ── Demo-Sequenz (ca. 5 Sekunden) ─────────────────────────────────
def run_demo() -> None:
# 1) Regenbogen-Laufband (2s)
rainbow = [
Color(255, 0, 0),
Color(255, 127, 0),
Color(255, 255, 0),
Color(0, 255, 0),
Color(0, 0, 255),
Color(75, 0, 130),
Color(148, 0, 211),
Color(255, 0, 100),
]
for offset in range(20):
for i in range(LED_COUNT):
strip.setPixelColor(i, rainbow[(i + offset) % len(rainbow)])
strip.show()
time.sleep(0.1)
# 2) Busy/Available blinken (1.5s)
for _ in range(3):
set_all(COLOR_BUSY)
time.sleep(0.25)
set_all(COLOR_AVAILABLE)
time.sleep(0.25)
# 3) Pixel-Welle (1.5s)
set_all(COLOR_OFF)
for i in range(LED_COUNT):
strip.setPixelColor(i, Color(0, 255, 150))
strip.show()
time.sleep(0.04)
for i in range(LED_COUNT):
strip.setPixelColor(i, COLOR_OFF)
strip.show()
time.sleep(0.04)
set_all(COLOR_OFF)
# ── App Lifecycle ──────────────────────────────────────────────────
@asynccontextmanager
async def lifespan(app: FastAPI):
strip.begin()
set_all(COLOR_OFF)
yield
set_all(COLOR_OFF)
app = FastAPI(
title="BusyLamp Controller",
version="1.0.0",
lifespan=lifespan,
)
# ── Endpunkte ──────────────────────────────────────────────────────
@app.get("/led/busy")
async def led_busy():
set_all(COLOR_BUSY)
state["status"] = "busy"
return {"status": "busy"}
@app.get("/led/available")
async def led_available():
set_all(COLOR_AVAILABLE)
state["status"] = "available"
return {"status": "available"}
@app.get("/led/off")
async def led_off():
set_all(COLOR_OFF)
state["status"] = "off"
return {"status": "off"}
@app.get("/led/demo")
async def led_demo():
state["status"] = "off"
await asyncio.to_thread(run_demo)
return {"status": "demo_complete"}
@app.get("/led/status")
async def led_status():
return {"status": state["status"]}