Englisch version   English version


Version Anpassungen
1.0 (2019-10-31) Initiale Version
1.1 (2019-11-09)  Falsche Packet-ID bei Publish und QoS=1
1.2 (2019-12-04) Benutzername wurde nicht weiter gegeben. Dies hat die Anmeldung bei Brokern verhindert, die eine Autorisierung verlangen.
1.3 (2019-12-04) Bei ungültiger Autorisierung wurde die Verbindung nicht unterbrochen.
1.4 (2019-12-05) Packet-IDs > 127 wurden falsch aufgelöst.

Motivation

Ein kleiner Raspberry Pi Zero dient in meinem WLAN als MQTT-Broker. Schön wäre, wenn ich mit dem App Inventor von MIT eigene Apps für mein Smartphone entwickeln könnte, mit der mit diesem Broker kommuniziert werden könnte. Es gibt zwar bereit MQTT-Extensions für den App Inventor, die benötigen jedoch zusätzliche JavaScript- oder externe Konfigurationsdateien. Die hier vorgestellte MQTT-Cleint-Komponente arbeitet vollkommen selbständig und benötigt keinerlei externen Elemente. Sie unterstützt vollständig das MQTT-Protokoll in der Version 3.1.1 (Ausnahme: Bei Subscribe und Unsubscribe kann nur ein einzelnes Topic angegeben werden, keine Liste. Solche Listen lassen sich mit dem App Inventor schlecht verarbeiten.).

In­halts­ver­zeich­nis

Download

Verwendung

Client einrichten

Verbindung zum Broker (Connect, Disconnect, ConnectionState, ConnectionStateChanged)

Topics abonnieren und Nachrichten empfangen (Subscribe, Unsubscribe, PublishedReceived)

Nachrichten versenden (Publish)

Binärdaten

Fehlerbehandlung

Beispiel

App MQTTKitchenLight

App MQTTKitchenLightControl

Werkzeuge

Download

Das ZIP-Archiv UrsAI2Mqtt zum Download. Das Archiv enthält den Quellcode, das kompilierte Binary zum Upload in den App Inventor und eine Beispiel-Anwendung.

Verwendung

Den OASIS-Standard zum MQTT-Protokoll findet man hier: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html. Eine ausführliche Erklärung der MQTT-Grundlagen gibt es bei HiveMQ: MQTT Essentials.

Bevor der MQTT-Client mit einem MQTT-Broker zu verbunden werden kann, müssen zunächst die Verbindungsdaten eingestellt werden (Abschnitt Client einrichten). Die Verbindung zum Broker wird dann mit Hilfe der Methode Connect erstellt (Abschnitt Verbindung zum Broker). Das Ereignis ConnectionStateChanged meldet jede Änderung des Verbindungszustands. Nach getaner Arbeit kann die Verbindung zum Broker über die Methode Disconnect wieder abgebaut werden.

Mit den verschieden Varianten der Methode Publish werden Nachrichten an den Broker versendet (Abschnitt Nachricht versenden).

Mit Hilfe der Methode Subscribe kann bestimmt werden, zu welchen Topics der Client Nachrichten erhalten möchte (Abschnitt Topic abonnieren). Das Abonnement der Nachrichten kann über die Methode Unsubscribe wieder aufgehoben werden. Der Empfang einer Nachricht zu abonnierten Thema wird über das Ereignis PublishedReceived mitgeteilt.

Die Komponente stellt auch Hilfsmittel zur Fehlerbehandlung bereit (Abschnitt Fehler).

Erläuterungen zu der Funktionsweise findet man im Kapitel MQTT: So funktioniert's

Client einrichten

Bevor eine Verbindung zu einem Broker hergestellt werden kann, müssen der Komponente die Verbindungsdaten mitgeteilt werden. Dies geschieht am einfachsten über das Eigenschaftenfenster des Designers.

Eigenschaften-Fenster im AI2 Desigener
Feld Bedeutung
String Broker Hostname oder IP-Adresse des Brokers.
String ClientID Optional: Eindeutiger Client-Name. Wird kein Wert angegeben, wird intern ein Zufallswert vergeben (GUID). Die Client-ID aller Clients, die mit einem Broker verbunden sind, müssen eindeutig sein.
int IoTimeout Timeout für den Netzwerktransfer. Netzwerk-Operationen müssen in diesem Zeitraum abgeschlossen sein. Die Angabe erfolgt in Sekunden. Nach Überschreiten dieser Zeit gilt die Verbindung als unterbrochen. Der Standardwert ist 5 Sekunden.
int KeepAlive Zeitraum für die erneute Überprüfung der Verbindung. Die Angabe erfolgt in Sekunden. Nach Überschreiten dieser Zeit gilt die Verbindung als unterbrochen.Der Standardwert ist 5 Sekunden.
String Password Optional: Passwort für die Authentifizierung.
int Port Port für die MQTT-Verbindung. Der Standardwert ist 1883.
String UserName Optional: Benutzername für Authentifizierung.

