Raspberry Pi Pico W: Asynchronous Web Server (MicroPython)

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.

Raspberry Pi Pico W Asynchronous Web Server MicroPython

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.

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.

MicroPython Firmware Raspberry Pi Pico 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:

Basic Web Server Concepts Raspberry Pi Pico W Board

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.

Raspberry Pi Pico Asynchronous web server

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.
Raspberry Pi Pico Basic Web Server

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.

Synchronous vs Asynchronous programming

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 W wiring two LEDs circuit diagram

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')

View raw code

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.

Testing the MicroPython Code Raspberry Pi Pico Board

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.

Raspberry Pi Pico W Asynchronous Programming
Testing Raspberry Pi Pico W Asynchronous Web Server script upload run

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.

Save Files to Raspberry Pi Pico Thonny IDE

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.

Micropython saving main.py file Thonny IDE

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.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Recommended Resources

Build a Home Automation System from Scratch » With Raspberry Pi, ESP8266, Arduino, and Node-RED.

Home Automation using ESP8266 eBook and video course » Build IoT and home automation projects.

Arduino Step-by-Step Projects » Build 25 Arduino projects with our course, even with no prior experience!

What to Read Next…


Enjoyed this project? Stay updated by subscribing our newsletter!

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.