Version Anpassungen
1 (2019-02-02) Initiale Version

Motivation

Der MIT App Inventor 2 erlaubt es, Daten zu persistieren. Eine Möglichkeit ist die Benutzung der TinyWebDB.

TinyWebDB

Diese Datenbank bietet die Möglichkeit, Daten über einen Server zentral abzulegen und abzurufen. Unter http://tinywebdb.appinventor.mit.edu steht ein frei zugänglicher Service zur Verfügung. Die Daten sind natürlich auch von jedermann einsehbar und werden nach einer gewissen Zeit gelöscht.

Befolgt man die Anweisungen unter Creating a Custom TinyWebDB Service kann man eine eigene Datenbank einrichten. Diese kann man anschließend in die Google Cloud hochladen und den Service von dort abrufen. In diesem Video wird sehr gut gezeigt wie das Ganze funktioniert. ggf. muss man noch den Pfad pythonw.exe in der Launcher-GUI (Edit->Preferences) einstellen.

Im FAQ habe ich gezeigt, wie man die Google App Engine von der Kommandozeile aus starten und konfigurieren kann. Damit hat man einen lauffähigen Datenbankserver auf dem PC, der z.B. über das WLAN mit einem Android-Gerät kommunizieren kann. Von Nachteil ist: Es muss die Google App Engine laufen und der Speicherort der Daten ist nicht bekannt (Wenn es jemand weiß: bitte melden!). Hier stellt sich die Frage der Datensicherheit.

Die Google App Engine ist ein ziemlich sperriges Ding. Um damit umgehen zu können, muss man schon ein paar Tage zum Studium der Dokumentation investieren. Außerdem ist Python nicht meine Lieblingssprache. Genau genommen bin ich nur in der Lage einfachste Programmecodes zu verstehen. Das ist nicht gut, wenn es einmal Probleme gibt.

Seit kurzem besitze ich einen Raspberry Pi Zero, ein kleines Gerät, das nicht viel Strom verbraucht und somit rund um die Uhr laufen kann. Darauf läuft bereits ein Web-Server und ein MQTT-Broker. Ein Datenbank-Web-Service für eine MIT-App-Inventor-App sollte da auch noch passen. Über einen Dyn-DNS-Service und Port-Weiterleitung sollte dann auch ein Zugriff von außerhalb des privaten WLANs möglich sein.

Der Plan ist nun zunächst einen Service auf Basis von .NET auf einem PC zu implementieren. Damit kenne ich mich aus und die Entwicklungsumgebung ist sehr leistungsfähig. Der nächste Schritt wäre dann eine Portierung zu Java und zuletzt dann die Installation als Java-Service auf dem Raspberry Pi.

In­halts­ver­zeich­nis

Dem Request auf's Bit geschaut

AI2 Testprogramm

Download

VB.NET Implementierung

Download

Klassenbibliothek UrsTinyWebDBServer

Applikation UrsTinyWebDBService

Java Implementierung

Download

Installation auf dem Raspberry Pi

Verwaltungsapp

Download


Dem Request auf's Bit geschaut

Zunächst einmal muss das Übertagungsprotokoll analysiert werden. Die App-Inventor-App sendet einen HTTP-Request an den Datenbank-Service und erhält eine Antwort mit den gewünschten Daten. Das Ganze sieht dann etwa wie folgt aus. Gesendet wurde ein Store-Request. Der Schlüsselwert war "Ärger" die zu speichernde Information "böse". Ich habe Begriffe mit Umlauten gewählt, weil man hier die Codierungsstufen besser nachverfolgen kann.

--- Request ----
POST /storeavalue HTTP/1.1
Accept: application/JSON
Content-Length: 36
Content-Type: application/x-www-form-urlencoded
Host: 192.168.178.99:8082
Connection: Keep-Alive

tag=%C3%84rger&value=%22b%C3%B6se%22

Der Request wird als HTTP-Post-Request mit der Ressourcenbezeichnung (Pfad) "/storeavalue" abgesetzt. Im Rumpfteil des Requests sind die beiden Query-Parameter (Variablen) tag und value angegeben. Sie sind URL-Encoded. Details hierzu bei Wikipedia URL-Encoding und im englischen Wikipedia POST (HTTP). Wenn das bei App-Inventor-Anleitung enthaltene HTML-Formular genutzt wird, kommt noch als dritter Parameter fmt hinzu. Hat dieser der Wert "html", soll die Antwort als HTML-Seite gesendet werden.

Nach der Decodierung ergibt sich (Hinweis erst die Parameter anhand des & separieren, dann die Werte einzeln URL-Dekodieren. Beim Dekodieren können weitere & auftauchen, die einen korrekten Split des Textes unmöglich machen):

tag=Ärger
value="böse"

Der Inhalt von value ist JSON-codiert, hier erkennbar an den umschließenden Anführungszeichen. Die App-Inventor-TinyWebDB-Schnittstelle erlaubt es auch Listen zu speichern. Dann ist value entsprechend kompliziert aufgebaut. Z.B. so (Liste mit einem String, einer Gleitkommazahl und einem booleschen Wert):

value=["Franz",1.3,true]

Den Wert sollte man direkt in diesem Format auch speichern. Es ermöglicht eine Vielzahl von Objekten und Typen in einem einzigen String abzuspeichern.

Die App erhält folgende Antwort:

--- Response ----
HTTP/1.1 200 OK
Content-Type: application/JSONrequest
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: origin, content-type
Date: Mon, 21 Jan 2019 16:19:39 GMT
Connection: keep-alive

["STORED","Ärger","\"böse\""]

Der Rumpfteil den JSON-codierten Wert des String-Arrays {"STORED", <tag>, <value>}. value ist hier quasi doppelt JSON-codiert, der der gelieferte und auch so abgespeicherte Wert bereits JSON-codiert ist.

 Beim Abruf von Daten ist die Ressourcenbezeichnung "/getvalue". Es ist nur der Query-Parameter tag vorhanden (ggf. auch fmt). Die Antwort ist analog:

["VALUE","Ärger","\"böse\""]

Das erste Feld der Antwort "STORED" oder "VALUE" wird von der App wird z.Zt. (Jan. 2019) nicht ausgewertet.

AI2 Testprogramm

Das AI2-Testprogramm kann einen String, eine Zahl oder eine kleine Liste über den TinyWebDB-Block abspeichern und wieder einlesen.

AI2 Testprogramm Screenshot AI2 Testprogramm Designer AI2 Testprogramm Blocks
Screenshot Designer Blocks

Download

Das AI2-Testprogramm (.aia) zum Download.

VB.NET Implementierung

Die VB-Applikation besteht aus zwei Projekten, der Klassenbibliothek UrsTinyWebDBServer, die einen einfachen Web-Server zur Kommunikation mit dem AI2-TinyWebDB-Block und eine einfache Datenbank zur Persistierung der Daten bereitstellt. Ein Windows-Forms-Projekt UrsTinyWebDBService, das diese Bibliothek einbindet und den Service im lokalen Netzwerk veröffentlicht. Hinzu kommt das NuGet-Paket Json.Net.

Download

Das ZIP-Archiv VB.NET UrsTinyWebDB enthält im Ordner Project das Visual-Studio-Projekt mit den Quellen, im Ordner Binary eine kompilierte Version im Ordner HTML HTML-Seiten zum Testen des Service von einem Browser aus und die Hilfe-Datei zur Klassenbibliothek.

Klassenbibliothek UrsTinyWebDBServer

Die Bibliothek enthält die Klasse WebDBServer, die die Kommunikation mit der App-Inventor-App übernimmt und die Requests in Aufrufe für eine Datenbank übersetzt. Das Interface ITinyWebDB deklariert die Methoden zum Zugriff auf eine Datenbank. Die Klasse DictionaryDBBase ist eine Basisklasse, die die Datenhaltung über einer generischen Dictionary realisiert. BinaryDictionaryDB persistiert die Daten im Binär-Format, JsonDictionaryDB in Json-Format. Der Code für alle Klassen ist relativ einfach gehalten und im Quelltext leicht nachzuvollziehen. Die Hilfe-Datei enthält viele zusätzliche Details.

