🌐 Level 6 – Wi-Fi Robotics

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

PartHow many?Pin connection / Notes
D1 R32 (ESP32)1USB cable (30 cm)
WiFi network (2.4 GHz)1SSID and PASSWORD ready
LED module1Signal → Pin 13, VCC, GND
Computer or phone1Same 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).
On this page