Englisch version   English version


Version Anpassungen
1.0 (2020-11-15) Initiale Version

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.).

Diese Version des MQTT-Client basiert auf dem Eclipse Paho Java Client (Die Klassenreferenz für tiefergehende Informationen). Sie ist nicht kompatibel mit der vorhergehenden Version, erlaubt es aber, SSL/TLS-Verbindungen für höhere Sicherheitsanforderungen zu nutzen.

In­halts­ver­zeich­nis

Download

Verwendung

Client einrichten

Verbindungsdaten

Identifizierung

Authentifizierung

Verbindungskontrolle

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

Methoden zum Verbindungsauf- und abbau

Verbindungszustände

Ereignis Verbindungszustand hat gewechselt

Fehler beim Verbindungsaufbau / -betrieb

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

Topics abonnieren

Abonnements aufheben

Nachrichten empfangen

Nachrichten versenden (Publish)

Binärdaten

Binärdaten II - Byte Array

Sonstige Methoden

IsNull

ToDictionary / FromDictionary

Fehlerbehandlung

Beispiele

SSL / TLC Test

Kitchenlight

App MQTTKitchenLight

App MQTTKitchenLightControl

Erstellen der Extension

Anpassungen des Eclipse Paho Client

Logging

Netzwerkmodule

Build-Vorgang

.jar-Bibliothek erstellen

Anpassen von build.xml

Sourcecode anpassen

Werkzeuge

Download

Das ZIP-Archiv UrsAI2PahoMqtt 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: 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.

Informationen zum Eclipse Paho Java Client. Die Klassenreferenz für tiefergehende Informationen.

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 MessageReceived 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.

Verbindungsdaten

Broker
Hostname oder IP-Adresse des Brokers.
Port
Port für die MQTT-Verbindung. Der Standardwert ist 1883.
Protocol
Protokoll-Typ. Erlaubte Angaben sind "TCP", "SSL" und "TLS". Andere Angaben werden ignoriert.

Identifizierung

ClientID
Optional: Eindeutiger Client-Name. Wird kein Wert angegeben, wird intern ein Zufallswert vergeben (GUID). Die Client-ID aller Clients, die mit einem Broker gleichzeitig verbunden sind, müssen eindeutig sein.

Authentifizierung

UserName
Optional: Benutzername für Authentifizierung.
UserPasword
Optional: Passwort für die Authentifizierung.

SSL/TLS: Server-Authentifizierung

Die Server-Authentifizierung (bei Protokoll SSL/TLS) wird über die Eigenschaften ClientCertFile und TruststoreFile eingestellt.

TrustedCertFile TruststoreFile Prüfung
Dateiname X Gegen das angegebene Zertifikat.
leer Dateiname Gegen die Zertifikate im Truststore. TruststorePassword erforderlich, wenn der Truststore mit einem Passwort gesichert ist.
leer leer Gegen ein CA-signiertes Zertifikat, das der Server beim Verbindungsaufbau übermittelt.
TrustedCertFile
Optional: Name der Zertifikatsdatei.
TruststoreFile
Optional: Name der Truststore-Datei.
TruststorePassword
Optional: Passwort zum Öffnen des Truststores.

SSL/TLS: Client-Authentifizierung

Die Client-Authentifizierung (bei Protokoll SSL/TLS) wird über die Eigenschaften ClientCertFile und ClientKeystoreFile eingestellt.

ClientCertFile ClientKeystoreFile Prüfung
Dateiname X Mit dem angegebenen Zertifikat und dem privaten Schlüssel in ClientKeyFile. ClientKeyPassword erforderlich, wenn die KeyFile mit einem Passwort gesichert ist.
leer Dateiname Mit dem Zertifikat und privatem Schlüssel im Keystore. ClientKeystorePassword erforderlich, wenn der Keystore mit einem Passwort gesichert ist.
leer leer Keine Client-Authentifizierung.

