Aus Linux-Magazin 01/2015

Raspberry Pi mit Python, Flask und Bootstrap kontrollieren

© alexsalcedo1, 123RF

Dank des Python-basierten Microframework Flask, dessen Name sich mit Flachmann übersetzen lässt, steuern die Nutzer im lokalen Netzwerk einen Raspberry Pi über ein Webinterface.

Um im lokalen Netzwerk Dienste auf dem Raspberry Pi zu starten, einen Radiosender einzustellen oder einen Blick auf den vorhandenen Festplattenplatz zu werfen, muss sich der User gewöhnlich per SSH auf dem Gerät anmelden und Bash-Befehle absetzen. Das ist nicht nur leicht unkomfortabel, sondern auch für weniger computeraffine Anwender sowie Smartphone- und Tablet-Nutzer eine unnötige Hürde.

Als Alternative warten diverse Remote-Control-Apps auf den Raspberry-Geek ([1], [2], [3]). Die laufen aber häufig nur auf Android- und I-OS-Geräten und bringen Desktopnutzern wenig. Sie verfolgen zudem oft einen bestimmten Zweck und lassen sich nur begrenzt an eigene Bedürfnisse anpassen.

Wer bequem Dienste auf dem Raspberry ausführen möchte, ohne dafür gleich eine plattformübergreifende App zu entwickeln, kann Webframeworks verwenden, die auf Javascript ([4], [5]) oder Python ([6], [7]) basieren. Das Linux-Magazin hat sich das Python-Framework Flask ([8], [9]) zur Brust genommen und den Flachmann auf einem Raspberry installiert. Dort verknüpft er eine Python-Basis mit Browser-Templates, die sich dank Bootstrap [10] und Responsive Design auch vom Smartphone aus nutzen lassen. Für den Einsatz genügen Kenntnisse in Flask, Python, HTML und CSS.

Flachmann dabei

Das Microframework ist eine schlanke Alternative zu dicken Frameworks wie Django, Twisted oder Zope. Es ist zu 100 Prozent kompatibel zu WSGI 1.0 [11]. Dieses Universal-Interface lässt Webserver und Webanwendungen Daten austauschen. WSGI-Anwendungen erstellt Flask mit Hilfe von Werkzeug [12], Templates legt Jinja 2 an [13].

Um einen Raspberry Pi im lokalen Netzwerk zu steuern, lässt sich der in Flask integrierte Server verwenden, der hauptsächlich Test- und Entwicklungszwecken dient. Er liefert HTML-Dateien aus, die den Nutzern als Interface dienen. Über HTML-Schaltflächen können sie Dienste ein- und ausschalten, über Formulare Daten eingeben, die Flask dann an Python-Programme weiterreicht. Umgekehrt liefern die Python-Funktionen Resultate zurück, die die Flask-Templates im HTML-Format aufbereiten.

Der Artikel konstruiert im Wesentlichen zwei HTML-Seiten. Die erste (Abbildung 1) liest exemplarisch ein paar Werte des Raspberry Pi aus, dazu gehören das aktuelle Datum, der freie Platz auf der Festplatte, die Uptime und der Name des angemeldeten Benutzers. Über die zweite Seite darf der Anwender einen VNC-Server starten und stoppen und den Raspberry Pi über das Webinterface neu starten (Abbildung 2).

Abbildung 1: Daten wie der Rechnername, der freie Speicherplatz und die Uptime lassen sich dank Bootstrap auch über das Smartphone abfragen.

Abbildung 1: Daten wie der Rechnername, der freie Speicherplatz und die Uptime lassen sich dank Bootstrap auch über das Smartphone abfragen.

Abbildung 2: Über HTML-Formulare verschicken Nutzer Daten oder starten und beenden Dienste.

Abbildung 2: Über HTML-Formulare verschicken Nutzer Daten oder starten und beenden Dienste.

Das sind natürlich nur Beispiele. Auf dem im Artikel beschriebenen Weg lassen sich auch GPIO-Pins auslesen, Musikserver steuern oder schlicht Webapplikationen schreiben, über die Mitarbeiter in einem lokalen Firmennetzwerk Daten eingeben oder auslesen. Kurzum, die Möglichkeiten sind vielfältig.

Einmal auftanken

Zunächst muss der Raspberry-Pi-Besitzer das Flask-Framework auf der Himbeere installieren. Das verwendete Raspian Wheezy bringt von Hause aus Python 2.7.3 mit, ein »sudo apt-get install python-flask pip« bringt Flask sowie Pythons Paketmanager »pip« auf den Rechner. Als VNC-Server kommt Tight-VNC-Server zum Einsatz, den ein »sudo apt-get install tightvncserver« auf den Minirechner spült.