Sämtliche Verbindungsdaten können auch über entsprechende Blocks eingestellt oder abgerufen werden:

Client über Methoden einrichten

Verbindung zum Broker (Connect, Disconnect, ConnectionState, ConnectionStateChanged)

Die Verbindung zum Broker wird über die Methode Connect hergestellt und über die Methode Disconnect wieder abgebaut. Weiterhin können externe Ereignisse Einfluss auf die Verbindung zum Broker haben, wie z.B. Verbindungsverlust zum Netzwerk. Der aktuelle Verbindungszustand kann über die Eigenschaft ConnectionState abgerufen werden. Wenn sich der Zustand der Verbindung ändert, wird das Ereignis ConnectionStateChanged ausgelöst.

Methoden zum Verbindungsauf- und abbau

Die Methode Connect gibt es in zwei Varianten, ohne (Connect) und mit "Letzter Wille" (ConnectWithLastWill).

Block Connect Verbindungsaufbau ohne Angabe des "Letzten Willens".
boolean CleanSession gibt an, ob an einer vorher abgebrochenen Session angeknüpft werden soll.
   
Methode Connect mit 'Last Will' Verbindungsaufbau mit Angabe eines "Letzten Willens".
boolean CleanSession gibt an, ob an einer vorher abgebrochenen Session angeknüpft werden soll. WillTopic, WillQoS, WillRetain und WillMessage entsprechen den Angaben unter der Methode Publish (siehe Abschnitt Nachrichten versenden).

Der geplante Abbau der Verbindung geschieht über die Methode Disconnect. Die Methode besitzt keine weiteren Parameter.

Verbindungsabbau   Beachte: Die bei 'Letzter Wille' hinterlegten Angaben kommen nur dann zum tragen, wenn die Verbindung irregulär unterbrochen wurde. Wird die Verbindung per Disconnect angebaut, wird der Letzte Wille verworfen. Hier ist der Client selbst dafür verantwortlich, dass vor dem Aufruf von Disconnect passende Nachrichten versandt werden.

Verbindungszustände

Der aktuelle Verbindungsstatus kann jederzeit über die Eigenschaft ConnectionState abgefragt werden.

Eigenschaft ConnectionState int ConnectionState: siehe folgende Tabelle.

Mögliche Zustände sind:

Code Zustand Bedeutung Erlaubte Methoden
0 Disconnected Der Client ist nicht mit einem Broker verbunden. Connect
1 Connecting Der Client versucht eine Verbindung zum Broker herzustellen. -
2 Connected Es besteht eine Verbindung zu einem Broker. Subscribe, Publish, Disconnect
3 Disconnecting Der Client baut die Verbindung zum Broker ab. -
4 ConnectionAborted Die Verbindung konnte auf Grund eines Fehlers nicht hergestellt werden oder wurde unterbrochen. Fehlerursache kann über die Eigenschaft LastErrorCode und LastErrorMessage abgerufen werden. Connect

Das nachfolgende Zustandsdiagramm erläutert den Vorgang des Verbindungsauf- und abbaus:

Zustandsdiagramm Verbindungszustand

Der MQTT-Client beginnt im Zustand span class="symbolNoWrap">Disconnected (0). Mit Aufruf der Methode Connect geht er in den Zustand Connecting (1) über. Es wird versucht eine eine eine TCP-Verbindung zum MQTT-Broker aufzubauen und eine MQTT-Nachricht vom Typ CONNECT an den Broker zu versenden. Gelingt dies nicht (Error) oder wird keine Nachricht vom Typ CONNACK vom Broker empfangen, geht der Client in den Zustand ConnectionAborted (4) über.

Wird eine Nachricht vom Typ CONNACK empfangen, wird anhand dieser überprüft, ob der Broker die Verbindungsanfrage des Clients akzeptiert. Ist dies nicht der Fall (CONNACK Error) wird die Netzwerkverbindung abgebaut und der Client geht in den Zustand ConnectionAborted (4) über. Akzeptiert der Broker die Verbindung (CONNACK ok), geht der Client in den Zustand Connected (2) über.

