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):

  1. Einstellungen → Automationen & Szenen → „+ AUTOMATION ERSTELLEN“
  2. „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.yaml Syntax: 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 PunkteTraces
  • 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:

  1. Wireshark Installation auf macOS
  2. Cuety Remote App auf dem Mac installiert (funktioniert auch auf macOS!)
  3. Netzwerk-Traffic aufgezeichnet mit Filter: ip.dst == xxx.xxx.xxx.xxx && udp.dstport == 16945
  4. Pakete analysiert: Verschiedene Szenen gedrückt und Hex-Dumps verglichen
  5. Muster erkannt: Byte 4 = Szene, Byte 14 = Counter, Byte 20 = Button State
  6. Protokoll nachgebaut in Python
  7. 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