Introduction to ESP32 WebSocket Communication

In this comprehensive tutorial, you'll learn how to create an ESP32 web server using the WebSocket communication protocol. We'll build a practical web interface that lets you remotely control your ESP32's outputs, with all connected clients receiving automatic updates when changes occur. This approach solves a common problem in traditional web server projects where multiple browser tabs or devices don't synchronize their displayed state without manual refreshing.

Tutorial-for-Creating-ESP32-Websocket-Server

If you've worked with ESP32 web servers before, you've likely encountered situations where opening multiple tabs or using several devices simultaneously causes display inconsistencies. Each client shows different state information until you refresh the page. WebSocket protocol elegantly solves this issue by maintaining persistent connections that allow instant bidirectional communication between server and clients.

Note: This tutorial builds upon work originally documented by Stéphane Calderoni, adapted here for clarity and practical application. WebSocket technology represents a significant advancement over traditional HTTP requests for IoT applications where real-time updates are essential.

Understanding WebSocket Technology

A WebSocket establishes a persistent, two-way communication channel between a client (like your web browser) and a server (your ESP32). Unlike traditional HTTP requests that follow a request-response pattern and then close the connection, WebSocket maintains an open TCP connection that allows either party to send data at any time.

websocket-vs-http

The connection begins with a WebSocket handshake—an HTTP request/response sequence that upgrades the connection protocol. This clever approach allows servers to handle both standard HTTP and WebSocket connections on the same port (typically port 80). Once established, the connection operates in full-duplex mode, meaning both server and client can send and receive data simultaneously.

For ESP32 IoT projects, this means your board can proactively send updates to connected web browsers without waiting for a request. Whether someone clicks a button on your web interface or you press a physical button connected to the ESP32, all connected clients receive the updated information instantly.

Project Overview and Functionality

ESP32-ESP8266-Control-Outputs-with-Web-Server-and-Physical-Button-Simultaneously

Let's examine what we'll create in this project:

The interface elegantly shows the current GPIO state, and any changes to this state update immediately across all connected web interfaces. This means if you have three browser tabs open on your laptop and two smartphones connected simultaneously, all five interfaces will show identical, synchronized information at all times.

How the WebSocket Communication Works

1 User Action

You click the "Toggle" button on the web page. This action initiates the WebSocket communication sequence.

2 Client Transmission

Your browser sends a "toggle" message via the WebSocket connection to the ESP32 server. This message is lightweight and instantly transmitted.

3 Server Processing

The ESP32 receives this message and toggles the LED state (OFF→ON or ON→OFF). The server logic processes the command and updates its internal state.

4 Broadcast Update

The ESP32 sends the new LED state to ALL connected clients also through WebSocket protocol. This broadcast ensures everyone sees the same state.

5 Client Update

Each connected browser receives the message and updates the LED state on the web page accordingly. This allows us to update all clients almost instantaneously when a change happens.

Performance Note: This entire process happens in milliseconds, creating a seamless, real-time control experience that traditional HTTP-based approaches cannot match.

Preparing Your Development Environment

Before we dive into the code, we need to set up our development environment properly. We'll program the ESP32 using Arduino IDE, so ensure you have it installed with ESP32 board support configured.

Required Components

  • ESP32 development board (any model with Wi-Fi)
  • USB cable for programming and power
  • Computer with Arduino IDE installed
  • Wi-Fi network for ESP32 connection
  • Web browser for testing (Chrome, Firefox, or Edge recommended)

Installing Required Libraries

Our project relies on two essential libraries that make WebSocket implementation straightforward:

  1. ESPAsyncWebServer by ESP32Async
  2. AsyncTCP by ESP32Async

Install these through the Arduino Library Manager:

1 Open Library Manager

Click the Library icon in the left sidebar (or navigate to Sketch → Include Library → Manage Libraries).

2 Install ESPAsyncWebServer

Search for "ESPAsyncWebServer" and install the version by "ESP32Async".

Install-ESPAsyncWebServer-Library-ArduinoIDE-2-f

