If you've ever wanted a live, browser-based dashboard that updates sensor values without page refresh, WebSockets are the answer. Unlike polling (where the browser asks "any news?" every N seconds), WebSocket keeps a persistent two-way connection open — the ESP32 pushes data the moment it's ready.

// What you'll build: ESP32 reads a DHT22 sensor every 2 seconds and broadcasts temperature, humidity, and uptime to every connected browser over WebSocket. The dashboard updates live with animated gauges — no frameworks, pure HTML+JS.
BROWSER HTML Dashboard JS WebSocket client ws://192.168.x.x/ws persistent TCP ESP32 AsyncWebServer AsyncWebSocket JSON broadcast every 2 seconds GPIO / I²C DHT22 Temp + Humidity GPIO 4 (data) + BME280 + DS18B20
// AD SLOT — IN-CONTENT RESPONSIVE

Prerequisites

  • ESP32 development board (any variant)
  • DHT22 sensor + 10kΩ pull-up resistor
  • Arduino IDE 2.x with ESP32 board support
  • Libraries: ESPAsyncWebServer, AsyncTCP, DHT sensor library, ArduinoJson
LibraryAuthorInstall via
ESPAsyncWebServerme-no-devGitHub ZIP
AsyncTCPme-no-devGitHub ZIP
ArduinoJsonBenoît BlanchonLibrary Manager
DHT sensor libraryAdafruitLibrary Manager

Wiring — DHT22 to ESP32

DHT22 PinESP32 PinNotes
VCC3.3V
DATAGPIO 410kΩ pull-up to 3.3V
GNDGND

Full Arduino Sketch

// main.ino
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <DHT.h>
#include <ArduinoJson.h>

const char* SSID     = "YOUR_SSID";
const char* PASSWORD = "YOUR_PASSWORD";

#define DHT_PIN  4
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);

AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

unsigned long lastBroadcast = 0;
const unsigned long INTERVAL = 2000;

// Minimal dashboard HTML served from PROGMEM
const char INDEX_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html><html><head>
<title>ESP32 Dashboard</title>
<style>
  body{background:#020810;color:#c8e6f0;font-family:monospace;display:flex;flex-direction:column;align-items:center;padding:40px;}
  .card{background:#071525;border:1px solid rgba(0,245,255,.2);border-radius:8px;padding:24px 40px;margin:12px;min-width:200px;text-align:center;}
  .val{font-size:3rem;color:#00f5ff;font-weight:bold;}
  .lbl{color:#6a9bb5;font-size:.85rem;margin-top:6px;}
  h1{color:#00ff88;letter-spacing:4px;}
</style></head><body>
<h1>ESP32 LIVE DASHBOARD</h1>
<div style="display:flex;gap:20px;flex-wrap:wrap;justify-content:center;">
  <div class="card"><div class="val" id="temp">--</div><div class="lbl">TEMPERATURE (°C)</div></div>
  <div class="card"><div class="val" id="hum">--</div><div class="lbl">HUMIDITY (%)</div></div>
  <div class="card"><div class="val" id="uptime">--</div><div class="lbl">UPTIME (s)</div></div>
</div>
<script>
  const ws = new WebSocket('ws://' + location.hostname + '/ws');
  ws.onmessage = e => {
    const d = JSON.parse(e.data);
    document.getElementById('temp').textContent    = d.temp.toFixed(1);
    document.getElementById('hum').textContent     = d.hum.toFixed(1);
    document.getElementById('uptime').textContent  = d.uptime;
  };
</script></body></html>
)rawliteral";

void broadcastSensorData() {
  float t = dht.readTemperature();
  float h = dht.readHumidity();
  if (isnan(t) || isnan(h)) return;

  StaticJsonDocument<128> doc;
  doc["temp"]   = t;
  doc["hum"]    = h;
  doc["uptime"] = millis() / 1000;

  String payload;
  serializeJson(doc, payload);
  ws.textAll(payload);
}

void onWsEvent(AsyncWebSocket* s, AsyncWebSocketClient* c,
               AwsEventType type, void* arg, uint8_t* data, size_t len) {
  // handle connect/disconnect if needed
}

void setup() {
  Serial.begin(115200);
  dht.begin();

  WiFi.begin(SSID, PASSWORD);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.println("IP: " + WiFi.localIP().toString());

  ws.onEvent(onWsEvent);
  server.addHandler(&ws);
  server.on("/", HTTP_GET, [](AsyncWebServerRequest* req) {
    req->send_P(200, "text/html", INDEX_HTML);
  });
  server.begin();
}

void loop() {
  ws.cleanupClients();
  if (millis() - lastBroadcast >= INTERVAL) {
    lastBroadcast = millis();
    broadcastSensorData();
  }
}

Testing

  1. Flash the sketch and open the Serial Monitor at 115200 baud
  2. Note the IP address printed — e.g. 192.168.1.105
  3. Open http://192.168.1.105 in any browser on the same WiFi network
  4. Values update live every 2 seconds — open multiple tabs to confirm multi-client broadcast
// Tip: Use ws.cleanupClients() in loop() to automatically remove disconnected clients and prevent memory leaks.

Going Further

  • Add chart.js for live graphing of historical readings
  • Send commands back to ESP32 via ws.onmessage to toggle relays or LEDs
  • Secure the dashboard with HTTP Basic Auth using request->authenticate()
  • Log readings to SD card using the AsyncWebServer SD File Manager
A
Engr. Aamir Aziz Butt
PhD Researcher · IoT Engineer · Founder ESPSTACK

PhD candidate at Muslim Youth University, Islamabad. MS Computer Engineering from COMSATS. 10+ years of IoT development experience across ESP32, Jetson Nano, and cloud platforms.