Der Abbau der Verbindung beginnt mit dem Aufruf der Methode Disconnect. Der Client geht in den Zustand Diconnecting (3) über. Als erstes wird eine Nachricht vom Typ DISCONNECT an den Broker versandt. Gelingt dies geht der Client in den Zustand Diconnected (0) über. Im Fehlerfall (Error) wechselt der Zustand auf ConnectionAborted (4).

Tritt im laufenden Betrieb ein Fehler auf (ReceiverTreadError), wird die Netzwerkverbindung abgebaut und der Zustand wechselt auf ConnectionAborted (4).

Ereignis Verbindungszustand hat gewechselt

Ändert sich der Verbindungszustand, wird das Ereignis ConnectionStateChanged ausgelöst.

Ereignis ConnectionStateChanged   int NewState: Numerischer Wert des Zustands (0..4, s.oben)
String StateString: Bezeichnung des Zustands ("Disconnected", etc)

Topics abonnieren und Nachrichten empfangen (Subscribe, Unsubscribe, PublishedReceived)

Topics abonnieren

Das Abonnieren von Nachrichtenthemen geschieht über die Methode Subscribe.

Methode Subscribe   String Topic: Topic zu dem Nachrichten  empfangen werden sollen. Wildcards sind erlaubt.
int QoS: Gewünschter Service-Level, mit dem diese Nachrichten empfangen werden sollen.

Über das Ereignis SuBackReceived meldet der Broker zurück, welcher maximaler QoS für dieses Thema unterstützt wird. Dies ist in der Regel der angeforderte Level.

Ereignis SuBackReceived   boolean Failure: Das Abonnement war nicht erfolgreich.
int MaxQoS: Höchster QoS für diese Topic.
String Topic: Topic, das abonniert wurde.

Abonnements aufheben

Das Aufheben der Abonnements geschieht über die Methode Unsubscribe.

Methode Unsubscribe   String Topic: Topic, das abbestellt werden soll. Wildcards sind erlaubt.

Nachrichten empfangen

Empfangene Nachrichten lösen das Ereignis PublishedReceived aus:

Ereignis PublishedReceived   String Topic: Topic dieser Nachricht.
String Payload: Nachrichteninhalt im Binärformat (s.u.)
String Message: Nachricht als String
boolean RetainFlag: Gibt an, ob es sich um eine aufbewahrte Nachricht handelt.
boolean DupFlag: Gibt an, ob es sich um eine Wiederholung des Versands handelt.

Nachrichteninhalte von MQTT-Nachrichten sind Byte-Felder. Diese Byte-Felder werden als String codiert über den Parameter Payload zur Verfügung gestellt. Das Codierverahren wird im Abschnitt Binärdaten erleutert. Meist handelt es sich jedoch um Texte um "UTF-8"-Format. Deshalb wird versucht, das Byte-Feld in einen Text umzuwandeln. Gelingt dies, steht der Text unter dem Parameter Message zur Verfügung. Andernfalls enthält Message einen Leerstring.

Nachrichten versenden (Publish)

Zum Versenden von Nachrichten stehen drei Methoden zur Verfügung.

Methode Publish Standard-Versand-Methode

Topic: Topic dieser Nachricht.
Message: Nachricht als String.
RetainFlag: Gibt an, ob es sich um eine aufbewahrte Nachricht handelt.
QoS: Service Level für diesen Versand.
Methode Publish   Vereinfachter Versand

Topic: Topic dieser Nachricht.
Message: Nachricht als String.
RetainFlag wird intern auf false gesetzt. QoS ist 0.
Methode Publish   Versand von Binär-Nachrichten

Topic: Topic dieser Nachricht.
BinaryMessage: Binärwerte codiert als String.
RetainFlag: Gibt an, ob es sich um eine aufbewahrte Nachricht handelt.
QoS: Service Level für diesen Versand.
     


Binärdaten

App Inventor kennt keine Byte-Felder. Byte-Felder werden auch nur sehr selten benötigt. In dieser Komponente werden binäre Daten über einen String verschlüsselt. Dies ist eine Zeichenfolge mit codierten Bytes, die durch ein Komma (’,’) oder ein Semikolon (';') voneinander getrennt sind.

Jedes Byte kann als „0xff“ oder „0xFF“ oder „0Xff“ oder „0XFF“ oder „#ff“ oder „#FF“ für HEX-Input oder „255“ für dezimalen Input oder „0377“ für den Oktal-Input codiert sein.

Man kann die Formate mischen: "0xFF;255,#ff" ist gültig.

