38. Lektion: Webserver

Aus Attraktor Wiki

Wechseln zu: Navigation, Suche

Webserver

https://github.com/pimoroni/phew


Wlan einrichten

Um einen Webserver betreiben zu können, müssen wir zuerst eine Verbindung zu einem Netzwerk herstellen.
Hier noch einmal die klassische Einrichtung einer Wlan-Verbindung:

import network

# Wlan Verbindung herstellen

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
    print('connecting to network...')
    wlan.connect('ssid', 'key')
    while not wlan.isconnected():
        pass
print('network config:', wlan.ifconfig())

Das Webserver Packet phew

Für den Raspberry Pi Pico W gibt es ein Packet, das alles enthält, was zur Einrichtung eines Webservers auf dem Pico erforderlich ist. Es heißt "phew" und ist von Github unter https://github.com/pimoroni/phew zu beziehen. Das Archiv muss entpackt und dann das Unterverzeichnis "phew" ins Filesystem des Pico kopiert werden.
Alternativ kann phew auch mit Thonny installiert werden phew bei PyPi.

Was phew! leistet:

  • ein einfacher Webserver
  • auf Geschwindigkeit optimiert (beim Import und während der Ausführung)
  • minimale Nutzung des Speichers
  • parametrisierte Weiterleitungsregeln /greet/<name>
  • Template-Engine, die Inline-Python-Ausdrücke {{name.lower()}} erlaubt
  • GET- und POST-Anforderungsmethoden
  • Dekodierung und Parsing von Abfragezeichenfolgen
  • Catchall-Handler für nicht weitergeleitete Anfragen
  • multipart/form-data, x-www-form-urlencoded, und JSON POST Körper
  • String-, Byte- oder Generator-basierte Antworten
  • connect_to_wifi und access_point Komfortmethoden

Wo es möglich ist, versucht phew! die Menge an Code und Einstellungen, die Sie als Entwickler vornehmen müssen, zu minimieren, indem es vernünftige Standardeinstellungen wählt und einige Kleinigkeiten versteckt, die nur selten angepasst werden müssen.

phew! installieren

phew! kann mit pip von der Kommandozeile oder von Ihrer bevorzugten IDE aus installiert werden.
In Thonny können Sie dies erreichen, indem Sie auf Werkzeuge -> Pakete verwalten klicken und nach micropython-phew suchen.

Der Webserver wird einfach gestartet. Es ist keine großartige Konfiguration erforderlich. Allerdings müssen vorher noch die Webseiten und ihre Routen definiert werden.

from phew import server

# hier werden die auszuliefernden Webseiten definiert.   Siehe nächste Codebox.

server.run()


Einfaches Beispiel

Ein Beispiel-Webserver, der auf Anforderung eine Zufallszahl zwischen 1 und 100 (oder optional den vom Aufrufer angegebenen Bereich) zurückgibt:

from phew import server, connect_to_wifi

connect_to_wifi("<ssid>", "<password>")

@server.route("/random", methods=["GET"])
def random_number(request):
  import random
  min = int(request.query.get("min", 0))
  max = int(request.query.get("max", 100))
  return str(random.randint(min, max))

@server.catchall()
def catchall(request):
  return "Not found", 404

server.run()

Einfaches Wlan verbinden

Phew bietet eine einfachere Möglichkeit die Verbindung zum Wlan herzustellen:

from phew import server, connect_to_wifi

connect_to_wifi("Attraktor", "blafablafa")

2023-12-07 17:37:01 [debug    / 143kB]   - connecting
2023-12-07 17:37:04 [debug    / 141kB]   - connecting
'192.168.5.120'

Lt. Beschreibung gibt es eine Funktion get_ip_address(). Bei mir hat sie aber nicht funktioniert und ich habe sie auch nicht im Quellcode finden können. Deshalb macht es Sinn, die IP-Adresse in eine Variable zu speichern:

from phew import server, connect_to_wifi

ip_adresse = connect_to_wifi("Attraktor", "blafablafa")

2023-12-07 17:48:47 [debug    / 146kB]   - got ip address

>>> ip_adresse
'192.168.5.120'

Webseiten generieren

Eine Webseite wird erstellt indem eine Funktion definiert wird, die den Inhalt des Response zurück gibt. Der Funktion muss request übergeben werden.
Damit der Server diese Funktion als Webseite erkennt muss sie entsprechend kenntlich gemacht werden. Dazu gibt es zwei Möglichkeiten:

Mit Decorator:

