MQTT2 DEVICE - Schritt für Schritt: Unterschied zwischen den Versionen
(weitere Details) |
|||
(11 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
== Einführung == | == Einführung == | ||
Das Protokoll [[MQTT]] ermöglicht einen flexiblen Datenaustausch zwischen unterschiedlichsten Geräten und FHEM und insbesondere auch bidirektionale Kommunikation von und zu FHEM. Es gibt dabei jedoch nur einen geringen Grad der Standardisierung des Datenaustauschs. In der Praxis sind daher relative viele unterschiedliche Wege aufzufinden, wie die Kommunikation via MQTT in den externen Geräten und Diensten konkret umgesetzt wurde - jeder Autor einer firmware oder Software kann dies so lösen, wie es ihm beliebt, und nicht jeder beherzigt dabei den Grundsatz, dass die Kommunikation via MQTT "leichtgewichtig" sein sollte. | Das Protokoll [[MQTT]] ermöglicht einen flexiblen Datenaustausch zwischen unterschiedlichsten Geräten und FHEM und insbesondere auch bidirektionale Kommunikation von und zu FHEM. Es gibt dabei jedoch nur einen geringen Grad der Standardisierung des Datenaustauschs. In der Praxis sind daher relative viele unterschiedliche Wege aufzufinden, wie die Kommunikation via MQTT in den externen Geräten und Diensten konkret umgesetzt wurde - jeder Autor einer firmware oder Software kann dies so lösen, wie es ihm beliebt, und nicht jeder beherzigt dabei den Grundsatz, dass die Kommunikation via MQTT "leichtgewichtig" sein sollte. | ||
Zeile 18: | Zeile 16: | ||
== Vorbereitung == | == Vorbereitung == | ||
=== MQTT2_SERVER === | === MQTT2_SERVER === | ||
Selbst, wenn grundsätzlich ein externer MQTT-Server IO-Device zum Einsatz kommt, ist sehr zu empfehlen, für die Beschäftigung mit einem neuen, unbekannten Device zunächst einen {{Link2CmdRef|Anker=MQTT2_SERVER|Lang=en|Label=MQTT2_SERVER}} einzurichten. Ist der Port 1883 bereits belegt, nimmt man einfach einen anderen Port, z.B. 1884: <code>define m2server MQTT2_SERVER 1884 global</code>. | Selbst, wenn grundsätzlich ein externer MQTT-Server IO-Device zum Einsatz kommt, ist sehr zu empfehlen, für die Beschäftigung mit einem neuen, unbekannten Device zunächst einen {{Link2CmdRef|Anker=MQTT2_SERVER|Lang=en|Label=MQTT2_SERVER}} einzurichten. Ist der Port 1883 bereits belegt, nimmt man einfach einen anderen Port, z.B. 1884: <code>define m2server MQTT2_SERVER 1884 global</code>. Sollte die Gegenstelle tiefer strukturierte Daten im JSON-Format über verschiedene Topics als Payload übermittelt, kann es ausnahmsweise hilfreich sein, '''in der Einrichtungsphase''' auch das Attribut "autocreate" am MQTT2_SERVER auf "complex" zu stellen: <code>attr m2server autocreate complex</code>. Weiter muss die allgemeine [[Autocreate|autocreate-Instanz]] aktiv sein. Für den Regelbetrieb und für einfache, bekannte Devices (sowie für solche, für die bereits attrTemplate vorhanden sind), wird ausdrücklich empfohlen, das ''autocreate''-Atribut am m2server gar nicht erst zu setzten, dann wird ''autocreate'' mit der (default) Einstellung ''simple'' verwendet. Die Hintergründe sind nachfolgend im Abschnitt zu ''json2nameValue()'' zu finden. | ||
=== Begrifflichkeiten === | === Begrifflichkeiten === | ||
Ein typisches, von "autocreate" erstelltes Gerät sieht dann z.b. (mit autocreate = simple am MQTT2_SERVER) so aus: | Ein typisches, von "autocreate" erstelltes Gerät sieht dann z.b. (mit autocreate = simple am MQTT2_SERVER) so aus: | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
defmod MQTT2_DVES_9B01BD MQTT2_DEVICE DVES_9B01BD | defmod MQTT2_DVES_9B01BD MQTT2_DEVICE DVES_9B01BD | ||
attr MQTT2_DVES_9B01BD readingList DVES_9B01BD:tele/DVES_9B01BD/STATE:.* { json2nameValue($EVENT) }\ | attr MQTT2_DVES_9B01BD readingList DVES_9B01BD:tele/DVES_9B01BD/STATE:.* { json2nameValue($EVENT) }\ | ||
Zeile 39: | Zeile 37: | ||
Dies ist die Gerätekennung (hier: DVES_9B01BD). Diese ist auch Bestandteil des ''define'', über diese wird ermittelt, zu welchem FHEM-Gerät via MQTT eingehende Informationen zugeordnet werden sollen. Diese Angabe ist weder im define noch in der readingList zwingend, aber in der Definition für den Hauptkanal eines Gerätes empfohlen. Es empfiehlt sich, die CID-Angaben bei eigenen readingList-Einträgen wegzulassen bzw. diese zu löschen. So kann einfacher zwischen automatisch generierten und eigenen Angaben unterschieden werden und die Geräte sind leichter zwischen verschiedenen IO-Modulen zu verschieben. | Dies ist die Gerätekennung (hier: DVES_9B01BD). Diese ist auch Bestandteil des ''define'', über diese wird ermittelt, zu welchem FHEM-Gerät via MQTT eingehende Informationen zugeordnet werden sollen. Diese Angabe ist weder im define noch in der readingList zwingend, aber in der Definition für den Hauptkanal eines Gerätes empfohlen. Es empfiehlt sich, die CID-Angaben bei eigenen readingList-Einträgen wegzulassen bzw. diese zu löschen. So kann einfacher zwischen automatisch generierten und eigenen Angaben unterschieden werden und die Geräte sind leichter zwischen verschiedenen IO-Modulen zu verschieben. | ||
==== Topic ==== | ==== Topic ==== | ||
Datenpunkt, an den eine Information gesendet wird. Empfangsseitig sind dies hier z.B. ''tele/DVES_9B01BD/STATE'' oder ''tele/DVES_9B01BD/LWT'' | Datenpunkt, an den eine Information gesendet wird. Empfangsseitig sind dies hier z.B. ''tele/DVES_9B01BD/STATE'' oder ''tele/DVES_9B01BD/LWT''. | ||
Enthält der Topic Doppelpunkte oder Sonderzeichen, kann dies zu Problemen führen. Da der Topic intern als ''regex'' behandelt wird, kann man sich das in solchen Sonderfälle dadurch zu nutze machen, dass man z.B problematische Zeichen durch Punkte oder "beliebige Zeichenfolgen" ersetzt. So kann z.B. aus dem per autocreate erstellten <code>attr reader readingList SMLReader/Strom/sensor/1/obis/1-0:1.8.0/255/value:.* Strombezug_tariflos</code> folgendes abgeleitet werden: <code>attr reader readingList SMLReader/Strom/sensor/1/obis/1-0.1.8.0/255/value:.* Strombezug_tariflos</code>, | |||
==== Payload ==== | ==== Payload ==== | ||
Der jeweilige Nachrichteninhalt. Da dieser nicht im vorhinein bekannt ist, wird er in der readingList typischerweise als "beliebige Zeichenfolge" (".*") notiert. | Der jeweilige Nachrichteninhalt. Da dieser nicht im vorhinein bekannt ist, wird er in der readingList typischerweise als "beliebige Zeichenfolge" (".*") notiert. | ||
Zeile 54: | Zeile 54: | ||
=== Projektseiten finden === | === Projektseiten finden === | ||
Viele Geräte, die das MQTT-Protokoll verwenden, haben eigene Projektseiten oder API-Beschreibungen, denen man entnehmen kann, wie mit dieser Gegenstelle Daten ausgetauscht werden können. Diese sollte man bei allen weiteren Schritten stets zur Hand haben. Dabei kann es neben der allgemeinen Beschreibung auch ein oder mehrere Detail-Seiten geben, auf denen nähere Informationen zu den spezifischen Geräte zu finden sein können. | Viele Geräte, die das MQTT-Protokoll verwenden, haben eigene Projektseiten oder API-Beschreibungen, denen man entnehmen kann, wie mit dieser Gegenstelle Daten ausgetauscht werden können. Diese sollte man bei allen weiteren Schritten stets zur Hand haben. Dabei kann es neben der allgemeinen Beschreibung auch ein oder mehrere Detail-Seiten geben, auf denen nähere Informationen zu den spezifischen Geräte zu finden sein können. | ||
=== MQTT - Datenverkehr mitlesen === | |||
Sowohl MQTT2_SERVER wie MQTT2_CLIENT bieten in der Detailansicht die Option ''Show MQTT traffic''. Darüber läßt sich der ein- und ausgehende Datenverkehr bequem mitlesen. Bei MQTT2_CLIENT wird dabei allerdings vorausgesetzt, dass er überhaupt Daten vom MQTT-Server erhält, also insbesondere die ''subscriptions'' korrekt gesetzt sind (falls per Attribut eingeschränkt). | |||
=== readingList === | === readingList === | ||
Zunächst empfiehlt es sich, einfach die Gegenstelle neu zu starten und (ggf. über ein FileLog) | Zunächst empfiehlt es sich, einfach die Gegenstelle neu zu starten und (ggf. über ein FileLog) aufzuzuzeichnen, was über welchen Topic wie oft an Informationen gesendet wird. Dabei kann und sollte durchaus - sofern dies möglich ist - der eine oder andere Schaltvorgang (z.B. über das Web-Interface der Gegenstelle) durchgeführt werden, sofern dies möglich ist (oder allgemeiner: möglichst viele bekannte Anweisungen ausführen lassen). Am Ende sollte man eine möglichst vollständige Auflistung in der readingList erhalten haben. | ||
Falls strukturierte Daten im JSON-Format als Payload verwendet werden, kann man diese auch zusätzlich ohne die Verarbeitung durch ''json2nameValue()'' aufzeichnen, z.B. indem man die betreffende Zeile in der readingList doppelt: | Falls strukturierte Daten im JSON-Format als Payload verwendet werden, kann man diese auch zusätzlich ohne die Verarbeitung durch ''json2nameValue()'' aufzeichnen, z.B. indem man die betreffende Zeile in der readingList doppelt: | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="text"> | ||
defmod MQTT2_DVES_9B01BD MQTT2_DEVICE DVES_9B01BD | defmod MQTT2_DVES_9B01BD MQTT2_DEVICE DVES_9B01BD | ||
attr MQTT2_DVES_9B01BD readingList tele/DVES_9B01BD/STATE:.* { json2nameValue($EVENT) }\ | attr MQTT2_DVES_9B01BD readingList tele/DVES_9B01BD/STATE:.* { json2nameValue($EVENT) }\ | ||
Zeile 84: | Zeile 87: | ||
== readingList optimieren== | == readingList optimieren== | ||
=== gute Reading-Namen - Klartext === | === gute Reading-Namen - Klartext === | ||
{{Randnotiz|RNTyp=Info|RNText=Beachte zur Vergabe von Reading-Namen v.a. auch [[DevelopmentGuidelinesReadings]] und [[DevelopmentGuidelinesAV]].}} | |||
Viele Gegenstellen senden z.B. einen online/offline-Status als "last will and testament" unter einem Topic, der mit "LWT" endet. Dieses hier sendet zwar passende Daten, aber an einen anderen Topic. In solchen Fällen man kann den von autocreate erzeugten Eintrag einfach anpassen: | Viele Gegenstellen senden z.B. einen online/offline-Status als "last will and testament" unter einem Topic, der mit "LWT" endet. Dieses hier sendet zwar passende Daten, aber an einen anderen Topic. In solchen Fällen man kann den von autocreate erzeugten Eintrag einfach anpassen: | ||
attr DEVICE readingList /dingtian/DEVNAME/out/lwt_availability:.* LWT | attr DEVICE readingList /dingtian/DEVNAME/out/lwt_availability:.* LWT | ||
Zeile 91: | Zeile 95: | ||
attr DEVICE readingList DEVNAME/relay/0:.* { $EVENT ? {state=>'on'} : {state=>'off'} }\ | attr DEVICE readingList DEVNAME/relay/0:.* { $EVENT ? {state=>'on'} : {state=>'off'} }\ | ||
DEVNAME/status:.* { $EVENT ? {LWT=>'Online'} : {LWT=>'Offline'} } | DEVNAME/status:.* { $EVENT ? {LWT=>'Online'} : {LWT=>'Offline'} } | ||
Weiteres Beispiel: Der "state" kommt in Großschreibung und soll in Kleinschreibung geändert werden: | |||
attr DEVICE readingList switchbot/esp32_2/bot/switchbottwo/state:.* { { state => lc $EVENT } } | |||
=== json2nameValue() === | === json2nameValue() === | ||
Zeile 96: | Zeile 102: | ||
* "gute Reading-Namen" auch aus JSON-Payloads erzeugen (2. und 3. Argument) | * "gute Reading-Namen" auch aus JSON-Payloads erzeugen (2. und 3. Argument) | ||
* unnötige Readings vorab ausfiltern (3., 4. und 5. Argument) | * unnötige Readings vorab ausfiltern (3., 4. und 5. Argument) | ||
Beispiele für die Verwendung der Argumente 1 bis 3 sind der commandref in der Erläuterung des Attributs ''jsonMap'' bei MQTT2_DEVICE zu entnehmen, die (wie die Argumente 2 und 3 optionalen) Argumente 4 und 5 entsprechen einem Positiv- bzw.- Negativ-Filter, | Beispiele für die Verwendung der Argumente 1 bis 3 sind der commandref in der Erläuterung des Attributs ''jsonMap'' bei MQTT2_DEVICE zu entnehmen, die (wie die Argumente 2 und 3 optionalen) Argumente 4 und 5 entsprechen einem Positiv- bzw.- Negativ-Filter. | ||
Wird (übergangsweise!) ''autocreate'' in der ''complex''-Variante eingestellt, werden für alle (bisher nicht bekannten) Topics Einträge wie dieser erzeugt: | |||
attr MQTT2_DVES_9B01BD readingList tele/DVES_9B01BD/STATE:.* { json2nameValue($EVENT,'STATE_',$JSONMAP) } | |||
* Das zweite Argument (''STATE_'') ist ein "Präfix", der allen aus dem JSON erzeugten Readings aus diesem Topic (zunächst) vorangestellt wird. Mit dessen Hilfe läßt sich rekonsturieren, aus welchem Topic die betreffende Information ursprünglich kam. In der Regel ist dies nach der Einrichtung nicht mehr wichtig, und dieses Argument kann auf "nichts" (zwei einfache Quotes) gestellt werden. Allerdings kann es in Einzelfällen sinnvoll sein, Präfixe zu verwenden, um zwischen scheinbar gleichen Werte aus unterschiedlichen Quellen zu unterscheiden. Dies kann man erst abschließend entscheiden, wenn alle von der Gegenstelle gelieferten Daten bekannt sind. | |||
* Das dritte Argument ''$JSONMAP'' kann in der Regel so belassen werden. Dann kann mit Hilfe des Attributs ''jsonMap'' eine Zuordnungstabelle für Namensänderungen für die zu generierenden Readingnamen erzeugt und/oder Werte schlicht gelöscht werden. | |||
<syntaxhighlight lang="perl6"> | |||
json2nameValue($EVENT,'STATE_',$JSONMAP,'positiv') | |||
json2nameValue($EVENT,'STATE_',$JSONMAP,'','negativ') | |||
</syntaxhighlight> | |||
* Das vierte Argument ist optional. Es ist ein (Positiv) Filter, der nur Readings in der Liste erscheinen lässt, die diesem Argument entsprechen, im Beispiel oben wären das alle Readings, die den Ausdruck "positiv" enthalten. Das Argument selber ist eine regexp. '''Achtung:''' Wird dieses Argument zusammen mit dem Attribut jsonMap benutzt, so wird zuerst das Mapping auf die neuen Readings-Bezeichnungen ausgeführt und erst dann dieser Filter ausgeführt, d. h. es muss auf die ersetzten Readings-Bezeichnungen gefiltert werden! | |||
* Das fünfte Argument ist ebenfalls optional. Es ist ein (Negativ) Filter. Im obigen Beispiel werden alle Readings, die den Ausdruck "negativ" enthalten, aus den Readings ausgefiltert. Ebenso wie das 4. Argument ist es eine regexp. Im Zusammenspiel mit jsonMap wird hier zuerst der Filter ausgewertet und erst anschließend erfolgt das Mapping auf die neuen Readings-Namen. | |||
=== Auswertung unterbinden === | === Auswertung unterbinden === | ||
Zeile 121: | Zeile 137: | ||
=== event-on-change-reading und Co. === | === event-on-change-reading und Co. === | ||
Da die firmwares häufig recht gesprächig programmiert sind, sollte man auf eine sinnvolle Begrenzung der durch Aktualisierungen verursachten Events besonderen Wert legen. Dazu ist in erster Linie das Attribut [[Event-on-change-reading|event-on-change-reading]] zu bearbeiten und ggf. passende threshold-Werte zu setzen, es empfiehlt sich allerdings, dabei auch zu untersuchen, inwieweit die weiteren, funktional ergänzenden Attribute zu setzen sind: | |||
* [[Event-min-interval|event-min-interval]] | |||
* [[Event-on-update-reading|event-on-update-reading]] | |||
* timestamp-on-change-reading und | |||
* [[Event-aggregator|event-aggregator]] | |||
=== gute Reading-Namen - userReadings === | |||
Manchmal werden per MQTT Werte übersendet, die so nicht direkt verwendbar sind. Beispiele hierfür wären: | |||
* Batteriespannungen in mV (z.B. bei zigbee2mqtt) | |||
* Farbwerte als Einzelreading (für die Anzeige in FHEM wird aber ein RGB-Wert benötigt) | |||
In diesen Fällen kann man zwar nicht ohne weiteres den Ausgangswert umrechnen lassen, (über externe Module wie readingsChange sehr wohl), aber es ist über den Weg "userReadings" ohne weiteres möglich, passende Readings zu generieren. Dabei sollte aber zur Verringerung der Systembelastung allgemein sowie ggf. falscher Ergebnisse unbedingt darauf geachtet werden, dass diese auch mit einer ''trigger''-Angabe versehen sind! | |||
== setList == | == setList == | ||
=== Allgemeines === | === Allgemeines === | ||
Die setList ist dazu gedacht, Kommandos zu definieren, welche an die Gegenstelle gesendet werden können. | |||
=== Syntax === | === Syntax === | ||
In der Regel besteht jede Zeile aus folgenden, per Leerzeichen getrennten Argumenten: | |||
* einem setter-Namen, ggf. ergänzt durch ein widget (s.u.) | |||
* einem Topic, unter dem die Payload gesendet werden soll und | |||
* der Payload. | |||
Es können diverse Variablen genutzt werden, u.A. auch $EVENT und $EVTPARTx-Elemente, die der Rückgabe (setter-Name und ggf. gesetzter Wert) aus dem jeweiligen "widget" entsprechen. | |||
=== on und off === | === on und off === | ||
Damit die weitergehenden Befehle wie ''on-for-timer'' aus den [[DevelopmentModuleIntro#X_Set|SetExtensions]] funktionieren, muss ein MQTT2_DEVICE die Befehle "on" und "off" kennen. Diese sollten also - wenn es eine Art "Hauptschalter" gibt - gesondert für diesen Hauptschalter angegeben werden. | |||
Hat ein Gerät mehrerer solcher "Hauptschalter", empfiehlt es sich, für jeden dieser Schalter eine eigene MQTT2_DEVICE-Instanz anzulegen. | |||
==== setStateList ==== | ==== setStateList ==== | ||
Gibt es in einem Device neben dem "Hauptschalter" weitere setzbare Readings (z.B. für Helligkeit und Farbe oder eine Temperatur), empfiehlt es sich v.a. dann, wenn das Gerät den Empfang (bzw. die Ausführung) von Befehlen bestätigt, nur die auf den jeweiligen Hauptschalter bezogenen ''set''-Anweisungen in ''state'' zu schreiben. Diese (z.B. on, off und toggle) wären dann in das ''setStateList''-Attribut aufzunehmen. | |||
==== setExtensionsEvent ==== | ==== setExtensionsEvent ==== | ||
Will man per SetExtensions realisierte laufende Timer visualisieren, empfiehlt es sich, dieses Attribut zu setzen. | |||
=== widgets === | === widgets === | ||
Zeile 141: | Zeile 178: | ||
== getList == | == getList == | ||
Die getList ist dazu gedacht, asynchrone Abfragen an die Gegenstelle zu ermöglichen. | |||
Die Syntax dabei ist | |||
* einem getter-Namen, ggf. ergänzt durch ein widget (s.u.) | |||
* Reading-Name, unter dem die Antwort erwartet wird | |||
* einem Topic, unter dem die Payload gesendet werden soll und | |||
* (optional) der Payload. | |||
Auch hier können diverse Variablen genutzt werden, und/oder das ganze für die Ausführung von Perl-Code genutzt werden. | |||
== periodicCmd == | == periodicCmd == | ||
Für regelmäßige Aufgaben (mit mind. minütlicher Frequenz) kann eine Liste von get- oder set-Kommandos angegeben werden. Dies kann für regelmäßige Abfragen ebenso genutzt werden wie z.B. zum Löschen veralteter Informationen/Readings. | |||
== Abschließende Aufgaben == | |||
Um das finale Aussehen des Gerätes zu beeinflussen, stehen dann noch die weiteren allgemeinen Attribute zur Verfügung, die in [[DeviceOverview anpassen]] beschrieben sind. | |||
== Hinweise == | |||
<references /> | |||
[[Kategorie:HOWTOS]] | |||
[[Kategorie:MQTT]] |
Aktuelle Version vom 28. Oktober 2024, 06:19 Uhr
Einführung
Das Protokoll MQTT ermöglicht einen flexiblen Datenaustausch zwischen unterschiedlichsten Geräten und FHEM und insbesondere auch bidirektionale Kommunikation von und zu FHEM. Es gibt dabei jedoch nur einen geringen Grad der Standardisierung des Datenaustauschs. In der Praxis sind daher relative viele unterschiedliche Wege aufzufinden, wie die Kommunikation via MQTT in den externen Geräten und Diensten konkret umgesetzt wurde - jeder Autor einer firmware oder Software kann dies so lösen, wie es ihm beliebt, und nicht jeder beherzigt dabei den Grundsatz, dass die Kommunikation via MQTT "leichtgewichtig" sein sollte.
Das Modul MQTT2_DEVICE bietet eine Vielzahl von Möglichkeiten, auf die verschiedensten Anforderungen einzugehen, und die ein- und ausgehenden Daten zu einem oder mehreren FHEM-Gerät/en zusammenzufassen. Eine Übersicht häufig vorkommender MQTT-Geräte ist in MQTT2-Module - Praxisbeispiele zu finden.
Ziel dieses Artikels ist die Darstellung der Schritte, die sich als zweckmäßig erwiesen haben zur Einrichtung von "guten" FHEM-Geräten.
Dabei soll am Ende erreicht werden:
- standardisierte set- (und ggf. get-)-Kommandos, insbesondere unter Beachtung der Developer Guidelines für Readings
- Schließen des Informationskreises von eventuellen Kommandos bis zur Rückmeldung des (externen) Gerätes oder Dienstes (im Folgenden: "Gegenstelle")
- standardisierte Reading-Namen, damit möglichst Auswertungen nicht speziell an das jeweilige Gerät angepasst werden müssen
- Reduzierung und Vermeidung von unnötigen Datenpunkten und Events
- Einrichten von regelmäßigen Abfrage-Timern (falls erforderlich!).
Vorbereitung
MQTT2_SERVER
Selbst, wenn grundsätzlich ein externer MQTT-Server IO-Device zum Einsatz kommt, ist sehr zu empfehlen, für die Beschäftigung mit einem neuen, unbekannten Device zunächst einen MQTT2_SERVER einzurichten. Ist der Port 1883 bereits belegt, nimmt man einfach einen anderen Port, z.B. 1884: define m2server MQTT2_SERVER 1884 global
. Sollte die Gegenstelle tiefer strukturierte Daten im JSON-Format über verschiedene Topics als Payload übermittelt, kann es ausnahmsweise hilfreich sein, in der Einrichtungsphase auch das Attribut "autocreate" am MQTT2_SERVER auf "complex" zu stellen: attr m2server autocreate complex
. Weiter muss die allgemeine autocreate-Instanz aktiv sein. Für den Regelbetrieb und für einfache, bekannte Devices (sowie für solche, für die bereits attrTemplate vorhanden sind), wird ausdrücklich empfohlen, das autocreate-Atribut am m2server gar nicht erst zu setzten, dann wird autocreate mit der (default) Einstellung simple verwendet. Die Hintergründe sind nachfolgend im Abschnitt zu json2nameValue() zu finden.
Begrifflichkeiten
Ein typisches, von "autocreate" erstelltes Gerät sieht dann z.b. (mit autocreate = simple am MQTT2_SERVER) so aus:
defmod MQTT2_DVES_9B01BD MQTT2_DEVICE DVES_9B01BD
attr MQTT2_DVES_9B01BD readingList DVES_9B01BD:tele/DVES_9B01BD/STATE:.* { json2nameValue($EVENT) }\
DVES_9B01BD:tele/DVES_9B01BD/LWT:.* LWT\
DVES_9B01BD:tele/DVES_9B01BD/UPTIME:.* { json2nameValue($EVENT) }\
DVES_9B01BD:tele/DVES_9B01BD/SENSOR:.* { json2nameValue($EVENT) }\
DVES_9B01BD:tele/DVES_9B01BD/INFO1:.* { json2nameValue($EVENT) }\
DVES_9B01BD:tele/DVES_9B01BD/INFO2:.* { json2nameValue($EVENT) }\
DVES_9B01BD:tele/DVES_9B01BD/INFO3:.* { json2nameValue($EVENT) }\
DVES_9B01BD:stat/DVES_9B01BD/RESULT:.* { json2nameValue($EVENT) }\
DVES_9B01BD:stat/DVES_9B01BD/STATE:.* { json2nameValue($EVENT) }
attr MQTT2_DVES_9B01BD room MQTT2_DEVICE
Wir unterscheiden bei jeder Zeile des readingList-Attributs vier Elemente, die ersten drei werden jeweils durch einen Doppelpunkt voneinander getrennt:
CID
Dies ist die Gerätekennung (hier: DVES_9B01BD). Diese ist auch Bestandteil des define, über diese wird ermittelt, zu welchem FHEM-Gerät via MQTT eingehende Informationen zugeordnet werden sollen. Diese Angabe ist weder im define noch in der readingList zwingend, aber in der Definition für den Hauptkanal eines Gerätes empfohlen. Es empfiehlt sich, die CID-Angaben bei eigenen readingList-Einträgen wegzulassen bzw. diese zu löschen. So kann einfacher zwischen automatisch generierten und eigenen Angaben unterschieden werden und die Geräte sind leichter zwischen verschiedenen IO-Modulen zu verschieben.
Topic
Datenpunkt, an den eine Information gesendet wird. Empfangsseitig sind dies hier z.B. tele/DVES_9B01BD/STATE oder tele/DVES_9B01BD/LWT.
Enthält der Topic Doppelpunkte oder Sonderzeichen, kann dies zu Problemen führen. Da der Topic intern als regex behandelt wird, kann man sich das in solchen Sonderfälle dadurch zu nutze machen, dass man z.B problematische Zeichen durch Punkte oder "beliebige Zeichenfolgen" ersetzt. So kann z.B. aus dem per autocreate erstellten attr reader readingList SMLReader/Strom/sensor/1/obis/1-0:1.8.0/255/value:.* Strombezug_tariflos
folgendes abgeleitet werden: attr reader readingList SMLReader/Strom/sensor/1/obis/1-0.1.8.0/255/value:.* Strombezug_tariflos
,
Payload
Der jeweilige Nachrichteninhalt. Da dieser nicht im vorhinein bekannt ist, wird er in der readingList typischerweise als "beliebige Zeichenfolge" (".*") notiert.
Auswertung
Dies kann entweder direkt der Reading-Name sein, dem die Payload zugeordnet werden soll, oder ein Perl-Ausdruck. Hier wird z.B. die eingehende Information für tele/DVES_9B01BD/LWT dem Reading LWT zugeordnet, während die Informationen aus tele/DVES_9B01BD/STATE an die Funktion json2nameValue() übergeben werden. $EVENT entspricht dabei der Payload.
defaults
Für unbekannte Gegenstellen ist zunächst immer zu empfehlen, deren "Grundeinstellungen" zu verwenden, und Anpassungen erst und nur insoweit vorzunehmen, als es für ein besseres Zusammenspiel mit FHEM sinnvoll ist. Abzuraten ist insbesondere von:
- Änderungen der Topics und Topic-Sturkturen (ausgenommen den Fall, dass schon andere Gegenstellen im Einsatz sind, die identische Topics verwenden)
- Vergabe von friendly names
Bestandsaufnahme
Projektseiten finden
Viele Geräte, die das MQTT-Protokoll verwenden, haben eigene Projektseiten oder API-Beschreibungen, denen man entnehmen kann, wie mit dieser Gegenstelle Daten ausgetauscht werden können. Diese sollte man bei allen weiteren Schritten stets zur Hand haben. Dabei kann es neben der allgemeinen Beschreibung auch ein oder mehrere Detail-Seiten geben, auf denen nähere Informationen zu den spezifischen Geräte zu finden sein können.
MQTT - Datenverkehr mitlesen
Sowohl MQTT2_SERVER wie MQTT2_CLIENT bieten in der Detailansicht die Option Show MQTT traffic. Darüber läßt sich der ein- und ausgehende Datenverkehr bequem mitlesen. Bei MQTT2_CLIENT wird dabei allerdings vorausgesetzt, dass er überhaupt Daten vom MQTT-Server erhält, also insbesondere die subscriptions korrekt gesetzt sind (falls per Attribut eingeschränkt).
readingList
Zunächst empfiehlt es sich, einfach die Gegenstelle neu zu starten und (ggf. über ein FileLog) aufzuzuzeichnen, was über welchen Topic wie oft an Informationen gesendet wird. Dabei kann und sollte durchaus - sofern dies möglich ist - der eine oder andere Schaltvorgang (z.B. über das Web-Interface der Gegenstelle) durchgeführt werden, sofern dies möglich ist (oder allgemeiner: möglichst viele bekannte Anweisungen ausführen lassen). Am Ende sollte man eine möglichst vollständige Auflistung in der readingList erhalten haben. Falls strukturierte Daten im JSON-Format als Payload verwendet werden, kann man diese auch zusätzlich ohne die Verarbeitung durch json2nameValue() aufzeichnen, z.B. indem man die betreffende Zeile in der readingList doppelt:
defmod MQTT2_DVES_9B01BD MQTT2_DEVICE DVES_9B01BD
attr MQTT2_DVES_9B01BD readingList tele/DVES_9B01BD/STATE:.* { json2nameValue($EVENT) }\
tele/DVES_9B01BD/STATE:.* json_STATE\
tele/DVES_9B01BD/LWT:.* LWT\
[...]
So kann man mit Hilfe des betreffenden Logs ggf. auch über ein externes Tool wie mosquitto_pub MQTT-Nachrichten an FHEM generieren, ohne darauf warten zu müssen, dass diese von der Gegenstelle selbst erzeugt werden.
ignoreRegexp
Manche Gegenstellen senden beim Start Konfigurationsinformationen, die allerdings nicht durch FHEM ausgewertet werden können. Die betreffenden Topics sollte man (allgemein) so in die ignoreRegexp beim MQTT2_SERVER bzw. MQTT2_CLIENT aufnehmen, dass derartige Informationen künftig gar nicht mehr an MQTT2_DEVICE weitergereicht werden. Danach kann man die betreffenden Zeilen aus der readingList löschen! Entsprechendes gilt für die hierüber generierten Readings.
Weiter ist zu empfehlen, in diese ignoreRegexp auch die Topics aufzunehmen, über die Gegenstellen typischerweise Kommandos entgegennehmen. Dies könnte z.B. so aussehen:
attr m2server ignoreRegexp shellies/[^/]+/command|cmnd/[^/]+/|homeassistant/.*/config|tasmota/discovery
Viele Geräte?
"split"
Sind auf einer Hardware mehrere "Hauptschalter" vorhanden (z.B. ein Relay-Board), ist sehr zu empfehlen, für jeden dieser "Hauptschalter" ein eigenes FHEM-Gerät anzulegen (für logische Einheiten wie Rollladenaktoren ggf. paarweise). MQTT2_DEVICE unterstützt SetExtensions und kann daher Kommandos wie "on-for-timer" über FHEM-interne Mechanismen gut umsetzen. Dies erfordert allerdings, dass die Kommandos "on" und "off" verfügbar sein müssen. Wird ein Gerät gesplittet, sollten im Gerät, das den ersten (Haupt-) Kanal repräsentiert dann alle Kommunikationsdaten gebündelt werden, Querverweise zu den weiteren Kanälen kann man über das spezielle Readings "associatedWith" herstellen.
bridgeRegexp
In eher seltenen Fällen kommt es vor, dass eine Gegenstelle eine Art "Brücke" zu einer Mehrzahl über diese Brücke anzusteuernder (oder zu empfangender) Aktoren oder Sensoren darstellt. In diesen Fällen kann es geboten sein, die eigentliche Gegenstelle als Hauptdevice darzustellen und für jede weitere Hardware (Sensor oder Aktor) dann ein oder mehrere Einzeldevices anzulegen. Dies kann mit Hilfe des Attributs bridgeRegexp automatisiert erfolgen, wenn sich der Sensor/Aktor aus der Topic-Struktur ablesen läßt.
readingList optimieren
gute Reading-Namen - Klartext
Viele Gegenstellen senden z.B. einen online/offline-Status als "last will and testament" unter einem Topic, der mit "LWT" endet. Dieses hier sendet zwar passende Daten, aber an einen anderen Topic. In solchen Fällen man kann den von autocreate erzeugten Eintrag einfach anpassen:
attr DEVICE readingList /dingtian/DEVNAME/out/lwt_availability:.* LWT
Bedingte Hash-Rückgaben
Manchmal erfolgt zwar die Übergabe eines Klartextes als $EVENT - allerdings nicht in der Form, wie man das in FHEM gerne hätte. Hier als "0" oder "1". Mit etwas Perl und der Rückgabe eines Hashes kann man so etwas beliebig umformen:
attr DEVICE readingList DEVNAME/relay/0:.* { $EVENT ? {state=>'on'} : {state=>'off'} }\ DEVNAME/status:.* { $EVENT ? {LWT=>'Online'} : {LWT=>'Offline'} }
Weiteres Beispiel: Der "state" kommt in Großschreibung und soll in Kleinschreibung geändert werden:
attr DEVICE readingList switchbot/esp32_2/bot/switchbottwo/state:.* { { state => lc $EVENT } }
json2nameValue()
Mit Hilfe der Funktion json2nameValue() (in Verbindung mit dem attribut jsonMap) lassen sich
- "gute Reading-Namen" auch aus JSON-Payloads erzeugen (2. und 3. Argument)
- unnötige Readings vorab ausfiltern (3., 4. und 5. Argument)
Beispiele für die Verwendung der Argumente 1 bis 3 sind der commandref in der Erläuterung des Attributs jsonMap bei MQTT2_DEVICE zu entnehmen, die (wie die Argumente 2 und 3 optionalen) Argumente 4 und 5 entsprechen einem Positiv- bzw.- Negativ-Filter. Wird (übergangsweise!) autocreate in der complex-Variante eingestellt, werden für alle (bisher nicht bekannten) Topics Einträge wie dieser erzeugt:
attr MQTT2_DVES_9B01BD readingList tele/DVES_9B01BD/STATE:.* { json2nameValue($EVENT,'STATE_',$JSONMAP) }
- Das zweite Argument (STATE_) ist ein "Präfix", der allen aus dem JSON erzeugten Readings aus diesem Topic (zunächst) vorangestellt wird. Mit dessen Hilfe läßt sich rekonsturieren, aus welchem Topic die betreffende Information ursprünglich kam. In der Regel ist dies nach der Einrichtung nicht mehr wichtig, und dieses Argument kann auf "nichts" (zwei einfache Quotes) gestellt werden. Allerdings kann es in Einzelfällen sinnvoll sein, Präfixe zu verwenden, um zwischen scheinbar gleichen Werte aus unterschiedlichen Quellen zu unterscheiden. Dies kann man erst abschließend entscheiden, wenn alle von der Gegenstelle gelieferten Daten bekannt sind.
- Das dritte Argument $JSONMAP kann in der Regel so belassen werden. Dann kann mit Hilfe des Attributs jsonMap eine Zuordnungstabelle für Namensänderungen für die zu generierenden Readingnamen erzeugt und/oder Werte schlicht gelöscht werden.
json2nameValue($EVENT,'STATE_',$JSONMAP,'positiv')
json2nameValue($EVENT,'STATE_',$JSONMAP,'','negativ')
- Das vierte Argument ist optional. Es ist ein (Positiv) Filter, der nur Readings in der Liste erscheinen lässt, die diesem Argument entsprechen, im Beispiel oben wären das alle Readings, die den Ausdruck "positiv" enthalten. Das Argument selber ist eine regexp. Achtung: Wird dieses Argument zusammen mit dem Attribut jsonMap benutzt, so wird zuerst das Mapping auf die neuen Readings-Bezeichnungen ausgeführt und erst dann dieser Filter ausgeführt, d. h. es muss auf die ersetzten Readings-Bezeichnungen gefiltert werden!
- Das fünfte Argument ist ebenfalls optional. Es ist ein (Negativ) Filter. Im obigen Beispiel werden alle Readings, die den Ausdruck "negativ" enthalten, aus den Readings ausgefiltert. Ebenso wie das 4. Argument ist es eine regexp. Im Zusammenspiel mit jsonMap wird hier zuerst der Filter ausgewertet und erst anschließend erfolgt das Mapping auf die neuen Readings-Namen.
Auswertung unterbinden
Indem man einen Perl-Aufruf festlegt (geschweifte Klammern), dieser aber nichts zurückgibt, kann man bestimmte unerwünschte Readings ausfiltern. Im einfachsten Fall wäre dies:
shellies/DEVNAME/temperature_f:.* {}
Komplexer mit Auswertung der Payload:
STATTOPIC/RESULT:.* { $EVENT =~ m{HSBColor...(\d+),(\d+),(\d+)} ? $2 eq ReadingsVal($NAME,'saturation','unknown') ? return : { saturation=>$2 } : return }
Perl
Wie am Beispiel der speziellen Funktion json2nameValue()
sowie der Hash-Rückgabe bereits dargestellt, ist es möglich, bei der Auswertung auch Perl-Funktionen einzusetzen, und diverse Variablen an diese zu übergeben. Ebenso ist es möglich, eigenen Perl-Code zu verwenden, der z.B. dann längere Zuordnungstabellen, Event-Reduzierungsmechanismen, ... enthalten kann.
Events optimieren
Leider senden relativ viele Gegenstellen in ihren Standardeinstellungen sehr viele Daten, auch ohne dass sich etwas geändert hätte. Dies erzeugt uU. in FHEM eine erhebliche Last, so dass es dringend zu empfehlen ist, alle Maßnahmen zu prüfen, durch die dieses Verhalten unterbunden oder vermindert werden kann. Dabei sollte möglichst frühzeitig eingegriffen werden, entsprechend den folgenden Handlungsoptionen: Daten, die die firmware gar nicht erst sendet, muss FHEM nicht am Interface-Modul entgegennehmen. Daten, die direkt am Interface-Modul verworfen werden (ignoreRegexp), muss das Client-Modul nicht auswerten. Readings, die man (an einem bestimmten Device) nicht benötigt, sollte man nicht erzeugen, damit keine Eventhandler aktiv werden müssen, unveränderte, aber gewünschte Daten müssen nicht unbedingt (immer) Events erzeugen.
firmware-Einstellungen
Bei manchen firmwares kann man einstellen, ob bzw. wie oft oder aus welchem Anlass Daten gesendet werden sollen. Es wird dringlich empfohlen, bei "gesprächigen" Gegenstellen zu recherchieren, ob und in welcher Weise diese derartige Möglichkeiten bietet. Falls solche nicht vorhanden sind, lohnt es sich, auf den betreffenden Projektseiten nachzufragen, ob es diese Optionen gibt - nicht selten hat sich ein firmware-Autor darüber schlicht noch keine Gedanken gemacht und baut in der nächsten Version ggf. entsprechende Optionen ein?
Auswertung unterbinden
(s.o. für Topics/Readings, die gar nicht benötigt werden). Dies ist insbesondere auch zu empfehlen, wenn identische Daten zum gleichen Zeitpunkt sowohl als Klartext wie auch in einem JSON-Format übermittelt werden. In solchen Fällen ist es eher zu empfehlen, die JSON-Zweige zu abonnieren und den Rest (ggf. über einen ignoreRegexp-Eintrag) gar nicht auszuwerten. Doppelungen sollten in jedem Fall vermieden werden! Grundsätzlich erzeugt jeder Topic eine eigene Event-Loop, mehrere Readings-updates, die aus einer einzigen JSON-Payload abgeleitet werden, erzeugen dagegen eine gemeinsame Event-Loop. Durch solche Maßnahmen läßt sich die Systembelastung deutlich reduzieren. Für Daten aus JSON-Payloads besteht darüber hinaus die Möglichkeit, diese komplett auszufiltern (siehe jsonMap weiter oben)
event-on-change-reading und Co.
Da die firmwares häufig recht gesprächig programmiert sind, sollte man auf eine sinnvolle Begrenzung der durch Aktualisierungen verursachten Events besonderen Wert legen. Dazu ist in erster Linie das Attribut event-on-change-reading zu bearbeiten und ggf. passende threshold-Werte zu setzen, es empfiehlt sich allerdings, dabei auch zu untersuchen, inwieweit die weiteren, funktional ergänzenden Attribute zu setzen sind:
- event-min-interval
- event-on-update-reading
- timestamp-on-change-reading und
- event-aggregator
gute Reading-Namen - userReadings
Manchmal werden per MQTT Werte übersendet, die so nicht direkt verwendbar sind. Beispiele hierfür wären:
- Batteriespannungen in mV (z.B. bei zigbee2mqtt)
- Farbwerte als Einzelreading (für die Anzeige in FHEM wird aber ein RGB-Wert benötigt)
In diesen Fällen kann man zwar nicht ohne weiteres den Ausgangswert umrechnen lassen, (über externe Module wie readingsChange sehr wohl), aber es ist über den Weg "userReadings" ohne weiteres möglich, passende Readings zu generieren. Dabei sollte aber zur Verringerung der Systembelastung allgemein sowie ggf. falscher Ergebnisse unbedingt darauf geachtet werden, dass diese auch mit einer trigger-Angabe versehen sind!
setList
Allgemeines
Die setList ist dazu gedacht, Kommandos zu definieren, welche an die Gegenstelle gesendet werden können.
Syntax
In der Regel besteht jede Zeile aus folgenden, per Leerzeichen getrennten Argumenten:
- einem setter-Namen, ggf. ergänzt durch ein widget (s.u.)
- einem Topic, unter dem die Payload gesendet werden soll und
- der Payload.
Es können diverse Variablen genutzt werden, u.A. auch $EVENT und $EVTPARTx-Elemente, die der Rückgabe (setter-Name und ggf. gesetzter Wert) aus dem jeweiligen "widget" entsprechen.
on und off
Damit die weitergehenden Befehle wie on-for-timer aus den SetExtensions funktionieren, muss ein MQTT2_DEVICE die Befehle "on" und "off" kennen. Diese sollten also - wenn es eine Art "Hauptschalter" gibt - gesondert für diesen Hauptschalter angegeben werden. Hat ein Gerät mehrerer solcher "Hauptschalter", empfiehlt es sich, für jeden dieser Schalter eine eigene MQTT2_DEVICE-Instanz anzulegen.
setStateList
Gibt es in einem Device neben dem "Hauptschalter" weitere setzbare Readings (z.B. für Helligkeit und Farbe oder eine Temperatur), empfiehlt es sich v.a. dann, wenn das Gerät den Empfang (bzw. die Ausführung) von Befehlen bestätigt, nur die auf den jeweiligen Hauptschalter bezogenen set-Anweisungen in state zu schreiben. Diese (z.B. on, off und toggle) wären dann in das setStateList-Attribut aufzunehmen.
setExtensionsEvent
Will man per SetExtensions realisierte laufende Timer visualisieren, empfiehlt es sich, dieses Attribut zu setzen.
widgets
In der setList können prinzipiell alle in Widgets dargestellten Eingabemöglichkeiten verwendet werden.
Perl-Kommandos
Eigentlich ist die setList dazu gedacht, eine publish-Anweisungen über das IODev abzusetzen. Da aber generisch auch Perl-Code aufgerufen werden kann, ist dies nicht zwingend, so dass zum einen aus Perl heraus auch mehrfach-Publishes ebenso möglich sind wie beliebige "fhem"-Kommandos, die gar nichts mit MQTT zu tun haben müssen (z.B. das regelmäßige Löschen von evtl. veralteten Readings am betreffenden Gerät). Wird Text zurückgegeben, wird dies als "<topic> <payload>" interpretiert und dies gepublisht, erfolgt gar keine Rückgabe, unterbleibt dies.
getList
Die getList ist dazu gedacht, asynchrone Abfragen an die Gegenstelle zu ermöglichen. Die Syntax dabei ist
- einem getter-Namen, ggf. ergänzt durch ein widget (s.u.)
- Reading-Name, unter dem die Antwort erwartet wird
- einem Topic, unter dem die Payload gesendet werden soll und
- (optional) der Payload.
Auch hier können diverse Variablen genutzt werden, und/oder das ganze für die Ausführung von Perl-Code genutzt werden.
periodicCmd
Für regelmäßige Aufgaben (mit mind. minütlicher Frequenz) kann eine Liste von get- oder set-Kommandos angegeben werden. Dies kann für regelmäßige Abfragen ebenso genutzt werden wie z.B. zum Löschen veralteter Informationen/Readings.
Abschließende Aufgaben
Um das finale Aussehen des Gerätes zu beeinflussen, stehen dann noch die weiteren allgemeinen Attribute zur Verfügung, die in DeviceOverview anpassen beschrieben sind.