ESP32 Part 2 - Wireless

E09. Local Web Server: Control from a Browser

Make the ESP32 serve a web page, then add ON and OFF links that switch an LED from your phone.

Estimated time 3-4 hours

Learning outcomes

  • Explain a browser request and the ESP32's reply
  • Serve a web page with WebServer
  • Link each web address (path) to a function with server.on()
  • Switch an LED from a phone on the same Wi-Fi

Parts and preparation

ESP32 DevKit, USB, LED + 220-330 ohm on GPIO 2 (or the on-board LED), a phone or PC on the same Wi-Fi.

Before power: inspect wiring, confirm supply voltage and ensure all connected circuits share GND.

Libraries for this lesson

In Arduino IDE 2 open Tools → Manage Libraries…. Search by the Library Manager name and install the package by the exact author below. Similar names from other authors can use different APIs and will break the example.

IncludeLibrary Manager nameAuthorInstall note
WiFi.hWiFiEspressifBuilt into the esp32 Arduino core.
WebServer.hWebServerEspressifBuilt into the esp32 Arduino core.

What you will do

Your ESP32 becomes a tiny website on your Wi-Fi.

Sketch 1 shows a Hello page when you type the ESP32's address into a browser. Sketch 2 adds ON and OFF links that switch the LED on GPIO 2.

Browser HTTP GET to ESP32 web server
The browser asks for a page; the ESP32 sends one back.

Request and reply

When you type http://192.168.1.42/on into a browser, the browser asks the ESP32 for the path /on. server.on("/on", handleOn) tells the ESP32 to run your function handleOn when that path is asked for. Your function does the job (LED on), then server.send() replies with a page.

200 in server.send(200, ...) is the code for 'OK, here is your page'.

PathFunctionWhat it does
/sendPageShows the page
/onhandleOnLED on, then shows the page
/offhandleOffLED off, then shows the page

Keep loop() free

loop() calls server.handleClient() over and over - that is how the ESP32 notices a browser asking. Do not put a long delay() in loop(), or the page stops loading.

Writing the page

The page is HTML, written as text. <h1> makes big text and <a href='/on'> makes a link to /on. Single quotes inside the HTML keep the double quotes free for the Arduino text. page += adds more text to the end of the page.

Wiring and safe build sequence

  1. GPIO 2 -> 220-330 ohm -> LED long leg; LED short leg -> GND (or use the on-board LED)
  2. Optional (sketch 2 photo): GPIO 2 -> gate resistor -> N-MOSFET gate (pull-down to GND) switching a 5 V high-power LED; common GND
  3. Phone or PC on the same Wi-Fi network
Power rule: switch off before moving wires. Arduino I/O pins are control signals; high-current loads require a driver and suitable external supply.

Worked sketch 1: A Hello page

Download .ino

What this sketch is for: Upload, copy the address from Serial (for example http://192.168.1.42) and open it in a browser on the same Wi-Fi.

#include <WiFi.h>                         // the Wi-Fi commands
#include <WebServer.h>                    // the web server commands

String ssid = "YOUR_WIFI_SSID";           // your Wi-Fi network name
String password = "YOUR_WIFI_PASSWORD";   // your Wi-Fi password

WebServer server(80);                     // a web server on port 80 (the normal web port)

void sendPage() {                         // runs when a browser asks for /
  server.send(200, "text/html", "<h1>Hello from the ESP32</h1>"); // 200 = OK, then the page
}

void setup() {                            // runs once
  Serial.begin(115200);                   // start Serial at the ESP32 speed

  WiFi.begin(ssid, password);             // start joining the network
  Serial.print("Connecting");             // tell the user we are trying
  while (WiFi.status() != WL_CONNECTED) { // repeat until we have joined
    delay(500);                           // wait half a second
    Serial.print(".");                    // print a dot each time
  }
  Serial.println(" connected");           // we are on the network

  Serial.print("Open http://");           // tell the user what to type
  Serial.println(WiFi.localIP());         // the ESP32's address

  server.on("/", sendPage);               // path / runs sendPage
  server.begin();                         // start the web server
}

void loop() {                             // runs over and over
  server.handleClient();                  // answer any browser that is asking
}

How the code works

  1. server.on("/", sendPage) - note there are no brackets after sendPage. You are naming the function, not running it.
  2. The phone or PC must be on the same Wi-Fi as the ESP32.

Test and record evidence

Expected result: Sketch 1: the browser shows Hello from the ESP32. Sketch 2: the browser shows ESP32 lamp with ON and OFF links; tapping them switches the LED.

Practical evidence checklist

Common faults and checks
  • Page never loads: the phone must be on the same Wi-Fi (not a guest network), and the address must match Serial exactly, starting with http://.
  • Page stops responding: make sure loop() has no long delay().
  • LED does not change: check the LED is on GPIO 2 with its resistor (or the MOSFET stage and shared GND).
Challenge (extension - optional): Add a third link, BLINK, at /blink that turns the LED on, waits 200 ms, turns it off and shows the page.

Check your understanding

Q1. What does server.on("/on", handleOn) do?

Show answer

Runs handleOn when a browser asks for the path /on.

Q2. Why must loop() call server.handleClient()?

Show answer

So the ESP32 notices and answers browsers.

Q3. What does the 200 in server.send(200, ...) mean?

Show answer

OK - here is your page.

Q4. Does the phone need to be on the same Wi-Fi?

Show answer

Yes - otherwise it cannot reach the ESP32's address.

Q5. What happens if loop() has delay(10000)?

Show answer

The page takes up to 10 seconds to respond, or times out.

Q6. What does <a href='/off'>OFF</a> make?

Show answer

A link with the word OFF that asks for the path /off.

Q7. Which Wi-Fi mode does this lesson use?

Show answer

Station - the ESP32 joins your network.

Code Along 23: ESP32: A Web Page as Text

Practise in your browser

Write and test this lesson's code on a virtual board. Work through the 1 exercises in order. Press Test and each one checks itself.

Exercises passed 0 / 1About 10-15 minOpen as its own page

ESP32 code on the virtual board

The virtual board is an Uno, not an ESP32, so there is no Wi-Fi here. You practise the code around it. A pot reading is a pretend number in a variable, like int reading = 2048;. The LED on GPIO 2 works the same; the test list calls it pin D2.

Exercise 23.1

Build a web page as text

Not started

A web server sends its page as one long String. page += adds text to the end. Add <p>LED is ON</p> when ledOn is true, or <p>LED is OFF</p> when it is false.

Sample output
<h1>ESP32 lamp</h1><p>LED is ON</p>
bool ledOn = true;   // pretend the LED is on

void setup() {
  Serial.begin(115200);
  String page = "<h1>ESP32 lamp</h1>";
  if (ledOn) {
    // add <p>LED is ON</p> to the page
  } else {
    // add <p>LED is OFF</p> to the page
  }
  Serial.println(page);
}

void loop() {
}

Turn on JavaScript to edit, run and test this code in the browser.

Want more? Code Along chapter 23 has a Deeper exercise that goes further than this lesson. It is optional.

Optional extension - skip on your first pass through this lesson.

A brightness slider with a password

This sketch joins sketch 2 and the analogWrite lesson from Part 1. The page shows a slider from 0 to 255. When you let go of it, the LED changes to that brightness.

It is also locked. Anyone else on your Wi-Fi could open sketch 2 and switch your lamp. This page asks for a name and password first, so only you can use it.

Before you upload, change CHANGE_ME to your own password. Do not reuse a real password from another account. If you forget, the sketch will not start the web server and Serial tells you what to fix.

To test: open the address from Serial. The browser asks for a name and password: type lamp and your password. Drag the slider and let go. Look at the address bar: it now ends in /?brightness=128 (or whatever you chose). That is how the slider sends its value.

#include <WiFi.h>                         // the Wi-Fi commands
#include <WebServer.h>                    // the web server commands

String ssid = "YOUR_WIFI_SSID";           // your Wi-Fi network name
String password = "YOUR_WIFI_PASSWORD";   // your Wi-Fi password
String webUser = "lamp";                  // the name the page asks for
String webPassword = "CHANGE_ME";         // the page password - pick your own
const int ledPin = 2;                     // the LED is on GPIO 2

WebServer server(80);                     // a web server on port 80
int brightness = 0;                       // the LED brightness: 0 (off) to 255 (full)

void handlePage() {                       // runs when a browser asks for /
  if (server.authenticate(webUser.c_str(), webPassword.c_str())) { // right name and password?
    if (server.hasArg("brightness")) {    // did the slider send a value?
      int newValue = server.arg("brightness").toInt(); // turn the text into a number (junk gives 0)
      brightness = constrain(newValue, 0, 255);        // keep it between 0 and 255
      analogWrite(ledPin, brightness);    // set the LED brightness
    }
    String page = "<meta name='viewport' content='width=device-width'>"; // fit a phone screen
    page += "<h1>ESP32 dimmer</h1>";      // a heading
    page += "<form action='/'>";          // a form that sends to /
    page += "<input type='range' name='brightness' min='0' max='255'"; // the slider
    page += " value='" + String(brightness) + "'"; // start it where the LED is now
    page += " onchange='this.form.submit()'>";     // send as soon as you let go
    page += "</form>";                    // end of the form
    page += "<p>Brightness: " + String(brightness) + "</p>"; // show the number
    server.send(200, "text/html", page);  // send the page to the browser
  } else {                                // no password yet, or the wrong one
    server.requestAuthentication();       // ask the browser for the name and password
  }
}

void setup() {                            // runs once
  Serial.begin(115200);                   // start Serial at the ESP32 speed
  pinMode(ledPin, OUTPUT);                // the LED pin is an output
  analogWrite(ledPin, brightness);        // start with the LED off

  WiFi.begin(ssid, password);             // start joining the network
  Serial.print("Connecting");             // tell the user we are trying
  while (WiFi.status() != WL_CONNECTED) { // repeat until we have joined
    delay(500);                           // wait half a second
    Serial.print(".");                    // print a dot each time
  }
  Serial.println(" connected");           // we are on the network

  if (webPassword == "CHANGE_ME") {       // still the example password?
    Serial.println("Change webPassword at the top of the sketch, then upload again."); // refuse to start
  } else {                                // a password of your own
    Serial.print("Open http://");         // tell the user what to type
    Serial.println(WiFi.localIP());       // the ESP32's address
    server.on("/", handlePage);           // path / shows the slider page
    server.begin();                       // start the web server
  }
}

void loop() {                             // runs over and over
  server.handleClient();                  // answer any browser that is asking
}

How the slider sketch stays safe

The slider is a form, like a search box. When you let go, onchange='this.form.submit()' sends it straight away, so you do not need a button. The browser asks for /?brightness=128, and server.arg("brightness") reads the 128.

server.authenticate() checks the name and password. If they are missing or wrong, server.requestAuthentication() makes the browser show a sign-in box. The browser remembers them until you close it, so you sign in once.

Never trust what arrives from a browser. Anyone can type /?brightness=9999 or /?brightness=abc into the address bar. toInt() turns text that is not a number into 0, and constrain() keeps the number between 0 and 255.

RiskWhat the sketch does
Someone else on your Wi-Fi opens the pageAsks for a name and password first
Everyone knows the example passwordWill not start the web server until you change CHANGE_ME
A strange value such as 9999 or abctoInt() and constrain() keep it between 0 and 255
The password shows up somewhereNever prints it to Serial or puts it on the page

What this lock does not do

The page uses http, not https, so nothing is scrambled. Someone on the same Wi-Fi with the right tools could still read the password as it travels. That is fine for a lamp at home, but it is why you should never reuse a real password here.

Keep the ESP32 on your home network only. Never set up your router to let the internet reach it. Real products use https and better logins, and that is beyond this course.