Visual Productions LPU-1 mit Home Assistant steuern
Einleitung
Das Visual Productions LPU-1 ist ein preiswerter DMX-Controller, der sich per iPad/iPhone-App (Cuety und Cuety Remote) steuern lässt. Ideal für kleinere Veranstaltungsorte, die ihre Bühnenbeleuchtung einfach steuern möchten.
Das Problem: Das LPU-1 bietet keine offizielle API für externe Steuerung. Die erweiterten Protokolle (HTTP, OSC, UDP, TCP) sind nur im teureren LPU-2 verfügbar.
In diesem Artikel zeige ich, wie man das LPU-1 trotzdem in Home Assistant integriert – durch Analyse des proprietären Protokolls.
Voraussetzungen
- Visual Productions LPU-1
- Home Assistant Installation mit SSH-Zugriff
- Python 3 (auf Home Assistant vorinstalliert)
- Grundkenntnisse in YAML
- Optional: Wireshark für eigene Protokoll-Analysen
Das Protokoll verstehen
Kurz-Zusammenfassung
Die Cuety Remote App kommuniziert mit dem LPU-1 über:
- Protokoll: UDP
- Port: 16945
- Paketgröße: 24 Bytes
- Aufbau: Button Press + Button Release
Paket-Struktur
Jeder Szenen-Aufruf besteht aus zwei UDP-Paketen:
Button Press (24 Bytes):
29 02 22 00 [SCENE] 00 00 00 00 00 ff ff 04 00 [CTR] 01 00 00 00 00 01 00 00 00
Button Release (24 Bytes):
29 02 22 00 [SCENE] 00 00 00 00 00 ff ff 04 00 [CTR] 01 00 00 00 00 00 00 00 00
Wichtige Bytes:
- Byte 4: Szenen-Nummer (0-indexed, d.h. Szene 1 = 0x00, Szene 5 = 0x04)
- Byte 14: Counter (muss sich ändern, sonst ignoriert das LPU-1 das Paket)
- Byte 20: Button-State (0x01 = gedrückt, 0x00 = losgelassen)
Schritt 1: Python-Script erstellen
Erstelle auf deinem Home Assistant Server die Datei /config/scripts/lpu_control.py:
#!/usr/bin/env python3
# lpu_control.py - Visual Productions LPU-1 Controller
# Steuert das LPU-1 über das proprietäre UDP-Protokoll
import socket
import time
import sys
import random
def trigger_scene(scene_number, host='xxx.xxx.xxx.xxx', port=16945):
"""
Triggert eine Szene auf dem LPU-1
Args:
scene_number: Szenen-Nummer 1-64 (wird intern zu 0-63 konvertiert)
host: IP-Adresse des LPU-1 (Standard: xxx.xxx.xxx.xxx)
port: UDP-Port (Standard: 16945)
"""
if scene_number == 0:
return # 0 = keine Aktion
scene_index = scene_number - 1 # 0-indexed
counter = random.randint(0, 255) # Zufälliger Counter
# Button Press Paket
press = bytearray([
0x29, 0x02, 0x22, 0x00,
scene_index, # Szenen-Nummer (Byte 4)
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x04, 0x00,
counter, 0x01, # Counter (Byte 14-15)
0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00 # Button State: pressed
])
# Button Release Paket
release = bytearray([
0x29, 0x02, 0x22, 0x00,
scene_index,
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x04, 0x00,
(counter + 1) & 0xFF, 0x01, # Counter +1
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 # Button State: released
])
# UDP-Paket senden
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(press, (host, port))
time.sleep(0.1) # 100ms Pause zwischen Press und Release
sock.sendto(release, (host, port))
sock.close()
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python3 lpu_control.py <scene_number>")
print("Example: python3 lpu_control.py 5")
sys.exit(1)
scene = int(sys.argv[1])
if scene < 0 or scene > 64:
print("Scene number must be between 0 and 64")
sys.exit(1)
trigger_scene(scene)
Wichtig: Passe die IP-Adresse in Zeile 11 an deine LPU-1 IP an!
Script ausführbar machen:
chmod +x /config/scripts/lpu_control.py
Test
# SSH auf Home Assistant
ssh root@homeassistant.local
# Script testen
python3 /config/scripts/lpu_control.py 1 # Szene 1
python3 /config/scripts/lpu_control.py 5 # Szene 5
Die Lichter sollten jetzt wechseln!
Schritt 2: Home Assistant Konfiguration
configuration.yaml
Füge folgendes zu deiner configuration.yaml hinzu:
# Input Number für Szenen-Auswahl
input_number:
lpu_buehne_szene:
name: "LPU Bühne Szene"
icon: mdi:spotlight-beam
min: 0
max: 64
step: 1
mode: box
# Shell Command zum Ausführen des Python-Scripts
shell_command:
lpu_trigger: "python3 /config/scripts/lpu_control.py {{ scene }}"
Wichtig: Falls du bereits einen shell_command: Block hast, füge nur die Zeile lpu_trigger hinzu!
Konfiguration neu laden
Entwicklerwerkzeuge → YAML → Alle YAML-Konfigurationen neu laden
Schritt 3: Automation erstellen
Die Automation verbindet die input_number Entity mit dem Shell Command.
Über die UI (empfohlen):
- Einstellungen → Automationen & Szenen → „+ AUTOMATION ERSTELLEN“
- „Leere Automation erstellen“
Auslöser:
- Auslösertyp: Zustand
- Entität:
input_number.lpu_buehne_szene
Aktion:
- Aktionstyp: Aktion aufrufen
- Aktion:
shell_command.lpu_trigger - Umschalten auf YAML-Modus und eingeben:
action: shell_command.lpu_trigger
data:
scene: "{{ states('input_number.lpu_buehne_szene') | int }}"
Name: LPU Bühne - Szene ändern
Speichern!
Alternative: YAML (automations.yaml)
automation:
- id: lpu_buehne_steuerung
alias: "LPU Bühne - Szene ändern"
description: "Triggert LPU-1 wenn Szene geändert wird"
trigger:
- platform: state
entity_id: input_number.lpu_buehne_szene
condition:
- condition: template
value_template: "{{ trigger.to_state.state != trigger.from_state.state }}"
action:
- service: shell_command.lpu_trigger
data:
scene: "{{ states('input_number.lpu_buehne_szene') | int }}"
mode: single
Schritt 4: Testen
Test über Aktionen
Entwicklerwerkzeuge → Aktionen
service: input_number.set_value
target:
entity_id: input_number.lpu_buehne_szene
data:
value: 5
Die Lichter sollten automatisch zu Szene 5 wechseln! 🎉
Verwendung
In Scripts (Hue + LPU kombiniert)
script:
licht_konzert_start:
alias: "Konzert Start"
sequence:
# Raumlicht (Hue)
- service: scene.turn_on
target:
entity_id: scene.hue_raum_konzert
# Bühnenlicht (LPU-1)
- service: input_number.set_value
target:
entity_id: input_number.lpu_buehne_szene
data:
value: 1
licht_pause:
alias: "Pause"
sequence:
- service: scene.turn_on
target:
entity_id: scene.hue_raum_normal
- service: input_number.set_value
target:
entity_id: input_number.lpu_buehne_szene
data:
value: 5
licht_abbau:
alias: "Abbau"
sequence:
- service: scene.turn_on
target:
entity_id: scene.hue_raum_hell
- service: input_number.set_value
target:
entity_id: input_number.lpu_buehne_szene
data:
value: 10
In Automationen
automation:
- alias: "Konzert beginnt automatisch"
trigger:
- platform: time
at: "20:00:00"
action:
- service: script.licht_konzert_start
- alias: "Pause nach 1 Stunde"
trigger:
- platform: time
at: "21:00:00"
action:
- service: script.licht_pause
Dashboard mit Buttons
type: vertical-stack
title: Bühnenlicht
cards:
- type: entities
entities:
- entity: input_number.lpu_buehne_szene
name: Szene direkt wählen
- type: horizontal-stack
cards:
- type: button
name: Konzert Start
icon: mdi:play
tap_action:
action: call-service
service: input_number.set_value
target:
entity_id: input_number.lpu_buehne_szene
data:
value: 1
- type: button
name: Pause
icon: mdi:pause
tap_action:
action: call-service
service: input_number.set_value
target:
entity_id: input_number.lpu_buehne_szene
data:
value: 5
- type: button
name: Abbau
icon: mdi:stop
tap_action:
action: call-service
service: input_number.set_value
target:
entity_id: input_number.lpu_buehne_szene
data:
value: 10
Erweiterte Konfiguration
Mehrere LPU-1 Geräte
Falls du mehrere LPU-1 Controller hast, erstelle für jeden ein eigenes Script:
lpu_buehne_control.py (mit IP 192.168.1.100)
lpu_bar_control.py (mit IP 192.168.1.101)
Dann in configuration.yaml:
input_number:
lpu_buehne_szene:
name: "Bühne Szene"
min: 0
max: 64
step: 1
lpu_bar_szene:
name: "Bar Szene"
min: 0
max: 64
step: 1
shell_command:
lpu_buehne_trigger: "python3 /config/scripts/lpu_buehne_control.py {{ scene }}"
lpu_bar_trigger: "python3 /config/scripts/lpu_bar_control.py {{ scene }}"
Dynamische IP-Konfiguration
Wenn sich die IP des LPU-1 ändern kann, erstelle eine input_text Entity:
input_text:
lpu_ip:
name: "LPU-1 IP-Adresse"
initial: "xxx.xxx.xxx.xxx"
icon: mdi:ip-network
Passe das Python-Script an, um die IP als Parameter zu akzeptieren, und ändere den shell_command:
shell_command:
lpu_trigger: "python3 /config/scripts/lpu_control.py {{ scene }} {{ states('input_text.lpu_ip') }}"
Troubleshooting
Script funktioniert nicht
Test im Terminal:
ssh root@homeassistant.local
python3 /config/scripts/lpu_control.py 1
Falls Fehler auftreten:
- Prüfe Dateiberechtigungen:
chmod +x /config/scripts/lpu_control.py - Prüfe IP-Adresse im Script
- Prüfe Netzwerkverbindung:
ping xxx.xxx.xxx.xxx
Shell Command nicht gefunden
- Prüfe
configuration.yamlSyntax: Entwicklerwerkzeuge → YAML → Konfiguration prüfen - Lade YAML neu: Entwicklerwerkzeuge → YAML → Alle YAML-Konfigurationen neu laden
- Achte auf korrekte Einrückung (2 Leerzeichen)
Automation triggert nicht
- Prüfe ob Automation aktiviert ist: Einstellungen → Automationen & Szenen
- Schaue in die Traces: Automation anklicken → Drei Punkte → Traces
- Prüfe Logs: Einstellungen → System → Protokolle
Lichter wechseln nicht zuverlässig
- Der Counter muss sich ändern! Das Script verwendet
random.randint()dafür - Prüfe ob das LPU-1 erreichbar ist
- Erhöhe ggf. die Pause zwischen Press und Release auf 0.2 Sekunden
Technische Details: Wie ich das Protokoll analysiert habe
Für Interessierte: So habe ich das Protokoll reverse-engineered:
- Wireshark Installation auf macOS
- Cuety Remote App auf dem Mac installiert (funktioniert auch auf macOS!)
- Netzwerk-Traffic aufgezeichnet mit Filter:
ip.dst == xxx.xxx.xxx.xxx && udp.dstport == 16945 - Pakete analysiert: Verschiedene Szenen gedrückt und Hex-Dumps verglichen
- Muster erkannt: Byte 4 = Szene, Byte 14 = Counter, Byte 20 = Button State
- Protokoll nachgebaut in Python
- Iterativ getestet bis es zuverlässig funktionierte
Fazit
Mit dieser Integration lässt sich das LPU-1 nahtlos in Home Assistant einbinden, obwohl es keine offizielle API bietet. Die Lösung ist stabil und funktioniert zuverlässig.
Vorteile:
- ✅ Vollständige Kontrolle über alle 64 Szenen
- ✅ Integration mit anderen Smart Home Geräten (Hue, Zigbee, etc.)
- ✅ Zeitgesteuerte Automationen möglich
- ✅ Dashboard mit Buttons
- ✅ Keine zusätzliche Hardware nötig
Nachteile:
- ❌ Kein Feedback vom LPU-1 (welche Szene ist aktiv?)
- ❌ Proprietäres Protokoll könnte sich mit Firmware-Updates ändern
- ❌ Funktioniert nur mit LPU-1, nicht mit LPU-2 (dieses hat ein HTTP API)
Für kleine bis mittelgroße Venues ist diese Lösung perfekt geeignet!
Quellen & Links
Hast du Fragen oder Verbesserungsvorschläge? Schreib einen Kommentar!
Letzte Aktualisierung: Januar 2026