Diese Version ist veraltet. Nutzen Sie stattdessen die neue Version UrsAI2PahoMqtt, die auf dem Eclipse Paho Java Client beruht.
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. |
1.5 (2020-01-24) | Das Senden von PINGREQUEST-Nachrichten korrigiert. |
1.6 (2020-01-30) | -PublishByteArray & PublishedByteArrayReceived
hinzugefügt. -KeepAlive konnte nicht geändert werden. |
1.7 (2020-03-06) | Mögliche Null-Pointer-Exception bei PublishByteArray
wird abgefangen Funktion IsNull hinzugefügt. |
1.8 (2020-03-20) | - Speicher für unbehandelte Nachrichten wird beim Disconnect gelöscht. - Auf Beendigung des MessageHandler-Threads wird beim Disconnect gewartet, bevor die TCP-Verbindung getrennt wird. - Bei Übertragungsfehlern (Xmit) enthält LastErrorMessage die Nachrichtenart die den Fehler verursacht hat. |
1.9 (2020-11-06) | Fehlerhafte String-Vergleiche behoben [String == "" -> String.isBlank()] |
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.).
Inhaltsverzeichnis
Verbindung zum Broker (Connect, Disconnect, ConnectionState, ConnectionStateChanged)
Topics abonnieren und Nachrichten empfangen (Subscribe, Unsubscribe, PublishedReceived)
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.
Den OASIS-Standard zum MQTT-Protokoll findet man hier: hhttp://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 href="#error") Fehler).
Erläuterungen zu der Funktionsweise findet man im Kapitel MQTT: So funktioniert's
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.
|
Sämtliche Verbindungsdaten können auch über entsprechende Blocks eingestellt oder abgerufen werden:
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.
Die Methode Connect gibt es in zwei Varianten, ohne (Connect) und mit "Letzter Wille" (ConnectWithLastWill).
Verbindungsaufbau ohne Angabe des "Letzten Willens". boolean CleanSession gibt an, ob an einer vorher abgebrochenen Session angeknüpft werden soll. |
|
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.
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. |
Der aktuelle Verbindungsstatus kann jederzeit über die Eigenschaft ConnectionState abgefragt werden.
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:
Der MQTT-Client beginnt im Zustand 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).
Ändert sich der Verbindungszustand, wird das Ereignis ConnectionStateChanged ausgelöst.
int NewState: Numerischer Wert des Zustands (0..4, s.oben) String StateString: Bezeichnung des Zustands ("Disconnected", etc) |
Das Abonnieren von Nachrichtenthemen geschieht über die 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.
boolean Failure: Das Abonnement war nicht erfolgreich. int MaxQoS: Höchster QoS für diese Topic. String Topic: Topic, das abonniert wurde. |
Das Aufheben der Abonnements geschieht über die Methode Unsubscribe.
String Topic: Topic, das abbestellt werden soll. Wildcards
sind erlaubt. |
Empfangene Nachrichten lösen das Ereignis PublishedReceived aus:
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.
Zum Versenden von Nachrichten stehen drei Methoden zur Verfügung.
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. |
||
Vereinfachter Versand Topic: Topic dieser Nachricht. Message: Nachricht als String. RetainFlag wird intern auf false gesetzt. QoS ist 0. |
||
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. |
||
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.
Für ein Projekt war es notwendig, Felder vom Type byte[] (Byte-Array) zu versenden und zu empfangen. App Inventor kann zwar nicht direkt mit Byte-Arrays umgehen, man kann sie jedoch als Variable vom generellen Typ Object zwischen Extensions austauschen. Für mein Web-Cam-Projekt konnten so JPEG-Images aufgenommen und per MQTT versandt werden. Das Android Camera-API liefert die JPEG-Daten als Byte-Array. Für die Behandlung von Byte-Arrays stehen die Blöcke PublishByteArray, SubscribeByteArray und PublishedByteArrayReceived zur Verfügung.
Dieser Block entspricht der Methode PublishEx. Statt des Parameters Message gibt es den Parameter ByteArray. Hier wird der Output einer anderen Extension angehängt, die ein Byte-Array liefert. Standard-Blöcke im App Inventor sind nicht in der Lage, Byte-Arrays zu erzeugen.
Dieser Block entspricht der Methode Subscribe. Alle empfangenen Nachrichten, die zu dem angegebenen Topic passen, werden als Byte-Array aufgefasst und an das Ereignis PublishedByteArrayReceived weiter geleitet. Wildcards für den Parameter Topic sind erlaubt. Bei der Wahl der Topics und der Subskriptionen muss man ein wenig aufpassen, dass die einkommenden Nachrichten auch richtig einsortiert werden. Der MQTT-Client prüft in der Reihenfolge der aufgerufenen Methoden SubscribeByteArray, ob unter dem übermittelten Topic ein ByteArray abonniert wurde. Sobald ein Treffer erzielt wird, wird PublishedByteArrayReceived aufgerufen. Erst, wenn hier kein Treffer erzielt wird, wird PublishedReceived ausgelöst.
Der Vergleich erfolgt nach folgenden Algorithmus: https://github.com/iosphere/mosquitto, Quelle lib/util_mosq.c, Methode mosquitto_topic_matches_sub.
Dieses Ereignis wird ausgelöst, wenn über das Topic der empfangenen Nachricht (s. SubscribeByteArray) erkannt wird, dass ein Byte-Array empfangen wurde. Die Variable ByteArray kann an eine Extension weiter gegeben werden, die Byte-Arrays verarbeiten kann. Eine direkte Verwendung durch App Inventor Standard-Blöcke ist nicht möglich.
Mit dieser Funktion kann man im Vorfeld testen, ob ein Zeiger auf ein Objekt zeigt.
Zur Behandlung von Fehlerfällen stehen folgende Eigenschaften zur Verfügung:
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 | DDer 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 | DDie Nachricht konnte nicht in ein Byte-Feld konvertiert werden. | Invalid binary code | Details zur Codierung: siehe Abschnitt Binärdaten. |
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.
MQTTKitchenLightControl hat einen Screen Settings, mit dem man die Verbindungsdaten einstellen kann. Bei MQTTKitchenLight ist kein "Settings"-Screen implementiert. UserName und Password müssen über die Designer-Properties bei der Erstellung der App angegeben werden. Bei der Eingabe der Daten muss man auf die Autovervollständigung von Android achten. Die fügt gern zusätzliche Zeichen, auch Leerzeichen, ein.
Folgen beiden Abbildungen zeigen Screenshots der App in zwei verschiedenen Zuständen mit Erläuterung der Bildschirmelemente.
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.
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.
Für die Erstellung eigener Extensions habe ich einige Tipps zusammengestellt: AI2 FAQ: Extensions entwickeln.