Tutorials

Topic-Design fürs Smart Home: eine praxistaugliche MQTT-Struktur

Eine praxistaugliche MQTT-Topic-Struktur fürs Smart Home: Räume und Geräte, State- vs. Command-Topics, Retained, Verfügbarkeit, Home-Assistant-Discovery und ACLs.

Aktualisiert · 9 Min. Lesezeit

Topic-Design fürs Smart Home: eine praxistaugliche MQTT-Struktur

Eine gute MQTT-Topic-Struktur fürs Smart Home ist langweilig und vorhersehbar: ein Zweig pro Raum, einer pro Gerät und ein fester Satz an Blatt-Topics für State, Befehle und Verfügbarkeit. Dieser Artikel schlägt eine konkrete Struktur zum Übernehmen vor, erklärt, warum Befehle und State auf getrennten Topics liegen, welche Nachrichten retained sein sollten und wie sich die Struktur auf Home-Assistant-Discovery und Broker-ACLs abbilden lässt.

Die Struktur
home/<room>/<device>/state          current state, JSON, retained
home/<room>/<device>/set            commands to the device, NOT retained
home/<room>/<device>/availability   "online" / "offline", retained, Last Will
home/<room>/<device>/event          one-off events (button press), NOT retained

Benennungsregeln

Topic-Namen sind Teil der API deines Zuhauses. Sie später zu ändern heißt, jede Automation anzufassen – also leg die Regeln einmal fest:

  • ASCII in Kleinbuchstaben, Wörter mit - verbunden: living-room, ceiling-light. Keine Leerzeichen, keine Umlaute, keine Emojis.
  • Kein führender Slash. /home/kitchen erzeugt eine leere erste Ebene und passt nicht auf home/#.
  • Nie mit $ beginnen: Dieses Präfix ist für Broker-Topics wie $SYS reserviert.
  • Substantive für Orte und Dinge, feste Wörter für die Blätter: state, set, availability, event. Mische nicht status, cmd und command über verschiedene Geräte.
  • Konstante Tiefe. Jedes Gerät sitzt genau auf home/<room>/<device>, sodass home/+/+/state immer „State aller Geräte“ bedeutet.

Allgemeinere Hinweise findest du in Best Practices für MQTT-Topic-Design.

Nach Raum oder nach ID?

StilBeispielGut fürNachteil
Nach Raumhome/kitchen/ceiling-light/stateTypische Wohnungen, handgeschriebene AutomationenUmzug eines Geräts ändert seine Topics
Nach IDhome/device/shelly-a8032a/stateGroße oder sich oft ändernde InstallationenRaum muss woanders gepflegt werden (Metadaten, HA-Bereiche)
Hybridhome/kitchen/shelly-a8032a/stateEindeutige Namen mit lesbarer GruppierungÄndert sich beim Umzug trotzdem

Für die meisten Wohnungen gewinnt die raumbasierte Struktur bei der Lesbarkeit. Hast du Geräte, die wandern (ein mobiler Heizlüfter, ein Sensor auf einem Rollwagen), gib ihnen eine stabile ID und häng sie unter einen neutralen Zweig wie home/mobile/.

Befehle vs. State

Die wichtigste Regel: Das Gerät publisht den State, alle anderen publishen Befehle, und beide teilen sich nie ein Topic.

Ein Schaltvorgang von Anfang bis Ende
app      ──PUBLISH home/kitchen/ceiling-light/set   {"state":"ON"}            (not retained)
light    ──applies the command, then
light    ──PUBLISH home/kitchen/ceiling-light/state {"state":"ON","brightness":255}  (retained)
app, HA  ◀─ receive the new state and update the UI

Warum trennen?

  • Das State-Topic spiegelt immer die Realität. Schlägt der Befehl fehl (Relais hängt, Gerät offline), zeigt das State-Topic weiterhin die Wahrheit, und die Oberfläche lügt nicht.
  • Keine Echo-Schleifen. Ein Gerät, das sein eigenes State-Topic abonniert, würde seine eigenen Updates empfangen. Mit getrenntem set und state abonniert jede Seite nur, was sie braucht.
  • Sichere ACLs. Ein Dashboard-User darf auf .../set schreiben und .../state lesen, kann aber den State des Geräts nicht fälschen.

