In this guide, you’ll learn how to build a basic asynchronous local web server with the Raspberry Pi Pico W programmed with MicroPython using the asyncio module. Using an asynchronous approach, the Raspberry Pi Pico W can handle multiple clients at a time and can also do other tasks while still waiting for clients to connect.
We’ll create a web page to control an LED on and off and fetch some random values generated by the Pico. At the same time, the Pico will continuously blink an LED and run some tasks in the loop to show the asynchronous nature of the program. This example can be easily modified or extended to control multiple outputs and fetch data from sensors instead of random numbers.
This project is similar to this one (Raspberry Pi Pico: Web Server – MicroPython), but uses asynchronous programming.
Want to learn more about asynchronous programming? Get started with this guide: Raspberry Pi Pico Asynchronous Programming – Run Multiple Tasks (MicroPython)
This tutorial is only compatible with the Raspberry Pi Pico W that comes with Wi-Fi support. Throughout this tutorial whenever we refer to the Raspberry Pi Pico, we’re referring to the Raspberry Pi Pico W.
New to the Raspberry Pi Pico? Start here: Getting Started with Raspberry Pi Pico.
Prerequisites
Before proceeding, make sure you check the following prerequisites.
MicroPython Firmware
To follow this tutorial you need MicroPython firmware installed in your Raspberry Pi Pico board. You also need an IDE to write and upload the code to your board.
The recommended MicroPython IDE for the Raspberry Pi Pico is Thonny IDE. Follow the next tutorial to learn how to install Thonny IDE, flash MicroPython firmware, and upload code to the board.
Basic Web Server Concepts
If you’re not familiar with basic web server concepts, we recommend following the next tutorial first:
Raspberry Pi Pico Asynchronous Web Server – Project Overview
Let’s take a quick look at the project we’ll build so that it’s easier to understand the code later on.
This web server behaves exactly like the one in this project, but it is asynchronous and non-blocking allowing you to handle multiple clients simultaneously and do other tasks while waiting for client requests.
Here’s what our example does:
- Creates a web server that serves a web page with:
- two buttons to control an LED on and off (GPIO 19)
- a section to display a random value (can be replaced with sensor readings)
- It runs a concurrent task that blinks an LED (GPIO 20)
- It also does other things in the main loop while still listening to client requests.
Want to learn more about the Raspberry Pi Pico? Take a look at our ebook: Learn Raspberry Pi Pico with MicroPython.
The asyncio MicroPython Module
To create the asynchronous web server, we’ll take advantage of the asyncio MicroPython module. This module is already included in MicroPython firmware by default, so you don’t need to install any additional Modules.
Advantages of Asynchronous Programming
The asyncio MicroPython module allows you to run multiple tasks concurrently, creating the illusion of multitasking and avoiding blocking your code on long-running tasks.
For example, your program can be waiting for the response of a server and still be able to do other tasks like checking if a button was pressed or blinking an LED at the same time. Asynchronous programming can be very useful in the case of setting the Raspberry Pi Pico as a web server because it allows it to handle multiple clients at the same time while still being able to run other tasks in the loop.
The MicroPython asyncio module is a lightweight asynchronous I/O framework inspired by Python’s asyncio module. You can check all the details about this MicroPython module on the following link: documentation for asyncio MicroPython module.
Wiring the Circuit
To test this project, wire two LEDs to the Raspberry Pi Pico. One LED is connected to GPIO 19 and another one is connected to GPIO 20. You can use the diagram below as a reference.
Parts Required
You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!
You can use any other GPIOs as long as you modify the code accordingly—take a look at the Raspberry Pi Pico Pinout.
Raspberry Pi Pico Asynchronous Web Server – MicroPython Code
Create a new file in Thonny IDE and copy the following code.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-w-asynchronous-web-server-micropython/
# Import necessary modules
import network
import asyncio
import socket
import time
import random
from machine import Pin
# Wi-Fi credentials
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
# Create several LEDs
led_blink = Pin(20, Pin.OUT)
led_control = Pin(19, Pin.OUT)
# Initialize variables
state = "OFF"
random_value = 0
# HTML template for the webpage
def webpage(random_value, state):
html = f"""
<!DOCTYPE html>
<html>
<head>
<title>Pico Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>Raspberry Pi Pico Web Server</h1>
<h2>Led Control</h2>
<form action="./lighton">
<input type="submit" value="Light on" />
</form>
<br>
<form action="./lightoff">
<input type="submit" value="Light off" />
</form>
<p>LED state: {state}</p>
<h2>Fetch New Value</h2>
<form action="./value">
<input type="submit" value="Fetch value" />
</form>
<p>Fetched value: {random_value}</p>
</body>
</html>
"""
return str(html)
# Init Wi-Fi Interface
def init_wifi(ssid, password):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
# Connect to your network
wlan.connect(ssid, password)
# Wait for Wi-Fi connection
connection_timeout = 10
while connection_timeout > 0:
print(wlan.status())
if wlan.status() >= 3:
break
connection_timeout -= 1
print('Waiting for Wi-Fi connection...')
time.sleep(1)
# Check if connection is successful
if wlan.status() != 3:
print('Failed to connect to Wi-Fi')
return False
else:
print('Connection successful!')
network_info = wlan.ifconfig()
print('IP address:', network_info[0])
return True
# Asynchronous functio to handle client's requests
async def handle_client(reader, writer):
global state
print("Client connected")
request_line = await reader.readline()
print('Request:', request_line)
# Skip HTTP request headers
while await reader.readline() != b"\r\n":
pass
request = str(request_line, 'utf-8').split()[1]
print('Request:', request)
# Process the request and update variables
if request == '/lighton?':
print('LED on')
led_control.value(1)
state = 'ON'
elif request == '/lightoff?':
print('LED off')
led_control.value(0)
state = 'OFF'
elif request == '/value?':
global random_value
random_value = random.randint(0, 20)
# Generate HTML response
response = webpage(random_value, state)
# Send the HTTP response and close the connection
writer.write('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
writer.write(response)
await writer.drain()
await writer.wait_closed()
print('Client Disconnected')
async def blink_led():
while True:
led_blink.toggle() # Toggle LED state
await asyncio.sleep(0.5) # Blink interval
async def main():
if not init_wifi(ssid, password):
print('Exiting program.')
return
# Start the server and run the event loop
print('Setting up server')
server = asyncio.start_server(handle_client, "0.0.0.0", 80)
asyncio.create_task(server)
asyncio.create_task(blink_led())
while True:
# Add other tasks that you might need to do in the loop
await asyncio.sleep(5)
print('This message will be printed every 5 seconds')
# Create an Event Loop
loop = asyncio.get_event_loop()
# Create a task to run the main function
loop.create_task(main())
try:
# Run the event loop indefinitely
loop.run_forever()
except Exception as e:
print('Error occured: ', e)
except KeyboardInterrupt:
print('Program Interrupted by the user')
This code creates an asynchronous web server. Before running the code, make sure you insert your network credentials.
# Wi-Fi credentials
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
How the Code Works
This code is similar to the one in this previous project. In this article, we’ll just take a look at the relevant parts of code for the asynchronous web server. So, if you’re still not familiar with a basic socket server, read the code explanation of this project first.
Importing the asyncio Module
First, we need to import the asyncio module to use its functionalities for asynchronous programming.
import asyncio
Asynchronous Functions
The functions handle_client() and blink_led() are defined as asynchronous functions (defined by the keywords async def) allowing them to run concurrently and cooperatively with other coroutines.
The main function main() is also defined as an asynchronous function to organize the startup of the server and other tasks.
An event loop asyncio.get_event_loop() is created to manage and execute asynchronous tasks.
loop = asyncio.get_event_loop()
The main function main() is registered as a task in the event loop using loop.create_task(main()), allowing it to be scheduled and executed asynchronously.
loop.create_task(main())
Asynchronous Server Setup
Instead of using traditional socket programming, the asyncio function asyncio.start_server() is used to create an asynchronous TCP server.
server = asyncio.start_server(handle_client, "0.0.0.0", 80)
The handle_client() coroutine is passed as the callback function to handle incoming client connections.
Basically, this previous line sets up a server that listens for incoming connections on a specified host and port. It returns an object that represents the server task (server).
When the server accepts a new client connection, it calls the handle_client function (or any other coroutine specified as the client handler).
Client Connection
When a client connects to the server, asyncio.start_server() creates a pair of stream objects: a StreamReader for reading data from the client and a StreamWriter for writing data to the client. Learn more about TCP stream connections with asyncio.
These stream objects (reader and writer) are passed as parameters to the coroutine specified as the client handler (handle_client in this case).
async def handle_client(reader, writer):
Inside the handle_client() function, the reader and writer objects are used to asynchronously read data from the client and write data back to the client, respectively.
The reader object (StreamReader) provides methods for reading data from the client’s socket connection asynchronously (await reader.readline()).
print("Client connected")
request_line = await reader.readline()
print('Request:', request_line)
The writer object (StreamWriter) provides methods for writing data to the client’s socket connection asynchronously (writer.write()).
writer.write('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
writer.write(response)
The reader and writer objects are provided by the asyncio.start_server() function when a client connects to the server. These objects allow the server to asynchronously communicate with the client by reading incoming data and writing outgoing data.
Running the Tasks Concurrently
The asyncio function asyncio.create_task() is used to create tasks for handling client connections and LED blinking concurrently. This allows the server to accept multiple client connections and blink the LED simultaneously without blocking.
asyncio.create_task(server)
asyncio.create_task(blink_led())
Additionally, we also have other asynchronous operations in the main() function using await asyncio.sleep()—the non-blocking version of time.sleep(). In this case, we’re simply printing a message to the shell every 5 seconds, but you can add any other tasks you need. You can add more operations if needed.
while True:
# Add other tasks that you might need to do in the loop
await asyncio.sleep(5)
print('This message will be printed every 5 seconds')
The event loop (loop.run_forever()) runs indefinitely, continuously processing tasks and handling client connections. This ensures that the server remains active and responsive to incoming requests.
loop.run_forever()
In summary, we use asynchronous operations throughout the code, such as reading from clients await reader.readline(), sending responses writer.write(), and toggling an LED await asyncio.sleep().
These operations allow the server to perform other tasks while waiting for operations to complete, making it non-blocking and more efficient.
Testing the Code
Run the previous code on your Raspberry Pi Pico.
After connecting to the internet, open a web browser on the same network and type the Pico IP address to access the web server.
You can open multiple tabs on your web browser or multiple devices at the same time without any problem and still be able to control the LED on and off and request a new random value.
All of this can be handled while blinking an LED and printing messages to the shell at different rates.
You should now understand that instead of blinking an LED and printing messages, you can run more complex asynchronous tasks and the Pico will still be able to handle the web server and serve multiple clients.
Uploading the Code to the Raspberry Pi Pico
If you want the Raspberry Pi Pico to run the webserver without being connected to your computer, you need to upload the code as main.py to the Raspberry Pi Pico filesystem. For that, after copying the code to a new file, go to File > Save as and select Raspberry Pi Pico.
Name the file main.py and click OK to save the file on the Raspberry Pi Pico. Now, it will run the main.py file on boot without the need to be connected to the computer.
Wrapping Up
In this tutorial, we’ve shown you how to build an asynchronous web server with the Raspberry Pi Pico using the asyncio MicroPython module. Using an asynchronous approach allows the Raspberry Pi Pico to handle multiple clients at the same time and still perform other tasks concurrently.
We hope you’ve found this tutorial useful. If you’re new to web servers with the Raspberry Pi Pico, we recommend starting with this more basic web server example and then proceeding to the asynchronous approach.
If you would like to learn more about the Raspberry Pi Pico, make sure you don’t miss our ebook:
Thanks for reading.
Hi, thanks for the server example, it works fine. But how could I communicate through a second Pico W as a client to the server, only wit html commands, without a HTML page? Is this possible. Do you have an example code for the asnycio module?
I only find examples to communicate between two Pico W boards with a socket command, but there the server cannot do something more than wait for commands.
Thanks in advance
Ray
The server requires a web browser to communicate with it, so run any (graphical) web browser on the client Pico W. (The client is running only 1 task – a web browser.)
Thank you so much for this! I have been going crazy trying to solve almost exactly this problem for the past couple days. I had basically given up before I tried one more google search and found this. I have never used asyncio before, but I have used Threading (which I’ve found is problematic on the Pico) I read all the asyncio stuff but was having a hard time understanding it and getting it to work. This is a great example.
Hi Sara and Rui,
It seems there is an error on the schematic : the blue wire is not at the right place…
Hi Sarah and Rui. Thanks once again for all the great tutorials. I’ve purchased 2 or 3 of your online books and used several of your ESP32 webserver tutorials to launch my projects. :-).
The ESP32 websockets tutorial was especially useful, as I used it to write sockTerm, which is basically a WiFi Terminal that I can plug into any of my projects and immediately control them just like using the Arduino IDE Terminal!!
I have just ordered a couple of Pico-Ws to play with, but I don’t do Python. It would be nice if you could write an async webserver tutorial for the Pico-W, in “C”. Thanks.
I should clarify, sockTerm uses a dedicated standalone ESP32, and interfaces to my various projects via Serial. So I can use it anyplace you might use an FTDI module, except the data is sent wirelessly.
Hi.
Thanks for your comment.
At the moment, we don’t have any web server tutorials for the Pico using Arduino IDE.
Regards,
Sara