Version | Anpassungen |
---|---|
1.0 (2020-11-15) | Initiale Version |
1.1 (2024-09-05) | Angepasst an Android 14 |
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.
Inhaltsverzeichnis
Verbindung zum Broker (Connect, Disconnect, ConnectionState, ConnectionStateChanged)
Methoden zum Verbindungsauf- und abbau
Ereignis Verbindungszustand hat gewechselt
Fehler beim Verbindungsaufbau / -betrieb
Topics abonnieren und Nachrichten empfangen (Subscribe, Unsubscribe, MessageReceived)
Nachrichten versenden (Publish)
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.
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
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.
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. |
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.
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 |
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:
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).
Ä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) |
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.
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. |
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 MessageReceived 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 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.
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 (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. |
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.
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.
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:
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. |
Diese kleine App stellt eine Verbindung zu Mosquitto-Test-Broker her. Sie erlaubt das Abonnieren und das Versenden von Nachrichten.
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.
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.
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.
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.
Bei Verzeichnisangaben in den folgenden Abschnitten muss <user> stets durch den entsprechenden Namen ersetzt werden!
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.
In Datei
C:/Users/<user>/appinventor-sources/appinventor/components/build.xml
muss folgender Eintrag in der Rubrik CopyComponentLibraries
<copy toFile="${public.deps.dir}/paho.jar" file="C:/Users/<user>/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.
Die Komponente erhält die zusätzliche Annotation
@UsesLibraries(libraries = "paho.jar")
Für die Erstellung eigener Extensions habe ich einige Tipps zusammengestellt: AI2 FAQ: Extensions entwickeln.