MQTT Einführung Teil 2
MQTT Teil 2
Konzepte und best practices
Vorwort
Wer den Teil 1 noch nicht gelesen hat, ist herzlich eingeladen das vorher noch schnell zu machen :)
Dieser Artikel beschreibt, etwas trocken, weiterführend Konzepte und Denkweisen des Übertragungsprotokolls MQTT. Es wird etwas technischer als die Einführung und der ein oder andere Vergleich zu anderen Protokollen wird auch gezogen. Vieles davon kann man auch so nachlesen, wenngleich nicht immer auf deutsch. Ich gehe davon aus, dass der Leser MQTT vornehmlich mit FHEM einsetzen will.
Ein Beispielkurs ist in Planung. Dort werden wir die Konzepte praktisch kennen lernen.
Retained Messages
Szenario
Stellen wir uns vor, wir starten FHEM neu. Wer HomeMatic Devices kennt weiß, spätestens nach einem Blick ins Logfile, dass nun zunächst ein Statusrequest aller Geräte erfolgt. Schließlich konnte sich zwischen Beenden und Neustart von FHEM etwas geändert haben.
- HomeMatic
- Es wird jedes Gerät angesprochen und abgefragt:
- "Lieber Lichtschalter, was tust du grade?"
- "Ich lasse das Licht über dir leuchten mein User."
MQTT hat hier ein anderes Konzept. Wenn in ein Topic gepostet wird, kann ein "retained Flag" gesetzt werden.
Das bedeutet
Die zuletzt gepostete Nachricht wird vom Broker nicht gelöscht. Publishen wir also den Status unseres Lichts:
- zuHause/1OG/Kueche/Licht/state "on" oder "off" + retained flag
Sobald wir dieses Topic auf einem Gerät subscriben, bekommen wir den Status angezeigt. Ein neu startendes FHEM ist so ein Fall.
Was bei HomeMatic erst über einen (zugegeben automatisierten Request) abläuft, kann MQTT mit den retained Messages auch so.
Trifft am Broker eine neue retained Message ein, so wird die alte Nachricht gelöscht. Zum absichtlichen Löschen einer retained Message, etwa weil man weiß, dass dieses Topic zukünftig nicht mehr benötigt wird, kann man eine leere Message posten.
Fazit
Retained Messages sind gut, um jeweils die letzte aktuelle Statusmeldung zu erhalten. Was damit nicht geht, ist z.B. die letzten 30 Messungen eines Temperatursensors zu speichern und auszuliefern! Sobald eine retained Message überschrieben wird, ist der alte Wert futsch.
Last Will and Testament (LWT)
Szenario
Letzter Wille und Testament.
Unser batteriebetriebener Sensor hat keinen Strom mehr, das billige 5V Netzteil stellt seinen Dienst ein, ein netter Nachbar eröffnet ein neues WLAN Netz welches unser Bestehendes gnadenlos stört, unsere süßen kleinen Miezekatzen finden Patchkabel so spannend, dass sie es für erfolgreiche Beißübungen verwenden… Gründe für einen Sensorausfall gibt es durchaus. Was passiert in FHEM? Der letzte Sensorwert bleibt im Reading stehen. Blöd wenn man erst abends feststellt, dass der Kühlschrank offen stand oder der Schaltbefehl zum Heizung einschalten nicht angekommen ist. Ersteres sorgt für aufgetautes Essen, letzteres für kalte Füße. Wie fängt man das ab?
FHEM-seitig könnte man einen Watchdog oder ein DOIF einrichten. Es muss geprüft werden, ob sich das entsprechende Reading in einer bestimmten Zeit verändert hat.
Möglich.
MQTT Lösung
Der Letzte Wille, das Testament (LWT)
Beim Anmelden an einen Broker kann ein Last Will - Topic angegeben werden. Ebenso eine Payload. Wenn das Gerät verschwindet ohne sich sauber abgemeldet zu haben, wird der Broker die gewünschte Nachricht auf dem hinterlegten Topic publishen. Das retained Flag kann auch gesetzt werden. Was bedeutet das für uns?
Zusätzlich zu:
- zuHause/1_OG/Kueche/Licht/state
- zuHause/1_OG/Kueche/Licht/set
könnten wir zusätzlich
- zuHause/1_OG/Kueche/Licht/status
definieren.
Beim Anmelden an den Broker schicken wir (mit retained flag) ein "online" Als LWT geben wir das gleiche Topic an, aber mit "offline" und retained flag. Meldet sich unser Device sauber ab (disconnect), wird das LWT nicht verwendet. Es dient nur dazu, einen Fehler deutlich zu machen.
Vorsicht
LWT bezieht sich auf die Client ID. Was das schon wieder? Jedes angemeldete Gerät hat eine einzige Client ID, unabhängig davon wie viele Topics es published und subscribed hat. LWT auf ein Topic zu beziehen macht wenig Sinn, wenn es mehrere Topics auf dem Gerät gibt. Obiges Beispiel ist also gut, wenn an dem MC genau dieses eine Licht hängt!
- zuHause/1OG/Arduino_1
Ist der bessere Weg! Jetzt können wir in FHEM auf online / offline reagieren und den davon abhängigen Geräten eine Fehlerbehandlung spendieren. Da ist unser DOIF wieder :)
Noch eine Falle
Wer denkt, er wirft alle Geräte in ein einziges retained last will Topic, sieht immer nur die letzte Meldung!
Böse:
zuHause/MQTT/Status (<= nicht nachmachen)
Fazit
MQTT kann einen eleganten Weg bieten, über das Ende eines Gerätes zu informieren. Genau wie bei HomeMatic aber auch, muss man diese Meldungen auswerten um was sinnvolles damit anzufangen! Im Fall eines einfachen Sensors ist es recht easy, bei einem Arduino Mega mit 30 Sensoren wird es dann aufwendig (außer man liest weiter bei den Filtern)
Organisation der Topics
Basics, erlaubte Zeichen
MQTT erlaubt prinzipiell den kompletten UTF8 Zeichensatz.
Außer:
- U+0001..U+001F control characters
- U+007F..U+009F control characters
Theoretisch gehen also Umlaute, Leerzeichen etc. Praktisch kann es passieren, dass ein Endgerät mit Umlauten einfach nix anfangen kann. Leerzeichen sind auch ein Problem, weil UTF 8 davon doch recht viele Möglichkeiten bietet. Ich würde daher empfehlen, Topicbezeichnungen eher wie Gerätenamen in FHEM zu betrachten: Unterstriche statt Leerzeichen, Umlaute ausschreiben (=> ü => ue...)
Groß- und Kleinschreibung muss beachtet werden. Ein Topic muss mindestens ein Zeichen haben, um gültig zu sein. Oh, und $ gehen auch gar nicht. Die verwendet der Broker nämlich für Interna...
Schlechtes Beispiel:
- /zuHause/
- nicht ganz falsch, aber der / am Anfang ist nur zusätzlicher Overhead, daher weglassen
Wirklich falsch:
- zuHause//1_OG
- Hier wird ein leeres Topic definiert. Das ist ungültig.
Basics, Strukturierung
MQTT kennt publishen und subscriben.
FHEM kennt für jedes Gerät einen "state" und ein "set" Kommando. Das ist bei MQTT nicht nötig, aber durchaus sehr sinnvoll.
Nehmen wir wieder unseren Lichtschalter: Ein Taster an einem Arduino mit einem Relais nebst Lampe daran.
Unglücklich:
- zuHause/1_OG/Kueche/Lichtschalter
Wenn da ein "on" steht, woher will man wissen ob es sich um einen Befehl oder um eine Statusmeldung handelt? Daher bauen wir die FHEM übliche Logik einfach nach:
Etwas glücklicher (kann man machen, man kann es aber schwer filtern)
- zuHause/1_OG/Kueche/Lichtschalter/set
- zuHause/1_OG/Kueche/Lichtschalter/state
Richtig schlau (warum, kommt bei den Filtern)
- set/zuHause/1_OG/Kueche/Lichtschalter
- state/zuHause/1_OG/Kueche/Lichtschalter/state
Jetzt wird es einfach:
auf dem set-Topic publishen wir immer Schaltbefehle. Mit oder ohne retained flag, darüber kann man streiten. Unser Arduino muss also dieses Topic abhören und auswerten.
Das state-Topic nehmen wir um darauf zu publishen (hier ist retained einfeutig praktisch), wie der Zustand unserer Lampe gerade ist. Unser Arduino muss dieses Topic nur schreiben, niemals selber lesen.
Damit haben wir eine saubere Trennung von Befehl und Status erreicht.
Aufbau eines Topics
Wie bereits geschrieben, kann man die Topics recht frei einfach selbst erfinden. Sinnvoll ist es jedoch, jedem gewünschten Wert ein eigenes Topic zu spendieren.
Ungünstig:
- zuHause/state/1_OG/Kueche
- => In diesem Topic Licht, Temperatur, Luftfeuchte, Bewegungsmelder etc zu publishen ist eine nicht schlaue Idee; das Problem beginnt damit, dass man erst mal selber rausfinden muss, welchen Wert man gerade hat. Viel Programmcode für quasi umsonst; "on 20.3 66 motion" ist nun nicht wirklich toll.
Besser:
- zuHause/state/1_OG/Kueche/Licht
- zuHause/state/1_OG/Kueche/Raumtemperatur
- zuHause/state/1_OG/Kueche/Luftfeuchte
- zuHause/state/1_OG/Kueche/Bewegungsmelder
=> Auf diese Weise weiß man vorher, welche (Zahlen)werte in einem Topic stehen und was sie bedeuten. Das hat man automatisch durch die geschickte Topic Wahl festgelegt.
Filter
Eine witzige Sache von MQTT sind Filter. Das ist in etwa wie in FHEM eine readingsGroup für Arme. Wir möchten schnell einen Überblick haben, welchen Status bestimmte Devices haben.
Es gibt zwei Arten von Filtern:
- Single Level Filter: +
- Multi Level Filter: #
Beispiele:
- zuHause/state/1_OG/Kueche/Temperatur
- zuHause/state/1_OG/Kueche/Kuehlschrank/Temperatur
- zuHause/state/1_OG/Kueche/Licht
- zuHause/state/1_OG/Wohnzimmer/Licht
- zuHause/state/1_OG/Schlafzimmer/Licht
Jetzt filtern wir:
"#" => muss immer der letzte Filter sein (es gibt Broker, die die # auch als Platzhalter innerhalb eines Topics erlauben, aber Standard ist das nicht)
- zuHause/state/# => gibt uns alle oben genannten Beispiel-Topics zurück
- zuHause/state/1_OG/+/Licht => gibt uns das Licht der Küche, des Wohn- und des Schlafzimmers
- zuHause/state/1_OG/+/Temperatur => gibt nur die Küchentemperatur (+ steht für genau 1 Topic, der Kühlschrank fällt also nicht in das Muster)
Diskussionspunkt, Devicenamen
Manchmal liest man auch den Tip, eine eindeutige Gerätebezeichnung in das Topic mit aufzunehmen.
- Arduino_1/state/Zimmer_1/Licht
- Arduino_1/state/Zimmer_1/Temperatur
- Arduino_1/state/Zimmer_2/Licht
- Arduino_2/state/Zimmer_3/Licht
Hat im wesentlichen folgenden Vorteil:
Wenn ich mir nur ansehen will, was ein bestimmter Mikrocontroller published, kann ich ihn sehr einfach filtern.
- Arduino_1/# => perfekt um alle Werte des Arduino_1 zu lesen und sonst nichts
Nachteilig:
Tauscht man später den Controller aus, muss man ihn wieder so nennen. Fasst man später Arduino_1 und Arduino_2 auf einem Arduino Mega zusammen, stimmen die Topics schlicht nicht mehr und man muss seine ganzen Devicenamen anpassen.
Der Artikel hat "Best Practices" versprochen - wo sind sie?
Schwierig. Was die Benennung der Topics betrifft, so vom Zeichensatz her, gibt es Regeln und best practices, die stehen oben.
Ob man jetzt eindeutige Gerätenamen vorne weg schreibt, oder ob man state und set weiter vorne im Topic Tree unterbringt oder hinten, ich fürchte da gibt es keine allgemein gültigen Regeln. MQTT lässt einem hier alle Freiheiten.
Das eine sieht eher nach FHEM aus (set und state am Ende), das andere lässt sich besser filtern. Hardcoreuser könnten auch auf die Idee kommen, einfach beide Varianten gleichzeitig zu nutzen. Für FHEM alleine ist das sicherlich nicht hilfreich (aber auch nicht störend).
Schlusswort
Hier endet vorübergehend unser kleiner Exkurs zu MQTT.
Wer Fragen oder Anregungen hat, bitte im Diskussionsthread dieses Artikels stellen.
Weiterhin haben wir ganz neu einen eigenen Forumsbereich für MQTT.
Je nachdem wie gut die Artikelserie ankommt, werde ich einen Teil 3 auflegen. Dieser ist dann zum "get your hands dirty", zum Hände dreckig machen. Darin könnte ein kleiner Programmierkurs in der Arduino IDE kommen. Mir schwebt vor, einen kleinen Sketch zu nehmen, diesen mit MQTT auszurüsten und nach und nach die Dinge wie LWT, retained messages etc. einzubauen. Das ist im wesentlichen mit wenigen Änderungen möglich. Also durchaus anfängertauglich.
Ich werde einen Arduino Uno oder Mega nehmen, sowie ein Ethernetshield mit W5100 Chip benutzen. Die Dinger kosten keine 6 € auf eBay. Die ENC28J60 (oder ähnliche) Chips werden explizit NICHT unterstützt werden! Wer lieber einen ESP8266 nimmt, der sei an dieser Stelle auf das Template von Pf@nne verwiesen hier zu finden.