Das Gerät sollte den neuen State nach dem Ausführen des Befehls publishen, auch wenn sich nichts geändert hat. Auf genau diese Bestätigung warten Oberflächen und Automationen. Für Request/Response-Muster mit Correlation-IDs siehe Command and Control über MQTT.

Was retained wird

TopicRetained?Warum
.../stateJaEin später geöffnetes Dashboard zeigt sofort den aktuellen State
.../availabilityJaNeue Subscriber wissen, welche Geräte offline sind
.../setNeinEin retained Befehl wird bei jedem Reconnect wiederholt: Geisterschaltungen nach einem Stromausfall
.../eventNein„Taste gedrückt“ ist nur im Moment des Geschehens sinnvoll
homeassistant/.../configJaDiscovery-Konfigurationen müssen Neustarts von Home Assistant überleben

Wurde ein Befehl versehentlich retained, löschst du ihn, indem du eine leere retained Nachricht auf dasselbe Topic publishst. Details unter Retained Messages.

Verfügbarkeit mit Last Will

Jedes Gerät setzt beim Verbinden einen Last Will auf sein availability-Topic: Payload offline, retained, QoS 1. Direkt nach dem Verbinden publisht es online, retained. Verliert das Gerät Strom oder WLAN, publisht der Broker offline, sobald der Keep Alive abläuft – nach etwa 1,5 × dem Keep-Alive-Intervall. Siehe MQTT Last Will.

Ein Gerät in 40 Zeilen

Dieses Python-Skript (paho-mqtt 2.x) verhält sich wie eine dimmbare Lampe, die der Struktur folgt. Die Beispiele verwenden das Präfix testmqtt/, damit sie gefahrlos auf einem geteilten Test-Endpoint laufen; bei dir zu Hause lässt du es weg.

fake_light.py
import json, os
import paho.mqtt.client as mqtt

BASE = "testmqtt/home/kitchen/ceiling-light"
state = {"state": "OFF", "brightness": 0}

def publish_state(client):
    client.publish(f"{BASE}/state", json.dumps(state), qos=1, retain=True)

def on_connect(client, userdata, flags, reason_code, properties):
    client.publish(f"{BASE}/availability", "online", qos=1, retain=True)
    client.subscribe(f"{BASE}/set", qos=1)
    publish_state(client)

def on_message(client, userdata, msg):
    try:
        cmd = json.loads(msg.payload)
    except ValueError:
        cmd = {"state": msg.payload.decode(errors="replace").strip().upper()}
    if not isinstance(cmd, dict):
        cmd = {}
    if cmd.get("state") in ("ON", "OFF"):
        state["state"] = cmd["state"]
    if isinstance(cmd.get("brightness"), int):
        state["brightness"] = max(0, min(255, cmd["brightness"]))
    publish_state(client)  # always confirm with the real state

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="kitchen-ceiling-light")
if os.getenv("MQTT_USER"):
    client.username_pw_set(os.environ["MQTT_USER"], os.getenv("MQTT_PASS"))