Für die grafische Darstellung hält das CSS-Framework Bootstrap her, das laut Wiki zu den beliebtesten Github-Projekten gehört und von den Twitter-Entwicklern stammt. Für Flask gibt es eine angepasste Bootstrap-Variante [14]:

sudo pip install flask-bootstrap

Im Homeverzeichnis erzeugt der Pi-Besitzer im nächsten Schritt ein Projektverzeichnis beliebigen Namens. Darin legt er die Ordner »static« und »templates« an. In »static« schaut Flask nach eigenen Bildern (im Unterverzeichnis »img« ), nach CSS- und Javascript-Dateien (»css« , »js« ) oder nach Fonts (»fonts« ). Der »templates« -Ordner beherbergt die HTML-Dateien oder besser Templates, denn wie sich gleich zeigt, handelt es sich nicht um klassische HTML-Dateien.

Der erste Schluck

Eine simple Anwendung zeigt Listing 1. Die Datei legt der Flasker in seinem Projektverzeichnis ab, im Test erhielt sie zunächst den einfallsreichen Namen »test.py« . Er führt sie über »python test.py« aus, öffnet im lokalen Netzwerk einen Browser und gibt

Listing 1

test.py

01 from flask import Flask
02 from flask import render_template
03 from flask_bootstrap import Bootstrap
04
05 app = Flask(__name__)
06 Bootstrap(app)
07
08 @app.route('/')
09 def hello_world():
10     return 'Hello World!'
11
12 @app.route('/hello.html')
13 def hello():
14     nachricht = "Hallo Welt!"
15     return render_template('hello.html', nachricht=nachricht)
16
17 if __name__ == "__main__":
18     app.run(host='0.0.0.0', port=8080, debug=True)
http://IP-Adresse des Raspi:8080

ein. Nun sollte eine weiße Seite mit den Worten »Hello World!« erscheinen.

In den Zeilen 3 und 6 integriert der Baumeister neben Flask auch noch Bootstrap. Bei »@app.route(‘/’)« handelt es sich um einen Decorator, der eine Python-Funktion an eine URL bindet. In diesem Fall heißt die Funktion »hello_world()« , und die URL ist das Wurzelverzeichnis »/« der App.

Der Decorator in der Zeile 12 erzeugt eine neue Route. Öffnet der Nutzer die Datei »http://IP-Adresse des Raspi:8080/hello.html« im Browser, erkennt Flask das und ruft die Funktion »hello()« auf. Ihre Ausgabe schickt »render_template()« an den Browser. In Listing 1 weist der Code der Variablen »nachricht« den Text »Hallo Welt!« zu, »render_template()« reicht also den Inhalt der Variablen an »hello.html« weiter. Als Folge erscheint im Browser ein schlichtes »Hallo Welt!« (Abbildung 3).

Abbildung 3: Die Navigationsleiste lässt sich als Datei »navbar.html« in alle Seiten einbinden.

Abbildung 3: Die Navigationsleiste lässt sich als Datei »navbar.html« in alle Seiten einbinden.

Die HTML-Datei »hello.html« (Listing 2) bindet in Zeile 2 zunächst das vorinstallierte Bootstrap-Template ein, dann folgen eine Navigationsleiste (Zeilen 3 bis 7) und ein Inhaltsblock, der von Zeile 9 bis 11 reicht. In Zeile 10 findet sich die Variable »nachricht« wieder, die Flask nun durch den Wert aus »test.py« ersetzt. Die Navigationsleiste aus Abbildung 3 holt Flask aus der Datei »templates/navbar.html« und legt sie in der Variablen »nav« ab (Zeile 3).

Listing 2

templates/hello.html

01 <!DOCTYPE html>
02 {% extends "bootstrap/base.html" %}
03 {% import "navbar.html" as nav %}
04
05 {% block navbar %}
06     {{ nav }}
07 {% endblock navbar %}
08
09 {% block content %}
10     {{ nachricht }}
11 {% endblock %}

Die Datei »navbar.html« muss der Anwender zuvor selbst anlegen, das Vorbild dafür stammt von [15]. Sie gehört in das Verzeichnis »templates« und lässt sich nach dem Muster der Zeilen 2 bis 7 in Listing 2 auch in andere Dateien einbinden. Die für das Raspi-Projekt angepasste Datei wartet mit den anderen Listings auf den Webseiten des Linux-Magazin [16].

Im LAN ist die Raspi-IP nur deshalb erreichbar, weil der Entwickler in Zeile 18 von Listing 1 die »0.0.0.0« anstelle der üblichen »127.0.0.1« verwendet. Würde er sich für die zweite Option entscheiden, liefe das Hello-World-Beispiel nur auf dem Raspberry Pi selbst – eine Sicherheitsmaßnahme, um böswillige Entwickler fernzuhalten. Allerdings steht sie auch den gutwilligen im Wege, bei denen die Flask-App zum Beispiel in einer virtuellen Maschine läuft.

