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("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("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('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('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 Variablen request die 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 /lighton oder /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

  • Der extrahierte Pfad wird überprüft:

    • Wenn der Pfad /lighton lautet, schaltet der Server die LED ein (led.value(1)) und setzt den Status auf "an".

    • Wenn der Pfad /lightoff lautet, schaltet der Server die LED aus (led.value(0)) und setzt den Status auf "aus".

(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 response gespeichert, 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.

1
2
3
4
5
6
# Warten, bis die Verbindung hergestellt ist
while not wlan.isconnected():
    pass

# Verbindung erfolgreich
print("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("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.