client.will_set(f"{BASE}/availability", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_forever()

Den State in on_connect zu publishen ist wichtig: Nach jedem Reconnect wird der retained State aufgefrischt, und die Subscriptions werden neu angelegt, auch wenn der Broker keine Session aufbewahrt hat.

Die Struktur testen

Beobachte State und Verfügbarkeit aller Geräte in einem Terminal:

Terminal 1 — beobachten
mosquitto_sub -h broker.connectmq.com -p 1883 -v \
  -t 'testmqtt/home/+/+/state' -t 'testmqtt/home/+/+/availability'

Sende Befehle aus einem zweiten:

Terminal 2 — Befehle
mosquitto_pub -h broker.connectmq.com -p 1883 -q 1 \
  -t 'testmqtt/home/kitchen/ceiling-light/set' -m '{"state":"ON","brightness":180}'

mosquitto_pub -h broker.connectmq.com -p 1883 -q 1 \
  -t 'testmqtt/home/kitchen/ceiling-light/set' -m 'off'

Prüfe dann die Verhaltensweisen, die die Struktur verspricht:

  1. Jeder Befehl erzeugt genau eine State-Nachricht.
  2. Starte Terminal 1 neu: Aktueller State und online erscheinen sofort (retained).
  3. Beende das Skript mit kill -9: Nach dem Keep-Alive-Timeout erscheint offline.
  4. Starte das Skript neu: Kein Befehl wird wiederholt, weil set nie retained war.

Dasselbe geht im Browser mit dem Online-MQTT-Client, und jedes Subscription-Muster kannst du mit dem Topic-Matcher gegen deine Topics prüfen.

Nützliche Subscriptions

BedarfSubscription
Alles im Haushome/#
Alles in der Küchehome/kitchen/#
State aller Gerätehome/+/+/state
Welche Geräte offline sindhome/+/+/availability
Alle Tastendrückehome/+/+/event

Weil die Tiefe konstant ist, reichen einstufige +-Wildcards, und # brauchst du außerhalb des Debuggens selten. Siehe MQTT-Wildcards im Detail.

Home-Assistant-Discovery

Home Assistant kann Entitäten automatisch aus retained Konfigurationsnachrichten unter homeassistant/<component>/<object_id>/config anlegen. Die Struktur lässt sich direkt auf die Discovery-Felder abbilden:

homeassistant/switch/kitchen_ceiling_light/config (retained)
{
  "name": "Kitchen ceiling light",
  "unique_id": "kitchen_ceiling_light",
  "state_topic": "home/kitchen/ceiling-light/state",
  "value_template": "{{ value_json.state }}",
  "command_topic": "home/kitchen/ceiling-light/set",
  "payload_on": "ON",
  "payload_off": "OFF",
  "availability_topic": "home/kitchen/ceiling-light/availability"
}

payload_available und payload_not_available sind standardmäßig online und offline – genau das, was der Last Will oben publisht. Die vollständige Einrichtung der Integration findest du unter Home Assistant und MQTT.

ACLs, die der Struktur folgen

Ein konsistenter Baum hält die Berechtigungen kurz:

ClientSchreibenLesen
Jedes Geräteigenes state, availability, eventeigenes set
Home Assistant / Automations-Hubhome/+/+/set, homeassistant/#home/#
Wand-Tablet mit Dashboardhome/+/+/sethome/+/+/state, home/+/+/availability
Gast- oder Monitoring-Usernichtshome/+/+/availability

Eine Zugangsberechtigung pro Gerät bedeutet: Eine kompromittierte Steckdose kann nur über sich selbst lügen, aber nicht dein Haustürschloss öffnen.

Die Struktur kostet nichts und zahlt sich mit jedem neuen Gerät aus: konstante Tiefe, getrenntes state und set, retained State und Verfügbarkeit, nie retained Befehle. Schreib die Regeln auf, teste jedes neue Gerät mit den vier Prüfungen oben, und deine Automationen funktionieren weiter, wenn das Zuhause wächst.

Häufig gestellte Fragen

Sollten MQTT-Command-Topics retained sein?

Nein. Ein retained Befehl wird dem Gerät bei jedem Reconnect erneut zugestellt, sodass sich eine Lampe nach einem Stromausfall von selbst einschalten kann. Halte State und Verfügbarkeit retained, sende Befehle ohne Retain-Flag.

Sollte der Raum Teil des Topics sein?

In einem typischen Zuhause ja: home/kitchen/ceiling-light ist gut lesbar, und mit home/kitchen/# abonnierst du einen ganzen Raum. Der Nachteil: Ziehst du ein Gerät um, ändern sich seine Topics. Größere Installationen nutzen oft stabile Geräte-IDs und führen den Raum als Metadatum.

Ein JSON-State-Topic oder ein Topic pro Eigenschaft?

Eine einzige JSON-State-Nachricht pro Gerät lässt sich leichter konsistent halten und entspricht dem, was die JSON-Templates von Home Assistant erwarten. Topics pro Eigenschaft sind für sehr kleine Sensoren und manche Tools einfacher. Entscheide dich für einen Stil und nutze ihn für alle Geräte.