Die Option »debug=True« aktiviert den Debugger, mit ihr bemerkt Flask aber auch Änderungen an der Codebasis und lädt die veränderten Seiten automatisch neu – falls das Framework nicht wegen eines Fehlers im Code abstürzt.

Hoch die Flaschen!

Das sind bereits viele der grundlegenden Prinzipien. Flask ermöglicht zwar noch wesentlich komplexere Setups, wie der Blick in die Dokumentation [17] beweist, aber es geht eben auch schlicht.

Abbildung 4 zeigt nun Auszüge der kompletten Raspberry-Pi-Anwendung. Die Datei »piserver.py« ersetzt »test.py« und im Ordner »img« warten mehrere Bilder. Letztlich verwendet die Raspi-App jedoch nur vier Templates: Neben der bereits erwähnten »navbar.html« gibt es die Hauptseite »main.html« , die erscheint, wenn ein User auf den Raspberry-Pi-Schriftzug aus Abbildung 1 klickt. Sie enthält neben der Navigationsleiste beliebigen Text, das Routing zeigen die Zeilen 4 und 5 in Listing 3.

Listing 3

piserve.py

01 # Header und Import-Statements warten unter [16].
02 [...]
03
04 @APP.route('/')
05 @APP.route('/main.html')
06 def mainhtml():
07     return render_template('main.html')
08
09
10 @APP.route('/stats.html')
11 def stats():
12     today = datetime.date.today()
13     system = platform.system()
14     node = platform.node()
15     arch = platform.machine()
16     user = os.getlogin()
17
18     space = os.statvfs('/home/'+user)
19     freespace = (space.f_frsize * space.f_bavail)/1024/1024
20
21     get_uptime = subprocess.Popen('uptime', stdout=subprocess.PIPE)
22     uptime = get_uptime.stdout.read()
23
24     return render_template('stats.html', today=today, system=
25                            system, node=node, arch=arch, user=user,
26                            freespace=freespace, uptime=uptime)
27
28
29 @APP.route('/dienste.html')
30 def dienst():
31     user = os.getlogin()
32     node = platform.node()
33     vnconline = None
34     if os.path.exists('/home/'+user+'/.vnc/'+node+':1.pid'):
35         vnconline = True
36
37     try:
38         myip = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
39         myip.connect(('8.8.8.8', 80))
40         getip = myip.getsockname()[0]
41         myip.close()
42     except StandardError:
43         getip = "IP nicht erkannt"
44
45     return render_template('dienste.html', vnconline=vnconline,
46                            getip=getip)
47
48
49 @APP.route('/<vncstatus>', methods=['POST'])
50 def vncsteer(vncstatus):
51     if vncstatus == "startserver":
52         try:
53             server_up = ["tightvncserver", ":1", "-geometry",
54                          "1024x768", "-depth", "24"]
55             subprocess.call(server_up)
56         except StandardError:
57             print "Server startet nicht oder läuft bereits."
58     if vncstatus == "stoppserver":
59         try:
60             server_down = ["tightvncserver", "-kill", ":1"]
61             subprocess.call(server_down)
62         except StandardError:
63             print "Server läuft nicht oder lässt sich nicht beenden."
64     return redirect('/dienste.html')
65
66
67 @APP.route('/reboot', methods=['POST'])
68 def reboot():
69     passwd = request.form['password']
70     rbt1 = subprocess.Popen(["echo", passwd], stdout=subprocess.PIPE)
71     rbt2 = subprocess.Popen(["sudo", "-S", "reboot"], stdin=rbt1.
72                             stdout, stdout=subprocess.PIPE)
73     print rbt2.communicate()[0]
74     return redirect('/dienste.html')
75
76 if __name__ == "__main__":
77     APP.run(host='0.0.0.0', port=8080, debug=True)
Abbildung 4: Die App, die den Raspi steuert, besteht vordergründig nur aus wenigen Dateien.

Abbildung 4: Die App, die den Raspi steuert, besteht vordergründig nur aus wenigen Dateien.

In den Dateien »stats.html« (Abbildung 1) und »dienste.html« (Abbildung 2) steckt jeweils eine Tabelle. Die erste zeigt Daten an, die »piserve.py« über den Raspberry Pi erhebt, die zweite liefert HTML-Formulare aus, über die User Dienste auf dem Raspi starten und stoppen. Schönheitspreise gewinnt das Interface nicht, demonstriert aber einige der Template-Optionen.

Flaschenpost

Die Hauptdatei in Listing 3 besteht grob aus fünf Blöcken, die jeweils mit »@APP« beginnen. Die »stats()« -Funktion ab Zeile 10 erhebt Daten zum Raspberry Pi, wobei die Zeilen 12 bis 16 ziemlich selbsterklärend sind und die ersten fünf Zeilen in Abbildung 1 ausmachen. Um den freien Platz im Homeverzeichnis zu messen, muss Python dann wissen, wo dieses ist (Zeile 18) und ein wenig rechnen (Zeile 19).