Der Keystore kann im PKCS#12-Format (Dateiendung "p12" oder "pfx") oder im Standardformat vorliegen.

Authentifizierung über eine Zertifikatsdatei
ClientCertFile
Optional: Name der Zertifikatsdatei.
ClientKeyFile
Optional: Name der Datei mit dem privaten Schlüssel. Pflichtangabe, wenn die Authentifizierung über eine Zertifikatsdatei erfolgt.
ClientKeyPassword
Optional: Passwort zum Öffnen der Schlüsseldatei.
ClientPemFormatted
Optional: Das Zertifikat und der Schlüssel liegen im PEM-Format vor.
Authentifizierung über einen Keystore
ClientKeystoreFile
Optional: Name der Keystore-Datei. Der Keystore kann im PKCS#12-Format (Dateiendung "p12" oder "pfx") oder im Standardformat vorliegen.
ClientKeystrorePassword
Optional: Passwort zum Öffnen des Keystores.

Verbindungskontrolle

ConnectionTimeout
Legt den Wert für das Verbindungszeitlimit fest. Dieser in Sekunden gemessene Wert definiert das maximale Zeitintervall, in dem der Client auf den Aufbau der Netzwerkverbindung zum MQTT-Server wartet. Das Standardzeitlimit beträgt 30 Sekunden. Der Wert 0 deaktiviert die Timeout-Verarbeitung, d.h. der Client wartet, bis die Netzwerkverbindung erfolgreich hergestellt wurde oder fehlschlägt.
TimeToWait
Legt die maximale Wartezeit in Millisekunden für den Abschluss einer Aktion fest, bevor die Steuerung an die aufrufende Anwendung zurückgegeben wird. Die Kontrolle wird zurückgegeben, wenn:
  • Die Aktion ist abgeschlossen oder
  • wenn das Timeout überschritten wird oder
  • wenn der Client getrennt / heruntergefahren wird.

Der Standardwert ist -1, was bedeutet, dass die Aktion keine Zeitüberschreitung aufweist. Im Falle einer Zeitüberschreitung wird die Aktion im Hintergrund fortgesetzt, bis sie abgeschlossen ist.
MaxInflight
Die maximale Anzahl Nachrichten die gesendet werden kann, ohne Bestätigungen zu erhalten. Der Standardwert ist 10. Bei hohem Datenaufkommen sollte der Wert erhöht werden.
KeepAlive
Dieser in Sekunden gemessene Wert definiert das maximale Zeitintervall zwischen gesendeten oder empfangenen Nachrichten. Dadurch kann der Client erkennen, ob der Server nicht mehr verfügbar ist, ohne auf das TCP/IP-Timeout warten zu müssen. Der Client stellt sicher, dass innerhalb jeder Keepalive-Periode mindestens eine Nachricht über das Netzwerk gesendet wird. Wenn während des Zeitraums keine datenbezogene Nachricht vorliegt, sendet der Client eine sehr kleine "Ping" -Nachricht, die der Server bestätigt. Der Wert 0 deaktiviert die Keepalive-Verarbeitung im Client.

Der Standardwert ist 60 Sekunden

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

Die Konstanten stehen als benannte Eigenschaften zur Verfügung:

Zur Vereinfachung der Zustandsabfrage wurde die Eigenschaften IsConnected und IsDisconnected implementiert:

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

Zustandsdiagramm Verbindungszustand

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, 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)

Fehler beim Verbindungsaufbau / -betrieb

Bevor versucht wird, eine Verbindung herzustellen, wird zunächst geprüft, ob der Zustand des Clients dies erlaubt (Zustand = Disconnected oder ConnectionAborted). Ist dies nicht der Fall, wird Versuch abgebrochen und das Ereignis ErrorOccurred mit dem Code 32300 ("Invalid State.") ausgelöst.

