ESP32 Web Server for Remote Device Control

ESP32 Web Server for Remote Device Control | MakeMindz
MakeMindz · ESP32 Projects

ESP32 Web Server
for Remote Device Control

Host a smart home control panel on your ESP32. Control lights, fan, heater and door lock from any browser on your Wi-Fi network — no internet required.

⏱ ~60 min 🏭 Intermediate 🔌 ESP32 DevKit 🌐 Wi-Fi Web Server 💻 Wokwi Simulation
01 · Overview

What Are We Building?

The ESP32 runs a lightweight HTTP web server on port 80. When you open the ESP32's IP address in any browser on your local network, you get a fully responsive control panel. Toggle lights, slide the fan speed, lock or unlock the door — all from your phone or laptop, with no cloud, no app and no internet.

💡Control 5 devices from browser
🌡️DHT22 temperature & humidity
🔠Password-protected login
📺OLED status display
PWM fan speed control
📩JSON status API
📱Mobile responsive UI
🏠No internet needed
💡 How to access:
  1. Flash the code to your ESP32 and power it on.
  2. Open the Serial Monitor at 115200 baud — note the IP address printed.
  3. Open a browser on the same Wi-Fi network and type that IP.
  4. Login with admin / 1234
02 · Components

What You Need

#ComponentQtyRole
1ESP32 DevKit V1×1Wi-Fi microcontroller — hosts the web server
2Yellow LED×1Living Room light indicator (Pin 26)
3White LED×1Bedroom light indicator (Pin 27)
4Blue LED×1Fan indicator (Pin 14)
5Red LED×1Heater indicator (Pin 12)
6Green LED×1Door Lock indicator (Pin 13)
7220Ω Resistors×5Current limiting for each LED
8DHT22 Sensor×1Temperature & humidity readings (Pin 15)
9SSD1306 OLED (128×64)×1Local status display via I2C
10Piezo Buzzer×1Audio feedback on device toggle (Pin 25)
11Breadboard & Jumper Wires×1Circuit assembly
12Wi-Fi Router / Phone Hotspot×1Network for browser & ESP32
03 · Step-by-Step Wiring

Circuit Connections

1

LED Bar — Living Room & Bedroom Lights

Connect each LED anode through a 220Ω resistor to its ESP32 pin. All cathodes go to GND.

💡 Living Room (Yellow)

Anode (+)Pin D26 via 220Ω
Cathode (–)GND

💡 Bedroom (White)

Anode (+)Pin D27 via 220Ω
Cathode (–)GND
2

Fan, Heater & Door Lock LEDs

Three more LEDs representing Fan (PWM), Heater and Door Lock. Same resistor-to-GND configuration.

🌀 Fan (Blue)

Anode (+)Pin D14 (PWM)
Cathode (–)GND via 220Ω

🔥 Heater (Red)

Anode (+)Pin D12 via 220Ω
Cathode (–)GND

🔒 Door Lock (Green)

Anode (+)Pin D13 via 220Ω
Cathode (–)GND
3

DHT22 Temperature & Humidity Sensor

VCC3.3V on ESP32
GNDGND on ESP32
SDA / DataPin D15
4

SSD1306 OLED Display (I2C)

VCC3.3V on ESP32
GNDGND on ESP32
SDAPin D21
SCLPin D22
5

Piezo Buzzer

+ PinPin D25
– PinGND on ESP32

Power tip: Use the ESP32's 3.3V pin for DHT22 and OLED, not 5V. For real relay/motor control, use optocoupler-isolated relay modules and an external power supply.

04 · Wokwi Simulation File

diagram.json

Copy this file and paste it as diagram.json in your Wokwi project alongside the code to run the full simulation.

