Beschreibung
Es handelt sich um einen MQTT-Broker, der komplett als ein Skript in IP-Symcon realisiert ist. Nach außen hin kommuniziert er mit MQTT-Clients über eine Server Socket. Innerhalb von IP-Symcon verwaltet er die benutzten Topics als eine Struktur von Kategorien, Variablen und Skripten.
Die Idee dahinter ist, folgenden funktionalen Überschneidungen von IP-Symcon und MQTT Rechnung zu tragen:
[ul]
[li]hierarchische Struktur benannter Knoten, in der Daten abgelegt und verbreitet, sowie Ereignisse ausgelöst und verarbeitet werden können[/li][li]dazu bestimmt, viele unterschiedliche Geräte miteinander zu verbinden und kommunizieren zu lassen[/li][li]Client-Server-Architektzur[/li][/ul]
Es wird bewusst mit dem Prinzip gebrochen, dass MQTT-Broker „normalerweise“ keine erweiterte Logik haben, sondern nur der Kommunikation zwischen den Clients dienen.
Einsatzbereich
Das Projekt habe ich aus Spaß an der Sache angefangen, aber es kann durchaus produktiv eingesetzt werden. Sinnvoll ist allerdings, bei einer eventuellen Migration anfangs noch einen „herkömmlichen“ Broker weiterhin bereit zu halten, bis sichergestellt ist dass alles korrekt funktioniert.
Features und Limitierungen
[ul]
[li]MQTT-Protokollversionen 3.1.1 sowie 3.1 werden unterstützt, allerdings wurden Kompromisse bei der Behandlung von Timeouts gemacht. Sie funktionieren, aber auf eine Weise die nicht standardkonform ist. Dies ist der fehlenden Möglichkeit geschuldet, in IP-Symcon eine TCP-Sitzung auf der Server Socket per Skript zu trennen.[/li][li]Es werden persistente sowie temporäre Client-Sitzungen unterstützt, jedoch weder Authentifizierung noch Verschlüsselung. Der Broker ist also ausschließlich für den Einsatz in einem lokalen, gesicherten Netzwerk bestimmt. Keinesfalls darf der Port der Server Socket nach außen hin geöffnet werden.[/li][li]Der Broker erstellt automatisch bei jeder eingehenden Veröffentlichung den notwendigen Ast im „Topic-Baum“, falls er noch nicht existiert. Topics, unter denen Nachrichten mit „Retain“-Flag publiziert werden, erstellt der Broker als Variablen. Dabei ist es unumgänglich, einen geeigneten Datentypen zu finden. Der Broker versucht, diesen bei bislang unbekannten Topics, zu „erraten“. Dieses Verhalten ist per Konfigurationsdatei auf Topic-Filter-Ebene abschaltbar. Näheres hierzu im Abschnitt „Funktionsweise“.[/li][li]Das Publizieren von Nachrichten mit Binärdaten wird nicht unterstützt.[/li][/ul]
Bestandteile
Der Broker besteht nach erfolgreicher Installation aus folgenden Teilen:
[ul]
[li]dem Core-Skript, welches die gesamte Programmlogik enthält,[/li][li]einer Server Socket für die Kommunikation mit den Clients,[/li][li]einer RegisterVariable zur Aufteilung des Traffics,[/li][li]einer Kategorie namens MQTT Sessions, unterhalb derer Variablen pro aktiver Session angelegt werden,[/li][li]einer Kategorie namens MQTT Topic Root, unterhalb derer die Topic-Struktur gespiegelt wird,[/li][li]einem Konfigurations-Skript in dem benutzerdefinierte Regeln festgelegt werden können.[/li][/ul]
Funktionsweise
Die Funktionsweise aus Sicht der MQTT-Clients unterscheidet sich nicht weiter von der eines „normalen“ MQTT-Brokers.
Entscheidend für die Anbindung der MQTT-Welt an IP-Symcon ist die Kategorie namens MQTT Topic Root. Sie entspricht der Wurzel aller MQTT-Topics in IP-Symcon. Alle darunter liegenden Topics werden durch entsprechend dem Topic-Namen pro Ebene benannte IP-Symcon-Objekte repräsentiert. Zusammen ergeben diese dann den Topic-Baum.
In MQTT sind die hierarchischen Ebenen der Topics durch Schrägstriche (/) getrennt. Beispielsweise könnte ein Topic foo/bar/myTopic heißen. Im Topic-Baum würde der Broker dies durch eine Kategorie foo darstellen, darunter eine Kategorie bar und darunter wiederum ein Objekt - Variable oder Skript - namens myTopic.
Im Abschnitt „Der Topic-Baum“ wird näher beschrieben, welche Objekte dieser enthalten kann und was sie bedeuten.
Aus Skripten in IP-Symcon heraus kann direkt eine Message auf einem beliebigen Topic publiziert werden. Dazu ist das Core-Skript mit IPS_RunScriptEx() aufzurufen. Näheres dazu im Abschnitt „Publizieren aus IP-Symcon“.
Im Skript namens MQTT User Config können unter Angabe von Topic-Filtern (durch Schrägstriche getrennt, inkl. Wildcards + und #) bestimmte Regeln festgelegt werden, damit sich bestimmte Topics anders als üblich verhalten. Näheres hierzu im Abschnitt „Benutzerkonfiguration“.
Der Topic-Baum
Folgende Objekt-Typen können im Topic-Baum vorkommen und werden ggf. automatisch durch den Broker angelegt:
[ul]
[li]Kategorien - diese werden angelegt für jede Topic-Ebene, unterhalb derer weitere Topics existieren[/li][li]Variablen - diese werden angelegt, um Topics mit persistenten Nachrichten (also mit gesetztem „Retain“-Flag) zu repräsentieren[/li][li]Skripte - diese werden angelegt, um programmatisch auf Veröffentlichungen in Topics zu reagieren, bei denen das „Retain“-Flag nicht gesetzt ist[/li][li]Dummy-Instanzen - diese werden angelegt, um empfangene Daten im JSON-Format (Tasmota u.ä.) darzustellen. Die JSON-Struktur wird deserialisiert und wiederum als Baum von Variablen und Kategorien unterhalb der entsprechenden Dummy-Instanz dargestellt. Der Name der Dummy-Instanz richtet sich ganz normal nach dem Namen der jeweiligen Topic-Ebene. Die Namen der Objekte darunter richten sich nach dem Inhalt des JSON-Datenstrings. Das Verhalten, JSON-Daten automatisch auf diese Weise darzustellen, ist in der Konfigurationsdatei abschaltbar.[/li][/ul]
Neben der automatischen Anlage von Objekten im Topic-Baum ist es auch dem Nutzer bzw. eigenen Skripten erlaubt, dort Objekte anzulegen. Hierbei bestehen folgende weitere Möglichkeiten:
[ul]
[li]Wildcard-Skript - wird auf einer Ebene im Baum ein Skript mit dem Namen # angelegt, so behandelt dieses Skript fortan alle Veröffentlichungen ab dieser Topic-Ebene. Der Broker legt dort keine Skripte pro Topic mehr an, führt aber vorhandene Skripte aus. Variablen bleiben unbeeinflusst.[/li][li]Links - ein Link zu einer Variablen oder einem Skript inner- oder außerhalb des Topic-Baums kann an beliebiger Stelle im Baum gesetzt werden und führt dazu, dass der Broker sich verhält, als sei dieses Objekt direkt an der entsprechenden Stelle, unter dem Namen des Links, vorhanden. Das Linken auf Kategorien ist nicht erlaubt und wird vom Broker ignoriert.[/li][li]Schatten-Variablen - um die jeweils letzte Nachricht eines bestimmten Topics, selbst wenn sie ohne Retain-Flag publiziert wurde, in IP-Symcon als Variable verfügbar zu machen, kann an entsprechender Stelle eine so genannte Schatten-Variable erstellt werden. Diese unterscheidet sich von einer herkömlichen Topic-Variable durch das voran gestellte Sternchen (*). Schatten-Variablen existieren nur zur Verarbeitung in IP-Symcon und sind für MQTT-Clients nicht sichtbar.[/li][/ul]
Andere Objekt-Typen wie (Geräte-)Instanzen sind im Topic-Baum nicht erlaubt und werden vom Broker ignoriert.
Nach Veränderungen im Topic-Baum durch den Benutzer, insbesondere nach Anlegen, Umbenennen oder Verschieben von Variablen, sollte das Core-Skript jeweils einmal in der Konsole ausgeführt werden, um die Änderungen zu übernehmen, damit der Broker bspw. Aktualisierungsereignisse für Variablen einzurichten kann. Es erfolgt zudem eine periodische Überprüfung der Topic-Struktur, allerdings nur alle paar Minuten.
Im Topic-Baum werden Objekte, im Unterschied zur sonst in IP-Symcon üblichen und ratsamen Praxis, absichtlich durch ihre Namen referenziert. Dies ermöglicht eine leicht nachvollziehbare sowie veränderbare Repräsentation der benannten Topic-Ebenen.
Verhalten von Variablen im Topic-Baum
Eine Variable im Topic-Baum steht für ein Topic, unter dem Messages mit gesetztem Retain-Flag - also persistente Nachrichten - publiziert werden. Die Variable enthält normalerweise also den Wert der letzten dort hinterlegten Nachricht, bei der das Retain-Flag gesetzt war. Der Broker legt eine Variable automatisch an, sobald unter dem entsprechenden Topic eine Message mit Retain-Flag publiziert wird.
Wird unter einem Topic, für das eine Variable existiert, eine Message ohne Retain-Flag publiziert, so bleibt die Variable davon unbeeinflusst und wird nicht auf den Wert der Message gesetzt. Auf eine solche Veröffentlichung kann nur mit einem Skript oder einer sog. Schatten-Variable (s.u.) reagiert werden.
Wird unter einem Topic, für das eine Variable existiert, eine leere Message mit Retain-Flag publiziert, so bedeutet dies in MQTT, dass die persistente Nachricht an dieser Stelle entfernt werden soll. Da es in IP-Symcon nicht hilfreich wäre wenn der Broker einfach die Variable löscht (weil dadurch Referenzen auf sie kaputt gehen würden) wird sie stattdessen als versteckt markiert, um eine „nicht mehr gesetzte“ persistente Nachricht zu repräsentieren.
Variablen im Topic-Baum, oder auch Variable außerhalb des Baums, auf die aus diesem heraus verlinkt wird, werden vom Broker auf Aktualisierung überwacht. Wann immer der Wert einer solchen Variable aktualisiert wird, publiziert der Broker automatisch die Änderung als Message unter dem entsprechenden Topic. Dadurch ist es beispielsweise sehr einfach möglich, einen Sensorwert für MQTT-Geräte verfügbar zu machen, indem man einfach von einer geeigneten Stelle im Baum aus auf die Statusvariable mit dem Sensorwert verlinkt.
Einen Sonderfall bilden Schatten-Variablen. Diese heißen ebenfalls entsprechend des (Sub-)Topics, allerdings mit vorangestelltem Sternchen (*). Sie werden vom Broker automatisch auf den Wert der letzten unter diesem Topic veröffentlichten Message gesetzt. Im Gegensatz zu „normalen“ Variablen geschieht das unabhängig davon, ob das Retain-Flag gesetzt wurde. Schatten-Variablen sind nur für die Verarbeitung innerhalb IP-Symcon bestimmt und für MQTT-Clients unsichtbar. Sie dienen als Alternative zur Verarbeitung per Skript.
Ein Link im Topic-Baum, der auf eine Variable verweist wird vom Broker so behandelt, als befinde sich die entsprechende Variable direkt an dieser Stelle, unter dem Namen des Links. Es ist zu beachten, dass auf Topics, für welche schreibgeschützte Variablen existieren (bspw. eine verlinkte Statusvariable eines Geräts) nicht publiziert werden darf. Versucht ein Client dies, so verwirft der Broker die Verbindung.
Skripte im Topic-Baum
Ein Skript im Topic-Baum dient zur Behandlung von publizierten Messages. Dabei erstellt der Broker für jede eingehende Message, die noch nicht anderweitig behandelt wird, automatisch ein Skript unter dem Namen des entsprechenden Topics. In diesem Fall verarbeitet das Skript dann zunächst nur die Messages für das jeweilige Topic.
Im Skript sind folgende Systemvariablen verfügbar:
[ul]
[li]$_IPS[‚SENDER‘] - der String „MQTT“[/li][li]$_IPS[‚TOPIC‘] - der komplette Topic-Pfad gemäß MQTT-Standard, durch Schrägstriche getrennt[/li][li]$_IPS[‚MESSAGE‘] - der Inhalt der publizierten Message[/li][li]$_IPS[‚RETAIN‘] - true oder false, je nachdem ob das Retain-Flag gesetzt wurde[/li][li]$_IPS[‚CLIENT‘] - die Client-ID des MQTT-Clients, der die Message publiziert hat[/li][/ul]
Wie bei Variablen können auch bei Skripten Links verwendet werden. So kann bspw. ein Skript die Messages von mehreren Topics verarbeiten, indem unter jedem Topic-Namen ein Link auf das Skript gesetzt wird. Dabei kann, aber muss das Skript nicht innerhalb des Topic-Baums liegen. Der Link wird vom Broker so verarbeitet, als befände sich das Skript direkt an der jeweiligen Stelle unter dem Namen des Links.
Es ist außerdem möglich, ein Skript mit dem Wildcard-Namen # zu erstellen. Der Broker ruft ein solches Skript für sämtliche Messages ab dieser Ebene auf. Dabei können weiterhin zusätzlich Skripte (oder Links auf Skripte) für einzelne Topics verwendet werden und auch diese ruft der Broker nach wie vor auf. Es werden durch den Broker jedoch ab dieser Ebene keine Skripte mehr automatisch erstellt.
Publizieren aus IP-Symcon
Es gibt zwei Wege, wie von IP-Symcon aus eine Message publiziert werden kann. Am einfachsten ist es, eine Variable zu verwenden, die innerhalb des Topic-Baums liegt oder von dort her verlinkt wird. Eine solche Variable wird vom Broker automatisch publiziert, wann immer ihr Wert aktualisiert wird. Um also bspw. eine Statusvariable einer existierenden Sensor-Instanz zu veröffentlichen, muss lediglich unter der gewünschten Kategorie im Topic-Baum ein Link auf die Statusvariable gesetzt werden. (Zu beachten ist, dass der Broker nach Anlage oder Veränderung von Variableb bzw. Links darauf einmal aus der Konsole ausgeführt werden muss, um seine Variablen-Ereignisse anzupassen!)
Um aus einem Skript heraus eine Message zu publizieren, kann das Core-Skript per IPS_RunScriptEx() aufgerufen werden. Dies sieht dann folgendermaßen aus:
$arr = array(
'TOPIC' => 'foo/bar/myTopic',
'MESSAGE' => 'Hello World!',
'RETAIN' => false, // optional, Default ist false
'CLIENT' => 'some_client_id' // optional
);
IPS_RunScriptEx(12345 /* <- durch ID des Core-Skripts bei euch ersetzen! */);
Datentypen und Datentyp-Eskalation
Der Broker versucht, wenn er eine Variable anlegt, möglichst automatisch einen sinnvollen Datentyp zu wählen. Wird beispielsweise als Message false oder true publiziert, so geht der Broker davon aus, dass es sich um einen booleschen Wert handelt. Bei numerischen Messages wird unterschieden, ob ein Dezimaltrennzeichen vorhanden ist und dementsprechend von Integer oder Float ausgegangen. Alle Messages, die nicht eindeutig (frei von Formatierungen wie führenden Nullen usw.) als Integer oder Float darstellbar sind, werden als String behandelt.
Leider kann der Broker mit diesem „Ratespiel“ auch daneben liegen. Schlimmstenfalls kann der Datentyp der zuvor automatisch erstellten Variable eine später empfangene Message nicht darstellen. Damit keine Messages unterschlagen werden, passiert dann folgendes: Der Broker benennt die existierende Variable um und hängt ein Suffix ([i]@bool[⁄I], [i]@int[⁄I] bzw. [i]@float[⁄I]) an. Er erstellt eine neue Variable an ihrer Stelle, deren Typ den Wert der neu empfangenen Message eindeutig darstellen kann. Dieses Verhalten wird als Datentyp-Eskalation bezeichnet.
Da die Datentyp-Eskalation zwar dem korrekten Funktionieren des MQTT-Brokers zugute kommt, aber innerhalb IP-Symcon unerwünschte Effekte haben kann, ist dieses Verhalten für bestimmte Topic-Filter per Benutzerkonfiguration abschaltbar. Alternativ können händisch Variablen vom Typ String erstellt werden - da Strings jedweden Nachrichteninhalt aufnehmen können findet hier prinzipiell keine Datentyp-Eskalation statt.
Variablen, die aufgrund der Datentyp-Eskalation umbenannt wurden, werden vom Broker aufgrund ihrer Suffix als solche erkannt und ihr Wert wird bei empfangenen Messages mit Retain-Flag weiterhin gesetzt (auf das Ergebnis nach Datentypkonvertierung).
Verlinkte Variablen sind grundsätzlich von der Datentyp-Eskalation ausgenommen, da der Broker keine Links automatisch erstellt. Beim Einsatz von Links muss der Benutzer dafür Sorge tragen, dass der Datentyp der verlinkten Variable geeignet ist, um die zu erwartenden Messages darzustellen. Es wird sonst der Wert nach Datentypkonvertierung gesetzt und weiter publiziert.
Variablenprofile
Die Möglichkeit, per Benutzerkonfiguration für bestimmte Topic-Filter Variablenprofile zu aktivieren, kann ein mächtiges Werkzeug sein. Normalerweise „versteht“ und versendet der Broker für eine numerische Variable ausschließlich unformatierte Daten. Sind jedoch Variablenprofile aktiviert, ergeben sich folgende erweiterte Möglichkeiten:
[ul]
[li]numerische Profile mit Associations anstelle von Status-Strings verwenden, sofern die möglichen Zustände bekannt sind,[/li][li]numerische Profile mit Einheitensuffix (oder Präfix),[/li][li]automatische Wahl eines geeigneten Profile durch den Broker bei Empfang einer Message auf einem Topic, für das Profile aktiviert sind[/li][/ul]
Bei der Verwendung von Variablenprofilen ist allerdings immer etwas Vorsicht geboten und es empfiehlt sich, für die entsprechenden Topics auch die Datentyp-Eskalation abzuschalten.
Benutzerkonfiguration
Die Konfigurationsdatei ist als Skript namens MQTT User Config zugänglich. Dieses Skript kann nicht direkt ausgeführt werden und wird lediglich dem Core-Skript verarbeitet. Es ist darauf zu achten, dass sich keine Syntaxfehler in der Konfigurationsdatei einschleichen, da diese dazu führen dass der Broker nicht mehr läuft!
Aktuell gibt es nur ein assoziatives Array in der Konfigurationsdatei:
$user_rules = array(
"test/hello" => USE_VAR_PROFILE | NO_TYPE_ESCALATION
);
Das Array besteht aus jeweils einem Topic-Filterstring auf der linken Seite, dem eines oder mehrere Konfigurations-Flags zugeordnet sind. Bei mehreren Flags müssen diese durch ein Pipe-Zeichen (|, bitweise ODER) verkettet werden.
Aktuell sind folgende Flags definiert:
[ul]
[li]NO_TYPE_ESCALATION - für den angegebenen Topic-Filter die Datentyp-Eskalation deaktivieren[/li][li]USE_VAR_PROFILE - für den angegebenen Topic-Filter bei Variablen deren Profile (falls gesetzt) anstatt von „Rohdaten“ versenden und empfangen[/li][li]NO_AUTO_INSTANCE_CREATION - für den angegebenen Topic-Filter das automatische Anlegen von Dummy-Instanzen bei empfangenen JSON-Daten deaktivieren [/li][/ul]
Download und Installation
Die jeweils aktuelle Version kann hier heruntergeladen werden:
mqttserver_v0_20.php.zip (23.7 KB)
Zur Installation ist der Inhalt des Skripts zu kopieren und als neues Skript in IP-Symcon einzufügen. Der Name des Skripts in IP-Symcon ist unerheblich und wird beim Setup automatisch angepasst.
Zum Setup wird das Skript jeweils einmal ausgeführt.
Beim Update kann genau so verfahren werden; hier wird der Inhalt des vorhandenen Skripts (nennt sich dann MQTT-Broker x.y Core) einfach mit dem Skript in der neuen Version überschrieben. Anschließend wird es erneut einmal ausgeführt.
Zur Deinstallation wird im Skript oben unter den Eingangskommentaren das Flag $uninstall auf true gesetzt und das Skript ausgeführt.