Raspberry Pi Pico W – Webserver programmieren

In diesem Beitrag zeige ich dir, wie du am Raspberry Pi Pico W einen kleinen Webserver programmieren und somit eine Webseite ausliefern kannst.

Den neuen Raspberry Pi Pico W habe ich dir bereits im kurzen Beitrag Raspberry Pi Pico W mit Wi-Fi Support vorgestellt und mit dem Vorgängermodell verglichen.

Hier soll es nun darum gehen, wie du diesen in MicroPython programmieren kannst und einen kleinen Webserver betreibst.

Für den Aufbau des Beitrages verwende ich den Blogbeitrag aus der offiziellen Quelle https://www.raspberrypi.com/news/how-to-run-a-webserver-on-raspberry-pi-pico-w, jedoch erweitere ich diese um eigene Bestandteile.

Vorbereiten des Raspberry Pi Pico W für die Programmierung in MicroPython

Zunächst wollte ich den mir vertrauten Mu Editor zum Programmieren nutzen. Dieser bot mir auch an eine entsprechende Firmware für MicroPython zu installieren. Jedoch ist diese nicht mit dem Pico W kompatibel und ich musste feststellen das die Firmware welche im oben genannten Dokument verlinkt wird (https://datasheets.raspberrypi.com/soft/micropython-firmware-pico-w-290622.uf2) zwingend für den Mikrocontroller benötigt wird.

Firmware für MicroPython aufspielen

Damit wir den Mikrocontroller in MicroPython programmieren können, müssen wir die entsprechende Firmware auf den Mikrocontroller kopieren. Du findest die aktuelle Firmware unter https://datasheets.raspberrypi.com/soft/micropython-firmware-pico-w-290622.uf2.

Wenn du die knapp 1,3 MB große UF2-Datei geladen hast, musst du deinen Mikrocontroller im entsprechenden Modus starten, dazu hältst du die Taste „BOOTSEL“ gedrückt und steckst den USB Stecker in die Buchse. Es sollte dann ein Laufwerk „RPI-RP2“ im Explorer angezeigt werden.

Auf dieses Laufwerk kopieren wir nun diese Datei. Nach dem erfolgreichen kopieren startet der Mikrocontroller neu und es wird kein Laufwerk angezeigt!

Einrichten der Entwicklungsumgebung Thonny

Zum Entwickeln nutzen wir die Entwicklungsumgebung Thonny welche du kostenfrei unter https://thonny.org/ für Microsoft Windows, macOS und Linux herunterladen kannst.

In meinem Fall verwende ich die Version für Microsoft Windows.

Wenn die knapp 17,5 MB große Datei heruntergeladen und installiert wurde (es müssen keine speziellen Einstellungen während des Setups getroffen werden). Kann diese gestartet und mit der Entwicklung begonnen werden.

Thonny IDE
Thonny IDE

Die Thonny IDE habe ich in diversen Beiträgen auf meinem Blog schon verwendet und möchte hier speziell darauf eingehen wie du deinen Raspberry Pi Pico W so programmierst das du diesen in dein Netzwerk einbindest.

Auswahl des richtigen Mikrocontrollers und Port

Wenn die Thonny IDE gestartet ist, müssen wir den richtigen Mikrocontroller und den Port wählen. Dazu klicken wir rechts unten in der Ecke auf den Text „Python 3.7.9“ und wählen aus dem Kontextmenü den Eintrag „Configure interpeter…“ aus.

Im neuen Dialog „Thonny Extras“ sollte der Reiter „Interpreter selektiert und geöffnet sein. Aus der Auswahlliste müssen wir dann den Eintrag „MicroPython (Raspberry Pi Pico)“ auswählen sowie den Port.

Solltest du mehrere Geräte an deinem Computer angeschlossen haben (das ist nichts Ungewöhnliches) dann kannst du zusätzlich im Geräte-Manager von Windows (Windows-Taste + X, Geräte-Manager) unter „Anschlüsse (COM & LPT)“ dir alle seriellen Geräte anzeigen lassen.

Testen der Installation

Bevor wir mit der Entwicklung starten, testen wir die Installation und führen ein kleines Skript auf der Konsole aus.

Dazu klicken wir im unteren Bereich der Thonny IDE in die „Kommandozeile“ und geben nachfolgendes Skript ein.

import time
while True:
    print("Hello World!")
    time.sleep(1)

Es sollte nun im Intervall von einer Sekunde die Zeichenkette „Hello World!“ ausgegeben werden.

Einbinden des Pico W in das WLAN Netzwerk

Im Dokument https://datasheets.raspberrypi.com/picow/connecting-to-the-internet-with-pico-w.pdf findest du ein einfaches Beispiel, wie du eine Verbindung zum WLAN aufbaust.

import network
import time

ssid = '*****'
password = '*******'

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

print("Waiting to connect:")
while not wlan.isconnected() and wlan.status() >= 0:
    print(".", end="")   
    time.sleep(1)

print("")
print(wlan.ifconfig())

Wenn wir das Skript mit dem „Play Button“ starten, dann wird auf der Konsole die Zeichenkette „Waiting to connect:“ ausgegeben und während der Schleife dann immer ein Punkt ausgegeben sowie eine Sekunde gewartet.

MicroPython v1.19.1-88-g74e33e714 on 2022-06-30; Raspberry Pi Pico W with RP2040

Type "help()" for more information.
>>> %Run -c $EDITOR_CONTENT
Waiting to connect:
.......
('192.168.178.70', '255.255.255.0', '192.168.178.1', '192.168.178.1')
>>> 

Wenn die Verbindung aufgebaut wurde, dann wird die IP-Adresse des Pico’s ausgegeben. Da wir jedoch keine Webseite deployen, gibt es nichts zusehen, wenn wir die IP-Adresse in den Browser eingeben.

Fehler beim reconnect & neues aufspielen von Code

Adresse in Benutzung

Bei der Entwicklung kann es auftreten, dass du neuen Code auf den Pi Pico W aufspielen möchtest, und die bereits vergebene IP-Adresse nicht freigegeben wird bzw. wiederverwendet werden kann.

Das wird mit der nachfolgenden Fehlermeldung quittiert:

Traceback (most recent call last):
  File "<stdin>", line 56, in <module>
OSError: [Errno 98] EADDRINUSE

Die Lösung dafür ist recht einfach, es muss lediglich das USB-Kabel vom Computer getrennt werden, ein paar Sekunden gewartet werden und der Stecker wieder eingesteckt werden.

In der Thonny IDE muss man zusätzlich noch die Schaltfläche „Stopp“ (oder Strg+F2) aus der Toolbar betätigt werden und danach kann man dann die Schaltfläche „Run“ (oder F5) betätigen.

Serielles Gerät in Benutzung

Wenn dir die nachfolge Fehlermeldung angezeigt wird, dann hilf meist nur das neustarten des Computers.

Unable to connect to COM32: could not open port 'COM32': PermissionError(13, 'Zugriff verweigert', None, 5)

If you have serial connection to the device from another program, then disconnect it there first.

Backend terminated or disconnected. Use 'Stop/Restart' to restart.

Ausliefern einer Webseite

Nachdem wir eine Verbindung mit dem lokalen Wi-Fi-Netzwerk aufgebaut haben, wollen wir eine kleine Webseite ausliefern. Zunächst soll nur eine einfache Zeichenkette „Hello World!“ dargestellt werden.

Ausgabe der Zeile "Hello World!" im Browser
Ausgabe der Zeile „Hello World!“ im Browser

In meinem Fall setzte ich den Text in eine Überschrift, HTML Tag H1.

import network
import socket
import time

from machine import Pin

ssid = '*****'
password = '******'

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

html = """<!DOCTYPE html>
<html>
    <head>
        <title>Raspberry Pi Pico W</title>
    </head>
    <body>
        <h1>Hello World!</h1>
    </body>
</html>
"""
print('waiting for connection...')
max_wait = 10
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('.', end='')
    time.sleep(1)

print('')

if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('connected')
    status = wlan.ifconfig()
    print('ip = ' + status[0])

addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('listening on', addr)

while True:
    try:
        cl, addr = s.accept()
        print('client connected from', addr)
       
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(html)
        cl.close()

    except OSError as e:
        cl.close()
        print('connection closed')

Wenn das Skript ausgeführt wird, dann wird nach dem Aufbau der Verbindung die IP-Adresse des Mikrocontrollers in der Kommandozeile angezeigt. Diese markieren und kopieren wir in die Zwischenablage. Im Browser (zbsp. Microsoft Edge) wird diese IP-Adresse eingefügt und mit Enter bestätigt.

Steuern von LEDs per Webseite

Im nächsten Schritt wollen wir jetzt ein paar LEDs steuern, welche wir an den Pico angeschlossen haben.

Wie oben bereits erwähnt, nutze ich das offizielle Tutorial der Raspberry Foundation als Grundlage für diesen Beitrag. Jedoch möchte ich etwas abweichen und einen anderen Weg einschlagen.

Im erwähnten Beitrag wird die LED mit dem Aufruf einer Adresse Ein bzw. Ausgeschaltet. Ich möchte gerne eine Schaltfläche für jede LED einbauen und per Ajax Request diese aktivieren.

Raspberry Pi Pico W - steuern von LEDs via Webseite
Dieses Video ansehen auf YouTube.

Pinout des Raspberry Pi Pico

Für den Aufbau der Schaltung gebe ich dir hier das Pinout des Raspberry Pi Pico an die Hand.

Pinout des Raspberry PI Pico
Pinout des Raspberry PI Pico

Aufbau der Schaltung

Wie man eine LED per MicroPython am Raspberry Pi Pico steuert, habe ich dir bereits im Beitrag Raspberry PI Pico #2 – LEDs steuern gezeigt.

In meinem Fall habe ich vier LEDs angeschlossen und das ergab bei mir folgende Schaltung:

Anschlussplan

BauteilRaspberry Pi Pico W
LED – gruenGPIO 14
LED – rotGPIO 15
LED – blauGPIO 13
LED – gelbGPIO 12
GNDGND, Pin 38

Quellcode – steuern von LEDs via Webseite

Hier nun der Quellcode, wie du die LEDs mithilfe von Buttons steuern (EIN/AUS) kannst.

Aufbau der Verbindung zum Wi-Fi Netzwerk

Zunächst bauen wir die Netzwerkverbindung auf und geben die IP-Adresse in der Kommandozeile aus.

Ausgabe der IP-Adresse bei erfolgreicher Verbindung zum Netzwerk
Ausgabe der IP-Adresse bei erfolgreicher Verbindung zum Netzwerk
import network
import socket
from time import sleep

ssid = '******'
password = '******'

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
    
max_wait = 10
print('Warte auf Verbindung')
while max_wait > 10:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1    
    sleep(1)

status = None
if wlan.status() != 3:
    raise RuntimeError('Aufbau der Verbindung fehlgeschlagen!')
else:
    status = wlan.ifconfig()
    print('Verbindung zu', ssid,'erfolgreich aufgebaut!', sep=' ')
    print('IP-Adresse: ' + status[0])

ipAddress = status[0]

addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.bind(addr)
s.listen(1)

Ausliefern der Webseite mit den Buttons

Die Webseite welche ausgeliefert wird enthält für jede LED eine Schaltfläche mit der Beschriftung „toogle“.

Webseite mit Schaltflächen zum aktivieren / deaktivieren der LEDs
Webseite mit Schaltflächen zum aktivieren / deaktivieren der LEDs

Beim klick auf diese Schaltfläche wird diese LED aktiviert / deaktiviert. Der Request wird dabei als Ajax-Request abgesendet, dieses hat den Vorteil das wir uns ein erneutes laden der Seite sparen.

<!DOCTYPE html>
<html>
    <head> <title>Raspberry Pi Pico W</title> </head>
    <style>
       label{width:120px; display:inline-block}
    </style>
    <body>
        <h1>controll leds</h1>
        <p><label>LED - GELB</label><input type='button' value='toggle' onclick='toggleLed("gelb")'/>
        <br/>
        <p><label>LED - BLAU</label><input type='button' value='toggle' onclick='toggleLed("blau")'/>
        <br/>
        <p><label>LED - ROT</label><input type='button' value='toggle' onclick='toggleLed("rot")'/>
        <br/>
        <p><label>LED - GRUEN</label><input type='button' value='toggle' onclick='toggleLed("gruen")'/>
        
        <script>
            function toggleLed(led){
                var xhttp = new XMLHttpRequest();
                xhttp.open('GET', '/led/'+led, true);
                xhttp.send();
            }
        </script>
    </body>
</html>
import network
import socket
from time import sleep

ssid = '******'
password = '********'

website = """
<!DOCTYPE html>
<html>
    <head> <title>Raspberry Pi Pico W</title> </head>
    <style>
       label{width:120px; display:inline-block}
    </style>
    <body>
        <h1>controll leds</h1>
        <p><label>LED - GELB</label><input type='button' value='toggle' onclick='toggleLed("gelb")'/>
        <br/>
        <p><label>LED - BLAU</label><input type='button' value='toggle' onclick='toggleLed("blau")'/>
        <br/>
        <p><label>LED - ROT</label><input type='button' value='toggle' onclick='toggleLed("rot")'/>
        <br/>
        <p><label>LED - GRUEN</label><input type='button' value='toggle' onclick='toggleLed("gruen")'/>
        
        <script>
            function toggleLed(led){
                var xhttp = new XMLHttpRequest();
                xhttp.open('GET', '/led/'+led, true);
                xhttp.send();
            }
        </script>
    </body>
</html>
"""

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
    
max_wait = 10
print('Warte auf Verbindung')
while max_wait > 10:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1    
    sleep(1)

status = None
if wlan.status() != 3:
    raise RuntimeError('Aufbau der Verbindung fehlgeschlagen!')
else:
    status = wlan.ifconfig()
    print('Verbindung zu', ssid,'erfolgreich aufgebaut!', sep=' ')
    print('IP-Adresse: ' + status[0])

ipAddress = status[0]

addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.bind(addr)
s.listen(1)

while True:
    try:
        cl, addr = s.accept()
        print('Verbindung vom Client ', addr, "angenommen!")
        request = cl.recv(1024)
        request = str(request)
               
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(website)
        cl.close()

    except OSError as e:
        cl.close()
        print('connection closed')

Auf einen Tastendruck „reagieren“

Wenn wir eine Schaltfläche betätigen dann wird die Adresse http://<IP-Adresse>/led/<farbe> aufgerufen. Dieses können wir im Code abfragen und wenn im Objekt request die Funktion „find“ das Ergebnis 6 liefert wurde diese Seite aufgerufen.

......

while True:
    try:
        cl, addr = s.accept()
        print('Verbindung vom Client ', addr, "angenommen!")
        request = cl.recv(1024)
        request = str(request)
        
        req_state_led_gelb = request.find('/led/gelb') == 6
......

akitieren / deaktieren einer LED

Damit wir eine LED ansteuern können, müssen wir diese zunächst definieren.

from machine import Pin

led_gelb = Pin(12, Pin.OUT)
led_blau = Pin(13, Pin.OUT)
led_gruen = Pin(14, Pin.OUT)
led_rot = Pin(15, Pin.OUT)

Zusätzlich benötigen wir noch ein Feld in welchem wir uns den letzten Status merken.

state_led_gelb = False
state_led_blau = False
state_led_gruen = False
state_led_rot = False

Wenn nun die Adresse http://<IP-Adresse>/led/gelb aufgerufen wurde, dann ist der Wert des Feldes „req_state_led_gelb“ True.

Nun müssen wir prüfen ob der letzte Status der LED auch True ist (also die LED ist aktiviert). Dann wird das entsprechende Feld auf False gesetzt und die LED deaktiviert.

 req_state_led_gelb = request.find('/led/gelb') == 6

 if req_state_led_gelb and state_led_gelb:
            state_led_gelb = False
            led_gelb.value(0)
        elif not req_state_led_gelb and state_led_gelb:
            state_led_gelb = True
            led_gelb.value(1)
        elif req_state_led_gelb and not state_led_gelb:
            state_led_gelb = True
            led_gelb.value(1)

Damit wir eine LED aktivieren / deaktivieren können müss

import network
import socket
from time import sleep
from machine import Pin

led_gelb = Pin(12, Pin.OUT)
led_blau = Pin(13, Pin.OUT)
led_gruen = Pin(14, Pin.OUT)
led_rot = Pin(15, Pin.OUT)

state_led_gelb = False
state_led_blau = False
state_led_gruen = False
state_led_rot = False

......

while True:
    try:
        cl, addr = s.accept()
        print('Verbindung vom Client ', addr, "angenommen!")
        request = cl.recv(1024)
        request = str(request)
        
        req_state_led_gelb = request.find('/led/gelb') == 6
        req_state_led_blau = request.find('/led/blau') == 6
        req_state_led_gruen = request.find('/led/gruen') == 6
        req_state_led_rot = request.find('/led/rot') == 6

        if req_state_led_gelb and state_led_gelb:
            state_led_gelb = False
            led_gelb.value(0)
        elif not req_state_led_gelb and state_led_gelb:
            state_led_gelb = True
            led_gelb.value(1)
        elif req_state_led_gelb and not state_led_gelb:
            state_led_gelb = True
            led_gelb.value(1)
            
        if req_state_led_blau and state_led_blau:
            state_led_blau = False
            led_blau.value(0)
        elif not req_state_led_blau and state_led_blau:
            state_led_blau = True
            led_blau.value(1)
        elif req_state_led_blau and not state_led_blau:
            state_led_blau = True
            led_blau.value(1)
            
        if req_state_led_rot and state_led_rot:
            state_led_rot = False
            led_rot.value(0)
        elif not req_state_led_rot and state_led_rot:
            state_led_rot = True
            led_rot.value(1)
        elif req_state_led_rot and not state_led_rot:
            state_led_rot = True
            led_rot.value(1)
            
        if req_state_led_gruen and state_led_gruen:
            state_led_gruen = False
            led_gruen.value(0)
        elif not req_state_led_gruen and state_led_gruen:
            state_led_gruen = True
            led_gruen.value(1)
        elif req_state_led_gruen and not state_led_gruen:
            state_led_gruen = True
            led_gruen.value(1)
            
        html = website
       
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(html)
        cl.close()

    except OSError as e:
        cl.close()
        print('connection closed')

Das fertige Programm

Hier nun das fertige Programm:

import network
import socket
from time import sleep
from machine import Pin

led_gelb = Pin(12, Pin.OUT)
led_blau = Pin(13, Pin.OUT)
led_gruen = Pin(14, Pin.OUT)
led_rot = Pin(15, Pin.OUT)

state_led_gelb = False
state_led_blau = False
state_led_gruen = False
state_led_rot = False

ssid = '******'
password = '*****'

website = """<!DOCTYPE html>
<html>
    <head> <title>Raspberry Pi Pico W</title> </head>
    <style>
       label{width:120px; display:inline-block}
    </style>
    <body>
        <h1>controll leds</h1>
        <p><label>LED - GELB</label><input type='button' value='toggle' onclick='toggleLed("gelb")'/>
        <br/>
        <p><label>LED - BLAU</label><input type='button' value='toggle' onclick='toggleLed("blau")'/>
        <br/>
        <p><label>LED - ROT</label><input type='button' value='toggle' onclick='toggleLed("rot")'/>
        <br/>
        <p><label>LED - GRUEN</label><input type='button' value='toggle' onclick='toggleLed("gruen")'/>
        
        <script>
            function toggleLed(led){
                var xhttp = new XMLHttpRequest();
                xhttp.open('GET', '/led/'+led, true);
                xhttp.send();
            }
        </script>
    </body>
</html>
"""

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
    
max_wait = 10
print('Warte auf Verbindung')
while max_wait > 10:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1    
    sleep(1)

status = None
if wlan.status() != 3:
    raise RuntimeError('Aufbau der Verbindung fehlgeschlagen!')
else:
    status = wlan.ifconfig()
    print('Verbindung zu', ssid,'erfolgreich aufgebaut!', sep=' ')
    print('IP-Adresse: ' + status[0])

ipAddress = status[0]

addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.bind(addr)
s.listen(1)

while True:
    try:
        cl, addr = s.accept()
        print('Verbindung vom Client ', addr, "angenommen!")
        request = cl.recv(1024)
        request = str(request)
        
        req_state_led_gelb = request.find('/led/gelb') == 6
        req_state_led_blau = request.find('/led/blau') == 6
        req_state_led_gruen = request.find('/led/gruen') == 6
        req_state_led_rot = request.find('/led/rot') == 6

        if req_state_led_gelb and state_led_gelb:
            state_led_gelb = False
            led_gelb.value(0)
        elif not req_state_led_gelb and state_led_gelb:
            state_led_gelb = True
            led_gelb.value(1)
        elif req_state_led_gelb and not state_led_gelb:
            state_led_gelb = True
            led_gelb.value(1)
            
        if req_state_led_blau and state_led_blau:
            state_led_blau = False
            led_blau.value(0)
        elif not req_state_led_blau and state_led_blau:
            state_led_blau = True
            led_blau.value(1)
        elif req_state_led_blau and not state_led_blau:
            state_led_blau = True
            led_blau.value(1)
            
        if req_state_led_rot and state_led_rot:
            state_led_rot = False
            led_rot.value(0)
        elif not req_state_led_rot and state_led_rot:
            state_led_rot = True
            led_rot.value(1)
        elif req_state_led_rot and not state_led_rot:
            state_led_rot = True
            led_rot.value(1)
            
        if req_state_led_gruen and state_led_gruen:
            state_led_gruen = False
            led_gruen.value(0)
        elif not req_state_led_gruen and state_led_gruen:
            state_led_gruen = True
            led_gruen.value(1)
        elif req_state_led_gruen and not state_led_gruen:
            state_led_gruen = True
            led_gruen.value(1)
            
        html = website
       
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(html)
        cl.close()

    except OSError as e:
        cl.close()
        print('connection closed')

Ein Kommentar

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht.