3.8 Webserver
Als Ausgangsbasis nehmen wir die Verbindung mit dem Netzwerk wie zuvor. Wenn wir einen Webserver bauen, benötigen wir natürlich zunächst eine Verbindung zum Router/Netzwerk.
|  | # WLAN-Verbindung herstellen
# J. Thomaschewski, 17.08.2024
import network
# WLAN aktivieren und verbinden. SSID und PASSWORD ersetzen
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect("SSID", "PASSWORD") # SSID und PASSWORD ersetzen
# Warten, bis die Verbindung hergestellt ist
while not wlan.isconnected():
    pass
# Verbindung erfolgreich
print(f"WLAN verbunden, IP-Adresse: {wlan.ifconfig()[0]}")
 | 
WLAN-Zugangsdaten auslagern
Damit die Zugangsdaten des WLANs (SSID und Passwort) nicht direkt im Quellcode stehen, möchten wir diese in eine separate Datei  wlanzugangsdaten.py ausgelagert. Diese Datei muss auf dem Pi Pico gespeichert werden. Der Code kann dann darauf zugreifen, um die Verbindung herzustellen, ohne die Daten im Hauptprogramm anpassen zu müssen. Die wlanzugangsdaten.py-Datei könnte z. B. so aussehen:
|  | # Datei wlanzugangsdaten.py
ssid = "DeinSSID"
passwd = "DeinPasswort"
 | 
Und die Datei zur Erstellung einer Wlan-Verbindung ändert sich in Zeile 6 und Zeile 11 wie folgt
|  | # WLAN-Verbindung herstellen, Zugangsdaten auslagern
# Datei: 3-8a-WLAN-Passworddatei.py
# J. Thomaschewski, 01.11.2024
import network
import wlanzugangsdaten
# WLAN aktivieren und verbinden. SSID und PASSWORD ersetzen
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(wlanzugangsdaten.ssid, wlanzugangsdaten.passwd) # SSID und PASSWORD ersetzen
# Warten, bis die Verbindung hergestellt ist
while not wlan.isconnected():
    pass
# Verbindung erfolgreich
print(f"WLAN verbunden, IP-Adresse: {wlan.ifconfig()[0]}")
 | 
Ein einfacher Webserver
Um einen einfachen Webserver zu erstellen, benötigen wir Sockets (siehe Zeilen 28-33). Ein Socket ist eine Schnittstelle, die eine Netzwerkverbindung ermöglicht. Er erlaubt unserem Webserver, mit Browsern zu kommunizieren. Über den Socket kann der Server Anfragen empfangen und Antworten zurücksenden.
|  | # Ein einfacher Webserver - Client schaltet interen LED ein/aus
# 3-8b-Webserver-LED.py
# J. Thomaschewski, 17.08.2024
import network
import socket
from machine import Pin
import time
import wlanzugangsdaten  # Zugang für das WLAN ausgelagert
led = Pin('LED', Pin.OUT)
# LED Anfangszustand
state = "aus"
# WLAN-Verbindung herstellen
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(wlanzugangsdaten.ssid, wlanzugangsdaten.passwd)
# Warten, bis die Verbindung hergestellt ist
while not wlan.isconnected():
    print('Versuche Wlan-Verbindung herzustellen')
    time.sleep(1)
print(f"Dies im Browser eingeben: http://{wlan.ifconfig()[0]}")
# Socket einrichten und lauschen
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen()
# Hauptschleife (a) Empfang von Daten, (b) Daten verarbeiten und (c) erstellen der HTML-Seite
while True:
    # (a) Empfang von Daten
    conn, addr = s.accept()
    request = conn.recv(1024)
    request = str(request)
    request = request.split()[1]
    print(f"Anfrage: {request}")
    # (b) Daten verarbeiten
    if request == '/lighton?':
        print("LED an")
        led.value(1)
        state = "an"
    elif request == '/lightoff?':
        print("LED aus")            
        led.value(0)
        state = 'aus'
    # (c) erstellen der HTML-Seite
    response = f"""
        <!DOCTYPE html>
        <html>
        <body>
            <h2>Die LED ist: {state}</h2>
            <form action="./lighton">
                <input type="submit" value="Licht an" />
            </form>
            <br>
            <form action="./lightoff">
                <input type="submit" value="Licht aus" />
            </form>
        </body>
        </html>
        """
    # HTTP-Antwort senden und Verbindung schließen
    conn.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
    conn.send(response)
    conn.close()
 | 
