Why WebSockets?
A web server on ESP32 is great for static pages, but once you need live sensor graphs, joystick controls, or real-time status indicators, HTTP polling becomes wasteful. WebSockets upgrade the HTTP handshake into a persistent bidirectional TCP pipe — the ESP32 pushes data the instant it changes, and the browser sends commands back on the same connection.
Libraries Required
Install both via Arduino Library Manager:
- ESPAsyncWebServer (by lacamera or me-no-dev)
- AsyncTCP (dependency, for ESP32)
Complete WebSocket LED Controller
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
const char* ssid = "YourSSID";
const char* password = "YourPassword";
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
const int LED_PIN = 2;
bool ledState = false;
// HTML page served from PROGMEM
const char INDEX_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html><html><head>
<meta charset='utf-8'><title>ESP32 WS</title>
<style>body{font-family:sans-serif;max-width:400px;margin:2rem auto}
.btn{padding:.6rem 1.4rem;font-size:1rem;cursor:pointer;border-radius:6px;border:none;color:#fff}
.on{background:#16a34a}.off{background:#dc2626}</style></head><body>
<h1>LED Control</h1>
<p>State: <strong id='state'>--</strong></p>
<button class='btn on' onclick='send("on")'>Turn ON</button>
<button class='btn off' onclick='send("off")'>Turn OFF</button>
<script>
var ws = new WebSocket('ws://' + location.host + '/ws');
ws.onmessage = e => document.getElementById('state').textContent = e.data;
ws.onclose = () => setTimeout(() => location.reload(), 3000);
function send(cmd) { ws.send(cmd); }
</script></body></html>
)rawliteral";
void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client,
AwsEventType type, void* arg, uint8_t* data, size_t len) {
if (type == WS_EVT_CONNECT) {
Serial.printf("WS client #%u connected\n", client->id());
client->text(ledState ? "ON" : "OFF"); // send current state on connect
}
else if (type == WS_EVT_DISCONNECT) {
Serial.printf("WS client #%u disconnected\n", client->id());
}
else if (type == WS_EVT_DATA) {
AwsFrameInfo* info = (AwsFrameInfo*)arg;
if (info->final && info->opcode == WS_TEXT) {
String msg = "";
for (size_t i = 0; i < len; i++) msg += (char)data[i];
if (msg == "on") { ledState = true; digitalWrite(LED_PIN, HIGH); }
if (msg == "off") { ledState = false; digitalWrite(LED_PIN, LOW); }
ws.textAll(ledState ? "ON" : "OFF"); // broadcast to all clients
}
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println("\nIP: " + 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(); // free disconnected client memory
}
Broadcasting Sensor Data Periodically
void loop() {
ws.cleanupClients();
static unsigned long last = 0;
if (millis() - last >= 1000) {
last = millis();
// Build JSON payload
float temp = 20.0 + (analogRead(34) / 4095.0) * 20.0;
char buf[64];
snprintf(buf, sizeof(buf),
"{\"temp\":%.1f,\"uptime\":%lu}", temp, millis() / 1000);
if (ws.count() > 0) { // only send if anyone is listening
ws.textAll(buf);
}
}
}
Ping/Pong and Connection Health
WebSocket connections can silently die when a client’s network changes. Configure pings to detect dead connections:
// In onWsEvent, handle the pong:
else if (type == WS_EVT_PONG) {
Serial.printf("Client #%u pong received\n", client->id());
}
// In loop(), ping all clients every 30 s:
static unsigned long lastPing = 0;
if (millis() - lastPing > 30000) {
lastPing = millis();
ws.pingAll();
}
Client-Side Reconnect
Add this JavaScript to any page to automatically reconnect when the ESP32 reboots:
function connectWS() {
const ws = new WebSocket('ws://' + location.host + '/ws');
ws.onopen = () => console.log('WS connected');
ws.onmessage = e => updateUI(JSON.parse(e.data));
ws.onclose = () => {
console.log('WS closed — reconnecting in 3 s');
setTimeout(connectWS, 3000);
};
ws.onerror = () => ws.close();
return ws;
}
const socket = connectWS();