IoT lab viva notes

Tavish Gupta, 23BCP064

Experiment 6: ESP32 Wi-Fi web server

The device becomes a server. The commands are URLs, the transport is TCP over Wi-Fi, and the user interface is an HTML page the device generates itself. This is the model behind router admin pages and many cheap smart plugs.

Objective

Problem
Control an LED from any browser on the same Wi-Fi network.
Interface
three routes: /on, /off, /blink (5 blinks of 300 ms on, 300 ms off). Any request returns the control page.
Learn
station mode and DHCP, client-server over TCP, the HTTP request/response format, and the cost of blocking in a server.

System

Phone browserHTTP clientWi-Fi routerDHCP, same LANESP32server on :80LEDGPIO 23GET /onTCP
Everything is on one LAN. The browser opens a TCP connection to the ESP32’s IP address on port 80.

Design decisions

Station mode (join the router)

Why
The ESP32 and the phone share the existing network; the phone keeps its internet connection.
Alternative
Access-point mode (WiFi.softAP()): the ESP32 creates its own network, typically at 192.168.4.1.
Switch when
No router available, or first-time setup before Wi-Fi credentials are known.

Raw WiFiServer with hand parsing

Why
Shows HTTP as it really is: text over a TCP socket, read character by character until the blank line.
Alternative
The WebServer library (server.on("/on", handler)) or an asynchronous web server.
Switch when
Any real project: routing, headers and concurrency are handled for you.

Commands as GET paths

Why
A button can be a plain link, so the page needs no JavaScript.
Alternative
POST requests or a small JSON API called from JavaScript.
Switch when
For state-changing actions, POST is the correct HTTP method: browsers and proxies may pre-fetch or cache GET.

Close the connection, then blink

Why
Blinking takes 3 s (5 × 600 ms). Replying and closing first lets the page load immediately.
Alternative
Non-blocking blink driven by millis() while the server keeps serving.
Switch when
Whenever a long action must not freeze the server.

HTML generated inside the sketch

Why
One file, nothing to upload separately.
Alternative
Store HTML/CSS/JS in the ESP32’s flash filesystem (LittleFS) and serve files.
Switch when
The page grows beyond a few lines.

What we did

  1. Wired an LED to GPIO 23 through 220 Ω.

    The output under control.

  2. Put the network name and password into the sketch and uploaded it.

    WiFi.begin() needs credentials; they are compiled into the firmware.

  3. Opened the Serial Monitor at 115200 and noted the IP address.

    The router assigns the address by DHCP; the sketch prints it because nothing else tells you what it is.

  4. Connected the phone to the same network and opened the IP address in a browser.

    The IP address is private to the LAN; from another network it is unreachable.

  5. Pressed TURN ON, TURN OFF and BLINK 5 TIMES.

    Each press is a separate HTTP request to a different path.

HTTP on the wire

Phone browserESP32 on port 80 GET /on HTTP/1.1 (+ headers, then a blank line) request has “GET /on”digitalWrite(23, HIGH) HTTP/1.1 200 OK + the HTML page with three buttons browser draws the pageeach button is a link then client.stop() closes the connection
One button press: connect, request, response, close.
Browser sendsMeaning
GET /on HTTP/1.1request line: method, path, version
Host: 192.168.x.x and other headersone Name: value per line, each ended by CR LF
an empty lineend of headers; GET has no body, so the request is complete
ESP32 repliesMeaning
HTTP/1.1 200 OKstatus line: success
Content-type:text/htmlhow to interpret the body
Connection: closethe server closes the TCP connection after the body
an empty line, then the HTMLheaders end, body follows

Reading the code

  • setup(): configure the pin, join Wi-Fi (blocking until connected), print the IP, start listening.
  • loop(): if a client has connected, read its request a character at a time, building the full request and the current line. When an empty line arrives, decide by path, send the response, break out, close, and only then run the blink if requested.
  • State per request: request (everything received), currentLine (the line being read), doBlink (a deferred action).

Wi-Fi web server

106 lines, exactly as in your lab file

#include <WiFi.h> const char* ssid     = "YOUR_SSID";const char* password = "YOUR_PASSWORD"; WiFiServer server(80);const int ledPin = 23;  // Blink settingsconst int BLINK_COUNT  = 5;    // how many times to blinkconst int BLINK_ON_MS  = 300;  // LED on durationconst int BLINK_OFF_MS = 300;  // LED off duration // Blinks the LED the given number of timesvoid blinkLED(int times) {  for (int i = 0; i < times; i++) {    digitalWrite(ledPin, HIGH);    delay(BLINK_ON_MS);    digitalWrite(ledPin, LOW);    delay(BLINK_OFF_MS);  }} void setup() {  Serial.begin(115200);  pinMode(ledPin, OUTPUT);  digitalWrite(ledPin, LOW);   Serial.print("Connecting to ");  Serial.println(ssid);  WiFi.begin(ssid, password);  while (WiFi.status() != WL_CONNECTED) {    delay(500);    Serial.print(".");  }   Serial.println("");  Serial.println("WiFi connected.");  Serial.print("IP address: ");  Serial.println(WiFi.localIP());  Serial.println("Open this IP in your phone/PC browser.");   server.begin();} void loop() {  WiFiClient client = server.available();   if (client) {    String currentLine = "";    String request     = "";    bool doBlink = false;          // set true if /blink was requested     while (client.connected()) {      if (client.available()) {        char c = client.read();        request += c;         if (c == '\n') {          if (currentLine.length() == 0) {            // ---- Blank line = end of HTTP request headers ----             if (request.indexOf("GET /blink") >= 0) {              doBlink = true;                   // blink AFTER replying            } else if (request.indexOf("GET /on") >= 0) {              digitalWrite(ledPin, HIGH);            } else if (request.indexOf("GET /off") >= 0) {              digitalWrite(ledPin, LOW);            }             // ---- Send the web page ----            client.println("HTTP/1.1 200 OK");            client.println("Content-type:text/html");            client.println("Connection: close");            client.println();             client.println("<!DOCTYPE html><html>");            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");            client.println("<title>ESP32 LED</title></head>");            client.println("<body style=\"font-family:Arial;text-align:center;\">");            client.println("<h1>ESP32 WiFi LED Control</h1>");            client.println("<p><a href=\"/blink\"><button style=\"padding:15px 30px;font-size:20px;\">BLINK 5 TIMES</button></a></p>");            client.println("<p><a href=\"/on\"><button>TURN ON</button></a></p>");            client.println("<p><a href=\"/off\"><button>TURN OFF</button></a></p>");            client.println("</body></html>");             break;          } else {            currentLine = "";          }        } else if (c != '\r') {          currentLine += c;        }      }    }     client.stop();   // close connection FIRST so the browser is not kept waiting     if (doBlink) {      Serial.println("Blinking 5 times...");      blinkLED(BLINK_COUNT);      Serial.println("Blink done.");    }  }}
  1. Line 1
    ESP32 Wi-Fi: association, DHCP, and TCP sockets through the lwIP stack.
  2. Lines 3 to 4
    Credentials (placeholders in your file).
  3. Line 6
    A TCP listener on port 80, the HTTP default.
  4. Line 7
    The controlled output.
  5. Lines 11 to 13
    Blink parameters as named constants.
  6. Line 16
    A blocking helper: times × (300 + 300) ms.
  7. Line 32
    Start association with the router.
  8. Lines 33 to 35
    Busy-wait until connected, printing progress. With wrong credentials this never exits.
  9. Line 41
    The DHCP-assigned address to type into the browser.
  10. Line 44
    Start accepting connections.
  11. Line 48
    Returns a connected client if one is waiting, otherwise an empty object, so loop() keeps polling. (Newer ESP32 cores rename this to accept().)
  12. Lines 51 to 53
    Per-request parser state.
  13. Lines 55 to 58
    Read one byte at a time and append it to the full request.
  14. Lines 60 to 61
    A newline while currentLine is empty means CR LF CR LF: the blank line ending the headers.
  15. Lines 64 to 69
    Route by substring. indexOf returns −1 if absent. /blink only sets a flag; /on and /off act immediately.
  16. Lines 73 to 76
    Status line, headers, and the blank line that separates them from the body.
  17. Lines 78 to 86
    The body: a page whose three buttons are wrapped in links, so tapping one makes the browser request that path.
  18. Line 88
    Response complete; leave the read loop.
  19. Lines 89 to 90
    A non-empty line ended: start the next one.
  20. Lines 92 to 93
    Accumulate characters except CR.
  21. Line 98
    Close the TCP connection so the browser finishes loading.
  22. Lines 100 to 103
    The deferred 3-second blink, after the client is gone.

Reading the outputs

Phone and breadboard.
Phone and breadboard.
  • The phone browser shows the generated page: heading “ESP32 WiFi LED Control” and the three buttons from the HTML in the code.
  • The address bar shows a private IP address: the request went over the local network.
  • The LED on the breadboard is lit after TURN ON: the /on route executed digitalWrite(23, HIGH).

Limits and redesign

  • No security: plain HTTP, no login. Anyone on the network can control it.
  • Only reachable on the LAN: behind NAT, nothing outside can open a connection to it. (This is exactly what Exp 8 solves.)
  • One client at a time, blocking: during the 3 s blink, or a slow client, no other request is served.
  • Fragile parsing: substring matching, no handling of slow or malformed requests, the whole request kept in a growing String.
  • Browsers also request /favicon.ico: it matches no route, so the page is just sent again; harmless here.
  • Redesign: WebServer or an async server, POST for actions, authentication, non-blocking blink, and for remote access a cloud or MQTT broker.

Say this in the viva

Viva questions

Why

Why does the code look for an empty line?

In HTTP the headers end with an empty line (CR LF CR LF). After it, a GET request is complete, so the server can respond.

Why can’t you open this page from mobile data?

The IP address is private to the LAN, and the router’s NAT does not forward incoming connections to the ESP32.

Why close the client before blinking?

Blinking blocks for 3 s; replying and closing first lets the browser finish loading at once.

What if

What if two phones press buttons at the same time?

The server handles one client at a time; the second waits until the first is closed, and during a blink both wait.

What if the router reboots and gives a new IP?

The old address stops working. Fixes: a DHCP reservation on the router, a static IP, or mDNS (for example esp32.local).

Design

How would you make this safe on a shared network?

Require authentication, use POST for actions, and ideally HTTPS; or do not expose a server at all and use a broker.

How would you show the LED’s current state on the page?

Track a state variable and insert it into the HTML on every response, or add a /state route returning JSON that the page polls.