Klasse WebDBServer

Die Klasse reagiert auf folgende URLs:

Den Programmfluss vom Eingang eines Request eines Clients bis zur Rückmeldung zeigt die folgende Grafik.

Programmfluss WebDBServer

Alle Methoden sind als Protected Overridable deklariert. Das erleichtert das Austauschen einzelner Methoden zum Test. Die Anbindung zur übergeordneten Applikation erfolgt über eine Vielzahl von Ereignissen (Events).

Datenbank-Klassen

Die abstrakte Basis-Klasse DictionaryDBBase implementiert das Interface ITinyWebDB und implementiert alle Methoden, die zur Datenhaltung in einer .NET-Dictionary notwendig sind. Lediglich die Methoden zur Persistierung müssen von den abgeleiteten Klassen bereit gestellt werden. Die Basis-Klasse übernimmt auch das Synchronisieren der der Threads (per SyncLock). Details sind im Quellcode und in der Hilfe-Datei zu finden.

Applikation UrsTinyWebDBService

VB-NET Screenshot

Die Applikation ist ebenfalls einfach gehalten. Sie startet ein Datenbank und per Schaltfläche eine Instanz der WebDBServer-Klasse. Die Ereignisse des laufenden Betriebs werden in einem Fenster protokoliert. Beim Beenden der Applikation wird die Datenbank persistiert. Die IP-Adresse ist mit 0.0.0.0 festgelegt, d.h. der Service empfängt und verarbeitet Requests von allen Netzwerk-Interfaces.

Java Implementierung

Die Java-Implementierung ist analog zur VB.NET-Implementierung. Als Datenhaltung habe ich hier nur die Ablage als Json-String in einer Datei implementiert.

Das Programm soll in Praxis auf einem Raspberry Pi im Hintergrund laufen. Deshalb muss das Rahmenprogramm angepasst werden. Auch hier läuft die Request-Bearbeitung in einem eigenen Thread. Zusätzlich prüft das Hauptprogramm in festgelegten Zeitabschnitten, ob der Datenbankinhalt geändert wurde (isDirty) und persistiert ggf. die geänderten Daten. Ebenfalls kann das Programm optional über Kommandozeilenparameter konfiguriert werden:

Download

Das Eclipse-Projekt zum Download enthält im Ordner UrsTinyWebDB den Eclipse-Projekt-Ordner, im Order Binary die kompilierte .jar-Datei und Ordner HTML HTML-Seiten zum Testen des Service von einem Browser aus. Die dort hinterlegte IP-Adresse ist die meines Raspberry Pi und muss abgeändert werden.

Installation auf dem Raspberry Pi

Wie man auf einem Raspberry Pi den Autostart von Programmen einrichtet, hat Jan Karres beschrieben: Raspberry Pi: Autostart von Programmen einrichten. Hier die spezielle Version für UrsTinyWebDB. Ich bin kein Linux-Spezialist. Wahrscheinlich kann man es besser machen. Für Tipps wäre ich dankbar.

Für den Fall, dass die Seite nicht mehr erreichbar ist, hier eine Kopie der betreffenden Stelle:

Voraussetzung: Raspbian oder vergleichbare Distribution installiert

Methode 1: update-rc.d

Das Starten eines Programms als sogenannter Dienst über ein Runlevel ist die saubere Lösung. Hierzu legt man ein Start-/Stop-Script im Verzeichnis /etc/init.d/ an, in dem Methoden wie start, stop, restart oder auch status definiert werden. Der Vorteil liegt drain, dass diese Vorgehensweise übersichtlich ist, man das Script auch nach dem Start des Raspberry Pis benutzen und damit die Instanz des Programms entsprechend steuern kann.

Step 1:

Zunächst benötigen wir ein Start-/Stop-Script. Für viele Programme gibt es seitens der Entwickler bereits ein solches, das bei Paketen meist automatisch im Ordner /etc/init.d/ angelegt wird oder, falls manuell installiert, sich in der Dokumentation des Projektes befindet. Gibt es ein solches Script noch nicht, muss man dieses selbst schreiben.

