Englisch version   English version


Version Anpassungen
1 (2018-01-27) Initiale Version
2 (2018-12-08) Seit Dezember 2018 gab es Probleme mit Netzwerkzugriffen. Diese Zugriffe müssen nun in separaten Threads ausgeführt werden. Die Komponente wurde entsprechend umgeschrieben.
Zusätzlich wurde die Eigenschaft InitErr eingeführt, über die Fehler bei der Initialisierung der Komponente abgerufen werden können.
3 (2019-01-11) Leider hatte die Version 2 ein paar Probleme. Die Version 3 ist neu strukturiert. Sie ist leider nicht kompatibel mit der vorhergehenden Version. Zum Update deshalb die unten aufgeführten Migrationshinweise beachten.
Die alte Version 2 ist hier archiviert.
3.1 (2019-04-29) Die neue Eigenschaft BinaryMode erlaubt das Übertragen von binären Daten. Diese Version ist kompatibel mit der Version 3.0. Beim Hochladen bleiben die bestehenden Verknüpfungen der Blöcke erhalten.
3.2 (2020-02-17) - DropSentToYourself wurde nach Start des Listeners nicht weitergereicht.
- Die Bestimmung der Local-Host-IP per Enumration über die Netzwerkinterfaces führt zu Problemen bei Endgeräten, die mehr als ein Interface besitzen. DropSentToYourself hat nicht funktioniert. DropSentToYourself vergleicht nun die Absender-Adresse mit allen vorhandenen NIC-Adressen. LocalHost ist nun die Adresse, mit der das Internet erreicht wird (google.com, 8.8.8.8).
4.0 (2020-07-15) Die bestehende Version war überaus "buggy", also mit vielen Fehlern behaftet. Es war halt eine der ersten Extensions, die ich geschrieben hatte. Ich habe das Projekt deshalb noch einmal komplett neu aufgesetzt. Der Vorteil: Die Bugs sind raus und es gibt erweiterte Methoden, die das Zusammenstellen der Blöcke im App Inventor vereinfachen. Der Nachteil ist, die Version ist nicht kompatibel zur alten.
Die alte Version ist hier archiviert.

Motivation

Für ein Projekt sollte eine Android-App entwickelt werden, die mit einem ESP8266(-Projekt) kommuniziert. Zur App-Entwicklung sollte wegen der einfachen Handhabung der MIT App Inventor 2 benutzt werden.

Die IP-Adressen der verfügbaren ESP8266-Geräte sollten im Projekt nicht fest vorgegeben werden. Die App soll selbst ermitteln, welche Geräte gerade aktiv sind und unter welchen Adressen sie angesprochen werden können.

Zur Erledigung dieser Aufgabe (Name Service) bietet sich die Nutzung der Broadcast-Funktion von UDP an. Man sendet einfach an einen vorher vereinbarten Port ein Broadcast-Datagramm an alle im lokalen Netzwerk vorhandenen Geräte mit der Aufforderung, die eigenen Verbindungsdaten zurück zu liefern. Die Geräte, die auf dem vereinbarten Port lauschen, senden daraufhin ihre IP und ggf. weitere Daten an den Absender zurück. Der Absender sammelt die Antworten auf und kennt damit alle aktiven Geräte. Da die Paket-Übermittlung bei UDP nicht garantiert ist, empfiehlt es sich, diesen Vorgang zu wiederholen und die Vereinigungsmenge der Antworten zu nutzen.

Um gezielt die Geräte eines Projekts ansprechen zu können, vereinbart man entweder unterschiedliche Ports oder eine Geräte-Kennung.

Nun kommt die Krux. Der App Inventor hat kein eingebautes UDP und ich habe auch keine funktionierende Erweiterung (Extension) gefunden. Also selber machen.

Hinweis:

Die Extension klappt einwandfrei, wenn sich Smartphone und Gegenstation im gleichen (lokalen) Netzwerk befinden. Ist das Smartphone nur per Mobilfunknetz verbunden, ist es i.d.R. nicht erreichbar. Der Grund ist, dass das Smartphone nicht direkt mit dem Internet verbunden ist, sondern nur mit dem lokalen Netzwerk des Mobilfunkanbieters.

Mobilfunk-Verbindung

Ein Gerät im eigenen LAN kann man von außerhalb per Port-Weiterleitung (port porwarding) am eigenen Router erreichen (ggf. via DDNS). Den Router des Providers wird man nicht beeinflussen können. Also senden vom Smartphone an ein Gerät im LAN funktioniert, umgekehrt geht es nicht.

Das gleiche gilt übrigens auch für TCP. Wenn das Smartphone nur mit dem Mobilfunknetz verbunden ist, kann die Verbindung kann nur vom Smartphone aus aufgebaut werden. Das Gerät im LAN muss als Server agieren, wartet also auf eine eingehende Verbindungsanforderung. Das Smartphone agiert als Client, initiiert den Verbindungsaufbau. Ist die Verbindung einmal eingerichtet, haben sich die Router intern über Adressen und Ports abgestimmt. Der Datentransfer ist dann in beide Richtungen möglich.


In­halts­ver­zeich­nis

Download

Verwendung

Datagramme versenden

Eigenschaften / Parameter

Referenz

Fehler-Codes

Datagramme empfangen

Referenz

Fehler-Codes

Binärdaten

Beispiel

Werkzeuge

Download

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

Verwendung

Die Extension enthält zwei Komponenten. Wenn sie in ein App Inventor Projekt importiert wird, erscheinen unter der Rubrik "Extension" zwei Einträge.

UDPListener ist eine Komponente die auf eingehende Datagramme lauscht und diese dem Projekt zur Verfügung stellt. UDPXmitter dient zum Versenden von Datagrammen.

Einige tiefer gehende Informationen zu UDP findet man auf meiner Seite .NET UdpClient: Aufs Bit geschaut. Insbesondere die Abschnitte über Adressierungsschemata und die Java-Schnittstelle sind auch für Android von Relevanz.

 

Datagramme versenden

Zum Versenden von Datagrammen wurden vier unterschiedliche Methoden implementiert. Sie sollen die Größe und Anzahl der notwendigen Programm-Blöcke im App Inventor reduzieren. Viele und große Blöcke machen ein AI2-Projekt schnell unübersichtlich.

Häufig ist die Ziel-Adresse immer die gleiche. Im Designer kann für diese Fälle die Ziel-Adressdaten (RemoteHost, RemotePort) hinterlegt werden. Xmit und XmitAsync senden dann die im Designer hinterlegten Adressen. Die Blöcke werden dadurch klein gehalten. Es ist praktisch nur eine "Zeile" notwendig.

XmitTo und XmitToAsync erlauben eine eigenständige Angabe der Ziel-Adresse.

Weiterhin unterscheiden sich die Methoden in der Art der Fehlerbehandlung. Die mit der Endung "...Async" quittieren den Versand einer Nachricht mit den Ereignissen AfterXmit und XmitFailure. AfterXmit wird nach jedem Versand ausgelöst. Wer nur an Fehlersituationen interessiert ist, verwendet das Ereignis XmitFailure, das im Fehlerfall zusätzlich ausgelöst wird. Die beiden anderen Methoden liefern die Fehlercodes als Rückgabewert.

In der Regel ist es nicht erforderlich festzulegen, von welchem lokalen Port aus Datagramme versandt werden. Sollte dies in speziellen Fällen doch einmal notwendig sein, kann die lokale Port-Nummer (LocalPort) im Designer festgelegt werden. Ist der Wert "0" (der Vorschlagswert), sucht sich das System einen freien Port zum Versenden aus.

Selbstverständlich können alle Designer-Properties per Block überschrieben werden.

Eigenschaften / Parameter

Zum Versand einer Nachricht sind drei Angaben notwendig: Ziel-Adresse, Absender-Adresse und die Nachricht selbst. Netzadressen bestehen aus zwei Komponenten: IP(-Adresse) und Port(-Nummer). Der Port wird meist mit einem gewissen Dienst verbunden (s. auch Wikipedia: Standardisierte Ports).

Diese Extension berücksichtigt nur IPv4-Adressen. Sie ist eine Kombination vier Zahlen, jeweils im Bereich 0..255. Eine vollständige Netzwerkadresse wird so angegeben "IP:Port", z.B. "192.168.178.35:2003".

Einige Adressen haben besondere Bedeutungen. Zu UDP-Adress-Schemata siehe UdpClient: Aufs Bit geschaut / Adressierungsschemata. Zu Endpunkten in Java UdpClient: Aufs Bit geschaut / Java

Die Geräte, die im Netzwerk miteinander kommunizieren werden als "Endpunkt" oder "Host" bezeichnet (es ist nicht genau das selbe, die Begriffe werden jedoch häufig gleichwertig benutzt). Es gibt also den Absender, der mit "local host" bezeichnet wird und den Empfänger, der "remote host" genannt wird.

Absender-Adresse

Um die Absender-Adresse braucht man sich im Regelfall nicht zu kümmern. Macht man keine Angaben, verwendet die Extension die Adresse "0.0.0.0:0". Diese Adresse bewirkt, dass sich das System (genauer der "Dienstanbieter", also die Netzwerk-Firmware) sich ein Netzwerk-Interface aussuchen kann, mit dem es die Ziel-Adresse erreichen kann. Dabei sucht es sich einen beliebigen freien Port aus.

Will man in speziellen Fällen die Absender-Adresse näher spezifizieren, kann man dies über die Eigenschaft LocalPort vornehmen. Ein Android-Device hat zu einer Zeit i.d.R. nur ein aktives Netzwerk-Interface. Dies ist entweder die mobile Datenverbindung oder das WLAN. Deshalb wurde eine Auswahl des lokalen Netzwerk-Interfaces nicht implementiert.

Ziel-Adresse

Die Ziel-Adresse muss auf jeden Fall angegeben werden. Dies geschieht entweder über die Designer-Eigenschaften RemoteHost und RemotePort oder als Angabe bei der Xmit...-Methode.

Bei RemoteHost kann entweder eine IP-Adresse oder der Name des Empfängers angegeben werden (z.B. "www.google.de"). Die Extension versucht dann, die zugehörige IP zu ermitteln. Für die Designer-Eigenschaft RemoteHost wird das Resultat der Ermittlung in der Eigenschaft RemoteIP ausgewiesen. Konnte die IP nicht ermittelt werden, ist RemoteIP leer. Ein Versand an diesen Host ist dann nicht möglich (wird zum Fehler UnknownRemoteHost (Code 1) führen).

Da sich IP-Adressen nur sehr selten ändern, wird die zu einem Host-Namen gehörende IP nur beim Festlegen des Namens ermittelt. Tritt unerwartet der Fehler UnknownRemoteHost (Code 1) auf, kann man versuchen, die Eigenschaft RemoteHost neu festzulegen. Die zugehörige IP-Adresse wird dann neu ermittelt.

Binär-Modus (BinaryMode)

Im AI2-Umfeld bestehen Nachrichten im Regelfall aus Texten. Nicht-Text-Angaben werden in Texte gewandelt:


 Zahlen werden in Texte umgewandelt. Der Empfänger erhält die vier Ziffernzeichen "1234".


Listen werden im JSON-Format übertragen. Der Empfänger erhält die Zeichenfolge [1234, "Ulli"].

Manche Empfänger benötigen jedoch bestimmte Byte-Folgen, z.B. das Byte 123 (Hex 7B) und nicht die Zeichenfolge "123". Ist der Binärmodus eingeschaltet, wird die Nachricht als kommaseparierte Folge von Byte-Angabe interpretiert und die Nachricht als Byte-Folge versandt (Details s.u. Binärdaten).

Referenz

Block Funktion Anmerkung
Eigenschaften
Legt die Port-Nummer fest, die zum senden einer Nachricht verwandt werden soll. Ist der Wert 0 oder negativ, ermittelt das Android-Betriebssystem einen freien Port.

0 ist der empfohlene Standardwert. Gültige Werte sind -∞...65535.

Diese Angabe wirkt sich auf alle Xmit...-Methoden einer Instanz der Extension aus.
Legt die (Standard-) Ziel-Adresse fest. Es kann eine IP-Adresse oder der Name angegeben werden.

Diese Adresse wird von den Methoden Xmit und XmitAsync verwendet.
Liefert die IP-Adresse, die zur Designer-Eigenschaft RemoteHost gehört. Konnte die IP-Adresse nicht ermittelt werden, wird eine leere Zeichenfolge zurück geliefert.

Das Versenden eines Datagramms schlägt dann fehl ( Fehler UnknownRemoteHost (Code 1).
Legt die (Standard-) Ziel-Port-Nummer fest. Dieser Port wird von den Methoden Xmit und XmitAsync verwendet.

Gültige Werte sind 1...65535.
Legt fest, ob die beim Senden übergebene Nachricht in eine Byte-Array konvertiert werden soll. Details zum Binär-Modus s.u. Binärdaten.
Liefert die Liste der IP-Adresse aller Netzwerk-Interfaces. In der Regel enthält diese Liste nur einen Eintrag, der mit der Angabe in LocalHost übereinstimmt.
Liefert die IP-Adresse des Standard-Netzwerk-Interfaces. Kann keine Verbindung zum Internet hergestellt werden, wird eine leere Zeichenfolge zurück geliefert.

Konkret wird versucht die Internet-Adresse "8.8.8.8" (Googles öffentlicher DNS-Server) zu erreichen. Das Android-Betriebssystem wählt hierzu ein passendes Netzwerk-Interface aus.
Angaben zum zuletzt aufgetretenen Fehler.
LastAction: Bezeichnung der Methode, die zuletzt ausgeführt wurde.
LastErrorCode: Fehler-Code
LastErrorMessage: Fehlerbezeichnung im (englischen) Klartext.
Ist bei der zuletzt ausgeführten Aktion kein Fehler aufgetreten, hat LastErrorCode den Wert 0 und LastErrorMessage liefert eine leere Zeichenfolge.
Methoden
Sendet die Nachricht an die über RemoteHost und RemotePort hinterlegte Ziel-Adresse.

Rückgabewert ist der Fehler-Code.
 
Sendet die Nachricht an die angegebene Ziel-Adresse.

Rückgabewert ist der Fehler-Code.
 
Sendet die Nachricht an die über RemoteHost und RemotePort hinterlegte Ziel-Adresse.

Der Erfolg der Übertragung wird über die Ereignisse AfterXmit und XmitFailure mitgeteilt.
siehe auch oben RemoteHost und RemoteIP.  
Sendet die Nachricht an die abgegebene Ziel-Adresse.

Der Erfolg der Übertragung wird über die Ereignisse AfterXmit und XmitFailure mitgeteilt.
siehe auch oben RemoteHost und RemoteIP.  
Ereignisse
Teilt das Ergebnis der Übertragung mit den asynchronen Methoden mit.
Success: true, wenn die Übertragung erfolgreich war, andernfalls false.
ErrorCode: Fehler-Code.
Das Ereignis wird nach jeder Übertragung mit den Methoden XmitAsync oder XmitToAsync ausgelöst.
XmitFailure wird bei einem Fehler bei der Übertragung mit den asynchronen Methoden zusätzlich zum Ereignis AfterXmit ausgelöst.
ErrorCode: Fehler-Code.
Wer nur an Fehlern interessiert ist, erspart sich die Abfrage von Success im Ereignis AfterXmit.

Fehler-Codes

Code Meldung Bedeutung
0   Versand erfolgreich.
1 Unknown Remote Host Die IP-Adresse des Empfängers ist ungültig oder konnte nicht ermittelt werden.
2 Invalid Local Port Die Angabe zu LocalPort ist ungültig oder der Port ist belegt.
3 Invalid Remote Port Die Angabe zu RemotePort ist ungültig.
4 Xmit failed Fehler bei der Übertragung.
5 Binary conversion failed Die angegebene Zeichenfolge konnte nicht in ein Byte-Array konvertiert werden.

 

Datagramme empfangen

Der Empfang der Datagramme erfolgt über die Komponente UDPListener. Nach dem Start überprüft diese Komponente in einer nebenläufigen Endlosschleife, ob Datagramm-Pakete eingetroffen sind. Ist dies der Fall wird der Applikation der Empfang über das Ereignis DataReceived mitgeteilt.

Die UDPListener-Komponente besitzt nur die beiden Methoden Start und Stop, die den Empfang von UDP-Datagrammen starten bzw. beenden. Dazu gibt es zwei Ereignisse: DataReceived wird ausgelöst, wenn ein Datagramm empfangen wurde, ListenerFailure zeigt an, dass die Empfangsschleife auf Grund eines Fehlers abgebrochen wurde. Über die Eigenschaft isRunning kann jederzeit abgefragt werden, ob die Empfangsschleife aktiv ist.

Broadcast-Nachrichten

UDP erlaubt es, Broadcast-Nachrichten zu versenden. Dabei entsteht das Problem, dass auch von der App selbst gesendete Datagramme empfangen werden. Über die Eigenschaft DropSentToYourself werden Datagramme heraus gefiltert, deren Absender die eigene IP-Adresse ist. Wenn UPD zur Interprozesskommunikation verwandt wird, wird diese dabei auch unterbunden.

Multiscreen-Apps

Das Öffnen eines zweiten Screens unterbricht den Nachrichtenempfang nicht! Der erste Screen erhält weiterhin die Ereignisse DataReceived und ListenerFailure. Die mit dem Ereignis verbundenen Blöcke werden weiterhin ausgeführt. Soll dies nicht geschehen, muss der Listener vor dem Öffnen des zweiten Screens gestoppt und nach der Rückkehr wieder gestartet werden.

Ebenfalls blockiert ein laufender Listener den zugehörigen Port. Im zweiten Screen kann auf diesem Port kein weiterer Listener gestartet werden. Sollen Datagramme auf dem gleichen Port wie im ersten Screen empfangen werden, muss der Listener des ersten Screens vor dem Öffnen des zweiten Screens gestoppt und im zweiten Screen wieder geöffnet werden.

Die Extension räumt auf, d.h. stoppt einen laufenden Listener, wenn ein Screen geschlossen wird. Es bleiben also letztendlich keine laufenden Listener übrig. Dieses Aufräumen erfolgt aber verzögert im Hintergrund. Das Ereignis OtherScreenClosed tritt deshalb ein, bevor der Listener intern gestoppt und der Port wieder freigegeben wurde. Wenn nach der Rückkehr aus dem zweiten Screen der Listener im ersten Screen auf dem gleichen Port wie im zweiten wieder im Ereignis OtherScreenClosed gestartet werden soll -was sich anbietet-

muss der Listener im zweiten Screen vor dem Schließen des Screens definitiv wieder gestoppt werden.

Wichtig ist, dass im zweiten Screen das Ereignis BackPressed abgefangen wird:

Binär-Modus (BinaryMode)

Im AI2-Umfeld bestehen Nachrichten im Regelfall aus Texten. In manchen Fällen besteht das Datagramm aber aus einer Folge von beliebigen Bytes (Byte-Array). Schalten man den Binärmodus ein, werden die empfangenen Bytes nicht direkt in Texte umgewandelt, sondern es werden die einzelnen Bytes als über Semikolons separierte Dezimalzahlen ausgegeben.

Erhält der Listener ein Datagramm mit dem Inhalt (hexadezimal) 48 61 6C 6C 6F, würde dieses normalerweise in den Test "Hallo" übersetzt werden. Bei eingeschaltetem Binärmodus wird "72;97;108;108;111" ausgegeben (Details s.u. Binärdaten).

Referenz

Block Funktion Anmerkung
Eigenschaften
Legt fest, ob die empfangene Nachricht als Byte-Array interpretiert werden soll. Das empfangene Paket wird in einen String mit einer Folge von Dezimalzahlen übersetzt.

Details zum Binär-Modus s.u. Binärdaten
Die Eigenschaft DropSentToYourself steuert das Verhalten beim Empfang Broadcast-Datagrammen. Der Block ist standardmäßig so eingestellt, dass Nachrichten ignoriert werden, die von der eigenen IP versandt wurden. Sollen diese dennoch empfangen werden, ist DropSentToYourself auf false zu setzen. Die Voreinstellung ist true.
Über die Eigenschaft isRunning kann abgefragt werden, ob der Listener aktuell aktiv ist.  
Liefert die Liste der IP-Adresse aller Netzwerk-Interfaces. In der Regel enthält diese Liste nur einen Eintrag, der mit der Angabe in LocalHost übereinstimmt.
Liefert die IP-Adresse des Standard-Netzwerk-Interfaces. Kann keine Verbindung zum Internet hergestellt werden, wird eine leere Zeichenfolge zurück geliefert.

Konkret wird versucht die Internet-Adresse "8.8.8.8" (Googles öffentlicher DNS-Server) zu erreichen. Das Android-Betriebssystem wählt hierzu ein passendes Netzwerk-Interface aus.
Angaben zum zuletzt aufgetretenen Fehler.
LastAction: Bezeichnung der Methode, die zuletzt ausgeführt wurde.
LastErrorCode: Fehler-Code
LastErrorMessage: Fehlerbezeichnung im (englischen) Klartext.
Ist bei der zuletzt ausgeführten Aktion kein Fehler aufgetreten, hat LastErrorCode den Wert 0 und LastErrorMessage liefert eine leere Zeichenfolge.
Methoden
Start startet das "Lauschen" auf Datagramme, die an den angegebenen Port gesendet werden (UDP-Server). War der Start nicht erfolgreich wird das Ereignis ListenerFailure ausgelöst.
Stop stoppt den Server. Ein mehrfaches Aufrufen von Stop ist unkritisch und führt nicht zu einem Fehler.
Ereignisse
Das Ereignis ListenerFailure wird ausgelöst, wenn der Listener-Vorgang nicht gestartet werden konnte oder auf Grund eines Fehlers beendet wurde.

ErrorCode: Grund der Beendigung.
Der Grund  kann z.B. der Abbruch einer Netzwerkverbindung sein.
Wenn Daten empfangen werden, wird das Ereignis DataReceived ausgelöst.

Data enthält die Datagramm-Daten RemoteIP und RemotePort die Absender-Adresse.
 

Fehler-Codes

Code Meldung Bedeutung
0   Kein Fehler.
6 Server thread aborted Der Server-Thread wurde auf Grund eines (Netzwerk-) Fehlers abgebrochen.
7 Listener already running Dieser Listener wurde bereits gestartet.

Binärdaten

Senden

Wenn BinaryMode auf true gesetzt ist, akzeptiert Xmit 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.

Algorithmus

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.

Empfangen

Wenn BinaryMode auf true gesetzt ist, wird das empfangene Paket in eine durch Semikola getrennte Zeichenfolge von Dezimalzahlen übersetzt.

Erhält der Listener ein Datagramm mit der Bytefolge (hexadezimal) 48 61 6C 6C 6F, würde dieses normalerweise in den Test "Hallo" übersetzt werden. Bei eingeschaltetem Binärmodus wird "72;97;108;108;111" ausgegeben. Man kann Text.Split verwenden, um eine Liste der Bytes abzurufen:

Beispiel

Screenshot   Designer

Die Blöcke im Beispiel sind nicht schwer zu verstehen. Den größten Bereich nimmt die Überprüfung valider Eingaben ein.

Werkzeuge

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

Die Entwicklung der Erweiterungen erfolgt mit Java. Ein Tutorial über Datagramme findet man in der Oracle Java Dokumentation. Dort findet man auch Informationen zur verwendeten Klasse DatagramSocket.