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.
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 retainedBenennungsregeln
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/kitchenerzeugt eine leere erste Ebene und passt nicht aufhome/#. - Nie mit
$beginnen: Dieses Präfix ist für Broker-Topics wie$SYSreserviert. - Substantive für Orte und Dinge, feste Wörter für die Blätter:
state,set,availability,event. Mische nichtstatus,cmdundcommandüber verschiedene Geräte. - Konstante Tiefe. Jedes Gerät sitzt genau auf
home/<room>/<device>, sodasshome/+/+/stateimmer „State aller Geräte“ bedeutet.
Allgemeinere Hinweise findest du in Best Practices für MQTT-Topic-Design.
Nach Raum oder nach ID?
| Stil | Beispiel | Gut für | Nachteil |
|---|---|---|---|
| Nach Raum | home/kitchen/ceiling-light/state | Typische Wohnungen, handgeschriebene Automationen | Umzug eines Geräts ändert seine Topics |
| Nach ID | home/device/shelly-a8032a/state | Große oder sich oft ändernde Installationen | Raum muss woanders gepflegt werden (Metadaten, HA-Bereiche) |
| Hybrid | home/kitchen/shelly-a8032a/state | Eindeutige 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.
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 UIWarum 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
setundstateabonniert jede Seite nur, was sie braucht. - Sichere ACLs. Ein Dashboard-User darf auf
.../setschreiben und.../statelesen, 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
| Topic | Retained? | Warum |
|---|---|---|
.../state | Ja | Ein später geöffnetes Dashboard zeigt sofort den aktuellen State |
.../availability | Ja | Neue Subscriber wissen, welche Geräte offline sind |
.../set | Nein | Ein retained Befehl wird bei jedem Reconnect wiederholt: Geisterschaltungen nach einem Stromausfall |
.../event | Nein | „Taste gedrückt“ ist nur im Moment des Geschehens sinnvoll |
homeassistant/.../config | Ja | Discovery-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.
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:
mosquitto_sub -h broker.connectmq.com -p 1883 -v \
-t 'testmqtt/home/+/+/state' -t 'testmqtt/home/+/+/availability'Sende Befehle aus einem zweiten:
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:
- Jeder Befehl erzeugt genau eine State-Nachricht.
- Starte Terminal 1 neu: Aktueller State und
onlineerscheinen sofort (retained). - Beende das Skript mit
kill -9: Nach dem Keep-Alive-Timeout erscheintoffline. - Starte das Skript neu: Kein Befehl wird wiederholt, weil
setnie 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
| Bedarf | Subscription |
|---|---|
| Alles im Haus | home/# |
| Alles in der Küche | home/kitchen/# |
| State aller Geräte | home/+/+/state |
| Welche Geräte offline sind | home/+/+/availability |
| Alle Tastendrücke | home/+/+/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:
{
"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:
| Client | Schreiben | Lesen |
|---|---|---|
| Jedes Gerät | eigenes state, availability, event | eigenes set |
| Home Assistant / Automations-Hub | home/+/+/set, homeassistant/# | home/# |
| Wand-Tablet mit Dashboard | home/+/+/set | home/+/+/state, home/+/+/availability |
| Gast- oder Monitoring-User | nichts | home/+/+/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.


