Lesson 04: Wireless — Connecting Your ESP32 to WiFi and Building a Web Controller
Robotics from Scratch · Lesson 04 · Beginner · ~20 min read · Updated April 2026
You Already Have Everything You Need
In Lesson 03, you built a button + LED circuit. Press the button, the LED toggles on or off. It works. It responds to your finger.
Now imagine that button is on your phone — sitting somewhere in your house — and the LED is across the room. Same idea. Press a button on a webpage, and the ESP32 toggles the LED. No wires between you and the robot. That is what this lesson is about.
The ESP32-C3 has WiFi built in. No shield to buy. No extra hardware. Just your board, your router, and your phone.
By the end of this lesson, you will open a webpage, tap a button, and watch an LED toggle on your breadboard — even if your laptop is in another room.
Let's begin.
What a Web Server Actually Is
Before we write any code, let me explain what we are building and why it works.
When you open a website — any website — two computers are involved: your computer (the client) and the website's computer (the server). Your computer asks for a page. The server says: here it is. That exchange is HTTP, the same protocol your browser uses every time you visit a URL.
A web server is a program that listens for those requests and responds with something useful — usually HTML, which is what web pages are made of.
Here is the key insight: the ESP32 is powerful enough to run a web server. It can listen for HTTP requests over WiFi, and it can send back an HTML page. That page can have a button on it. When you tap that button, the browser sends a request back to the ESP32. The ESP32 receives it, processes it, toggles the LED, and sends back a new page confirming the action.
So when you are done: your phone is the client. The ESP32 is the server. The LED is the output. And the router in the middle carries the messages.
The ESP32 becomes a website. You become the visitor. The LED is the response.
The WiFi Credentials
You need two things from your router: the network name (SSID) and the password (PSK). Your phone knows these — it connects to the same network. Write them down somewhere. You will put them in the code.
Keep in mind: this code talks to your router directly. It does not go through the internet. The ESP32 cannot visit websites or fetch data — it is only on your local network. This is fine for what we are building. It also means your router's firewall naturally protects the ESP32 from the outside world.
Wiring — No Changes Needed
The circuit is identical to Lesson 03. Wire the red LED to GPIO 2. Wire the push button to GPIO 4. The WiFi part does not need any new wiring — the antenna is inside the ESP32-C3 chip.
The Code — Connecting to WiFi
First, connect the ESP32 to your WiFi network. This takes about 10 lines of MicroPython:
```
import network
import time
Create a WiFi interface object
network.STA_IF means "station mode" — the ESP32 joins an existing network
(as opposed to AP mode, where the ESP32 would create its own network)
wlan = network.WLAN(network.STA_IF)
Activate the interface
wlan.active(True)
Connect to your router
Replace 'YOUR_SSID' with your WiFi network name
Replace 'YOUR_PASSWORD' with your WiFi password
wlan.connect('YOUR_SSID', 'YOUR_PASSWORD')
Wait for the connection to establish
max_wait() blocks until the ESP32 gets an IP address or times out
max_wait = 10
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print('waiting for connection...')
time.sleep(1)
Check the connection status
3 = connected
-1 = no link
-2 = no beacon (wrong password or network not found)
if wlan.status() == 3:
print('Connected!')
print('IP address:', wlan.ifconfig()[0])
else:
print('Connection failed. Check your SSID and password.')
print('Status code:', wlan.status())
```
Run this. Open your serial terminal. You should see "waiting for connection..." printed several times, and then "Connected!" followed by an IP address. That IP address looks like four numbers separated by dots — something like 192.168.1.42.
Write that IP address down. You will need it for the next step.
If you see "Connection failed," double-check your SSID and password. Make sure the network is a 2.4GHz network — ESP32-C3 does not support 5GHz WiFi networks. Many routers have both, and your phone connects to the 5GHz one automatically. Make sure your ESP32 is connecting to the 2.4GHz SSID.
The Code — Running a Web Server
Now that we have an IP address, we can run a web server. Here is the full code:
```
import network
import socket
import time
from machine import Pin
─── WiFi Setup ────────────────────────────────────────────────────────────
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('YOUR_SSID', 'YOUR_PASSWORD')
while not wlan.isconnected():
time.sleep(0.5)
print('Connected! IP:', wlan.ifconfig()[0])
─── Hardware Setup ────────────────────────────────────────────────────────
led = Pin(2, Pin.OUT)
led_state = False
─── HTML Page ─────────────────────────────────────────────────────────────
This is what the browser receives when it visits the ESP32's IP address.
The page has one button that sends a request to /toggle when clicked.
The {led_state} part gets replaced with the current state before sending.
def webpage(state):
if state:
status = "ON"
color = "#00d992"
btn_text = "Turn OFF"
else:
status = "OFF"
color = "#ff4444"
btn_text = "Turn ON"
html = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ESP32 LED Controller</title>
<style>
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0d0d0d;
color: #f0ece8;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
}}
.card {{
background: #1a1a1a;
border: 1px solid #2d2d2d;
border-radius: 16px;
padding: 40px;
text-align: center;
max-width: 360px;
width: 90%;
}}
h1 {{
font-size: 1.4rem;
margin-bottom: 0.5rem;
font-weight: 600;
}}
.status {{
font-size: 2.5rem;
font-weight: 700;
margin: 20px 0;
color: {color};
}}
.btn {{
background: {color};
color: #0d0d0d;
border: none;
border-radius: 8px;
padding: 14px 32px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
text-decoration: none;
display: inline-block;
transition: opacity 0.2s;
}}
.btn:hover {{
opacity: 0.85;
}}
.footer {{
margin-top: 24px;
font-size: 0.75rem;
color: #666;
}}
</style>
</head>
<body>
<div class="card">
<h1>ESP32 LED Controller</h1>
<div class="status">{status}</div>
<a href="/toggle" class="btn">{btn_text}</a>
<div class="footer">Connected to CryptoTavern Robotics</div>
</div>
</body>
</html>
"""
return html
─── Web Server ────────────────────────────────────────────────────────────
Open a socket on port 80 — this is the standard HTTP port
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)
print('Web server running. Visit http://' + wlan.ifconfig()[0])
while True:
# Accept incoming connections
conn, client_addr = s.accept()
print('Client connected from:', client_addr)
# Receive the HTTP request
request = conn.recv(1024).decode()
# Check if the request is for /toggle
# If so, flip the LED state
if '/toggle' in request:
led_state = not led_state
led.value(1 if led_state else 0)
# Send the HTML page back to the browser
response = webpage(led_state)
conn.send('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n')
conn.send(response)
conn.close()
```
Save this as main.py on your ESP32 and run it. Open your serial terminal. You should see:
```
Connected! IP: 192.168.1.42
Web server running. Visit http://192.168.1.42
```
Now open a web browser on any device on your network — your phone, your laptop, your tablet. Type that IP address into the address bar and hit Enter.
The webpage appears. It shows the current LED state. Tap the button. The ESP32 receives the request, toggles the LED, and sends back a new page showing the updated state. Walk over to your breadboard. The LED changed.
You just controlled hardware over WiFi. From a webpage. On a $4 chip.
How It Works — Step by Step
Let me break down the key parts:
The socket
A socket is a communication endpoint. We create one bound to port 80 (the HTTP port) on all available IP addresses. When your browser "calls" the ESP32's IP address on port 80, the socket answers.
The accept() loop
```
conn, client_addr = s.accept()
```
This line waits for a visitor. It pauses the program until someone navigates to the ESP32's IP. When someone does, it creates a connection object (conn) and records who contacted it.
The request
```
request = conn.recv(1024).decode()
```
The ESP32 reads what the browser sent. It is just text — something like "GET /toggle HTTP/1.1". The only thing we care about is whether "/toggle" is in that string.
The toggle
```
if '/toggle' in request:
led_state = not led_state
led.value(1 if led_state else 0)
```
Every time someone visits /toggle, we flip the LED state. This mirrors exactly what the physical button did in Lesson 03.
The response
```
response = webpage(led_state)
conn.send('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n')
conn.send(response)
```
The server sends back an HTTP response — a header that says "here is HTML" and then the HTML page itself. The browser renders it, and you see the button and status.
The Circuit — Visual Reference
```
ESP32-C3 on breadboard (same wiring as Lesson 03)
+----------------------------------------------------+
| 1 2 3 4 5 6 7 8 9 10|
+----------------------------------------------------+
| |
a | [3.3V] ---- wire ----+----[Button]---- (leg 1) |
| | |
b | | (leg 3) |
| | |
c | +----+----+ |
| | | |
d | [GPIO2] ---[330Ω]---+---[LED long leg] |
| | | |
e | +----+----+ |
| [LED short leg] --- [GND] |
| |
f | |
+----------------------------------------------------+
WiFi connection (no new wiring needed):
ESP32-C3 ←──USB──→ Laptop (for serial monitor)
│
└── WiFi ─────→ Router ────→ Phone/Browser
(local network)
Circuit components (same as Lesson 03):
- Red LED on GPIO 2
- 330Ω resistor in series with LED
- Push button on GPIO 4 (pull-down)
- No new components needed for WiFi
```
The WiFi is entirely internal to the ESP32-C3. No new wires.
Your Turn Challenge — Two LEDs, Two Buttons
Right now the webpage controls one LED. Make it control two.
Add a second LED (use GPIO 3 this time, with its own 330Ω resistor) and a second button on the webpage.
When you are done, the webpage will have:
- A status showing the state of both LEDs
- A "Toggle Red LED" button
- A "Toggle Green LED" button
The circuit:
```
GPIO 2 → 330Ω resistor → Red LED → GND
GPIO 3 → 330Ω resistor → Green LED → GND
```
The challenge: modify the HTML to show both LEDs and their states, and add a second toggle endpoint (or use query parameters to specify which LED to toggle).
Hint: instead of just /toggle, use /toggle?led=2 and /toggle?led=3. Parse the query string in the request to decide which LED to flip.
When it works, open the page on your phone and control both LEDs independently from the same webpage. That is a two-channel web controller.
Key Concepts Learned
- Web server: A program that listens for HTTP requests and responds with data — in our case, HTML. The ESP32 runs one.
- Station mode (STA_IF): The ESP32 joins an existing WiFi network as a client device, rather than creating its own.
- Socket: A communication endpoint. A socket bound to port 80 accepts HTTP traffic.
- HTTP: The protocol browsers and servers use to exchange web pages. Requests and responses, one at a time.
- HTML response: When you visit the ESP32's IP, it sends back an HTML document. The browser renders it as a webpage.
- GET request: The type of HTTP request a browser sends when you visit a URL or click a link. We read the URL path to decide what action to take.
- No new hardware needed: WiFi is built into the ESP32-C3. The antenna is on the board.
Troubleshooting
The ESP32 connects to WiFi but the webpage does not load.
Make sure your phone or laptop is on the same network as the ESP32. They must share the router. Try opening the IP address directly in the browser.
The webpage loads but the button does not work.
Check that you used the correct IP address for the ESP32. If your router assigned a new DHCP address, the serial terminal will show the new one. Update your browser's address bar.
The connection is very slow.
The ESP32 is not a fast web server — it can handle one request at a time. Do not click the button multiple times rapidly. Wait for the page to reload between clicks.
The LED state on the webpage does not match the actual LED.
The webpage shows the state at the time the page was loaded. If you click the button on the physical breadboard (not the webpage), the webpage still shows the old state until you refresh. This is normal — the webpage reads the state when it renders, not continuously.
What's Next
You just made the ESP32 network-accessible. It has an IP address on your local network, it serves webpages, and it responds to browser requests by toggling hardware. That is the complete architecture of an IoT device.
In Lesson 05, we add motors. The same toggle pattern you just learned will control motor direction. You will build a small robot you can drive from your phone. The web server architecture scales directly — same pattern, more outputs.
From a blinking LED to a web-controlled robot in three lessons. That is not a small thing.
You just gave your robot a remote control. Built it yourself. That is the real milestone.
CryptoTavern Robotics from Scratch is an evolving course. Each lesson improves over time as the community learns. If something was unclear, if you got stuck, or if something just worked and you want to say so — that is what the course is for.
Next lesson: Lesson 05 — Moving Parts: Motors, Drivers, and Your First Robot