diagram.json — ESP32 Web Server
{
  "version": 1,
  "author": "ESP32 Web Server Control",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-esp32-devkit-v1", "id": "esp", "top": 0, "left": 0, "attrs": {} },
    { "type": "wokwi-led", "id": "led1", "top": -124.8, "left": 230.4, "attrs": { "color": "yellow", "label": "Living Room" } },
    { "type": "wokwi-resistor", "id": "r1", "top": -67.2, "left": 230.4, "rotate": 90, "attrs": { "value": "220" } },
    { "type": "wokwi-led", "id": "led2", "top": -124.8, "left": 307.2, "attrs": { "color": "white", "label": "Bedroom" } },
    { "type": "wokwi-resistor", "id": "r2", "top": -67.2, "left": 307.2, "rotate": 90, "attrs": { "value": "220" } },
    { "type": "wokwi-led", "id": "led3", "top": -124.8, "left": 384, "attrs": { "color": "blue", "label": "Fan" } },
    { "type": "wokwi-resistor", "id": "r3", "top": -67.2, "left": 384, "rotate": 90, "attrs": { "value": "220" } },
    { "type": "wokwi-led", "id": "led4", "top": -124.8, "left": 460.8, "attrs": { "color": "red", "label": "Heater" } },
    { "type": "wokwi-resistor", "id": "r4", "top": -67.2, "left": 460.8, "rotate": 90, "attrs": { "value": "220" } },
    { "type": "wokwi-led", "id": "led5", "top": -124.8, "left": 537.6, "attrs": { "color": "green", "label": "Door Lock" } },
    { "type": "wokwi-resistor", "id": "r5", "top": -67.2, "left": 537.6, "rotate": 90, "attrs": { "value": "220" } },
    { "type": "wokwi-dht22", "id": "dht1", "top": -86.4, "left": 105.6, "attrs": { "temperature": "25", "humidity": "55" } },
    { "type": "wokwi-ssd1306", "id": "oled1", "top": 141.1, "left": 289.9, "attrs": { "i2cAddress": "0x3C" } },
    { "type": "wokwi-buzzer", "id": "bz1", "top": 96, "left": 518.4, "attrs": { "volume": "0.3" } }
  ],
  "connections": [
    [ "esp:TX0", "$serialMonitor:RX", "", [] ],
    [ "esp:RX0", "$serialMonitor:TX", "", [] ],
    [ "led1:A", "esp:D26", "yellow", ["v0"] ],
    [ "led1:C", "r1:1", "yellow", ["v0"] ],
    [ "r1:2", "esp:GND.1", "black", ["v0"] ],
    [ "led2:A", "esp:D27", "white", ["v0"] ],
    [ "led2:C", "r2:1", "white", ["v0"] ],
    [ "r2:2", "esp:GND.1", "black", ["v0"] ],
    [ "led3:A", "esp:D14", "blue", ["v0"] ],
    [ "led3:C", "r3:1", "blue", ["v0"] ],
    [ "r3:2", "esp:GND.1", "black", ["v0"] ],
    [ "led4:A", "esp:D12", "red", ["v0"] ],
    [ "led4:C", "r4:1", "red", ["v0"] ],
    [ "r4:2", "esp:GND.1", "black", ["v0"] ],
    [ "led5:A", "esp:D13", "green", ["v0"] ],
    [ "led5:C", "r5:1", "green", ["v0"] ],
    [ "r5:2", "esp:GND.1", "black", ["v0"] ],
    [ "dht1:VCC", "esp:3V3", "red", ["v0"] ],
    [ "dht1:GND", "esp:GND.1", "black", ["v0"] ],
    [ "dht1:SDA", "esp:D15", "green", ["v0"] ],
    [ "oled1:VCC", "esp:3V3", "red", ["h0"] ],
    [ "oled1:GND", "esp:GND.2", "black", ["h0"] ],
    [ "oled1:SDA", "esp:D21", "blue", ["h0"] ],
    [ "oled1:SCL", "esp:D22", "yellow", ["h0"] ],
    [ "bz1:1", "esp:D25", "purple", ["v0"] ],
    [ "bz1:2", "esp:GND.2", "black", ["v0"] ]
  ],
  "dependencies": {}
}
05 · Web Interface

What You See in the Browser

When you open the ESP32's IP in any browser, you get this control panel. Toggle switches and sliders update the hardware in real time.

🏠 Smart Home Control Panel
http://192.168.1.x/
25.0
Temperature °C
55.0
Humidity %
-72
Wi-Fi dBm
💡
Living Room
Pin D26
● OFF
🌙
Bedroom
Pin D27
● OFF
🌀
Ceiling Fan
Pin D14 (PWM)
● 0%
Speed: 0%
🔥
Heater
Pin D12
● OFF
🚪
Door Lock
Pin D13
🔒 LOCKED

🌐 This is a live demo of the control panel — buttons actually work in this preview!

06 · HTTP API Routes

Control via URL

Every device can be controlled by simply visiting a URL — great for automation scripts, Tasker, or any HTTP client.

GET/Main control panel (requires login)
GET/statusReturns JSON with all device states + temperature
GET/living-room/onTurn Living Room light ON
GET/living-room/offTurn Living Room light OFF
GET/bedroom/onTurn Bedroom light ON
GET/bedroom/offTurn Bedroom light OFF
GET/fan/set?speed=75Set fan PWM speed 0–100%
GET/heater/onTurn Heater ON
GET/heater/offTurn Heater OFF
GET/door/lockLock the door
GET/door/unlockUnlock the door
GET/all/onTurn all lights ON simultaneously
GET/all/offTurn all devices OFF
GET/logoutLog out of the web panel
// /status returns JSON like:
{
  "livingRoomLight": true,
  "bedroomLight": false,
  "fanSpeed": 75,
  "temperature": 25.00,
  "humidity": 55.00,
  "uptime": 120
}
07 · Arduino Code

ESP32 Sketch

Install these libraries first via Arduino Library Manager: DHT sensor library, Adafruit GFX, Adafruit SSD1306. The WiFi.h and WebServer.h libraries come built-in with the ESP32 Arduino core.

esp32_webserver.ino
/*
 * ESP32 Web Server for Remote Device Control
 * MakeMindz.com | makemindz.com
 * Features: 5 devices, DHT22 sensor, OLED display,
 *           PWM fan control, HTTP Basic Auth, JSON API
 */

#include <WiFi.h>
#include <WebServer.h>
#include <DHT.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// ── Wi-Fi Credentials ─────────────────────────
const char* ssid     = "Wokwi-GUEST"; // Change for real network
const char* password = "";

// ── Web Server (port 80) ──────────────────────
WebServer server(80);

// ── Auth Credentials ──────────────────────────
const char* webUsername = "admin";
const char* webPassword = "1234";

// ── Pin Definitions ───────────────────────────
#define LED1_PIN      26  // Living Room
#define LED2_PIN      27  // Bedroom
#define FAN_PIN       14  // Fan (PWM)
#define HEATER_PIN    12  // Heater
#define DOOR_LOCK_PIN 13  // Door Lock
#define DHT_PIN       15  // DHT22 data
#define BUZZER_PIN    25  // Buzzer
#define DHT_TYPE      DHT22

// ── Sensor & Display Objects ──────────────────
DHT dht(DHT_PIN, DHT_TYPE);
Adafruit_SSD1306 display(128, 64, &Wire, -1);

// ── Device State Struct ───────────────────────
struct {
  bool  livingRoomLight = false;
  bool  bedroomLight    = false;
  bool  doorLock        = true;   // Locked by default
  bool  heater          = false;
  int   fanSpeed        = 0;      // 0–100 %
  float temperature     = 0;
  float humidity        = 0;
} devices;

unsigned long lastSensor = 0;

// ── Helpers ───────────────────────────────────
void applyStates() {
  digitalWrite(LED1_PIN,      devices.livingRoomLight);
  digitalWrite(LED2_PIN,      devices.bedroomLight);
  digitalWrite(HEATER_PIN,    devices.heater);
  digitalWrite(DOOR_LOCK_PIN, devices.doorLock);
  ledcWrite(FAN_PIN, map(devices.fanSpeed, 0, 100, 0, 255));
}

void readSensors() {
  devices.temperature = dht.readTemperature();
  devices.humidity    = dht.readHumidity();
  if (isnan(devices.temperature)) devices.temperature = 0;
  if (isnan(devices.humidity))    devices.humidity    = 0;
}

void updateOLED() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("SMART HOME");
  display.drawLine(0, 9, 128, 9, SSD1306_WHITE);
  display.setCursor(0, 12); display.print("Temp: "); display.print(devices.temperature, 1); display.println("C");
  display.setCursor(0, 22); display.print("Living: "); display.println(devices.livingRoomLight ? "ON" : "OFF");
  display.setCursor(0, 32); display.print("Bedroom: "); display.println(devices.bedroomLight ? "ON" : "OFF");
  display.setCursor(0, 42); display.print("Fan: "); display.print(devices.fanSpeed); display.println("%");
  display.setCursor(0, 52); display.print("Door: "); display.println(devices.doorLock ? "LOCKED" : "OPEN");
  display.display();
}

bool checkAuth() {
  if (!server.authenticate(webUsername, webPassword)) {
    server.requestAuthentication(); return false;
  }
  return true;
}

void redirectHome() {
  server.sendHeader("Location", "/"); server.send(303);
}

// ── Route Handlers ────────────────────────────
void handleStatus() {
  String j = "{";
  j += "\"livingRoomLight\":" + String(devices.livingRoomLight ? "true" : "false") + ",";
  j += "\"bedroomLight\":"    + String(devices.bedroomLight    ? "true" : "false") + ",";
  j += "\"fanSpeed\":"        + String(devices.fanSpeed)                            + ",";
  j += "\"heater\":"          + String(devices.heater          ? "true" : "false") + ",";
  j += "\"doorLock\":"        + String(devices.doorLock        ? "true" : "false") + ",";
  j += "\"temperature\":"     + String(devices.temperature, 2)                     + ",";
  j += "\"humidity\":"        + String(devices.humidity, 2)                        + ",";
  j += "\"uptime\":"          + String(millis() / 1000) + "}";
  server.send(200, "application/json", j);
}

void setup() {
  Serial.begin(115200);
  pinMode(LED1_PIN, OUTPUT);      pinMode(LED2_PIN, OUTPUT);
  pinMode(HEATER_PIN, OUTPUT);    pinMode(DOOR_LOCK_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  ledcAttach(FAN_PIN, 5000, 8); // ESP32 Arduino Core 3.x API
  dht.begin();
  if (display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { display.clearDisplay(); display.display(); }
  applyStates();
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println("\nIP: " + WiFi.localIP().toString());
  server.on("/",               []() { if(checkAuth()) server.send(200, "text/html", generateHTML()); });
  server.on("/status",         handleStatus);
  server.on("/living-room/on", []() { if(checkAuth()){ devices.livingRoomLight=true;  applyStates(); redirectHome(); }});
  server.on("/living-room/off",[]() { if(checkAuth()){ devices.livingRoomLight=false; applyStates(); redirectHome(); }});
  server.on("/bedroom/on",     []() { if(checkAuth()){ devices.bedroomLight=true;    applyStates(); redirectHome(); }});
  server.on("/bedroom/off",    []() { if(checkAuth()){ devices.bedroomLight=false;   applyStates(); redirectHome(); }});
  server.on("/heater/on",      []() { if(checkAuth()){ devices.heater=true;          applyStates(); redirectHome(); }});
  server.on("/heater/off",     []() { if(checkAuth()){ devices.heater=false;         applyStates(); redirectHome(); }});
  server.on("/door/lock",      []() { if(checkAuth()){ devices.doorLock=true;        applyStates(); redirectHome(); }});
  server.on("/door/unlock",    []() { if(checkAuth()){ devices.doorLock=false;       applyStates(); redirectHome(); }});
  server.on("/all/on",         []() { if(checkAuth()){ devices.livingRoomLight=devices.bedroomLight=true; applyStates(); redirectHome(); }});
  server.on("/all/off",        []() { if(checkAuth()){ devices.livingRoomLight=devices.bedroomLight=devices.heater=false; devices.fanSpeed=0; applyStates(); redirectHome(); }});
  server.on("/fan/set", []() {
    if(!checkAuth()) return;
    if(server.hasArg("speed")) devices.fanSpeed = constrain(server.arg("speed").toInt(), 0, 100);
    applyStates(); redirectHome();
  });
  server.begin();
  Serial.println("Server ready at http://" + WiFi.localIP().toString());
  tone(BUZZER_PIN, 1000, 100); delay(150); tone(BUZZER_PIN, 1500, 100);
}

void loop() {
  server.handleClient();
  if (millis() - lastSensor >= 2000) {
    lastSensor = millis();
    readSensors();
    updateOLED();
  }
}
💡 Libraries to install in Arduino IDE:
DHT sensor library Adafruit GFX Library Adafruit SSD1306 WiFi.h (built-in) WebServer.h (built-in)
08 · Try It Online

Run in Wokwi Simulator

No hardware required. Open the Wokwi simulation, paste the diagram.json and code, then start the simulation. The Serial Monitor will show the IP address — click the link to open the web panel inside Wokwi's virtual browser.

Open Wokwi Simulation

The full circuit with ESP32, 5 LEDs, DHT22, OLED and buzzer is pre-configured. Start simulation and copy the IP from the Serial Monitor to open the dashboard.

▶ Launch Wokwi ↗
🧪 What to try in Wokwi:
  1. Start simulation — watch the OLED display show the startup message.
  2. Open the Serial Monitor — copy the IP address printed after Wi-Fi connects.
  3. Click the network link in the Wokwi panel to open the web control page.
  4. Toggle each device and watch the corresponding LED light up.
  5. Drag the fan slider to 75% and watch the blue LED brightness change.
  6. Challenge: Change the Wi-Fi SSID and password to use your real router.
  7. Advanced: Add a /toggle route that flips any device without visiting ON/OFF separately.
09 · Check Your Understanding

Quick Quiz

Click an option to check your answer.

Q1. Why does the ESP32 need to be on the same Wi-Fi network as your phone/laptop?

To share internet access
So the browser can reach the ESP32’s local IP address
To enable Bluetooth pairing

Q2. What does ledcAttach(FAN_PIN, 5000, 8) do?

Sets the fan pin to digitalWrite HIGH
Configures PWM on pin D14 at 5kHz with 8-bit resolution
Reads an analog voltage from the fan

Q3. What does visiting /status return?

The HTML control panel
A JSON object with all device states and sensor readings
A command to refresh the OLED display

Q4. Why does the handleRoot function call server.authenticate()?

To make the page load faster
To re-connect to Wi-Fi before serving the page
To require username/password before anyone can control devices
10 · Applications

Where Is This Used?

🏠Smart HomeControl lights and appliances from your phone
🏭WorkshopRemote control of equipment and lighting
🏢Office AutomationOccupancy-based lighting control
🏫Lab DemonstrationsTeach IoT web server concepts
💻IoT DashboardBase for more complex MQTT/cloud systems
🚫Access ControlRemote door lock from any browser
🚀 Level-Up Extensions:
  • Replace LEDs with relay modules to control real AC appliances
  • Add mDNS (esp32.local) so you never need to remember the IP
  • Add WebSocket support for real-time push updates without page refresh
  • Add SPIFFS/LittleFS to serve HTML from the flash filesystem
  • Connect to MQTT broker (Mosquitto) for cloud-free remote access
  • Add a scheduling system using the DS3231 RTC module

Comments

try for free