Ist der Zustand zulässig, geht der Client in den Zustand Connecting über. Alle weiteren Fehler führen zum Auslösen des Ereignisses ConnectionStateChanged mit dem Zustand ConnectionAborted. Weitere Details finden sich im Abschnitt Fehlerbehandlung.

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

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.

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 MessageReceived aus:

Ereignis MessageReceived   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 Codierverfahren wird im Abschnitt Binärdaten erläutert. 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 (s. nächster Abschnitt)

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.

Binärdaten II - Byte-Array

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.

PublishByteArray

Methode PublishbyteArray

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.

SubscribeByteArray

Methode SubscriveByteArray

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.

PublishedByteArrayReceived

Event PublishedByteArrayReceived

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.

Sonstige Methoden

IsNull

Funktion IsNull

Mit dieser Funktion kann man im Vorfeld testen, ob ein Zeiger auf ein Objekt zeigt.

ToDictionary / FromDictionary

Diese Funktionen erleichtert die Verwaltung von Verbindungsdaten. ToDictionary erstellt eine Dictionary mit allen Eigenschaften des Objekts mit folgenden Schlüsseln: Broker, Port, ConnectionTimeout, TimeToWait, KeepAlive, ClientID, UserName, UserPassword, Protocol, MaxInflight, TrustedCertFile, TruststoreFile, TruststorePassword, ClientCertFile, ClientKeyFile, ClientKeyPassword, ClientPemFormatted, ClientKeystoreFile, ClientKeystorePassword.

FromDictionary erlaubt es, die Eigenschaften eines MQTT-Client-Objekts mit den Daten aus einer Dictionary zu laden. Es müssen stets alle oben angegebenen Schlüssel in der Dictionary vorhanden sein.

Fehlerbehandlung

Fehler werden über das Ereignis ErrorOccurred gemeldet oder über ConnectionStateChanged, wenn die Verbindung abgebrochen wurde (ConnectionAborted, Code 4).

ActionName: Name der Funktion, die fehlerhaft ausgeführt wurde, z.B. "Subscribe".
ErrorCode: Fehlernummer.
ErrorMessage: zugehöriger Text (in Englisch).

Weitere Details zur Fehlerursache können über diese Eigenschaften abgerufen werden:

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. "Invalid State."

LastExceptionCause: Text der Exception, die den Fehler ausgelöst hat.

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. Die Fehlernummern 1..32203 sind Fehlernummern des Paho-Clients.

Code Bedeutung Text
0 Verbindung wurde erfolgreich hergestellt.  
1 Die angeforderte Protokollversion wird vom Server nicht unterstützt. Invalid protocol version.
2 Der Server hat die angegebene Client-ID abgelehnt. Invalid client ID.
3 Der Broker war nicht verfügbar, um die Anfrage zu bearbeiten. Broker unavailable.
4 Die Authentifizierung beim Server ist aufgrund eines falschen Benutzernamens oder Kennworts fehlgeschlagen. Bad user name or password.
5 Nicht berechtigt, den angeforderten Vorgang auszuführen. Not authorized to connect.
6 Ein unerwarteter Fehler ist aufgetreten. Unexpected error.
128 Fehler beim Subscribe - vom Server abgelehnt. Error from subscribe.
32000 Zeitüberschreitung beim Warten auf eine Antwort vom Server. Der Server reagiert nicht mehr auf Keep-Alive-Nachrichten. Timed out waiting for a response.
32001 Interner Fehler, der dadurch verursacht wird, dass keine neuen Nachrichten-IDs verfügbar sind. No new message ID available
32002 Zeitüberschreitung beim Schreiben von Nachrichten an den Server. Timed out at writing.
32100 Der Client ist bereits verbunden. Already connected.
32101 Der Client ist bereits getrennt. Already disconnected.
32102 Der Client trennt derzeit die Verbindung und kann keine neuen Aufgaben annehmen.

