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
uasyncioto 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
- Theory: Why asynchronous networking matters
- MicroPython networking basics
- Async HTTP client exercise
- Building a simple async HTTP service
- 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
uasynciolets 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
networkfor Wi-Fi connectivityuasynciofor asynchronous task schedulingsocketanduasynciostreams 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
- Upload the code as
main.py. - Reset the Pico.
- Note the IP address in the Thonny shell.
- Open the IP address in a browser.
- 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
awaitwhere 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.
uasyncioenables 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:
- A client that fetches a public API every 30 seconds and prints a parsed value.
- A web server page that shows the onboard LED state and request count.
- 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