Man kann auch Leerzeichen vor und nach der Zahl einfügen: „0xFF;  255,  #ff ”ist ebenfalls gültig.

Ein nachfolgendes Komma oder Semikolon wird ignoriert: "0xFF; 255, #ff" und "0xFF; 255, #ff;" sind identisch.

Beim Datenempfang wird das empfangene Paket in eine durch Semikola getrennte Zeichenfolge von Dezimalzahlen übersetzt, z.B. "123;33;0;44". In der AI2-App kann man String.Split verwenden, um eine Liste der Bytes abzurufen.

Für die Konvertierung wird dieser Algorithmus verwandt:
1) Ersetzen aller Kommas durch Semikolons
2) Splitten der Zeichenfolge per Semikolons
3) Führende und nachfolgende Leerzeichen löschen
4) Konvertierung in Integer mit Integer.decode ()
5) Auf Werte <0 oder> 255 prüfen.

Fehlerbehandlung

Zur Behandlung von Fehlerfällen stehen folgende Eigenschaften zur Verfügung:

Eigenschaften zur Fehlerbehandlung LastAction: Bezeichnung der zuletzt ausgeführten Aktion, z.B. "Connect"
LastErrorCode: Fehler-Code des zuletzt aufgetretenen Fehlers (s.u.)
LastErrorMessage: Text zum Code, z.B. "Unacceptable protocol version"

Wichtiger Hinweis: Die genannten Eigenschaften enthalten nur Hinweise auf wahrscheinliche Fehlerereignisse. Viele Aktionen werden asynchron ausgeführt. Da ist die Fehlerverfolgung nicht immer einwandfrei möglich.

Code Bedeutung Text Anmerkung
0 Verbindung wurde erfolgreich hergestellt.    
1 Verbindung abgelehnt, inakzeptable Protokollversion. Der Server unterstützt nicht die vom Client angeforderte Stufe des MQTT-Protokolls. Unacceptable protocol version Vom Broker per CONNACK-Nachricht zurück gemeldet.
2 Verbindung abgelehnt, Kennung abgelehnt. Die Client-ID ist korrektes UTF-8, aber vom Server nicht zugelassen.  Identifier rejected Vom Broker per CONNACK-Nachricht zurück gemeldet.
3 Verbindung abgelehnt, Server nicht verfügbar. Die Netzwerkverbindung wurde hergestellt, aber der MQTT-Dienst ist nicht verfügbar. Server unavailable Vom Broker per CONNACK-Nachricht zurück gemeldet.
4 Verbindung abgelehnt, falscher Benutzername oder Passwort. Die Daten im Benutzernamen oder Passwort sind fehlerhaft. Bad user name or password Vom Broker per CONNACK-Nachricht zurück gemeldet.
5 Verbindung abgelehnt, nicht autorisiert. Der Client ist nicht berechtigt, eine Verbindung herzustellen. Not authorized Vom Broker per CONNACK-Nachricht zurück gemeldet.
-1 Der Aufruf dieser Methode ist im aktuellen Betriebszustand nicht möglich. Invalid state Der aktuelle Zustand muss Disconnected oder ConnecteionAborted sein. IsDisconnected liefert den passenden Wert.
-2 Der Port-Parameter liegt außerhalb des Bereichs gültiger Port-Werte oder der Hostname-Parameter ist leer. Invalid Endpoint Problem beim Zusammenstellen des IP-Endpunkts (Java InetSocketAddress)
oder
Java Socket Fehler
-3 Ein Sicherheitsmanager ist aktiv und die Berechtigung zum Auflösen des Hostnamens wird verweigert. Security violation Problem beim Zusammenstellen des IP-Endpunkts (Java InetSocketAddress)
-4 Zeitüberschreitung beim Verbindungsaufbau. Connection timeout Java Socket Fehler
-5 Während des Verbindungsaufbaus ist ein Fehler aufgetreten. IO-Error Interner Fehler Java Socket Fehler
-6 Die Socket-Streams konnten nicht angelegt werden. Socket getStream problem Interner Fehler Java Socket Fehler
-7 Eine für den Sperrmodus spezifische Operation wurde im falschen Sperrmodus aufgerufen. Illegal blocking mode Formaler Interner Fehler, sollte niemals auftauchen.
-8 Timeout beim Datenempfang. Read timeout Zeitüberschreitung beim Einlesen einer eingegangen Nachricht.
-9 Die empfangene Nachricht hat ein ungültiges Format. Invalid format  
-10 Fehler beim Versenden einer MQTT-Nachricht. Xmit failure  
-11 Der Server hat auf eine Ping-Anfrage nicht geantwortet. Ping failure Die Verbindung zum Server besteht nicht mehr.
-12 Topic nicht angegeben oder leer. Empty topic Die Angabe eines Topic ist bei manchen Nachrichten Pflicht.
-13 Die Nachricht konnte nicht in ein Byte-Feld konvertiert werden. Invalid binary code Details zur Codierung: siehe Abschnitt Binärdaten.