Hauptschleife zur Verarbeitung der Anfragen
Der folgende Code beschreibt die Hauptschleife des Webservers, die aus drei wichtigen Blöcken besteht: 
(a) Empfang von Daten, (b) Datenverarbeitung und (c) Erstellen der HTML-Seite.
|  | while True:
    # (a) Empfang von Daten
    conn, addr = s.accept()  # Eine Verbindung vom Client wird akzeptiert
    request = conn.recv(1024)  # Die Anfrage des Clients wird empfangen
    request = str(request)  # Die Anfrage des Clienst wird in einen String umgewandelt
    request = request.split()[1]  # Der relevante Teil der Anfrage wird abgesplittet
    print(f"Anfrage: {request}")
    # (b) Daten verarbeiten
    if request == '/lighton?':  # Prüfen, ob der Pfad "lighton" ist
        print("LED an")
        led.value(1)  # LED einschalten
        state = "an"
    elif request == '/lightoff?':  # Prüfen, ob der Pfad "lightoff" ist
        print("LED aus")            
        led.value(0)  # LED ausschalten
        state = 'aus'
    # (c) Erstellen der HTML-Seite
    response = f"""
        <!DOCTYPE html>
        <html>
        <body>
            <h2>Die LED ist: {state}</h2>
            <form action="./lighton">
                <input type="submit" value="Licht an" />
            </form>
            <br>
            <form action="./lightoff">
                <input type="submit" value="Licht aus" />
            </form>
        </body>
        </html>
        """
    # HTTP-Antwort senden und Verbindung schließen
    conn.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
    conn.send(response)
    conn.close()
 | 
(a) Empfang von Daten
- 
conn, addr = s.accept(): Der Server akzeptiert eine Verbindung vom Client (z. B. einem Webbrowser).
 
- 
request = conn.recv(1024): Der Server speichert in der Variablenrequestdie Anfrage des Clients, die bis zu 1024 Bytes umfassen kann.
 
- 
request = str(request): Die Anfrage des Clienst ist ein Byte-Object und wird in einen String umgewandelt.
 
- 
request = request.split()[1]:  In der Anfrage (Request Line) steht nach einem Leerzeichen an der 2. Position entweder/lightonoder/lightoff. Eine typische Request Line sieht wie folgt aus:
 "GET /lighton HTTP/1.1\r\nHIER FOLGEN WEITERE ZEILEN AUS DEM HTTP-HEADER\r\n\r\n"
 
(b) Daten verarbeiten
(c) Erstellen der HTML-Seite
- 
Der Server erstellt eine HTML-Seite mit dem aktuellen LED-Status ({state}).
 
- 
Zwei Schaltflächen ermöglichen es die LED einzuschalten (/lighton) oder auszuschalten (/lightoff).
 
- 
Der HTML-Code wird als responsegespeichert, um später an den Client gesendet zu werden.
 
Nach diesen Schritten sendet der Server die HTML-Antwort an den Client und schließt die Verbindung, sodass die Seite im Browser angezeigt werden kann.
Verbesserter WLAN-Verbindungsaufbau
Der bisherige Verbindungsaufbau war nicht ideal. 
|  | # Warten, bis die Verbindung hergestellt ist
while not wlan.isconnected():
    pass
# Verbindung erfolgreich
print(f"WLAN verbunden, IP-Adresse: {wlan.ifconfig()[0]}")
 | 
Ein besserer Ansatz mit einem Timeout könnte so aussehen:
|  | # Warten, bis die Verbindung hergestellt ist
connectionTimeout = 10
while connectionTimeout > 0:
    if wlan.status() == 3:
        break
    connectionTimeout -= 1
    print('Warte auf WLAN-Verbindung...')
    time.sleep(1)
# Überprüfen, ob die Verbindung erfolgreich war
if wlan.status() == 3:
    print(f"WLAN verbunden, IP-Adresse: {wlan.ifconfig()[0]}")
else:
    raise RuntimeError('Netzwerkverbindung konnte nicht hergestellt werden')
 | 
Dieser Code wartet auf die WLAN-Verbindung, bis ein Timeout abgelaufen ist, und gibt eine Fehlermeldung aus, wenn die Verbindung nicht erfolgreich hergestellt werden konnte.
Übung
Beschreiben Sie, wie der Timeout funktioniert.
 
Der WLAN-Status kann folgende Werte annehmen
| Status-Code | Konstante | Beschreibung | 
| 0 | STAT_IDLE | Keine Verbindung und keine Aktivität. | 
| 1 | STAT_CONNECTING | Verbindung wird hergestellt. | 
| -3 | STAT_WRONG_PASSWORD | Verbindung fehlgeschlagen aufgrund eines falschen Passworts. | 
| -2 | STAT_NO_AP_FOUND | Verbindung fehlgeschlagen, da kein Access Point gefunden wurde. | 
| -1 | STAT_CONNECT_FAIL | Verbindung aus anderen Gründen fehlgeschlagen. | 
| 3 | STAT_GOT_IP | Verbindung erfolgreich hergestellt und IP-Adresse erhalten. | 
Wie es weitergeht...
Sie haben hier nun einen Einstieg in den Raspberry Pi Pico bekommen und nun konzentrieren wir uns im nächten Kapitel auf die Python-Syntax, damit sie diesen Einstieg alleine ausbauen können.