Ein Decorator ist ein Konzept, dass eine Funktion dekoriert. D.h. Er ergänzt die Funktion, fügt also eine zusätzliche Funktionalität hinzu.
Es muss dem Decorator die Route - die Zeichen, die in der URL hinter der IP-Adresse stehen - und die HTTP-Methode (GET oder POST) ünergeben werden. Die Definition folgt in der nächsten Zeile.

@server.route("/random", methods=["GET"])
def random_number(request):
    import random
    min = int(request.query.get("min", 0))
    max = int(request.query.get("max", 100))
    return str(random.randint(min, max))

Mit Route-Tabelle:

# Webseiten definieren

def random_number(request):
    import random
    min = int(request.query.get("min", 0))
    max = int(request.query.get("max", 100))
    return str(random.randint(min, max))

def info_site(request):
      return "<h1>Infoseite vom Raspberry Pi Pico W</h1>"

# Routing Tabelle

server.add_route("/random", random_number, methods=["GET"])
server.add_route("/info", info_site, methods=["GET"])

Response erzeugen

Der Response ist ein String der alle Informationen enthält die an den Client zurückgegeben werden sollen.
Innerhalb der Definition kann alles das gemacht werden, was auch in einer Funktion möglich ist. Der Return-Wert ist der Response. Dieser kann auf unterschiedliche Weise erzeugt werden.

Einfacher Text

Hier steht hinter return ein String, der den Response enthält. Dieser kann, aber muss nicht, HTML-Auszeichnungen enthalten. Es kann ein kurzer Text sein, oder auch eine umfangreiche Webseite mit HTML-Header und -body. Als zweites Argument kann der HTTP-Code - hier 404 - agegeben werden.

@server.catchall()
def catch_all(request):
    return "<h1>Hallo, diese Webseite gibt es nicht!</h1>", 404

# oder mit f-string:

@server.route("/test", methods=["GET"])
def test_seite(request):
    version = 1.0.0
    return f"Aktuelle Version {version}", 200

# oder mit einer Funktion:

@server.route("/random", methods=["GET"])
def random_number(request):
    import random
    min = int(request.query.get("min", 0))
    max = int(request.query.get("max", 100))
    return str(random.randint(min, max))
In einer Variablen
@server.route("/info", methods=["GET"])
def info_seite(request):
    content = '''
                 <html>
                     <header>
                          ....
                     </header>
                     <body>
                          ....
                     </body>
                 </html>
              '''
    return content, 200
Aus einer Datei

Um den Response aus einer Datei zurück zu geben gibt es die Funktion render_template().
So kann eine komplette Webseite aus einer Datei zurückgegeben werden, es können aber auch Werte in das Webseiten-Template eingesetzt werden.

from phew.template import render_template
HTDOCS = 'htdocs/'

@server.route("/test", methods=["GET"])
def test_site(request):
    return render_template(HTDOCS + "html-test_001.html")

Wenn die Seite nicht existiert

Der Server gibt üblicherweise die der Route zugeordnete Webseite zurück. Wenn eine Webseite angefordert wird, die nicht existiert, wird die Servermethode catchall() aufgerufen.

@server.catchall()
def catch_all(request):
      return "<h1>Hallo, diese Webseite gibt es nicht!</h1>", 404

# oder aus einer Datei in /htdocs:

HTDOCS = 'htdocs/'

@server.catchall()
def catch_all(request):
    return render_template(HTDOCS + 'error_404.html')

Beispiel

Die verschiedenen Return-Methoden ausprobieren

Hier gibt es ein Programm, das den phew-Server einrichtet und einige Seiten mit verschiedenen Return-Methoden bereitstellt. Welche Seiten abgerufen werden können ist unter IP/info abrufbar.

# webserver_002.py
#
# V.0.0.1
# 07.12.2023
# Peter Stöck
#
# Hier werden die verschiedenen Returnmethoden getestet.



from phew import server, connect_to_wifi
from phew.template import render_template


# Konstanten und Variablen
HTDOCS = 'htdocs/'
ip_adresse = None

SSID = 'Attraktor'
PW = 'blafablafa'

# Wlan Verbindung herstellen

ip_adresse = connect_to_wifi(SSID, PW)
print(ip_adresse)

# Websiten erstellen

@server.route("/string1", methods=["GET"])
def string1(request):
    return str(42) + ' ist string1'

@server.route("/string2", methods=["GET"])
def string2(request):
    return '''<h1>Infoseite vom Raspberry Pi Pico W</h1><br>
            Dieses ist string2'''

@server.route("/string3", methods=["GET"])
def string3(request):
    return render_template(HTDOCS + "html-test_002.html")