Dazu legen wir eine Datei mit unserem Wunschnamen im Verzeichnis /etc/init.d/ an. Im Beispiel nenne ich das Script skeleton, dessen Startscript ich als Vorlage nehme. Jedes Startscript beginnt mit dem Shebang, der definiert in welcher Sprache es geschrieben wurde. Darauf folgt ein Kopfteil als Kommentar in dem angegeben werden sollte für welches Programm das Script ist (Provides), mögliche Abhängigkeiten (Required-Start, Required-Stop), das Runlevel (Default-Start, Default-Stop; Voreinstellung sollte im Normalfall passend sein) wie auch eine kurze (Short-Description) und eine lange Beschreibung (Description) über die Funktionalität des Scripts. Daraufhin folgen die Aktionen in einer sogenannten switch-case-Anweisung. Hier sind # START, # STOP, # RESTART ggf. usw. durch die entsprechenden Kommandos zu ersetzen, die das Programm starten, stoppen bzw. neustarten.

In dem Editor nano kann man mittels STRG + X, Y und Enter speichern.

sudo nano /etc/init.d/skeleton
#!/bin/sh
### BEGIN INIT INFO
# Provides:          Für welches Programm ist das Script?
# Required-Start:    
# Required-Stop:     
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Kurze Beschreibung
# Description:       Längere Beschreibung
### END INIT INFO

# Actions
case "$1" in
    start)
        # START
        ;;
    stop)
        # STOP
        ;;
    restart)
        # RESTART
        ;;
esac

exit 0
Step 2:

Nun müssen wir der Datei noch die Rechte zum Ausführen zuweisen. Dies können wir mit folgendem Befehl machen (skeleton durch Name der Datei ersetzen).

sudo chmod +x /etc/init.d/skeleton
Step 3:

Jetzt können wir update-rc.d anweisen das Start-/Stop-Script automatisch in den zuvor definierten Runlevels zu starten und damit auch beim Systemstart.

sudo update-rc.d skeleton defaults
Step 4:

Falls wir im laufenden Betrieb das Start-/Stop-Script manuell verwenden möchten, müssen wir das Script gefolgt von dem entsprechenden Funktionsnamen ausführen. Für das Beispielscript ergäben sich folgende Möglichkeiten.

sudo /etc/init.d/skeleton start
sudo /etc/init.d/skeleton stop
sudo /etc/init.d/skeleton restart

Ich habe für das Programm das Verzeichnis /usr/bin/ullisroboterseite/ angelegt und urstinywebdb.jar dort hinein kopiert. Für die Datendatei wurde das Verzeichnis /usr/share/ullisroboterseite/ eingerichtet. Datendatei selbst heißt urstinywebdb.json. Das Startscript sieht dann wie folgt aus:

#!/bin/sh
### BEGIN INIT INFO
# Provides:          URS Tiny WebDB
# Required-Start     $local_fs $network
# Required-Stop:     $local_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Web-Datenbank-Service für MIT App Invertor TinyWebDB
# Description:       Web-Datenbank-Service für MIT App Invertor TinyWebDB
### END INIT INFO

# Actions
case "$1" in
    start)
        # START
        echo "Starting URS Tiny WebDB ..."
        java -jar /usr/bin/ullisroboterseite/urstinywebdb.jar -f /usr/share/ullisroboterseite/urstinywebdb.json -t 1
        ;;
    stop)
        # STOP
        echo "Stopping URS Tiny WebDB ..."
        pkill -f 'java.*urstinywebdb*'
        ;;
esac

exit 0

Verwaltungsapp

Ein kleines Programm zeigt die in der Datenbank enthaltenen Datensätze an und erlaubt das Löschen. Die Datensätze sind nach Schlüsseln aufsteigend sortiert.

Verwaltungsapp

Das Programm ist nicht sehr flexibel. IP-Adresse des Servers und der Port müssen im Code geändert werden. Als Messagebox wurde die UrsExtendedMsgBox verwandt.

Download

Das ZIP-Archiv enthält im Ordner Project das Visual-Studio-Projekt mit den Quellen, im Ordner Binary eine kompilierte Version. Download VB.NET UrsTinyWebDBAdmin.