Will der Entwickler einen Bash-Befehl an den Raspberry senden und die Ausgabe wieder einlesen, greift er am besten zur Klasse »subprocess.Popen« , die ein »uptime« -Kommando absetzt und sich die Ausgabe über »stdout=subprocess.PIPE« merkt (Zeile 21). Die landet in der nächsten Zeile in der Variablen »uptime« , bevor »render_template()« alle Variablen an »stats()« retourniert.

Dienstbarer Flaschengeist

Die Funktion »dienst()« (Zeile 30) besorgt sich zunächst Benutzer- und Rechnernamen und setzt dann die Variable »vnconline« auf »None« . Findet die folgende IF-Schleife aber eine PID-Datei im Verzeichnis »~/.vnc« , geht sie davon aus, dass der Tight-VNC-Server läuft, und setzt »vnconline« auf »True« . Eine »socket()« -Funktion ermittelt die lokale IP-Adresse des Raspi und gibt eine Fehlermeldung aus, falls das scheitert.

Beide Variablen trifft man in der Datei »dienste.html« wieder, die Listing 4 auszugsweise zeigt. Ist der VNC-Server offline (»vnconline = None« ), zeigt die Weboberfläche keine IP-Adresse an und der Button trägt die Aufschrift »Server starten« . Ist »vcnonline« hingegen wahr, erfährt der Raspberry-Benutzer dank »{{ getip }}« , welche IP-Adresse er im VNC-Betrachter aufrufen muss. If-Abfragen beherrscht die Jinja-2-Templating-Engine also auch.

Listing 4

dienste.html (Auszug)

01 [...]
02 <td>{% if not vnconline %}
03         IP-Adresse: Unbekannt (Offline)
04        {% else %}
05             IP-Adresse: {{ getip }}:5901 (Online)
06        {% endif %}
07 </td>
08 [...]
09 <td>{% if not vnconline %}
10         <form class="navbar-form navbar-left" action="/startserver" method="post">
11             <input type="submit" class="btn btn-default" value="VNC-Server starten"></input>
12         </form>
13 [...]

Will der Admin den VNC-Server starten und klickt dazu auf den Button »VNC-Server starten« , hängt das Formular im HTML-Quellcode beim Posten den String »/startserver« an die URL (Listing 4, Zeile 10). Nun wird der Block in den Zeilen 50 bis 64 des Python-Skripts in Listing 3 aktiv. Er erkennt dank des Platzhalters »<vncstatus>« und einer If-Abfrage, ob er den Server starten (Zeile 51) oder stoppen (Zeile 58) soll, was die »subprocess.call()« -Funktion übernimmt. Ist das getan, zeigt der Browser dank der »redirect()« -Funktion von Flask wieder die Seite »dienste.html« an.

Noch ein kräftiger Zug

Ähnlich funktioniert der Code ab Zeile 67, nur dass der Nutzer hier sein Rootpasswort in eine HTML-Zeile eingibt, um den Raspi neu zu starten. Das Eingabefeld ist auf 128 Zeichen begrenzt, die Eingabe findet verdeckt statt. Dennoch schickt Flask das Rootpasswort im Klartext über das Netz, das sollten Programmierer im Hinterkopf behalten.

Taucht in der URL das Stichwort »/reboot« auf, liest die Flask-Methode »request.form()« zunächst das Passwort und legt es in »passwd« ab (Zeile 69). Eine Instanz der Klasse »subprocess.Popen« setzt auf dem Raspberry Pi einen »echo« -Befehl ab, gefolgt von dem Passwort, und legt das Ergebnis in »rbt1« ab (Zeile 70). Die nächste Instanz von »subprocess.Popen« übergibt die Ausgabe des »echo« -Befehls als Standardeingabe an »sudo -S reboot« (Zeile 71).

Im dritten Schritt sendet »communicate()[0]« die Daten. Die Python-Dokumentation empfiehlt diese Funktion, da »stdout=PIPE« und »stderr=PIPE« eventuell Deadlocks hervorrufen.

Fazit

Im lokalen Netzwerk lässt sich ein Raspberry dank Flask mit recht wenig Aufwand steuern. Das Microframework besticht besonders durch die einfache Handhabung. Aufwändiger wird es, soll der Raspi als Server über das Internet verfügbar sein. Obwohl ein Microframework, fühlt sich Flask aber auch im professionellen Umfeld zu Hause und bandelt mit bekannten Webservern wie Apache, Nginx oder Lighthttp an [18]. Es beherrscht sichere Authentifizierung, Sessions mit kryptographisch unterschriebenen Cookies, Caching, Internationalisierung und bringt eine Abstraktionsschicht für Datenbanken mit.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben