Level 3Unit A · Session 3
3

Unit A · On the network

The first value on a screen

What we are making: a web page, served by your board, showing a live sensor reading. You open it on your phone. Nothing is plugged into your phone.

📷
images/l3-s3-hero-phone.png
Photo: a phone showing the served page, next to the ESP32 on the bench

The idea

A web server sounds enormous. It is not. It is a program that waits for somebody to ask for a page, and sends back some text. The text happens to be HTML, which the phone knows how to draw.

Your board has enough memory to do this comfortably. Every WiFi router, every printer, every smart plug in the building does exactly this and nothing more.

Tank Alarm — Team 4http://192.168.4.1Water level23 cmLast updated just now

What you are building today. Yes, really — that is served by the board.

Wire it up

Any analogue sensor. We will use the ultrasonic from Level 2, but a light sensor or a potentiometer works just as well for today.

1
ESP32
1
HC-SR04 ultrasonic
4
Jumper wires
HC-SR045 V moduleVCCTrigEchoGNDESP323.3 V logic5VGPIO 5GPIO 18GNDorder outdividerEcho outputs 5 V. It must go through the divider, never straight in.Trig is an output from the ESP32, so 3.3 V leaving the board is fine —the module reads it happily. Only Echo comes back at 5 V, and only Echoneeds protecting. That is why exactly one of the four wires is dashed.A 3.3 V ultrasonic module needs none of this. Check what is in your bin first.
Four wires, one of which will kill the board if you wire it directly.
⚠ Careful
The HC-SR04 is a 5V module and its Echo pin outputs 5V. On an ESP32 that pin needs a divider. Do not skip it. This is the 3.3V rule from page 4, in real life.

The divider, drawn properly

Two resistors and nothing else. They split the 5V coming back from Echo so the ESP32 sees about 3.3V — exactly what it can handle, and not a volt more.

HC-SR04Echo (5 V out)1 kΩESP32 GPIO 182 kΩGND5 V in, 3.3 V out. Two resistors, nothing else.5 V × 2k ÷ (1k + 2k) = 3.3 VTrig does not need this. Only Echo.
Build this on a corner of your breadboard before Echo touches the ESP32.
  • The 1kΩ sits in the path from Echo. The 2kΩ runs from that junction down to GND.
  • The ESP32 pin taps the junction between the two resistors. That is the whole trick.
  • Any 1:2 pair works — 10k and 20k is also fine and draws less current.
  • Swap the two resistors and the ESP32 sees 1.7V, which it reads as permanently LOW. Nothing is damaged; nothing works either.
Pins used5185VGND

The code

Longer than anything in Level 2. Read it in three parts: the sensor, the page, the server.

Your program
1#include <WiFi.h>
2#include <WebServer.h>
3 
4const char* AP_NAME = "ROBOT-01";
5const char* AP_PASS = "robotics2027";
6const int TRIG = 5;
7const int ECHO = 18;
8 
9WebServer server(80); // 80 is the normal port for web pages
10 
11int readDistance() {
12 digitalWrite(TRIG, LOW); delayMicroseconds(2);
13 digitalWrite(TRIG, HIGH); delayMicroseconds(10);
14 digitalWrite(TRIG, LOW);
15 long us = pulseIn(ECHO, HIGH, 30000); // give up after 30ms
16 if (us == 0) return -1; // -1 means "no reading"
17 return us * 0.034 / 2;
18}
19 
20void handleRoot() {
21 int cm = readDistance();
22 String page = "<html><head><meta http-equiv='refresh' content='2'>";
23 page += "<meta name='viewport' content='width=device-width'></head><body>";
24 page += "<h2>Tank Alarm - Team 4</h2>";
25 page += "<p>Water level: <b>" + String(cm) + " cm</b></p>";
26 page += "</body></html>";
27 server.send(200, "text/html", page);
28}
29 
30void setup() {
31 Serial.begin(115200);
32 pinMode(TRIG, OUTPUT);
33 pinMode(ECHO, INPUT);
34 WiFi.softAP(AP_NAME, AP_PASS);
35 Serial.println(WiFi.softAPIP());
36 server.on("/", handleRoot);
37 server.begin();
38}
39 
40void loop() {
41 server.handleClient(); // must run constantly. No long delays in here.
42}

Line by line, the four that matter

  • Line 16 — pulseIn with a timeout. Without that third number, a sensor that never answers freezes your whole program forever. This is the first piece of surviving-in-the-field code you have written.
  • Line 17 — returning -1 for 'no reading' instead of a wrong number. You will thank yourself in Session 6.
  • Line 22 — refresh content='2' tells the phone to reload every 2 seconds. It is a cheap trick and it is how your page appears live.
  • Line 36 — “when somebody asks for /, run handleRoot”. That is the entire routing system.
  • Line 41 — no delay() in loop(). Every millisecond you spend in delay is a millisecond you are not answering the phone.
💡 Tip
String with + is how you glue text and numbers together. It is convenient and slightly wasteful. On a board this size, convenient wins.

Try it

  1. Upload. Open the serial monitor, confirm it printed 192.168.4.1.
  2. On your phone: WiFi settings, join ROBOT-01, password robotics2027.
  3. Your phone will complain there is no internet. Choose to stay connected anyway.
  4. Open a browser. Type 192.168.4.1 exactly. Not google, not a search — the address bar.
  5. Move your hand in front of the sensor. The number changes within two seconds.
What you seeWhyWhat to do
Phone leaves the network by itselfIt hunts for internet.Turn off 'auto-switch to mobile data' for that network.
Page loads once then stopsloop() is blocked.Look for a delay() you added.
Shows -1 constantlySensor not answering.Check Trig/Echo not swapped, and the divider.
Browser searches instead of loadingYou typed it into the search box.Use the address bar. Add http:// if it helps.

What breaks it

Unplug the sensor while the page is open. The number becomes −1 and stays on the page. Your page is now confidently displaying a fake value. Remember that feeling — Session 6 is about it.

Challenge optional — only if you finish early
  • Serve two different readings on the same page.
2. Getting on a network