Dies kann auftreten, wenn auf ein Token gewartet und dann der Client getrennt wird.
Currently disconnecting.
32103 Die Verbindung zum Server kann nicht hergestellt werden. Unable to connect to server.
32104 Der Client ist nicht mit dem Server verbunden. Die Methode Connect... muss zuerst aufgerufen werden. Es ist auch möglich, dass die Verbindung unterbrochen wurde. Not connected.
32105 Server-URI und bereitgestellte SocketFactory stimmen nicht überein.
URIs, die mit "tcp: //" beginnen, müssen eine javax.net.SocketFactory verwenden, und URIs, die mit "ssl: //" beginnen, müssen eine javax.net.ssl.SSLSocketFactory verwenden.

Dieser Fehler kann aufgrund der Implementierung des Wrappers nicht auftreten.
URI and SocketFactory do not match.
32106 SSL-Konfigurationsfehler. SSL configuration error.
32107  Disconnect innerhalb des Ereignisses MessageReceived aufgerufen.

Diese Methode wird vom Client-Thread aufgerufen und darf nicht zur Steuerung der Trennung verwendet werden.
Disconnecting not allowed.
32108 Protokollfehler: Die Nachricht wurde nicht als gültiges MQTT-Paket erkannt.

Mögliche Gründe hierfür sind die Verbindung zu einem Nicht-MQTT-Server oder die Verbindung zu einem SSL-Server-Port, wenn der Client kein SSL verwendet.
Unrecognized packet.
32109 Der Client wurde unerwartet vom Server getrennt. Connection lost.
32110 Bei einem bereits laufenden Verbindungsvorgang kann jeweils nur eine Verbindung hergestellt werden.

Dieser Fehler kann aufgrund der Implementierung des Wrappers nicht auftreten.
A connect already in progress.
32111 Der Client ist geschlossen - in diesem Status sind keine Vorgänge auf dem Client zulässig.

Dieser Fehler kann aufgrund der Implementierung des Wrappers nicht auftreten.
The client is closed.
32201 Es wurde eine Anforderung zur Verwendung eines Tokens gestellt, das bereits einer anderen Aktion zugeordnet ist.

Dieser Fehler kann aufgrund der Implementierung des Wrappers nicht auftreten.
Token already in use.
32202 Es wurde eine Anfrage zum Senden einer Nachricht gestellt, aber die maximale Anzahl von Inflight-Nachrichten wurde bereits erreicht. Too many publishes in progress.
32300 Der aktuelle Zustand lässt die gewünschte Aktion nicht zu.

Verbindungsaufbau: Der Zustand muss Disconnected (Code 0) oder ConnectionAborted (Code 4) sein.
Senden (Subscribe, Unsubscribe, Publish...): Der Zustand muss Connected (Code 2) sein.
Invalid State.
32301 Das angegebene Topic ist leer.

Bei ConnectWithLastWill, Subscribe, Unsubscribe, Publish... ist die Angabe eines Topics verpflichtend.
Empty topic.
32302 Methode PublishBinary: Der angegebene String kann nicht in das Binärformat konvertiert werden.

Siehe Anleitung im Abschnitt Binärdaten.
Invalid binary code.
32303 Methode PublishByteArray: Das Argument ByteArray ist nicht vom Typ Byte-Array (byte[ ]). Not a byte array.
32304 Methode Connect...: Ungültige Verbindungsdaten. Invalid connection parameters.
32305 Methode FromDictionary: Das angegebene Dictionary enthält nicht alle notwendigen Felder. Invalid dictionary content.
32306 Methode Connect...: Die bei TruststoreFile angegebene Datei kann nicht geladen werden. Cannot load truststore file.
32307 Methode Connect...: Die bei TrustedCertFile angegebene Datei kann nicht geladen werden. Cannot load trusted certificate file.
32308 Methode Connect...: Die bei ClientCertFile oder ClientKeyFile angegebene Datei kann nicht geladen werden. Cannot load client certificate file or key file.
32309 Methode Connect...: Die bei ClientKeystoreFile angegebene Datei kann nicht geladen werden. Cannot load client keystore file.