Beispiel

Das Beispiel enthält zwei Apps, die miteinander per MQTT kommunizieren. Die eine App, MQTTKitchenLight, emuliert eine einfache Lampe. Über einen Schalter kann die Lampe an und ausgeschaltet werden.

Diese Lampe verbindet sich mit einem MQTT-Broker verbinden und, wenn verbunden, publiziert den aktuellen Zustand (on, off, offline). Außerdem kann die Lampe Kommandos per MQTT empfangen (toggle) und reagiert entsprechend.

Die zweite App, MQTTKitchenLightControl, empfängt per MQTT die Zustandsnachrichten der ersten App und zeigt diesen an. Über einen Schalter können Kommandos zum Umschalten an die erste App geschickt werden.

App MQTTKitchenLight

Folgen beiden Abbildungen zeigen Screenshots der App in zwei verschiedenen Zuständen mit Erläuterung der Bildschirmelemente.

App MQTTKitchenLight   App MQTTKitchenLight

Funktionsweise

MQTTKitchenLight, also die emulierte Lampe, beginnt mit dem Lampen-Zustand off. Wird die Schaltfläche "On/Off" gedrückt, wechselt der Zustand nach on und beim nächsten Drücken wieder auf off. Bei jedem Wechsel des Lampen-Zustands sendet die App ihren aktuellen Zustand, also "on" oder "off", unter dem Topic "home/kitchen/light/state" an den MQTT-Broker. Die Nachricht wird mit gesetzten Retain-Flag versandt, damit später hinzukommende Controller beim Anmelden an den Broker gleich den aktuellen Zustand übermittelt erhalten.

Der Controller muss ebenfalls wissen, ob die App, also die Lampe, online ist. Deshalb versendet die App vor dem der Verbindungstrennung unter dem gleichem Topic die Nachricht "offline". Um dies auch bei einem unvorhergesehen Verbindungsabbruch zu gewährleisten, wird beim Verbinden mit dem Server ein entsprechender "Letzter Wille" eingerichtet.

Die App abonniert den Topic "home/kitchen/light/cmd". Erhält sie eine Nachricht unter diesem Topic, wird die Lampe umgeschaltet. Der Nachrichteninhalt sollte "toggle" sein, wird aber nicht ausgewertet.

App MQTTKitchenLightControl

App MQTTKitchenLightControl   App MQTTKitchenLightControl   App MQTTKitchenLightControl   App MQTTKitchenLightControl

Funktionsweise

Die App kennt drei Zustände für die Lampe: unbekannt (offline), an (on) und aus (off). Im unverbundenen Zustand ist der Zustand prinzipiell unbekannt. Nach Verbindung mit dem Broker erhält die App eine Zustandsinformation vom Broker (Topic "home/kitchen/light/state"). Die App MQTTKitchenLight hinterlegt den aktuellen Zustand der Lampe als Nachricht mit gesetzten Retain-Flag, bzw. den Zustand offline, wenn die Verbindung getrennt oder unterbrochen (Last Will) wird. Die App stellt den Lampenzustand entsprechend dar.

Im verbunden Zustand (App verbunden und aktueller Zustand on oder oder off) ist die Schaltfläche "On/Off" aktiviert. Wird sie gedrückt, sendet die App die Nachricht "toggle" unter dem Topic "home/kitchen/light/cmd" an den Broker. Erhält die App MQTTKitchenLight diese Meldung, schaltet die den Lampenzustand um und sendet eine entsprechende Nachricht mit dem neuen Zustand.

Die App besitzt einen zusätzlichen Dialog mit dem die Verbindungsdaten eingestellt werden können.

Im System können beliebig viele Apps vom Typ MQTTKitchenLightControl betrieben werden und eine einzelne App vom Typ MQTTKitchenLight steuern. Will man mehrere Apps vom Typ MQTTKitchenLight steuern, muss man die Topics konfigurierbar machen.

Werkzeuge

Für die Erstellung eigener Extensions habe ich einige Tipps zusammengestellt: AI2 FAQ: Extensions entwickeln.