Project 6.4: "R32-PC WiFi Communication"
What you’ll learn
- ✅ WiFi join: Connect your R32 (ESP32) to a WiFi network and print IP info.
- ✅ Simple web server: Host a tiny page on the board with useful endpoints.
- ✅ Send data to PC: Transmit text lines via WiFi sockets so your computer receives live info.
- ✅ Control from browser: Toggle an LED and read status using HTTP paths.
- ✅ HTTP client: Make a basic GET request to a cloud server and print the response.
Blocks glossary (used in this project)
- WiFi WLAN STA: Connect in station mode using SSID and PASSWORD.
- Sockets (TCP/UDP): Open network connections to serve web pages or send data.
- HTTP text: Craft minimal GET/response headers for browser communication.
- Digital output: Control an on‑board LED with ON/OFF from web requests.
- Serial println: Print NET:CONNECTED, IP, and endpoint actions for clarity.
What you need
| Part | How many? | Pin connection / Notes |
|---|---|---|
| D1 R32 (ESP32) | 1 | USB cable (30 cm) |
| WiFi network (2.4 GHz) | 1 | SSID and PASSWORD ready |
| LED module | 1 | Signal → Pin 13, VCC, GND |
| Computer or phone | 1 | Same WiFi as the board |
Notes
- Use 2.4 GHz WiFi (ESP32 STA mode).
- Make sure your computer and the R32 are on the same network.
- Write down the R32’s IP address once connected.
Before you start
- USB cable is plugged in
- Serial monitor is open
- Quick test:
print("Ready!") # Confirm serial is working so you can see messages
Microprojects 1–5
Microproject 6.4.1 – WiFi network connection
Goal: Connect to WiFi and print IP address, gateway, and connection state.
Blocks used:
- WLAN STA: Join SSID/PASSWORD.
- Serial println: NET:CONNECTED and IP.
MicroPython code:
import network # Import network to manage WiFi
import time # Import time for small waits
SSID = "YourSSID" # Replace with your WiFi name (SSID)
PASSWORD = "YourPassword" # Replace with your WiFi password
wlan = network.WLAN(network.STA_IF) # Create a WiFi station interface
wlan.active(True) # Turn WiFi on (active)
print("NET:WLAN_ACTIVE") # Confirm WiFi is active
wlan.connect(SSID, PASSWORD) # Start connection to the WiFi network
print("NET:CONNECTING", SSID) # Print SSID to show which network
for _ in range(20): # Try for ~10 seconds (20 * 0.5 s)
if wlan.isconnected(): # Check if connected
break # Exit the loop if connection is established
time.sleep(0.5) # Wait 0.5 seconds between checks
if wlan.isconnected(): # If WiFi connection succeeded
ip, subnet, gateway, dns = wlan.ifconfig() # Read IP configuration tuple
print("NET:CONNECTED") # Confirm connection
print("IP:", ip, "GW:", gateway, "DNS:", dns) # Print IP, gateway, DNS
else: # If WiFi connection failed
print("NET:FAILED") # Print failure status
Reflection: Seeing IP info proves your robot is “on the network.”
Challenge:
- Easy: Increase attempts to 30 for networks that respond slowly.
- Harder: Print “NET:RETRY” every 3 seconds until connected.
Microproject 6.4.2 – Basic web server in R32
Goal: Serve a tiny web page and respond to simple GET requests.
Blocks used:
- Socket TCP: Listen on port 80.
- HTTP headers: Send status line + content.
MicroPython code:
import socket # Import socket to create a web server
import network # Import network to check connection
if not network.WLAN(network.STA_IF).isconnected(): # Ensure WiFi is connected
print("NET:ERROR_NOT_CONNECTED") # Warn if no WiFi
else: # If connected
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] # Resolve address for port 80
s = socket.socket() # Create a TCP socket
s.bind(addr) # Bind to all interfaces on port 80
s.listen(1) # Start listening for one client at a time
print("WEB:LISTENING", addr) # Print where the server is listening
html = """HTTP/1.1 200 OK\r
Content-Type: text/html\r
Connection: close\r
\r
<html><body><h1>R32 Web</h1><p>Endpoints: /, /status</p></body></html>""" # Minimal HTML response
while True: # Serve forever loop
cl, remote = s.accept() # Accept a client connection
print("WEB:CLIENT", remote) # Print client info
req = cl.recv(512) # Read up to 512 bytes of request
text = req.decode() # Decode bytes to text
print("WEB:REQ", text.split('\r\n')[0]) # Print the first request line
if "GET /status" in text: # If the path is /status
resp = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nSTATUS:OK" # Plain status response
cl.send(resp) # Send status response
else: # For any other path
cl.send(html) # Send default HTML page
cl.close() # Close client socket after response
Reflection: A tiny server is enough—browsers just need a valid header and content.
Challenge:
- Easy: Add a new endpoint “/hello” that returns “HELLO FROM R32”.
- Harder: Print client IPs into a list to show recent visitors.
Microproject 6.4.3 – Sending data to PC via WiFi
Goal: Send a line of text to your computer using UDP (simple and robust).
Blocks used:
- Socket UDP: Send with sendto to PC IP:PORT.
- Serial println: Print DATA:SENT.
MicroPython code:
import socket # Import socket for UDP
import network # Import network to ensure WiFi
PC_IP = "192.168.1.100" # Replace with your computer's IP (same network)
PC_PORT = 5005 # Choose a UDP port (also open a receiver on your PC)
if not network.WLAN(network.STA_IF).isconnected(): # Check WiFi connection
print("NET:ERROR_NOT_CONNECTED") # Warn if not connected
else: # If connected
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create a UDP socket
msg = b"R32->PC: Hello via UDP" # Prepare a short bytes message
u.sendto(msg, (PC_IP, PC_PORT)) # Send message to PC IP and port
print("DATA:SENT", PC_IP, PC_PORT) # Print that data was sent
u.close() # Close UDP socket after sending
Reflection: UDP is perfect for quick “hello” messages—no heavy connection needed.
Challenge:
- Easy: Send values like “TEMP:25” and “STATUS:OK”.
- Harder: Send every 2 seconds in a loop and handle failures gracefully.
Microproject 6.4.4 – Controlling R32 from a web browser
Goal: Parse paths like /led=on and /led=off to control an LED on Pin 13.
Blocks used:
- Digital output: Turn LED on/off.
- HTTP paths: Read the request line and match.
MicroPython code:
import socket # Import socket to extend the web server
import machine # Import machine to control pins
import network # Import network to check WiFi
led = machine.Pin(13, machine.Pin.OUT) # Create LED output on Pin 13
print("LED:READY on Pin 13") # Confirm LED setup
if not network.WLAN(network.STA_IF).isconnected(): # Ensure WiFi is connected
print("NET:ERROR_NOT_CONNECTED") # Warn if not connected
else: # If connected
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] # Resolve address for port 80
s = socket.socket() # Create TCP socket
s.bind(addr) # Bind to all interfaces on port 80
s.listen(1) # Listen for one client at a time
print("WEB:CTRL_LISTEN", addr) # Print server address for control
while True: # Control server loop
cl, remote = s.accept() # Accept client connection
print("WEB:CLIENT", remote) # Print client info
req = cl.recv(512) # Read request bytes
text = req.decode() # Decode to text
line = text.split('\r\n')[0] # Get request line (first line)
print("WEB:REQ", line) # Print request line
if "GET /led=on" in line: # If path asks to turn LED ON
led.value(1) # Turn LED ON
resp = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nLED:ON" # Response text
cl.send(resp) # Send response
elif "GET /led=off" in line: # If path asks to turn LED OFF
led.value(0) # Turn LED OFF
resp = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nLED:OFF" # Response text
cl.send(resp) # Send response
elif "GET /status" in line: # If status path
val = "1" if led.value() == 1 else "0" # Read LED state as string
resp = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nLED_STATE:" + val # Response text
cl.send(resp) # Send response
else: # Any other path
resp = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nERR:UNKNOWN_PATH" # Not found
cl.send(resp) # Send 404 response
cl.close() # Close connection
Reflection: Browser control makes your robot feel remote‑friendly—simple paths are enough.
Challenge:
- Easy: Add /blink to blink the LED once.
- Harder: Add /mode=SAFE or /mode=ACTIVE and print current mode.
Microproject 6.4.5 – HTTP client for sending data to the cloud
Goal: Make a minimal HTTP GET request to a server and print the response header.
Blocks used:
- Socket TCP: Connect to host:80.
- HTTP GET: Send text request with Host header.
MicroPython code:
import socket # Import socket for HTTP client
import network # Import network to check WiFi
HOST = "example.com" # Replace with your cloud host (must resolve on your network)
PATH = "/status" # Replace with desired path for GET request
if not network.WLAN(network.STA_IF).isconnected(): # Ensure WiFi is connected
print("NET:ERROR_NOT_CONNECTED") # Warn if not connected
else: # If connected
addr_info = socket.getaddrinfo(HOST, 80)[0][-1] # Resolve host and port
s = socket.socket() # Create TCP socket
s.connect(addr_info) # Connect to the server address
print("HTTP:CONNECT", HOST) # Print connection status
req = "GET " + PATH + " HTTP/1.1\r\nHost: " + HOST + "\r\nConnection: close\r\n\r\n" # Build GET request
s.send(req) # Send the request to the server
data = s.recv(256) # Read some response bytes (header chunk)
print("HTTP:RESP_HDR", data.decode(errors="ignore")) # Print header chunk
s.close() # Close the socket after reading
Reflection: Crafting your own GET shows how the web works—text lines are powerful.
Challenge:
- Easy: Print only the first response status line.
- Harder: Add a small loop to read the whole response in chunks.
Main project – R32‑PC WiFi communication
Blocks steps (with glossary)
- WiFi join: Connect and print IP.
- Web server: Serve endpoints and control LED.
- PC data send: Transmit UDP messages to a chosen PC IP:PORT.
- Browser control: Parse /led=on/off and /status.
- Cloud GET: Send a minimal HTTP client request and print header.
MicroPython code (mirroring blocks)
# Project 6.4 – R32-PC WiFi Communication
import network # Manage WiFi connections
import socket # Build TCP/UDP servers and clients
import machine # Control pins (LED)
import time # Add delays for pacing
SSID = "YourSSID" # Put your WiFi SSID here
PASSWORD = "YourPassword" # Put your WiFi password here
PC_IP = "192.168.1.100" # Put your PC IP here (same network)
PC_PORT = 5005 # UDP port on your PC (open a receiver)
HOST = "example.com" # Cloud host for GET (replace as needed)
PATH = "/status" # Cloud path for GET (replace as needed)
led = machine.Pin(13, machine.Pin.OUT) # LED on Pin 13 for control demo
print("LED:READY on Pin 13") # Confirm LED setup
wlan = network.WLAN(network.STA_IF) # Create STA WiFi interface
wlan.active(True) # Turn WiFi on
print("NET:WLAN_ACTIVE") # Confirm WiFi active
wlan.connect(SSID, PASSWORD) # Connect to WiFi
print("NET:CONNECTING", SSID) # Show SSID being used
for _ in range(30): # Try for ~15 seconds
if wlan.isconnected(): # Check connection
break # Stop waiting if connected
time.sleep(0.5) # Wait a bit
if not wlan.isconnected(): # If connection failed
print("NET:FAILED") # Report failure
else: # If connected
ip, subnet, gateway, dns = wlan.ifconfig() # Read IP config
print("NET:CONNECTED") # Report success
print("IP:", ip, "GW:", gateway, "DNS:", dns) # Print IP info
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] # Resolve port 80 address
srv = socket.socket() # Create a TCP server socket
srv.bind(addr) # Bind to all interfaces on port 80
srv.listen(1) # Listen for one client at a time
print("WEB:LISTENING", addr) # Show listening address
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket for PC data
print("UDP:READY", PC_IP, PC_PORT) # Confirm UDP target
last_heartbeat = time.ticks_ms() # Store last heartbeat timestamp
while True: # Main loop combines server + periodic UDP + optional client GET
# Accept one HTTP client (non-blocking style, with small timeout)
srv.settimeout(0.2) # Set a short timeout so loop continues
try: # Try to accept a client quickly
cl, remote = srv.accept() # Accept a client connection
print("WEB:CLIENT", remote) # Print client info
req = cl.recv(512) # Read request bytes
text = req.decode() # Decode request to text
line = text.split('\r\n')[0] # Get request line
print("WEB:REQ", line) # Print request line
if "GET /led=on" in line: # LED ON path
led.value(1) # Turn LED ON
resp = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nLED:ON" # Response text
cl.send(resp) # Send response
elif "GET /led=off" in line: # LED OFF path
led.value(0) # Turn LED OFF
resp = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nLED:OFF" # Response text
cl.send(resp) # Send response
elif "GET /status" in line: # Status path
val = "1" if led.value() == 1 else "0" # Read LED state
resp = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nLED_STATE:" + val # Response text
cl.send(resp) # Send response
else: # Unknown path
resp = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nERR:UNKNOWN_PATH" # 404 response
cl.send(resp) # Send response
cl.close() # Close the client connection
except OSError: # If accept timed out
pass # Continue loop without error
# Periodic UDP send to PC (heartbeat or status)
now = time.ticks_ms() # Current ms time
if time.ticks_diff(now, last_heartbeat) > 2000: # Every 2 seconds
msg = b"HB: R32 alive; LED=" + (b"1" if led.value() == 1 else b"0") # Build heartbeat message
udp.sendto(msg, (PC_IP, PC_PORT)) # Send UDP message to PC
print("UDP:SENT", PC_IP, PC_PORT) # Print send confirmation
last_heartbeat = now # Update heartbeat time
# Optional: one-off cloud GET (comment out if not needed)
# addr_info = socket.getaddrinfo(HOST, 80)[0][-1] # Resolve host for GET
# cli = socket.socket() # Create client socket
# cli.settimeout(1.0) # Short timeout to avoid blocking
# try: # Attempt GET
# cli.connect(addr_info) # Connect to host
# req = "GET " + PATH + " HTTP/1.1\r\nHost: " + HOST + "\r\nConnection: close\r\n\r\n" # Build GET request
# cli.send(req) # Send request
# data = cli.recv(256) # Read part of response
# print("HTTP:RESP_HDR", data.decode(errors="ignore")) # Print header chunk
# finally: # Always run cleanup
# cli.close() # Close client socket
time.sleep(0.05) # Small delay to keep loop responsive
# Note: press reset or Ctrl+C in REPL to stop the server loop safely
External explanation
- What it teaches: Networking basics with ESP32: join WiFi, serve pages, parse paths, send UDP to PCs, and compose minimal HTTP GETs.
- Why it works: Browsers expect simple text headers; sockets send and receive these strings; periodic UDP lets your PC see live robot heartbeats without heavy protocols.
- Key concept: “Connect → serve → control → send.”
Story time
You type a tiny URL on your phone, and your robot answers. A tap turns on its LED. Your laptop receives a heartbeat every two seconds—like it’s waving, “I’m here.”
Debugging (2)
Debugging 6.4.1 – It does not connect to WiFi
Problem: NET:FAILED prints or IP never appears.
Clues: SSID/PASSWORD might be wrong, or network is 5 GHz only.
Broken code:
wlan.connect("WrongName", "badpass") # Typo in credentials
Fixed code:
SSID = "YourSSID" # Double-check exact SSID
PASSWORD = "YourPassword" # Double-check exact password
# Keep 2.4 GHz; add more retries
for _ in range(40): # Extend wait
if wlan.isconnected():
break
time.sleep(0.5)
Why it works: Correct credentials and enough wait time allow the ESP32 to join 2.4 GHz networks.
Avoid next time: Confirm SSID/PASSWORD and use 2.4 GHz; print retries clearly.
Debugging 6.4.2 – Web server not accessible
Problem: Browser can’t reach the page; timeouts occur.
Clues: Wrong IP, firewall blocks port 80, or server not listening.
Broken code:
s.listen(0) # Zero backlog may prevent connections
Fixed code:
s.listen(1) # Allow 1 pending client
print("WEB:LISTENING", addr) # Print listening address
# Ensure browser uses http://<R32_IP> and both on same network
Why it works: Proper listen backlog and correct IP/URL ensure the browser connects.
Avoid next time: Print IP and listening address; test with a simple /status path first.
Final checklist
- NET:CONNECTED prints and shows IP/GW/DNS
- Web server responds to /, /status, /led=on, /led=off
- UDP heartbeat reaches the PC at the chosen IP:PORT
- Browser control of LED works reliably
- Optional cloud GET prints a response header without blocking
Extras
- 🧠 Student tip: Bookmark http:///status on your phone to check robot state quickly.
- 🧑🏫 Instructor tip: Have students diagram request/response flows for each endpoint to cement understanding.
- 📖 Glossary:
- STA mode: ESP32 connects to a WiFi network as a client.
- UDP: Lightweight message sending without a persistent connection.
- HTTP header: The text lines (status + fields) before the page content.
- 💡 Mini tips:
- Use short timeouts in servers so your main loop stays responsive.
- Send small, labeled messages (“HB: …”) for easier debugging on the PC.
- Secure your endpoints later by checking simple tokens (e.g., /led=on&key=123).