Beispiele

SSL / TLC Test

Diese kleine App stellt eine Verbindung zu Mosquitto-Test-Broker her. Sie erlaubt das Abonnieren und das Versenden von Nachrichten.

Kitchenlight

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.

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.

Erstellen der Extension

Anpassungen des Eclipse Paho Client

Der Original Eclipse Paho Java Client Version 1.2.5 lässt sich leider nicht direkt verwenden. Der Logging-Mechanismus benutzt Methoden, die in der App-Inventor-Umgebung nicht zur Verfügung stehen. Außerdem klappt das Laden der Netzwerkmodule nicht. Folgende Anpassung wurde durchgeführt.

Die Klasse UrsDummyComponent wurde hinzugefügt, damit beim nachfolgenden Build die benötigten Dateien erstellt werden.

Logging

Im Package org.eclipse.paho.client.mqttv3.logging wurde die Klasse UrsLogger hinzugefügt. Diese implementiert das Interface Logger, jedoch mit leeren Methoden. Die Methoden getLogger der Klasse LoggerFactory wurden so abgeändert, dass sie ein Objekt der Klasse UrsLogger zurück geben.

Damit ist der Logging-Mechanismus des Paho Clients außer Betrieb gesetzt. Wenn das interne Logging wieder aktiviert werden soll, müssen die leeren Methoden der UrsLogger-Klasse die Funktionalitäten auf die Android Log-Klasse abgebildet werden.

Neztwerkmodule

Im Paho Client werden die Netzwerkmodule dynamisch anhand ihrer Klasse (Ableitung von NetworkModuleFactory) erkannt und geladen. In der Klasse NetworkModuleService wurden deshalb die vorhanden Module als statische Liste eingetragen.

Build-Vorgang

Bei Verzeichnisangaben in den folgenden Abschnitten muss <user> stets durch den entsprechenden Namen ersetzt werden!

.jar-Bibliothek erstellen

Um eine Bibliothek zu erstellen, die später in die Extension eingebunden werden kann, kann wie folgt vorgegangen werden. Die Methode basiert auf einer Installation der Entwicklungsumgebung wie sie im unter AI2 FAQ: Extensions entwickeln beschrieben wurde.

Zunächst werden die Quellen in das Verzeichnis C:\Users\<user>\appinventor-sources\appinventor\components\src kopiert.

Neben dem Verzeichnis com für die AI2-Quellen und de für meine Extensions gibt es nun das Verzeichnis org mit den Paho-Quellen. Übersetzt man nun wie gewohnt ("ant extensions" im "Git Bash"-Fenster), wird die .jar-Bibliothek

 C:\Users\<user>\appinventor-sources\appinventor\components\build\externalComponents-class\org.eclipse.paho.client.mqttv3.jar

erstellt. Diese Datei benennt man um in ursmod-paho-1.2.5.jar und kopiert sie in den Ordner

C:/Users/<user>/appinventor-sources/appinventor/components/src/de/ullisroboterseite/ursai2pahomqtt.

Das ist der Quellen-Ordner für die Extension.

Anpassen von build.xml

In Datei

C:/Users/<user>/appinventor-sources/appinventor/components/build.xml

muss folgender Eintrag in der Rubrik CopyComponentLibraries ergänzt werden (eine Zeile!):

<copy toFile="${public.deps.dir}/paho.jar" file="C:/Users/<user>i/appinventor-sources/appinventor/components/src/de/ullisroboterseite/ursai2pahomqtt/ursmod-paho-1.2.5.jar " />

Dies bewirkt, dass beim erstellen der Extension, die Bibliothek aus dem Quellen-Verzeichnis in den vorgesehenen Order kopiert wird und eingebunden werden kann.

Die Quellen für den Paho Client können nun wieder entfernt werden.

Sourcecode anpassen

Die Komponente erhält die zusätzliche Annotation

@UsesLibraries(libraries = "paho.jar")

Werkzeuge

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