Skip to content

Session 3: Building Async HTTP Clients and Simple Services

Synopsis

Introduces asynchronous request-response patterns, basic HTTP interactions, and lightweight service endpoints suitable for Pico 2 W projects.

Session Content

Session 3: Building Async HTTP Clients and Simple Services

Duration: ~45 minutes
Audience: Python developers with basic programming knowledge
Platform: Raspberry Pi Pico 2 W
Language: MicroPython
IDE: Thonny


Session Goals

By the end of this session, learners will be able to:

  • Use uasyncio to manage multiple tasks on the Pico 2 W.
  • Connect the Pico 2 W to Wi-Fi and perform HTTP requests.
  • Build a simple asynchronous HTTP client.
  • Create a minimal asynchronous HTTP service on the Pico.
  • Understand how asynchronous networking improves responsiveness for IoT projects.

Prerequisites

  • Raspberry Pi Pico 2 W with MicroPython installed
  • USB cable
  • Thonny IDE installed on the computer
  • Basic familiarity with Python syntax
  • Basic understanding of functions, loops, and modules

Hardware Needed

  • Raspberry Pi Pico 2 W
  • Breadboard
  • 1 LED
  • 1 resistor (220–330 Ω)
  • Jumper wires

Development Environment Setup

1. Install Thonny

  • Download and install Thonny from: https://thonny.org/

2. Connect Pico 2 W to Thonny

  • Plug the Pico 2 W into your computer via USB.
  • Open Thonny.
  • Go to Tools → Options → Interpreter.
  • Select MicroPython (Raspberry Pi Pico).
  • Choose the correct port.
  • Click OK.

3. Verify MicroPython

In the Thonny shell, run:

import sys
print(sys.implementation)

Expected output:

(sysname='micropython', ...)

4. Wi-Fi Credentials

Prepare your Wi-Fi SSID and password before the hands-on exercise.


Session Outline

  1. Theory: Why asynchronous networking matters
  2. MicroPython networking basics
  3. Async HTTP client exercise
  4. Building a simple async HTTP service
  5. Review and extension ideas

1) Theory: Why Asynchronous Networking Matters

On microcontrollers, blocking code can freeze the entire device while waiting for network operations.

Blocking example

  • A request to a server may take seconds.
  • During that time, the Pico cannot do other work if the code is blocking.

Asynchronous approach

  • uasyncio lets the Pico switch between tasks.
  • One task can read sensors while another handles networking.
  • The result is more responsive and scalable IoT applications.

Typical use cases

  • Periodic sensor reading
  • Updating a display
  • Sending telemetry to a server
  • Hosting a tiny web interface for control/monitoring

2) MicroPython Networking Basics

MicroPython provides networking support through network and socket libraries.

Key modules

  • network for Wi-Fi connectivity
  • uasyncio for asynchronous task scheduling
  • socket and uasyncio streams for communication

Important concepts

  • Station mode (STA_IF): Pico joins an existing Wi-Fi network
  • Access point mode (AP_IF): Pico creates its own Wi-Fi network
  • HTTP client: Pico requests data from a server
  • HTTP service: Pico listens for requests and returns responses

3) Hands-On Exercise: Async HTTP Client

In this exercise, the Pico 2 W will: - connect to Wi-Fi - periodically fetch a web page - print the response status and a small preview of the result

File: main.py

# main.py
# Async HTTP client for Raspberry Pi Pico 2 W
# Connects to Wi-Fi and fetches data from a web server periodically.

import network
import uasyncio as asyncio
import urequests
import time

# Wi-Fi credentials
SSID = "YOUR_WIFI_SSID"
PASSWORD = "YOUR_WIFI_PASSWORD"

# HTTP endpoint to fetch
URL = "http://worldtimeapi.org/api/timezone/Etc/UTC"


def connect_wifi():
    """Connect to Wi-Fi in station mode."""
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    if not wlan.isconnected():
        print("Connecting to Wi-Fi...")
        wlan.connect(SSID, PASSWORD)

        timeout = 15
        while timeout > 0 and not wlan.isconnected():
            time.sleep(1)
            timeout -= 1
            print("Waiting for connection...")

    if wlan.isconnected():
        print("Connected!")
        print("IP address:", wlan.ifconfig()[0])
        return True

    print("Wi-Fi connection failed")
    return False


