Auf einem eingebetteten System (z.B.: Raspberry Pi) soll ein Webserver es ermöglichen interaktiv auf LEDs oder angeschlossene elektronische Schaltkreise zuzugreifen.
Siehe auch Kapitel "Webseiten mit HTML und CSS".
Wird eine Internetadresse (URL, Uniform resource locator) im Browser
angegeben, so sucht dieser nach einer Datei mit dem Namen "index.html
". Diese
HTML-Datei (HyperText Markup Language) besteht im einfachsten Fall aus dem
Kopf mit Titel und dem Body mit Inhalt:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Meine statische Webseite</title>
</head>
<body>
<h1>Dies ist die Überschrift</h1>
<p>Das ist ein Absatz</p>
</body>
</html>
Den obigen HTML-Code speichern wir auf dem Raspi unter dem Namen: "index.html
"
im Verzeichnis "/home/pi/webserver
" das wir neu erstellen (mkdir webserver
).
Nachdem wir in dieses Verzeichnis gewechselt sind (cd ~/webserver
) starten wir
den Webserver mit dem folgenden Befehl:
python3 -m http.server 8000
Wir verwenden hier nicht den Standardport 80 für Webserver, da wir sonst Root-
Rechte auf dem Raspi benötigen. Unser Raspi-Server lauscht nun auf Port 8000.
Der Schalter "-m
" ist nötig damit nachher Module als Skript ausgeführt werden
können.
Auf dem PC können wir die Webseite jetzt mit folgender URL aufrufen, wobei
natürlich die richtige IP-Adresse einzugeben ist:
http://172.16.249.30:8000
Den Webserver kann man auch lokal testen, also auf dem Gerät auf dem der Webserver gestartet wurde. Die Standard- IP-Adresse des lokalen Geräts lautet dann 127.0.0.1 (http://127.0.0.1:8000).
Mit dem Common Gateway Interface (CGI) ist es möglich Python-
Programme auf dem Webserver auszuführen. Durch diese besteht dann die
Möglichkeit dynamische Webseiten zu erstellen, also Seiten, deren Inhalt durch
den Nutzer verändert werden kann.
Die Programme müssen sich zwingend im Unterverzeichnis "cgi-bin
" im Webserver-
Verzeichnis befinden (/home/pi/webserver/cgi-bin
). Das erste Python-Programm
soll die gleiche statische Webseite wie vorhin erzeugen.
Damit CGI weiß, welche Programmiersprache wir einsetzen, müssen wir dies in der
ersten Zeile mitteilen. Die Zeile teilt CGI zusätzlich mit, dass die Python
Umgebung (Programme) im Verzeichnis "/usr/bin
" zu finden ist. Da wir deutsche
Umlaute verwenden, müssen wir Python zusätzlich mitteilen mit welcher Kodierung
wir arbeiten (dies muss in einer der ersten beiden Zeilen erfolgen!).
Der HTML-Code wird mit der print()
-Methode ausgegeben. Mit drei Hochkomma
besteht die Möglichkeit mehrere Zeilen zu drucken.
Der ausgegebene Code landet, wenn CGI ihn ausführt, allerdings nicht auf der
Standardausgabe, sondern gleich im Browser.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#webserver_WS2.py
print('''
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8"/>
<title>Meine statische Webseite</title>
</head>
<body>
<h1>Dies ist die Überschrift</h1>
<p>Das ist ein Absatz</p>
</body>
</html>
''')
Erstelle die Python-Dateien auf dem Raspi (Linux). Die Zeilen müssen mit einem Linefeed enden (LF, 0x0A
). Dateien die in Windows erstellt wurden, enthalten ein zusätzliches Carriage Return (CR, 0x0D
) Zeichen, das bewirkt, dass ein Fehler auftritt und die Datei nicht angezeigt wird.
Erstelle das Verzeichnis "cgi-bin
". Speichere das obige Programm mit dem Namen
"webserver_WS2.py
" im Verzeichnis "/cgi-bin
" ab. Wechsle in das Verzeichnis
und teste das Programm (python3 webserver_WS2.py
).
Damit CGI die Datei ausführt, benötigt man die nötigen Rechte für eine
ausführbare Datei. Führe den folgenden Befehl im Unterverzeichnis "/cgi-bin
"
aus:
chmod u+x webserver_WS2.py
(Auf der graphischen Oberfläche kann dies auch im Dateimanager erledigt werden (Rechtsklick Properties) oder mit dem Midnight Commander (MC) in einem Terminal-Fenster).
Danach können wir im Server-Verzeichnis webserver
("cd ..
") den Server
starten. Dazu erstellen wir eine python Datei mit folgendem Inhalt:
#!/usr/bin/env python
import http.server
http.server.HTTPServer(("", 8000),http.server.CGIHTTPRequestHandler).serve_forever()
und nennen sie webserver_cgi.py.
Wir starten dann den Server mit dem Befehl in einem eigenen Fenster:
python3 webserver_cgi.py
Achtung, dies muss im Verzeichnis webserver
passieren, da der Server sonst die Dateien nicht findet. Im Terminal-Fenster des Webservers tauchen nun auch Fehlermeldungen auf, sollte das Python Programm aus dem cgi-bin Verzeichnis nicht richtig funktionieren.
Im Webbrowser rufen wir jetzt unsere Seite mit der folgenden URL auf (eigene IP-Adresse einsetzen!):
http://172.16.249.30:8000/cgi-bin/webserver_WS2.py
Die Ausgaben unseres Python Programms landen nicht mehr auf der Standardausgabe, sondern werden zum Webserver gesendet. Über diesen Weg kann ein eingebettetes System uns jetzt Informationen mitteilen.
Sollte ein Programm nicht
An einem Raspberry Pi ist, über die 1-Wire Schnittstelle, ein Temperatursensor vom Typ DS18B20 angeschlossen. Die Temperatur soll im Webbrowser angezeigt werden, und bei jedem Reload der Webseite aktualisiert werden. Der Sensor ist mit 3,3V, Masse und Pin Nummer 4 zu verbinden. Der Pull-Up- Widerstand von 4,7k zieht die Datenleitung auf 3,3V (siehe Kapitel: Schnittstellen mit dem Raspberry Pi). Hier das entsprechende Programm.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# webserver_WS3.py
import glob
deviceFolder = glob.glob('/sys/bus/w1/devices/28*') # find the file
deviceFolderString = deviceFolder[0]
deviceFile = deviceFolderString + '/w1_slave'
def readTemp():
try:
f = open(deviceFile, 'r')
except IOError:
print('IOError')
line1 = f.readline()
line2 = f.readline()
f.close()
pos = line2.find('t=')
if pos != -1:
tempString = line2[pos + 2:]
temp = round(float(tempString) / 1000.0, 1)
else:
print('error')
return temp
temperature = (readTemp())
print('''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Mein Raspi Fieberthermometer</title>
</head>
<body>
<h1>Mein Raspi Fieberthermometer</h1>
<p>Die Temperatur beträgt: <b>%s Grad Celsius<b/></p>
</body>
</html>
''' % temperature)
Sollen mehrere Variablen ausgegeben werden, so sind diese in Klammern, getrennt durch Komma hinter dem Prozentzeichen anzugeben. Beispiel:
print('''
...
<p>Der erste Schalter ist <b>%sgeschaltet</b>.</p>
<p>Der zweite Schalter ist <b>%sgeschaltet</b>.</p>
<p>Der dritte Schalter ist <b>%sgeschaltet</b>.</p>
...
''' % (var0,var1,var2))
Speichere das obige Programm mit dem Namen "webserver_WS3.py
" im Verzeichnis
"/cgi-bin
" und mache es ausführbar (chmod u+x webserver_WS3.py
. Verdrahte die
Hardware am Raspberry Pi und teste das Programm indem du es im Webbrowser mit
der folgenden URL aufrufst: (eigene IP-Adresse einsetzen!):
http://172.16.249.30:8000/cgi-bin/webserver_WS3.py
Schreibe ein eigenes Programm, das am Raspberry Pi vier Schalter abfragt und den
Zustand der Schalter im Browser darstellt.
Nutze dazu den 8-Bit I/O Port-Expander-Chip PCF8574 (siehe Kapitel:
Schnittstellen mit dem Raspberry Pi). Er ermöglicht es den Raspi über den I²C-
Bus um 8 digitale Ein- bzw. Ausgänge zu erweitern. Ein über den I²C-Bus
gesendetes Byte wird parallel an 8 Pins ausgegeben bzw eingelesen. Verbinde den
Chip mit dem Raspi und schließe vier Taster (lila Drähte dienen als Schalter) an
P0 bis P3 an. Weitere Informationen zum PCF8574:
http://www.nxp.com/documents/data_sheet/PCF8574.pdf
Nenne das Programm "webserver_WS4.py
" (Verzeichnis "/cgi-bin
" und chmod u+x
webserver_WS4.py
!) und teste es.
Verdrahtungsplan PCF8574P:
Um unserem eingebetteten System Informationen zukommen zu lassen, nutzen wir
Befehlszeilen-Parameter, die wir dem Python-Programm mitgeben. Auf den
ersten Parameter kann mit sys.argv[1]
zugegriffen werden.Dazu muss nur das
Modul sys
mit import sys
eingebunden werden. Der Parameter wird als
Zeichenkette (string) übergeben.
Es soll im folgenden eine LED ein- und ausgeschaltet werden.
Die Ausgänge des PCF8574 sind "open collector" Ausgänge und liefern maximal
25mA. Beim Anschließen von LEDs (gegen 5V) sind deshalb Vorwiderstände nicht
unbedingt nötig. Die LEDs werden durch Ausgabe einer Null eingeschaltet.
Um einzelne LEDs zu schalten wird eine klassische Maskierung (einlesen,
maskieren, ausgeben) verwendet (siehe "Die Maskierung von Daten" in
http://weigu.lu/tutorials/avr_assembler/pdf/MICEL_MODUL_A.pdf Kapitel 3). Mit der ODER-Verknüpfung (Python |
) werden einzelne Pins auf Eins gesetzt und mit der UND-Verknüpfung (Python &
) werden einzelne Pins auf Null gesetzt.
Um falsche Eingaben mit einer Fehlermeldung abfangen zu können, verwenden wir
eine try/except
-Anweisung.
Hier der Code eines Programms, das eine LED an P4 des Port-Expander-Chip PCF8574 ein bzw. ausschaltet:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# webserver_WS5.py
# install i2c-tools and smbus with:
# sudo apt-get install i2c-tools python-smbus
# reboot: sudo reboot
# test with: i2cdetect -y 1 (for Pi Model B rev. 2)
import smbus
import sys
port = 1 # (0 for rev.1, 1 for rev 2!)
bus = smbus.SMBus(port)
expanderAddr = 0x20
din = bus.read_byte(expanderAddr)
try:
state = sys.argv[1]
if (state == '0'):
din = din | 0x10 # P4 = 1 LED ausschalten
comment = "Die <b>LED</b> wurde <b>ausgeschaltet<b/>."
elif (state == '1'):
din = din & 0xEF # P4 = 0 LED einschalten
comment = "Die <b>LED</b> wurde <b>eingeschaltet<b/>."
else:
comment = "Nur die 0 und die 1 sind als Parameter gültig!"
except:
comment = "Fehler: Kein gültiger Parameter!"
bus.write_byte(expanderAddr, din)
print('''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Der erleuchtete Raspi</title>
</head>
<body>
<h1>Der erleuchtete Raspi</h1>
<p>%s</p>
</body>
</html>
''' % comment)
Im Webbrowser rufen wir jetzt unsere Seite mit der folgenden URL auf (eigene IP-Adresse einsetzen!):
http://172.16.249.30:8000/cgi-bin/webserver_WS5.py?0
zum Ausschalten und mit
http://172.16.249.30:8000/cgi-bin/webserver_WS5.py?1
zum Einschalten. Das Fragezeichen wurde zur Trennung verwendet, da Leerzeichen nicht erlaubt sind.
Teste das Programm. Denke daran es zuerst ausführbar zu machen.
Um die komplizierten URLs nicht auswendig kennen zu müssen bauen wir klickbare Links mit den jeweiligen URLs in unsere Seite mit ein:
print('''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Der erleuchtete Raspi</title>
</head>
<body>
<h1>Der erleuchtete Raspi</h1>
<p><a href="http://172.16.249.30:8000/cgi-bin/webserver_WS6.py?0">LED an P4
ausschalten<a/></p>
<br>
<p><a href="http://172.16.249.30:8000/cgi-bin/webserver_WS6.py?1">LED an P4
einschalten<a/></p>
<br>
<hr>
<p>%s</p>
</body>
</html>
''' % comment)
Erweitere das Programm um die Links und teste es als webserver_WS6.py
.
Erweitere das Programm auf vier schaltbare LEDs und um einen Link, der
zusätzlich die Zustände der vier Schalter anzeigt. Teste das Programm als
webserver_WS7.py
.
Verpasse der Seite ein wenig "Style" sowie ein favicon. Nutze Bilder statt
Text für die Links (Entsprechende Icons findest du hier:
http://www.clker.com/clipart-red-led-off-1.html.).
Dies lässt sich mit folgendem HTML-Code verwirklichen:
<a href="http://172.16.249.30:8000/cgi-bin/webserver_WS7.py?L4_0">
<img src="../led_off.png" alt="LED off"></a>
Die Datei style.css
und die Bilddateien müssen sich im Verzeichnis webserver
befinden.
Schreibe ein Programm, das einen als Parameter übergebenen Text als Lauflicht
auf dem 14-Segment Display (siehe Kapitel: Schnittstellen mit dem Raspberry Pi)
ausgibt. Teste das Programm als webserver_WS8.py
.
Leider sind mehrere Fragezeichen zur Trennung von mehreren Parametern nicht
erlaubt. Möchte man mehrere Parameter übergeben, so ist es am einfachsten diese
in einen Parameter zu packen. Dazu verwendet man das &
-Zeichen. Um vier
LEDs gleichzeitig zu schalten könnte man zum Beispiel:
http://172.16.249.30:8000/cgi-bin/webserver_WS8.py?1&0&1&1&Hallo
versenden (1. LED ein, 2. LED aus, 3. LED ein, 4. LED ein, 5. Parameter
Text"Hallo").
Mit der split()
-Methode wird der gepackte Parameter dann in seine Einzelteile
zerlegt:
try:
para = sys.argv[1]
state = para.split ('&')
comment5 = state[4]
maskOff = 0x00
maskOn = 0xFF
if (state[0] == '0'):
maskOff = maskOff | 0x10 # P4 = 1 LED ausschalten
comment1 = "Die <b>LED P4</b> wurde <b>ausgeschaltet</b>."
elif (state[0] == '1'):
maskOn = maskOn & 0xEF # P4 = 0 LED einschalten
comment1 = "Die <b>LED P4</b> wurde <b>eingeschaltet</b>."
else:
comment1 = "Nur die 0 und die 1 sind als Parameter gültig!"
if (state[1] == '0'):
maskOff = maskOff | 0x20
comment2 = "Die <b>LED P5</b> wurde <b>ausgeschaltet</b>."
elif (state[1] == '1'):
maskOn = maskOn & 0xDF
comment2 = "Die <b>LED P5</b> wurde <b>eingeschaltet</b>."
else:
comment2 = "Nur die 0 und die 1 sind als Parameter gültig!"
if (state[2] == '0'):
maskOff = maskOff | 0x40
comment3 = "Die <b>LED P6</b> wurde <b>ausgeschaltet</b>."
elif (state[2] == '1'):
maskOn = maskOn & 0xBF
comment3 = "Die <b>LED P6</b> wurde <b>eingeschaltet</b>."
else:
comment3 = "Nur die 0 und die 1 sind als Parameter gültig!"
if (state[3] == '0'):
maskOff = maskOff | 0x80
comment4 = "Die <b>LED P7</b> wurde <b>ausgeschaltet</b>."
elif (state[3] == '1'):
maskOn = maskOn & 0x7F
comment4 = "Die <b>LED P7</b> wurde <b>eingeschaltet</b>."
else:
comment4 = "Nur die 0 und die 1 sind als Parameter gültig!"
except:
comment1 = "Fehler: Kein gültiger Parameter!"
comment2 = "Fehler: Kein gültiger Parameter!"
comment3 = "Fehler: Kein gültiger Parameter!"
comment4 = "Fehler: Kein gültiger Parameter!"
Teste die Variante mit der gleichzeitigen Übergabe mehrerer Parameter. Teste das
Programm als webserver_WS9.py
.
Schreibe ein Programm, das den Inhalt der Parameter über die serielle Schnittstelle des Raspi versendet.
Erweitere die vorige Aufgabe, so dass zusätzlich der Text aus einer Textdatei
(serial_in.txt
) im Browser angezeigt wird. Ein zweites Python-Programm soll im
Hintergrund laufen, die serielle Schnittstelle der Raspi kontinuierlich
abfragen, und empfangene Zeilen in die obige Textdatei schreiben.
Quellen: