๐ŸŒ๐Ÿ”ง Alin Li ยท ESP32 Web Server

Build a Web Server
on a Tiny Chip!

Turn a $5 ESP32 microcontroller into a web server that you control from your phone's browser. Toggle LEDs, read sensors, and build a smart home dashboard โ€” all running on a chip smaller than your thumb. Step by step, line by line, fully commented code.

What You Need ๐Ÿ›’Start Building โšก
๐Ÿค” WHAT ARE WE BUILDING?

A Web Server on a $5 Chip

๐ŸŒ Your ESP32 Becomes a Website!

A web server is a program that listens for requests from web browsers and sends back web pages. When you visit google.com, a Google server sends you a web page. In this project, YOUR ESP32 will do the same thing โ€” but on your local Wi-Fi network.

You'll open your phone's browser, type the ESP32's IP address (like 192.168.1.42), and see a web page SERVED by your tiny chip. That page will have buttons to control LEDs, display sensor readings, and more โ€” all in real time!

๐Ÿง  Key Concept: How a Web Server Works

1. Your browser sends a request โ†’ "Hey server, give me the page at /"

2. The server processes the request โ†’ "They want the homepage, let me build it"

3. The server sends a response โ†’ an HTML page with buttons and data

4. Your browser renders the HTML โ†’ you see a beautiful page with controls!

This is the same process that powers EVERY website on Earth โ€” and your ESP32 will do it.

๐Ÿ›’ WHAT YOU NEED

Shopping List โ€” Total ~$15โ€“$25

๐Ÿ”ง

ESP32 Dev Board

ESP32-WROOM-32 or ESP32-S3

~$5โ€“$10
๐Ÿ”Œ

USB Cable

Micro-USB or USB-C (depends on board)

~$2
๐Ÿ”ด

LEDs (3โ€“5 pcs)

Any color โ€” red, green, blue

~$1
โšก

Resistors (220ฮฉ)

One per LED to protect them

~$1
๐ŸŒก๏ธ

DHT11 Sensor (optional)

Temperature + humidity sensor

~$2
๐Ÿงต

Breadboard + Jumper Wires

For connecting everything

~$3
๐Ÿ’ก Minimum Setup

For Project 1, you ONLY need the ESP32 board + USB cable โ€” the ESP32 has a built-in LED! You can start coding immediately without buying any extra components. Add LEDs and sensors for Projects 2 and 3.

๐Ÿ’ป SETUP

Step 0: Set Up Arduino IDE for ESP32

0

Install Arduino IDE + ESP32 Board Support

โฑ๏ธ 10 minutes (one-time setup)

1. Download Arduino IDE from arduino.cc/en/software (free, open source)

2. Open Arduino IDE โ†’ Go to File โ†’ Preferences

3. In "Additional Board Manager URLs", paste this URL:

Board Manager URL
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

4. Go to Tools โ†’ Board โ†’ Boards Manager โ†’ Search "ESP32" โ†’ Install "esp32 by Espressif Systems"

5. Connect your ESP32 via USB โ†’ Go to Tools โ†’ Board โ†’ Select "ESP32 Dev Module"

6. Go to Tools โ†’ Port โ†’ Select the COM port (e.g., COM3 on Windows, /dev/ttyUSB0 on Linux)

๐Ÿง Linux Users

You may need to add yourself to the dialout group: sudo usermod -a -G dialout $USER then log out and back in. This gives permission to access the USB serial port.

๐ŸŸข PROJECT 1

Control the Built-in LED from Your Browser

Your first web server โ€” no extra hardware needed!

๐Ÿ“ What This Project Does

The ESP32 connects to your Wi-Fi, starts a web server, and serves a page with ON/OFF buttons. When you click a button, the browser sends a request to the ESP32, which turns the built-in LED on or off. You see the result instantly โ€” from your phone or any device on the same Wi-Fi!

project1_led_webserver.ino โ€” Full Code with Every Line Explained
// ============================================ // ALIN LI โ€” ESP32 Web Server: Control an LED // Project 1: Built-in LED, no extra hardware! // ============================================ // STEP 1: Include the Wi-Fi library // This library lets the ESP32 connect to Wi-Fi networks // It comes pre-installed with the ESP32 board package #include <WiFi.h> // STEP 2: Set your Wi-Fi credentials // Replace these with YOUR home Wi-Fi name and password! const char* ssid = "YOUR_WIFI_NAME"; // Your Wi-Fi network name const char* password = "YOUR_WIFI_PASSWORD"; // Your Wi-Fi password // STEP 3: Create a web server on port 80 // Port 80 is the default HTTP port โ€” browsers use it automatically // When you type an IP address, the browser connects to port 80 WiFiServer server(80); // STEP 4: Define the LED pin // Most ESP32 boards have a built-in LED on pin 2 // (Check your specific board โ€” some use pin 5 or 13) const int LED_PIN = 2; // STEP 5: Variable to track LED state // We need to remember if the LED is ON or OFF // so we can show the correct status on the web page bool ledState = false; // ============================================ // SETUP โ€” Runs ONCE when ESP32 starts // ============================================ void setup() { // Start serial communication at 115200 baud // This lets us see messages in the Arduino Serial Monitor // (Tools โ†’ Serial Monitor โ†’ set to 115200 baud) Serial.begin(115200); // Set the LED pin as OUTPUT // OUTPUT means we're SENDING a signal (turning LED on/off) // (INPUT would mean we're READING a signal, like a sensor) pinMode(LED_PIN, OUTPUT); // Make sure LED starts OFF digitalWrite(LED_PIN, LOW); // Connect to Wi-Fi Serial.print("Connecting to Wi-Fi: "); Serial.println(ssid); // WiFi.begin() starts the connection process // It sends your credentials to the router WiFi.begin(ssid, password); // Wait until connected โ€” the while loop keeps checking // WiFi.status() returns WL_CONNECTED when successful while (WiFi.status() != WL_CONNECTED) { delay(500); // Wait half a second Serial.print("."); // Print a dot so we know it's trying } // Connected! Print the IP address // This is the address you'll type in your browser Serial.println(""); Serial.println("โœ… Connected to Wi-Fi!"); Serial.print("๐Ÿ“ก IP Address: "); Serial.println(WiFi.localIP()); // ^^^ WRITE DOWN THIS IP ADDRESS! You'll need it! // Start the web server // Now the ESP32 is LISTENING for browser connections server.begin(); Serial.println("๐ŸŒ Web server started!"); } // ============================================ // LOOP โ€” Runs FOREVER, over and over // ============================================ void loop() { // Check if a client (browser) has connected // server.available() returns a client object if someone connected WiFiClient client = server.available(); // If no client connected, skip and check again if (!client) { return; } // A client connected! Wait for them to send data Serial.println("๐Ÿ‘ค New client connected!"); while (!client.available()) { delay(1); } // Read the first line of the HTTP request // It looks like: "GET /on HTTP/1.1" or "GET /off HTTP/1.1" String request = client.readStringUntil('\r'); Serial.println(request); // ============================================ // HANDLE THE REQUEST โ€” What did the user click? // ============================================ // If the URL contains "/on" โ†’ turn LED ON if (request.indexOf("/on") != -1) { digitalWrite(LED_PIN, HIGH); // HIGH = 3.3V = LED ON ledState = true; Serial.println("๐Ÿ’ก LED turned ON"); } // If the URL contains "/off" โ†’ turn LED OFF if (request.indexOf("/off") != -1) { digitalWrite(LED_PIN, LOW); // LOW = 0V = LED OFF ledState = false; Serial.println("๐ŸŒ‘ LED turned OFF"); } // ============================================ // BUILD THE WEB PAGE (HTML) // This is what the browser will display! // ============================================ // Send HTTP response headers // "HTTP/1.1 200 OK" means "request successful" // "Content-Type: text/html" means "I'm sending a web page" client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(""); // Empty line = end of headers // Now send the HTML page client.println("<!DOCTYPE html><html>"); client.println("<head>"); client.println("<title>Alin Li ESP32 Controller</title>"); client.println("<meta name='viewport' content='width=device-width'>"); // CSS styling โ€” makes it look nice on phones! client.println("<style>"); client.println("body{font-family:Arial;text-align:center;background:#1a1a2e;color:white;padding:40px 20px;}"); client.println("h1{font-size:2rem;margin-bottom:10px;}"); client.println(".status{font-size:3rem;margin:20px 0;}"); client.println(".btn{display:inline-block;padding:20px 50px;margin:10px;"); client.println("border-radius:50px;font-size:1.2rem;font-weight:bold;"); client.println("text-decoration:none;color:white;}"); client.println(".on{background:#16a34a;}.off{background:#dc2626;}"); client.println("</style></head><body>"); // Page content client.println("<h1>๐Ÿ”ง Alin Li ESP32 Controller</h1>"); client.println("<p>Control the LED from your browser!</p>"); // Show current LED status with emoji if (ledState) { client.println("<div class='status'>๐Ÿ’ก LED is ON</div>"); } else { client.println("<div class='status'>๐ŸŒ‘ LED is OFF</div>"); } // Buttons that link to /on and /off // When clicked, the browser navigates to that URL // which triggers our if statements above! client.println("<a href='/on' class='btn on'>Turn ON</a>"); client.println("<a href='/off' class='btn off'>Turn OFF</a>"); client.println("</body></html>"); // Done! Flush sends all data and close the connection client.flush(); Serial.println("๐Ÿ“ค Page sent to client!"); }
โ–ถ๏ธ

Upload & Test!

โฑ๏ธ 2 minutes

1. Change "YOUR_WIFI_NAME" and "YOUR_WIFI_PASSWORD" to your real Wi-Fi

2. Click the Upload button (โ†’ arrow) in Arduino IDE

3. Open Tools โ†’ Serial Monitor (set to 115200 baud)

4. Wait for the message: "๐Ÿ“ก IP Address: 192.168.1.XX"

5. On your phone, open a browser and type that IP address!

6. ๐ŸŽ‰ You see your web page! Click the buttons โ€” the LED responds!

๐ŸŽ‰ Congratulations!

You just built a web server on a $5 chip! Your phone is talking to a microcontroller through Wi-Fi and HTTP โ€” the same protocol that powers the entire World Wide Web. This is real IoT (Internet of Things) engineering!

๐ŸŽฏ Activity 1: Customize Your Page!

Now that it works, try these changes:

  1. Change the page title from "Alin Li ESP32 Controller" to YOUR name
  2. Change the background color โ€” try #0f172a (dark blue) or #064e3b (dark green)
  3. Add a third button that makes the LED blink 5 times (hint: use a for loop with delay())
  4. Add your name and today's date at the bottom of the page
๐Ÿ”ด๐ŸŸข๐Ÿ”ต PROJECT 2

Control 3 External LEDs

Control red, green, and blue LEDs independently!

๐Ÿ”Œ Wiring Diagram
    ESP32              LEDs
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚          โ”‚
    โ”‚  GPIO 26 โ”œโ”€โ”€โ”€โ”€โ”€[220ฮฉ]โ”€โ”€โ”€โ”€๐Ÿ”ด Red LEDโ”€โ”€โ”
    โ”‚          โ”‚                              โ”‚
    โ”‚  GPIO 27 โ”œโ”€โ”€โ”€โ”€โ”€[220ฮฉ]โ”€โ”€โ”€โ”€๐ŸŸข Green LEDโ”€โ”ค
    โ”‚          โ”‚                              โ”‚
    โ”‚  GPIO 14 โ”œโ”€โ”€โ”€โ”€โ”€[220ฮฉ]โ”€โ”€โ”€โ”€๐Ÿ”ต Blue LEDโ”€โ”€โ”ค
    โ”‚          โ”‚                              โ”‚
    โ”‚  GND    โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
    โ”‚          โ”‚          (all LED negatives
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜           share ground)
    
โšก Why the 220ฮฉ Resistor?

LEDs are hungry โ€” they'll try to draw too much current and burn out. The resistor limits current to a safe level (~15mA). Always use a resistor with each LED! Without it, you'll destroy the LED and possibly damage the ESP32 pin.

project2_three_leds.ino โ€” Control 3 LEDs from browser
#include <WiFi.h> const char* ssid = "YOUR_WIFI_NAME"; const char* password = "YOUR_WIFI_PASSWORD"; WiFiServer server(80); // Define pins for each LED const int RED_PIN = 26; const int GREEN_PIN = 27; const int BLUE_PIN = 14; // Track each LED's state bool redOn = false, greenOn = false, blueOn = false; void setup() { Serial.begin(115200); pinMode(RED_PIN, OUTPUT); pinMode(GREEN_PIN, OUTPUT); pinMode(BLUE_PIN, OUTPUT); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nโœ… IP: " + WiFi.localIP().toString()); server.begin(); } void loop() { WiFiClient client = server.available(); if (!client) return; while (!client.available()) { delay(1); } String req = client.readStringUntil('\r'); // Toggle each LED based on the URL if (req.indexOf("/red") != -1) { redOn = !redOn; digitalWrite(RED_PIN, redOn); } if (req.indexOf("/green") != -1) { greenOn = !greenOn; digitalWrite(GREEN_PIN, greenOn); } if (req.indexOf("/blue") != -1) { blueOn = !blueOn; digitalWrite(BLUE_PIN, blueOn); } // Build a nice web page with 3 toggle buttons client.println("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n"); client.println("<!DOCTYPE html><html><head>"); client.println("<meta name='viewport' content='width=device-width'>"); client.println("<style>"); client.println("body{font-family:Arial;text-align:center;background:#0f172a;color:#e2e8f0;padding:40px 20px;}"); client.println("h1{font-size:1.8rem;} .btns{display:flex;flex-direction:column;gap:14px;max-width:300px;margin:20px auto;}"); client.println(".b{padding:18px;border-radius:14px;font-size:1.1rem;font-weight:bold;text-decoration:none;color:white;display:block;}"); client.println("</style></head><body>"); client.println("<h1>๐Ÿ”ง Alin Li LED Controller</h1>"); client.println("<div class='btns'>"); // Each button shows current state and links to toggle URL client.print("<a class='b' style='background:"); client.print(redOn ? "#dc2626" : "#333"); client.println("' href='/red'>๐Ÿ”ด Red: " + String(redOn ? "ON" : "OFF") + "</a>"); client.print("<a class='b' style='background:"); client.print(greenOn ? "#16a34a" : "#333"); client.println("' href='/green'>๐ŸŸข Green: " + String(greenOn ? "ON" : "OFF") + "</a>"); client.print("<a class='b' style='background:"); client.print(blueOn ? "#2563eb" : "#333"); client.println("' href='/blue'>๐Ÿ”ต Blue: " + String(blueOn ? "ON" : "OFF") + "</a>"); client.println("</div></body></html>"); client.flush(); }

๐ŸŽฏ Activity 2: Expand It!

  1. Add a 4th LED on GPIO 25 โ€” add the button and logic yourself!
  2. Add an "ALL ON" button that turns all 3 LEDs on at once (hint: new URL /all)
  3. Add an "ALL OFF" button
  4. Add a "DISCO" button that blinks all LEDs in a random pattern 10 times
  5. Challenge: Add a brightness slider using PWM (analogWrite() or ledcWrite() on ESP32)
๐ŸŒก๏ธ PROJECT 3

Temperature & Humidity Dashboard

A live sensor dashboard with auto-refresh!

๐Ÿ”Œ DHT11 Sensor Wiring
    ESP32           DHT11
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”
    โ”‚          โ”‚    โ”‚     โ”‚
    โ”‚  3.3V   โ”œโ”€โ”€โ”€โ”€โ”ค VCC โ”‚  (Power)
    โ”‚          โ”‚    โ”‚     โ”‚
    โ”‚  GPIO 4 โ”œโ”€โ”€โ”€โ”€โ”ค DATAโ”‚  (Signal)
    โ”‚          โ”‚    โ”‚     โ”‚
    โ”‚  GND    โ”œโ”€โ”€โ”€โ”€โ”ค GND โ”‚  (Ground)
    โ”‚          โ”‚    โ”‚     โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”˜

    Note: Add a 10kฮฉ pull-up resistor between DATA and VCC
    (some DHT11 modules have it built-in)
    
๐Ÿ“š Install DHT Library First!

In Arduino IDE: Sketch โ†’ Include Library โ†’ Manage Libraries โ†’ Search "DHT sensor library" by Adafruit โ†’ Install. Also install "Adafruit Unified Sensor" when prompted.

project3_sensor_dashboard.ino โ€” Live Temperature Dashboard
#include <WiFi.h> #include <DHT.h> // DHT sensor library const char* ssid = "YOUR_WIFI_NAME"; const char* password = "YOUR_WIFI_PASSWORD"; WiFiServer server(80); // DHT11 sensor on GPIO 4 #define DHT_PIN 4 #define DHT_TYPE DHT11 DHT dht(DHT_PIN, DHT_TYPE); const int LED_PIN = 2; bool ledOn = false; void setup() { Serial.begin(115200); pinMode(LED_PIN, OUTPUT); dht.begin(); // Initialize the DHT sensor WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\n๐Ÿ“ก IP: " + WiFi.localIP().toString()); server.begin(); } void loop() { WiFiClient client = server.available(); if (!client) return; while (!client.available()) { delay(1); } String req = client.readStringUntil('\r'); if (req.indexOf("/toggle") != -1) { ledOn = !ledOn; digitalWrite(LED_PIN, ledOn); } // Read sensor values float temp = dht.readTemperature(); // Celsius float hum = dht.readHumidity(); // Percentage // Check if readings failed String tempStr = isnan(temp) ? "Error" : String(temp, 1) + "ยฐC"; String humStr = isnan(hum) ? "Error" : String(hum, 1) + "%"; // Build beautiful dashboard page client.println("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n"); client.println("<!DOCTYPE html><html><head>"); client.println("<meta name='viewport' content='width=device-width'>"); // AUTO-REFRESH every 5 seconds! // This makes the page reload to show updated sensor data client.println("<meta http-equiv='refresh' content='5'>"); client.println("<style>"); client.println("body{font-family:Arial;text-align:center;background:#0f172a;color:#e2e8f0;padding:30px 20px;}"); client.println("h1{font-size:1.5rem;color:#60a5fa;}"); client.println(".cards{display:flex;gap:14px;justify-content:center;flex-wrap:wrap;margin:20px 0;}"); client.println(".card{background:#1e293b;border-radius:16px;padding:24px;min-width:140px;}"); client.println(".card .icon{font-size:2.5rem;}"); client.println(".card .val{font-size:2rem;font-weight:bold;margin:8px 0;}"); client.println(".card .label{font-size:.8rem;color:#94a3b8;}"); client.println(".btn{display:inline-block;padding:16px 40px;border-radius:50px;margin-top:16px;"); client.println("font-size:1rem;font-weight:bold;text-decoration:none;color:white;}"); client.println("</style></head><body>"); client.println("<h1>๐ŸŒ Alin Li Sensor Dashboard</h1>"); client.println("<p style='color:#94a3b8;font-size:calc(.85rem * var(--alinli-text-scale,1));'>Auto-refreshes every 5 seconds</p>"); client.println("<div class='cards'>"); // Temperature card client.println("<div class='card'><div class='icon'>๐ŸŒก๏ธ</div>"); client.println("<div class='val' style='color:#f59e0b'>" + tempStr + "</div>"); client.println("<div class='label'>Temperature</div></div>"); // Humidity card client.println("<div class='card'><div class='icon'>๐Ÿ’ง</div>"); client.println("<div class='val' style='color:#60a5fa'>" + humStr + "</div>"); client.println("<div class='label'>Humidity</div></div>"); // LED status card client.println("<div class='card'><div class='icon'>" + String(ledOn ? "๐Ÿ’ก" : "๐ŸŒ‘") + "</div>"); client.println("<div class='val'>" + String(ledOn ? "ON" : "OFF") + "</div>"); client.println("<div class='label'>LED</div></div>"); client.println("</div>"); // Toggle LED button client.print("<a class='btn' style='background:"); client.print(ledOn ? "#dc2626" : "#16a34a"); client.println("' href='/toggle'>" + String(ledOn ? "Turn OFF" : "Turn ON") + "</a>"); client.println("</body></html>"); client.flush(); }

๐ŸŽฏ Activity 3: Make It Your Own!

  1. Add a warning: if temperature > 30ยฐC, show the card in RED with "๐Ÿ”ฅ HOT!"
  2. Add a counter that shows how many times the page has been visited (hint: global int visits = 0; and visits++ in loop)
  3. Log temperature every minute to Serial Monitor and calculate the average
  4. Add a light sensor (LDR on an analog pin) and show light level as a percentage
  5. Boss Challenge: Add a chart that shows the last 10 temperature readings using HTML <div> bars
๐Ÿง  KEY CONCEPTS

What You Learned

๐ŸŒ HTTP Protocol

Browsers speak HTTP (HyperText Transfer Protocol). A request like GET /on HTTP/1.1 means "give me the page at /on." The server responds with 200 OK + HTML content. This is the foundation of the entire web โ€” and your ESP32 speaks it now!

๐Ÿ“ก Wi-Fi + TCP/IP

The ESP32 connects to your router, gets an IP address (like 192.168.1.42), and listens on port 80. When your phone sends a request to that IP, the router forwards it to the ESP32. This is networking in action.

๐Ÿ”Œ GPIO (General Purpose Input/Output)

GPIO pins can be set as OUTPUT (send voltage to control LEDs, motors, relays) or INPUT (read sensors, buttons). digitalWrite(pin, HIGH) sends 3.3V. digitalRead(pin) reads the voltage. This is how software controls the physical world.

๐Ÿ“„ HTML + CSS

The web page your ESP32 sends is real HTML + CSS โ€” the same technology behind every website. You're building full-stack IoT: hardware (ESP32 + sensors) + backend (C++ web server) + frontend (HTML/CSS page). That's 3 skill domains in one project!

๐Ÿ”„ Client-Server Architecture

Your phone is the client (asks for pages). Your ESP32 is the server (sends pages + executes commands). This is the same architecture that powers Netflix, Google, banking apps, and everything online.

๐Ÿš€ WHAT'S NEXT?

Level Up Your ESP32 Projects

โšก Use ESPAsyncWebServer (Professional)

The WiFiServer we used is basic. For real projects, use the ESPAsyncWebServer library โ€” it handles multiple clients simultaneously, supports WebSockets (real-time updates without page refresh), file serving from SPIFFS/LittleFS, and template processing. It's what professional IoT products use.

๐Ÿ“ฑ Add AJAX for No-Page-Refresh Updates

Instead of reloading the page to update sensor data, use JavaScript fetch() to request data from the ESP32 in the background and update the page dynamically. The dashboard updates live without flickering!

๐Ÿ  Connect to Home Assistant

Make your ESP32 work with Home Assistant (open-source home automation) using ESPHome โ€” a YAML-based firmware that turns ESP32 into smart home sensors and controllers without writing C++ code.

๐Ÿ”’ Add Tailscale for Remote Access

Want to access your dashboard from outside your home? Don't use port forwarding! Install Tailscale on a Raspberry Pi on the same network โ€” now you can access your ESP32 dashboard from anywhere through an encrypted tunnel.

๐Ÿ“Š Store Data with InfluxDB + Grafana

Send sensor readings to InfluxDB (time-series database) and visualize them with Grafana dashboards โ€” see weeks of temperature history, humidity trends, and alerts. All open source.

๐ŸŒ๐Ÿ”งโœจ

A $5 Chip. A Web Server. Your Creation.

You built a real web server on a chip smaller than your thumb. You learned HTTP, Wi-Fi networking, GPIO control, HTML, and client-server architecture โ€” all in one project. These are the same skills used by IoT engineers at companies like Tesla, Philips Hue, and Ring. The difference? They had expensive equipment. You did it for $5.

ยซูˆูŽุฃูŽู„ูŽู†ูŽู‘ุง ู„ูŽู‡ู ุงู„ู’ุญูŽุฏููŠุฏูŽยป

A tiny chip of silicon โ€” softened iron of our age. With knowledge and code, you gave it the power to serve web pages, control lights, and read the temperature. Technology is not magic โ€” it's knowledge applied with intention. โœจ