async def fetch_url():
    """Fetch data from the configured URL."""
    try:
        print("Sending HTTP GET request...")
        response = urequests.get(URL)
        print("Status code:", response.status_code)

        # Read only a small part of the response for display
        text = response.text
        print("Response preview:")
        print(text[:200])

        response.close()

    except Exception as e:
        print("HTTP request failed:", e)


async def periodic_fetch():
    """Fetch the URL every 10 seconds."""
    while True:
        await fetch_url()
        await asyncio.sleep(10)


async def heartbeat():
    """Simple task to show the event loop is alive."""
    led = None
    try:
        from machine import Pin
        led = Pin("LED", Pin.OUT)
    except Exception:
        pass

    while True:
        if led:
            led.toggle()
        print("Heartbeat")
        await asyncio.sleep(1)


async def main():
    """Main async entry point."""
    if not connect_wifi():
        return

    print("Starting async tasks...")
    asyncio.create_task(heartbeat())
    await periodic_fetch()


try:
    asyncio.run(main())
finally:
    asyncio.new_event_loop()

Expected output

Connecting to Wi-Fi...
Waiting for connection...
Connected!
IP address: 192.168.1.42
Starting async tasks...
Heartbeat
Sending HTTP GET request...
Status code: 200
Response preview:
{"utc_offset":"+00:00","timezone":"Etc/UTC", ...}
Heartbeat
Heartbeat
Sending HTTP GET request...
Status code: 200
Response preview:
{"utc_offset":"+00:00","timezone":"Etc/UTC", ...}

Discussion points

  • heartbeat() runs alongside the HTTP fetch task.
  • asyncio.create_task() starts concurrent work.
  • await asyncio.sleep() yields control to the scheduler.

4) Hands-On Exercise: Simple Async HTTP Service

Now we will build a basic web server on the Pico 2 W that: - joins Wi-Fi - listens on port 80 - serves a simple HTML page - toggles the onboard LED via query string

File: main.py

# main.py
# Minimal async HTTP server for Raspberry Pi Pico 2 W.
# Serves a web page and allows LED control.

import network
import uasyncio as asyncio
from machine import Pin
import time

# Wi-Fi credentials
SSID = "YOUR_WIFI_SSID"
PASSWORD = "YOUR_WIFI_PASSWORD"

# Onboard LED
led = Pin("LED", Pin.OUT)

# Store LED state
led_state = "OFF"


def connect_wifi():
    """Connect the Pico to a Wi-Fi network."""
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    if not wlan.isconnected():
        print("Connecting to Wi-Fi...")
        wlan.connect(SSID, PASSWORD)

        for _ in range(15):
            if wlan.isconnected():
                break
            time.sleep(1)
            print("Waiting for connection...")

    if wlan.isconnected():
        print("Connected!")
        print("IP address:", wlan.ifconfig()[0])
        return wlan.ifconfig()[0]

    raise RuntimeError("Could not connect to Wi-Fi")


def build_page():
    """Return a simple HTML page."""
    html = """<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Pico 2 W Async Server</title>
</head>
<body>
    <h1>Raspberry Pi Pico 2 W</h1>
    <p>LED State: {state}</p>
    <p><a href="/?led=on">Turn LED ON</a></p>
    <p><a href="/?led=off">Turn LED OFF</a></p>
</body>
</html>
"""
    return html.format(state=led_state)


async def handle_client(reader, writer):
    """Handle one incoming HTTP client connection."""
    global led_state

    try:
        request_line = await reader.readline()
        if not request_line:
            await writer.aclose()
            return

        request = request_line.decode()
        print("Request:", request.strip())

        # Read and ignore headers
        while True:
            header = await reader.readline()
            if header == b"\r\n" or not header:
                break

        # Simple query parsing
        if "GET /?led=on" in request:
            led.value(1)
            led_state = "ON"
        elif "GET /?led=off" in request:
            led.value(0)
            led_state = "OFF"

        response = build_page()
        content_length = len(response)

        await writer.awrite("HTTP/1.1 200 OK\r\n")
        await writer.awrite("Content-Type: text/html\r\n")
        await writer.awrite("Content-Length: {}\r\n".format(content_length))
        await writer.awrite("Connection: close\r\n\r\n")
        await writer.awrite(response)
        await writer.aclose()

    except Exception as e:
        print("Client error:", e)
        try:
            await writer.aclose()
        except Exception:
            pass


async def main():
    """Main server loop."""
    ip = connect_wifi()
    print("Open this IP in your browser:", ip)

    server = await asyncio.start_server(handle_client, "0.0.0.0", 80)
    print("HTTP server running on port 80")

    while True:
        await asyncio.sleep(60)


try:
    asyncio.run(main())
finally:
    asyncio.new_event_loop()

Expected output

Connecting to Wi-Fi...
Waiting for connection...
Connected!
IP address: 192.168.1.42
Open this IP in your browser: 192.168.1.42
HTTP server running on port 80
Request: GET / HTTP/1.1
Request: GET /?led=on HTTP/1.1
Request: GET /?led=off HTTP/1.1

How to use

  1. Upload the code as main.py.
  2. Reset the Pico.
  3. Note the IP address in the Thonny shell.
  4. Open the IP address in a browser.
  5. Click the links to toggle the LED.

5) Guided Practice

Exercise A: Modify the client

Change the HTTP client to fetch a different endpoint, such as: - a weather API - a local server endpoint - a simple text file hosted online

Task

  • Update URL
  • Print the first 100 characters of the response
  • Add a second async task that blinks the LED every 500 ms

Exercise B: Modify the server

Extend the web page to display: - current LED state - number of requests received - a timestamp for the last request

Hint

Use global variables to store counters and update them in handle_client().


6) Best Practices for Async IoT on Pico 2 W

  • Keep async tasks short and cooperative.
  • Use await asyncio.sleep() to yield control.
  • Close HTTP responses and writers properly.
  • Avoid downloading large payloads unless necessary.
  • Handle exceptions around networking code.
  • Keep HTML responses small and simple.
  • Use clear state variables for shared device status.

7) Common Issues and Troubleshooting

Wi-Fi not connecting

  • Check SSID and password
  • Ensure the network is 2.4 GHz
  • Move closer to the access point
  • Restart the Pico

HTTP request fails

  • Check the URL
  • Confirm internet access
  • Try a simpler endpoint
  • Make sure the server supports plain HTTP if using urequests

Browser cannot reach the Pico server

  • Confirm the Pico IP address
  • Ensure the computer is on the same Wi-Fi network
  • Check that port 80 is not blocked

uasyncio issues

  • Make sure you are using await where needed
  • Avoid blocking calls inside async functions
  • Do not run long loops without yielding

8) Session Wrap-Up

Key takeaways

  • Asynchronous programming helps the Pico remain responsive.
  • uasyncio enables concurrent sensor, display, and network tasks.
  • The Pico 2 W can act as both HTTP client and server.
  • Simple web services are practical for IoT control panels and dashboards.

Next session preview

  • Using async tasks with sensors and actuators
  • Sending telemetry to cloud services
  • Building a responsive IoT data logger

9) Quick Reference

Useful imports

import uasyncio as asyncio
import network
import urequests
from machine import Pin

Common async patterns

async def task():
    while True:
        print("working")
        await asyncio.sleep(1)
asyncio.create_task(task())
await asyncio.sleep(5)

Wi-Fi connect pattern

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect("SSID", "PASSWORD")

10) Exit Task

Build one of the following:

  1. A client that fetches a public API every 30 seconds and prints a parsed value.
  2. A web server page that shows the onboard LED state and request count.
  3. An async app that blinks the LED while serving web requests.

Success criteria

  • Code runs without crashing
  • Wi-Fi connects successfully
  • At least one async task runs concurrently with networking
  • Hardware control works from the browser or console

Back to Chapter | Back to Master Plan | Previous Session | Next Session