3 Install AsyncTCP

Search for "AsyncTCP" and install the version by "ESP32Async".

Library Insight: These libraries handle the complex asynchronous network operations, allowing our ESP32 to manage multiple connections simultaneously without blocking operations. The asynchronous nature means the ESP32 can handle WebSocket communications while simultaneously performing other tasks in your project.

Complete ESP32 WebSocket Server Code

Here's the complete code for our ESP32 WebSocket server. Copy this into your Arduino IDE, and we'll break down how it works section by section:

// Import required libraries #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; bool ledState = 0; const int ledPin = 2; // Create AsyncWebServer object on port 80 AsyncWebServer server(80); AsyncWebSocket ws("/ws"); const char index_html[] PROGMEM = R"rawliteral( <!DOCTYPE HTML><html> <head> <title>ESP Web Server</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" href="data:,"> <style> html { font-family: Arial, Helvetica, sans-serif; text-align: center; } h1 { font-size: 1.8rem; color: white; } h2{ font-size: 1.5rem; font-weight: bold; color: #143642; } .topnav { overflow: hidden; background-color: #143642; } body { margin: 0; } .content { padding: 30px; max-width: 600px; margin: 0 auto; } .card { background-color: #F8F7F9;; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); padding-top:10px; padding-bottom:20px; } .button { padding: 15px 50px; font-size: 24px; text-align: center; outline: none; color: #fff; background-color: #0f8b8d; border: none; border-radius: 5px; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-tap-highlight-color: rgba(0,0,0,0); } .button:active { background-color: #0f8b8d; box-shadow: 2 2px #CDCDCD; transform: translateY(2px); } .state { font-size: 1.5rem; color:#8c8c8c; font-weight: bold; } </style> <title>ESP Web Server</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" href="data:,"> </head> <body> <div class="topnav"> <h1>ESP WebSocket Server</h1> </div> <div class="content"> <div class="card"> <h2>Output - GPIO 2</h2> <p class="state">state: <span id="state">%STATE%</span></p> <p><button id="button" class="button">Toggle</button></p> </div> </div> <script> var gateway = `ws://${window.location.hostname}/ws`; var websocket; window.addEventListener('load', onLoad); function initWebSocket() { console.log('Trying to open a WebSocket connection...'); websocket = new WebSocket(gateway); websocket.onopen = onOpen; websocket.onclose = onClose; websocket.onmessage = onMessage; } function onOpen(event) { console.log('Connection opened'); } function onClose(event) { console.log('Connection closed'); setTimeout(initWebSocket, 2000); } function onMessage(event) { var state; if (event.data == "1"){ state = "ON"; } else{ state = "OFF"; } document.getElementById('state').innerHTML = state; } function onLoad(event) { initWebSocket(); initButton(); } function initButton() { document.getElementById('button').addEventListener('click', toggle); } function toggle(){ websocket.send('toggle'); } </script> </body> </html> )rawliteral"; void notifyClients() { ws.textAll(String(ledState)); } void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) { AwsFrameInfo *info = (AwsFrameInfo*)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { data[len] = 0; if (strcmp((char*)data, "toggle") == 0) { ledState = !ledState; notifyClients(); } } } void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { switch (type) { case WS_EVT_CONNECT: Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); break; case WS_EVT_DISCONNECT: Serial.printf("WebSocket client #%u disconnected\n", client->id()); break; case WS_EVT_DATA: handleWebSocketMessage(arg, data, len); break; case WS_EVT_PONG: case WS_EVT_ERROR: break; } } void initWebSocket() { ws.onEvent(onEvent); server.addHandler(&ws); } String processor(const String& var){ Serial.println(var); if(var == "STATE"){ if (ledState){ return "ON"; } else{ return "OFF"; } } return String(); } void setup(){ // Serial port for debugging purposes Serial.begin(115200); pinMode(ledPin, OUTPUT); digitalWrite(ledPin, LOW); // Connect to Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi.."); } // Print ESP Local IP Address Serial.println(WiFi.localIP()); initWebSocket(); // Route for root / web page server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(200, "text/html", index_html, processor); }); // Start server server.begin(); } void loop() { ws.cleanupClients(); digitalWrite(ledPin, ledState); }
Important Configuration Step: Insert your actual Wi-Fi credentials in these variables:
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Code Explanation and Breakdown

