Pico W: serve a web page from the chip over Wi-Fi
Connect the Pico W to Wi-Fi and serve an HTML page from the chip. The smallest useful web server on a $6 microcontroller.
The Pico W has Wi-Fi. Combined with MicroPython’s socket library, you can
serve a web page from the chip. No Arduino IDE, no ESP-IDF setup, just a
small Python script and a USB cable.
This tutorial gets you from a fresh Pico W to “I have a web page running on my microcontroller” in about 30 minutes.
What you need
- Raspberry Pi Pico W (the W version is required for Wi-Fi)
- MicroPython firmware installed (covered in the previous tutorial)
- A known Wi-Fi network
Step 1: connect to Wi-Fi
Save this as main.py:
import network
import time
ssid = "your-wifi-ssid"
password = "your-wifi-password"
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
print("Connecting to Wi-Fi...")
while not wlan.isconnected():
print(".", end="")
time.sleep(1)
print()
print("Connected:", wlan.ifconfig())
The ifconfig() shows the IP address, subnet, gateway, and DNS. Save the
IP address; you will need it.
Step 2: serve a web page
MicroPython (Pico)
Replace main.py with:
import network
import socket
import time
from machine import Pin
ssid = "your-wifi-ssid"
password = "your-wifi-password"
led = Pin("LED", Pin.OUT)
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while not wlan.isconnected():
print("Connecting...")
time.sleep(1)
ip = wlan.ifconfig()[0]
print(f"Web server running on http://{ip}")
addr = socket.getaddrinfo(ip, 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(5)
while True:
try:
cl, addr = s.accept()
print("Client connected from", addr)
request = cl.recv(1024).decode("utf-8")
print("Request:", request.split("\r\n")[0])
if "/led/on" in request:
led.value(1)
response = "LED is now ON"
elif "/led/off" in request:
led.value(0)
response = "LED is now OFF"
else:
response = """
<html>
<head><title>Pico W</title></head>
<body>
<h1>Pico W Web Server</h1>
<p><a href="/led/on">Turn LED on</a></p>
<p><a href="/led/off">Turn LED off</a></p>
</body>
</html>
"""
cl.send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n")
cl.send(response)
cl.close()
except OSError as e:
print("Error:", e)
cl.close()
Save and reboot the Pico. Watch the REPL for the IP address. Open that in a browser. Click the links to toggle the onboard LED.
Arduino (Pico)
The Pico W Arduino core supports the WiFi and WebServer libraries
the same way the ESP32 does (Earle Philhower’s arduino-pico core
ships with both).
#include <WiFi.h>
#include <WebServer.h>
const char* ssid = "your-wifi-ssid";
const char* password = "your-wifi-password";
WebServer server(80);
void handleRoot() {
server.send(200, "text/html",
"<h1>Pico W Web Server</h1>"
"<p><a href=\"/led/on\">Turn LED on</a></p>"
"<p><a href=\"/led/off\">Turn LED off</a></p>");
}
void handleLedOn() {
digitalWrite(LED_BUILTIN, HIGH);
server.send(200, "text/plain", "LED is now ON");
}
void handleLedOff() {
digitalWrite(LED_BUILTIN, LOW);
server.send(200, "text/plain", "LED is now OFF");
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println();
Serial.print("IP: ");
Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.on("/led/on", handleLedOn);
server.on("/led/off", handleLedOff);
server.begin();
}
void loop() {
server.handleClient();
}
Same wiring (no wiring changes needed for the onboard LED). The
WiFi and WebServer libraries are the same API as the ESP32, so most
tutorials that use the ESP32 Wi-Fi libraries port over with no
changes.
What you learned
- The
networkmodule handles Wi-Fi on the Pico W. - The
socketmodule is the standard Python socket library. - A basic HTTP server is just
accept()->recv()->send()->close(). - You can use the request path (
/led/on) to trigger different actions.
Common pitfalls
- The HTML response does not include the right headers. The browser
expects
Content-Type: text/htmlfor HTML. Without it, the browser shows the raw HTML or downloads it. - The request is too long.
recv(1024)reads up to 1024 bytes. For larger requests, loop until you have it all (most browser requests fit in 1024 bytes). - Multiple connections at once. The single-threaded server above
handles one connection at a time. For a real site, use
socket.settimeout()and a pool, or move toasyncio(covered in the book Pico Wi-Fi Projects).
Reading a sensor over Wi-Fi
Combine with the DHT22 or DS18B20 tutorial:
import dht
import machine
sensor = dht.DHT22(machine.Pin(4))
# in the request handler:
sensor.measure()
temp = sensor.temperature()
hum = sensor.humidity()
response = f"<h1>{temp:.1f} C, {hum:.1f} %</h1>"
Now the page shows the current temperature and humidity.
The asyncio version
For projects with multiple things happening at once (e.g. reading a sensor
every 5 seconds and serving a web page at the same time), MicroPython
supports asyncio:
import asyncio
import network
from machine import Pin
async def blink():
led = Pin("LED", Pin.OUT)
while True:
led.toggle()
await asyncio.sleep(1)
async def main():
asyncio.create_task(blink())
# ... other tasks ...
asyncio.run(main())
The asyncio version is in the book Pico Wi-Fi Projects.
When the Wi-Fi keeps dropping
The Pico W’s Wi-Fi is not as stable as the ESP32’s. If your connection is flaky:
- Add a reconnect loop:
def ensure_wifi():
if not wlan.isconnected():
print("Reconnecting...")
wlan.disconnect()
time.sleep(1)
wlan.connect(ssid, password)
while not wlan.isconnected():
time.sleep(1)
print("Reconnected")
- Call
ensure_wifi()periodically from your main loop. - Use a fixed Wi-Fi channel (set in the router). Auto-channel selection can confuse the Pico W.
When to use Pico W vs. ESP32
- Pico W: $6, MicroPython or C, very low power, fewer peripherals. Great for “sensor reading + simple web interface” projects.
- ESP32: $4-8, Arduino or MicroPython, more RAM, more peripherals, BLE. Better for complex projects, MQTT-heavy stuff, anything with Bluetooth.
The rule of thumb: if you are doing Wi-Fi + a sensor or two, the Pico W is often the better pick (cheaper, easier to deploy, MicroPython). If you need BLE, MQTT with persistent sessions, or anything with multiple concurrent connections, use the ESP32.
What to build next
- A weather station with multiple sensors.
- A MQTT client (publish readings to a broker).
- A simple web-based UI with sliders and buttons.
The MQTT client is in the book Pico Wi-Fi Projects. The weather station is one of the next tutorials on this site.