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
(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.
| # 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.