@server.route("/var", methods=["GET"])
def var(request):
    content = 'Hallo, ich komme aus einer Variablen'
    return content

@server.route("/hallo/<name>", methods=["GET"])
def hallo(request, name):
    return f"Hallo {name}!"

@server.route("/info", methods=["GET"])
def string3(request):
    content = '''
                 <h1>Infoseite vom phew-Server</h1>
                 Folgende Seiten sind abrufbar:<br>
                 /string1<br>
                 /string2<br>
                 /string3<br>
                 /var<br>
                 /hallo/Hier Name eingeben<br>
                 /info - diese Seite<br>
              '''
    return content

@server.catchall()
def catch_all(request):
    return "<h1>Hallo, diese Webseite gibt es nicht!</h1><br>Sie steht für catch_all.", 404

# Server starten - nun geht nichts anderes mehr:(

server.run()

Hier die Datei html-test_002.html. Sie muss in das Verzeichnis htdocs kopiert werden.

<h1>&Uuml;berschrift</h1>
Dieses ist eine kleine HTML-Testseite.
<br>
Sie steht f&uuml;r string3
<br>
Noch ist es nur ein einfacher Test!

Ein praktisches Beispiel

Dieses Programm ermöglicht es die LED ein-/auszuschalten und die Sensoren abzufragen.

# webserver_003.py
#
# Version 0.0.1
# 07.12.2023
# Peter Stöck
#
# Dieses Programm hat folgende Funktionen:
# LED ein- und ausschalten
#

from phew import server, connect_to_wifi
from phew.template import render_template
from machine import Pin


# Konstanten und Variablen
HTDOCS = 'htdocs/'
ip_adresse = None
pin_von_led = 22

SSID = 'Attraktor'
PW = 'blafablafa'

# Led aktivieren

led_pin = Pin(pin_von_led, Pin.OUT)
led_pin.value(0)

# Wlan Verbindung herstellen

ip_adresse = connect_to_wifi(SSID, PW)
print(ip_adresse)


# Webseiten erstellen

@server.route("/led/<led_status>", methods=["GET"])
def led_ctrl(request, led_status):
    if led_status == 'ein':
        led_pin.value(1)
    elif led_status == 'aus':
        led_pin.value(0)
    else:
        return "<h1>Hallo, diese Funktion gibt es nicht!</h1>", 404
    return f"LED wurde {led_status}geschaltet."

@server.catchall()
def catch_all(request):
    return "<h1>Hallo, diese Webseite gibt es nicht!</h1>", 404

# Server starten - nun geht nichts anderes mehr:(

server.run()

HTML-Seiten mit Daten ergänzen

Meistens möchte man nicht nur eine statische HTML-Seite ausgeben, sondern z.B. Sensordaten übertragen. Beim Micropython Demoboard könnte es die Temperatur vom BME280 Sensor sein.
Es wäre dann praktisch, wenn der HTML-Code aus einer Datei käme und der Temperaturwert eingefügt wird. Phew bietet dafür Template Expressions:

from phew.template import render_template
HTDOCS = 'htdocs/'

temperatur = 23

text = render_template(HTDOCS + "temp2.html", temp=str(temperatur))

for i in text:
    print(i)

Das Ergebnis:

b'<h1>Micropython Demoboard Temperatur</h1>\nDieses ist eine kleine HTML-Testseite.\n<br>\nTemperatur vom BME280: '
23
b' \xc2\xb0C\n<br>\nNoch ist es nur ein einfacher Test!'

Phew erzeugt den HTML-Code nicht für die ganze Seite im Stück, sondern erzeugt kleine Blöcke, so dass der Speicherbedarf reduziert wird.

Beim schreiben der Template-Seite muss darauf geachtet werden, dass für Sonderzeichen und Umlaute HTML-Code geschrieben wird. UTF-8 führt heir zu ungewünschten Ausgaben.

<h1>Micropython Demoboard Temperatur</h1>
Dieses ist eine kleine HTML-Testseite.
<br>
Temperatur vom BME280: {{temp}} °C
<br>
Noch ist es nur ein einfacher Test!

Für das °-Zeichen muss im HTML-Code &deg; stehen! Das Wiki macht daraus automatisch °! Deshalb konnte ich es im Codeblock nicht richtig darstellen.

Lager

Navigation

Zurück zur "Micropython Kurs 2023 Teil 2" Startseite
Zurück zur "Micropython Kurs 2023" Startseite
Zurück zur Programmieren Startseite
Zurück zur Wiki Startseite