Now let's examine how this code works in detail, section by section:

Library Imports and Network Configuration

We begin by importing necessary libraries. The WiFi.h provides ESP32 Wi-Fi functionality, while AsyncTCP.h and ESPAsyncWebServer.h give us the asynchronous web server capabilities with WebSocket support.

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

The network credentials section is where you personalize the code for your environment. Make sure to use your actual Wi-Fi network name and password.

GPIO Configuration and Server Objects

We define variables to track the LED state and which pin we're controlling:

bool ledState = 0; // Tracks current LED state (0=OFF, 1=ON)
const int ledPin = 2; // GPIO 2, which controls the built-in LED

Next, we create the web server and WebSocket objects:

AsyncWebServer server(80); // HTTP server on port 80
AsyncWebSocket ws("/ws"); // WebSocket endpoint at /ws path

The server handles regular HTTP requests, while the WebSocket object manages the persistent connections for real-time communication.

HTML, CSS, and JavaScript Interface

The index_html variable contains our complete web interface. Let's examine its key components:

1 HTML Structure

The page has a clean, responsive design with:

  • A navigation header showing "ESP WebSocket Server"
  • A content card displaying the GPIO 2 output status
  • A toggle button for changing the LED state

2 CSS Styling

We've included attractive styling that creates a modern card-based interface with:

  • Responsive design that works on mobile and desktop
  • Visual feedback for button presses
  • Clear typography and color coding

3 JavaScript WebSocket Implementation

The script section handles all client-side WebSocket operations:

var gateway = `ws://${window.location.hostname}/ws`;
var websocket;

This JavaScript establishes the WebSocket connection to the ESP32, defines event handlers for connection events, and manages the button click interactions. The onMessage function processes incoming WebSocket messages to update the displayed state.

WebSocket Event Handling on ESP32

The C++ code includes several crucial functions for WebSocket management:

When a "toggle" message arrives from any client, this function flips the ledState variable and calls notifyClients() to broadcast the change to all connected devices.

void notifyClients() {
ws.textAll(String(ledState));
}

This simple but powerful function sends the current LED state to every connected WebSocket client, ensuring perfect synchronization.

Template Processing Function

The processor() function replaces placeholders in our HTML template with dynamic values:

String processor(const String& var){
if(var == "STATE"){
return ledState ? "ON" : "OFF";
}
return String();
}

When a client first loads the web page, the %STATE% placeholder in the HTML gets replaced with either "ON" or "OFF" based on the current LED state.

Setup and Loop Functions

The setup() function initializes all components:

  1. Starts serial communication for debugging
  2. Configures the LED pin as an output
  3. Connects to Wi-Fi
  4. Initializes WebSocket handlers
  5. Sets up the HTTP route for the main page
  6. Starts the web server

The loop() function continuously:

Install-ESPAsyncWebServer-Library-ArduinoIDE-2-f

Uploading and Testing Your WebSocket Server

Once you've inserted your Wi-Fi credentials, upload the code to your ESP32:

1 Select Board and Port

Select your ESP32 board in Arduino IDE (Tools → Board) and choose the correct port (Tools → Port).

2 Upload Code

Click the Upload button and wait for the compilation and upload to complete.

3 Monitor Serial Output

Open the Serial Monitor (Tools → Serial Monitor) at 115200 baud to see the ESP32 connecting to Wi-Fi and displaying its IP address.

get-IP-address

4 Access Web Interface

Note the IP address displayed in the Serial Monitor. Open a web browser and navigate to this IP address to access your WebSocket control interface.

Demonstration and Real-World Testing

Open a web browser and navigate to your ESP32's IP address. You'll see the clean web interface with the current GPIO state and a "Toggle" button.

Try these tests to verify WebSocket functionality:

Test Scenarios

  • Single Device Test: Click the toggle button and observe the state change
  • Multiple Tabs Test: Open several browser tabs to the same IP address. Click the button in one tab—all tabs should update simultaneously
  • Multiple Devices Test: Access the web interface from different devices (phone, tablet, another computer). Changes from any device should appear on all devices instantly
  • Connection Resilience: The JavaScript automatically attempts to reconnect every 2 seconds if the WebSocket connection drops, ensuring robustness
ESP8266-Multiple-Sliders-Web-Server-PWM-LEDs

Expanding the Project

While we're controlling just GPIO 2 in this example, you can easily extend this to:

Control Multiple GPIOs

Add more buttons and state variables to control additional outputs. You would need to:

  1. Add more state variables and pin definitions
  2. Expand the HTML interface with additional controls
  3. Modify the WebSocket message protocol to handle multiple controls
  4. Update the message handler to process different commands

Read Sensor Data

Push sensor readings to all connected clients automatically. This is perfect for:

  • Temperature/humidity monitoring
  • Motion detection alerts
  • Environmental sensor dashboards
  • Real-time data visualization

Advanced WebSocket Features

Enhance your project with advanced capabilities:

  • Create a Dashboard: Build a complete IoT control panel with multiple controls and indicators
  • Add Authentication: Implement WebSocket authentication for secure applications
  • Use the SPIFFS Filesystem: Store HTML, CSS, and JavaScript in separate files for easier management
  • Implement Binary Data: Send binary data for more efficient communication

Troubleshooting Common Issues

WebSocket Connection Fails

If WebSocket connections fail to establish:

  • Ensure both ESPAsyncWebServer and AsyncTCP libraries are installed correctly
  • Check that your firewall isn't blocking WebSocket connections (port 80)
  • Verify the ESP32 is connected to Wi-Fi (check Serial Monitor)
  • Try accessing with different browsers (Chrome, Firefox, Edge)

Can't Connect to Wi-Fi

If the ESP32 fails to connect to your Wi-Fi network:

  • Verify your credentials and that you're using 2.4GHz network (ESP32 doesn't support 5GHz)
  • Check that your Wi-Fi network is within range
  • Ensure your router isn't blocking the ESP32 MAC address
  • Try resetting the ESP32 and uploading the code again

Multiple Clients Not Syncing

If connected clients show different states:

  • Check that notifyClients() is called after every state change
  • Verify all clients are connecting to the same ESP32 IP address
  • Open browser developer tools (F12) and check the Console tab for JavaScript errors
  • Ensure all clients have JavaScript enabled

Compilation Errors

If you encounter compilation errors in Arduino IDE:

  • Ensure you have ESP32 board support installed in Arduino IDE
  • Verify you've installed the correct library versions
  • Check for duplicate library installations
  • Try cleaning the build cache (Sketch → Clean Cache)
ESP32-troubleshooting

Conclusion

You've successfully built an ESP32 WebSocket server that enables real-time, synchronized control of GPIO outputs across multiple clients. This approach represents a significant improvement over traditional HTTP-based ESP32 web servers for applications requiring instant updates and multi-client synchronization.

Project Summary: The WebSocket protocol opens up exciting possibilities for ESP32 projects, from home automation systems with multiple control points to collaborative IoT applications where several users need to see the same real-time data. You now have a solid foundation to build upon for your own IoT projects.

Remember that while we embedded the HTML directly in the Arduino sketch for simplicity, larger projects benefit from using the SPIFFS filesystem to store web resources separately. This makes editing your interface much easier without modifying and re-uploading the entire program.

This implementation provides a solid foundation you can adapt and expand for your specific IoT projects. The real-time bidirectional communication capabilities of WebSockets significantly enhance the user experience of ESP32 web interfaces, making them feel more like native applications than traditional web pages.

Experiment with adding more controls, integrating sensor data, or creating a complete home automation dashboard. The WebSocket approach scales well and maintains excellent performance even with multiple connected clients. Happy coding with your ESP32 WebSocket server!