Files
busylight_modular/README.md
T
christianandClaude Opus 5 11c8042de0 fix: Vorgaenger bei Uebernahme der Zentrale zurueckstellen
Bisher blieb der bisherige Besetzer der Zentrale nach einer Uebernahme
ohne Zuordnung. In der Praxis heisst das: er hat gar kein Licht mehr, und
es musste immer erst jemand "zurueck auf mein Tuerschild" klicken, damit
der Zustand wieder stimmte.

Die Uebernahme setzt den Vorgaenger jetzt selbst auf seinen gemerkten
Heimatplatz zurueck. Ist der inzwischen jemand anderem zugeordnet oder
war keiner gemerkt, bleibt er ohne Zuordnung - die Meldung auf der Seite
nennt den jeweiligen Grund.

Dabei darf das eben freigegebene Segment des neuen Besetzers nicht
ausgeschaltet werden, wenn der Vorgaenger genau dorthin zurueckkehrt -
sonst waere sein Licht sofort wieder aus.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 08:37:26 +00:00

345 lines
13 KiB
Markdown

# Busylight WLED Controller
Web-Anwendung zur Steuerung von WLED-Busylights basierend auf Microsoft Teams Presence-Status, Zeiterfassung und Telefonanlage (Webhook).
Jedes WLED-Geraet verfuegt ueber zwei Segmente (oben/unten), die jeweils einem Benutzer zugeordnet werden. Die Farbe des Segments wird automatisch anhand des Anwesenheitsstatus, des Teams-Status und des Telefonstatus gesetzt.
## Features
- **Dashboard** mit Echtzeit-Uebersicht aller Busylights (Zeiterfassungs-, Teams- und Telefon-Status als Badges)
- **Benutzer-Verwaltung** mit automatischem Sync aus Microsoft Entra ID (Azure AD)
- **WLED-Geraete** hinzufuegen, bearbeiten, entfernen mit automatischem Status-Check (online/offline, LED-Anzahl, Firmware)
- **Zuordnung** von Benutzern zu WLED-Segmenten per Dropdown
- **Farb-Regeln** mit Color-Picker fuer jeden Status
- **Telefon-Webhook** fuer Telefonanlage (callstart/callend auf Port 8081)
- **Modulare Zeiterfassung** per CSV oder REST API - kompatibel mit jeder Zeiterfassung
- **Webbasierte Azure-Authentifizierung** direkt im Browser (kein Terminal noetig)
- **Zentrale (Self-Service)** - wechselnde Besetzung des Empfangs-Busylights per Durchwahl, ohne Admin-Passwort
- **Einstellungen** fuer Azure, Polling-Intervall und Zeiterfassung
- **Protokoll** der letzten Statusaenderungen
## Funktionsweise
| Quelle | Beschreibung | Auswirkung auf WLED |
|--------|-------------|---------------------|
| Zeiterfassung | Anwesend / Abwesend / Homeoffice | Grundfarbe (gruen / aus / gelb) |
| Microsoft Teams | Verfuegbar / Beschaeftigt / Nicht stoeren / etc. | Farbe wenn anwesend |
| Telefonanlage (Webhook) | Telefoniert / Frei | Rot wenn aktiv, danach zurueck auf Zeiterfassungs-Status |
**Prioritaet:** Telefon > Teams > Zeiterfassung. Die WLED-Farbe wird nur gesetzt wenn der Mitarbeiter laut Zeiterfassung anwesend oder im Homeoffice ist.
## Voraussetzungen
- Linux-Server (Debian 13+ empfohlen)
- Python 3.10+
- Netzwerkzugriff auf die WLED-Geraete (HTTP Port 80)
- Microsoft Entra ID App-Registrierung (siehe [Azure-Einrichtung](docs/azure-einrichtung.md))
- Optional: Zeiterfassung per CSV oder REST API
- Optional: Telefonanlage mit Webhook-Unterstuetzung (z.B. Starface)
## Installation
### Schnellinstallation (Debian 13)
```bash
curl -s https://git.cynfo.net/christian/busylight_modular/raw/branch/master/install.sh | bash
```
### Manuelle Installation
```bash
# Repository klonen
git clone https://git.cynfo.net/christian/busylight_modular.git /opt/busylight
cd /opt/busylight
# Python Virtual Environment erstellen
python3 -m venv venv
source venv/bin/activate
# Abhaengigkeiten installieren
pip install -r requirements.txt
```
## Einrichtung
### 1. Azure verbinden
Detaillierte Anleitung: [docs/azure-einrichtung.md](docs/azure-einrichtung.md)
Kurzfassung:
1. App-Registrierung in Azure erstellen
2. Oeffentliche Clientflows aktivieren
3. API-Berechtigungen setzen (User.Read.All, Presence.Read.All)
4. Im Web-Interface unter **Einstellungen** die IDs eintragen
5. Auf **"Mit Azure verbinden"** klicken und im Browser anmelden
### 2. Benutzer synchronisieren
Unter **Benutzer** auf "Aus Azure synchronisieren" klicken. Anschliessend:
- Benutzer aktivieren/deaktivieren (nur aktive Benutzer werden abgefragt)
- Personalnummer zuordnen (fuer Zeiterfassung)
- Telefon-Durchwahl zuordnen (fuer Starface-Webhook)
### 3. WLED-Geraete anlegen
Unter **Tuerschilder** die Hostnamen/IP-Adressen der WLED-Busylights eintragen.
Der Status (online/offline, LEDs, Firmware) wird automatisch alle 4 Minuten geprueft.
### 4. Zuordnungen erstellen
Unter **Zuordnungen** jeden aktiven Benutzer einem WLED-Geraet und Segment zuweisen:
- Segment 0 = unten
- Segment 1 = oben
### 5. Farben anpassen
Unter **Farb-Regeln** die Farben fuer jeden Status per Color-Picker anpassen:
| Status | Beschreibung |
|--------|-------------|
| `abwesend` | Benutzer nicht anwesend |
| `homeoffice` | Benutzer im Homeoffice |
| `phone_active` | Benutzer telefoniert (Webhook) |
| `teams_available` | Teams: Verfuegbar |
| `teams_busy` | Teams: Beschaeftigt |
| `teams_donotdisturb` | Teams: Nicht stoeren |
| `teams_inacall` | Teams: In einem Anruf |
| `teams_inameeting` | Teams: In einer Besprechung |
| `teams_presenting` | Teams: Praesentiert |
| `teams_away` | Teams: Abwesend |
| `teams_offline` | Teams: Offline |
| `teams_outofoffice` | Teams: Abwesend (ganztaegig) |
| `teams_inactive` | Teams: Inaktiv |
| `teams_offwork` | Teams: Nicht bei der Arbeit |
## Zeiterfassung
Die Zeiterfassung ist modular - es gibt zwei Wege den Anwesenheitsstatus zu liefern:
### Option A: CSV-Datei
Eine CSV-Datei mit dem Anwesenheitsstatus wird periodisch gelesen. Das Format:
```
~~00100~~,~~1~~
~~00101~~,~~0~~
~~00102~~,~~2~~
```
Status-Codes (konfigurierbar in den Einstellungen):
- `~~0~~` = Abwesend
- `~~1~~` = Anwesend
- `~~2~~` = Homeoffice
Die CSV kann von jedem Zeiterfassungssystem per Script erzeugt werden.
In den **Einstellungen** den Pfad zur CSV eintragen.
### Option B: REST API
Externe Systeme koennen den Status direkt per API setzen (Port 8081):
```bash
# Status setzen
curl -X POST -H "Content-Type: application/json" \
-d '{"status":"anwesend"}' \
http://SERVER:8081/api/attendance/00100
# Alle Status abfragen
curl http://SERVER:8081/api/attendance
```
Beide Optionen koennen parallel genutzt werden.
## Telefonanlage (Webhook)
Die Anwendung startet automatisch einen Webhook-Server auf **Port 8081** fuer die Anbindung einer Telefonanlage (z.B. Starface).
### Webhook-Endpoints
```
GET/POST http://SERVER:8081/{durchwahl}/callstart - Anruf gestartet (WLED -> Rot)
GET/POST http://SERVER:8081/{durchwahl}/callend - Anruf beendet (WLED -> zurueck auf Zeiterfassungs-Status)
```
### Einrichtung
1. Unter **Benutzer** im Web-Interface die **Durchwahl** pro User eintragen (z.B. `123`)
2. In der Telefonanlage den Webhook konfigurieren:
- Anrufbeginn: `http://SERVER-IP:8081/123/callstart`
- Anrufende: `http://SERVER-IP:8081/123/callend`
### Verhalten
- `callstart`: Setzt WLED auf Telefon-Farbe (Standard: Rot), aber **nur** wenn der Mitarbeiter laut Zeiterfassung anwesend oder im Homeoffice ist
- `callend`: Setzt WLED zurueck auf den Zeiterfassungs-Status. Der Teams-Status wird beim naechsten Polling-Zyklus (15s) automatisch aktualisiert
- Waehrend eines aktiven Telefonats ueberschreibt das Teams-Polling die Farbe nicht
- Die Telefon-Farbe (`phone_active`) ist unter **Farb-Regeln** konfigurierbar
## Zentrale (Self-Service)
Ein Busylight im Empfangsbereich wird wechselnd besetzt. Damit die Mitarbeiter das
selbst umstellen koennen, gibt es eine eigene Seite, die **ohne Admin-Passwort**
erreichbar ist und die Durchwahl als Kennung nutzt.
### Einrichtung
1. Unter **Benutzer** die **Durchwahl** pro Mitarbeiter eintragen (dieselbe wie fuer den Telefon-Webhook)
2. Unter **Einstellungen > Zentrale (Self-Service)** das Busylight des Empfangs und das Segment auswaehlen
3. Die Seite `http://SERVER:8080/zentrale` an die Mitarbeiter geben
### Bedienung
```
http://SERVER:8080/zentrale - Durchwahl eingeben und Zentrale uebernehmen
http://SERVER:8080/zentrale/{durchwahl} - Direktlink pro Person, z.B. als QR-Code
```
Der Direktlink zeigt nur den Stand an und aendert nichts - das Umstellen passiert
erst per Knopfdruck.
### Verhalten
- **Zentrale uebernehmen**: Der Mitarbeiter wird auf das Segment der Zentrale
gesetzt. Das eigene Tuerschild gibt er dabei ab, weil ein Mitarbeiter nur einer
Zuordnung gleichzeitig zugewiesen sein kann. Der alte Platz wird gemerkt und das
Segment ausgeschaltet.
- **Zurueck auf mein Tuerschild**: Der gemerkte Platz wird wiederhergestellt und die
Zentrale freigegeben (Farbe `abwesend`, Standard: aus).
- **Vorgaenger**: Wer die Zentrale abgibt, wird dabei automatisch auf sein eigenes
Tuerschild zurueckgesetzt. Er muss also nicht erst selbst "zurueck" klicken, damit
der Nachfolger uebernehmen kann. Ist sein Tuerschild inzwischen jemand anderem
zugeordnet oder war keines gemerkt, bleibt er ohne Zuordnung - die Meldung auf der
Seite sagt, welcher der beiden Faelle vorliegt.
- **Belegter Heimatplatz**: Ist das eigene Tuerschild inzwischen jemand anderem
zugeordnet, wird die Rueckkehr abgelehnt und nichts geaendert. Dann muss der
Administrator die Zuordnung aufloesen.
- **Sofortiges Umschalten**: Die Farbe wird direkt ans Geraet geschickt. Das Polling
allein wuerde nicht reichen, weil es nur bei einer Statusaenderung sendet und der
Status des Mitarbeiters beim Platzwechsel unveraendert ist.
- **Protokoll**: Jeder Wechsel landet unter **Protokoll** (`zentrale` beim Uebernehmen,
`zentrale_frei` beim Freigeben).
- **Vorrang des Administrators**: Wird ein Mitarbeiter unter **Zuordnungen** neu
zugeordnet, verfaellt sein gemerkter Heimatplatz - die ausdrueckliche Zuordnung
gilt. Wird seine Zuordnung dort nur geloescht, bleibt der gemerkte Platz erhalten,
damit er von der Zentrale zurueckkehren kann.
### Sicherheit
Die Durchwahl ist eine Kennung, kein Geheimnis. Wer Port 8080 erreicht, kann jeden
Kollegen auf die Zentrale setzen. Fuer den internen Gebrauch ist das gewollt - die
Aenderung ist sofort sichtbar, im Protokoll nachvollziehbar und mit einem Klick
zurueckzunehmen. Die Seite gehoert deshalb nicht ins Internet.
## Starten
### Manuell (zum Testen)
```bash
cd /opt/busylight
source venv/bin/activate
python app.py
```
Dabei werden zwei Server gestartet:
- **Port 8080**: Web-Oberflaeche
- **Port 8081**: Webhook-Server (Telefonanlage + Zeiterfassungs-API)
### Als systemd-Service (Produktivbetrieb)
```bash
# Service installieren (bei manueller Installation)
sudo cp busylight.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now busylight
# Status pruefen
sudo systemctl status busylight
# Logs anzeigen
sudo journalctl -u busylight -f
# Update
cd /opt/busylight && git pull
sudo systemctl restart busylight
```
## Dashboard
Das **Dashboard** zeigt alle Busylights mit dem aktuellen Status in Echtzeit an und aktualisiert sich automatisch alle 10 Sekunden. Pro Benutzer werden drei Status-Badges angezeigt:
- **Zeiterfassung**: Anwesend (gruen) / Abwesend (grau) / Homeoffice (gelb)
- **Teams**: Verfuegbar / Beschaeftigt / Nicht stoeren / Offline / etc.
- **Telefon**: Telefoniert (rot) / Frei (grau)
## Hintergrund-Jobs
| Job | Intervall | Beschreibung |
|-----|-----------|-------------|
| Status-Polling | 15 Sekunden (konfigurierbar) | Teams-Presence und Zeiterfassungs-Status abfragen, WLED aktualisieren |
| Device-Check | 240 Sekunden | Alle WLED-Geraete auf Erreichbarkeit pruefen |
## Projektstruktur
```
busylight/
├── app.py # FastAPI Web-Anwendung + Azure Auth + Startup
├── webhook.py # Webhook-Server + Zeiterfassungs-API (Port 8081)
├── database.py # SQLite Datenbank-Schema und Hilfsfunktionen
├── graph_client.py # Microsoft Graph API Client
├── wled_client.py # WLED HTTP API Client
├── scheduler.py # Hintergrund-Jobs (Polling + Device-Check)
├── install.sh # Installer fuer Debian 13
├── requirements.txt # Python-Abhaengigkeiten
├── busylight.service # systemd Service-Datei
├── docs/ # Dokumentation
├── static/
│ └── style.css # CSS Styles
└── templates/ # Jinja2 HTML-Templates (u.a. zentrale.html)
```
## Ports und Firewall
| Port | Richtung | Zweck |
|------|----------|-------|
| 8080/tcp | Eingehend | Web-Oberflaeche |
| 8081/tcp | Eingehend | Webhook-API (Telefon + Zeiterfassung) |
| 80/tcp | Ausgehend | WLED HTTP API |
| 443/tcp | Ausgehend | Microsoft Graph API |
## Troubleshooting
### Dashboard zeigt nur graue Segmente
- Zeiterfassung pruefen (CSV-Pfad oder API-Status)
- Benutzer muessen aktiviert sein UND eine Personalnummer haben
- Zuordnung Benutzer -> WLED-Geraet + Segment muss vorhanden sein
### Teams-Status wird nicht angezeigt
- Azure-Authentifizierung pruefen (Sidebar zeigt "Azure verbunden")
- API-Berechtigungen in Azure pruefen (Presence.Read.All)
### Telefon-Webhook reagiert nicht
- Port 8081 erreichbar? `curl http://SERVER:8081/`
- Durchwahl beim Benutzer eingetragen?
- Benutzer muss laut Zeiterfassung anwesend oder im Homeoffice sein
### Azure Auth schlaegt fehl
- "Oeffentliche Clientflows zulassen" muss in der Azure App aktiviert sein
- Siehe [docs/azure-einrichtung.md](docs/azure-einrichtung.md) fuer Details
### WLED-Geraete offline
- Unter Tuerschilder den Status pruefen ("Alle jetzt pruefen")
- Netzwerkverbindung pruefen: `curl http://HOSTNAME/json/info`
### Zentrale-Seite sagt "noch nicht eingerichtet"
Unter **Einstellungen > Zentrale (Self-Service)** ist kein Busylight ausgewaehlt.
Ohne diese Einstellung weiss die Anwendung nicht, welches Geraet der Empfang ist.
### Durchwahl wird auf der Zentrale-Seite nicht erkannt
Die Durchwahl muss unter **Benutzer** exakt so eingetragen sein wie eingegeben, und
der Benutzer muss **aktiv** sein. Inaktive Benutzer werden bewusst abgewiesen.
### Nach git pull aendert sich nichts
- Service neustarten: `sudo systemctl restart busylight`