Let�s say we need to create a project that uses the ESP32 to control a light bulb over WiFi. The implementation is quite simple: we will set up the ESP32 in soft-AP or STA mode, allowing it to serve a web page displaying the light switch�s state as either �on� or �off.� When a user toggles the light switch in the browser, the ESP32 will receive an HTTP request. In response, it will adjust the light bulb�s state accordingly, and send a response back to the browser. That�s exactly what we did when building a simple ESP32 web server, and it works perfectly.

However, there is a small problem with this solution. The web is a multi-user system, which means that many people can connect to the same web server. To put it another way, the server is a shared resource. What happens if two users try to switch the light bulb on or off at the same time? The two browser interfaces will fall out of sync and won�t accurately show the actual state of the light bulb. For this reason, it is not a suitable solution for such a system.

Instead, we can take advantage of the bi-directional capabilities of the WebSocket communication protocol to build a responsive system in which a click on the light switch is immediately relayed to the server and broadcast to all connected browsers, so that the light switch state is always synchronized for all users.

That�s precisely what you�ll learn in this tutorial: how to create a web server with the ESP32 using the WebSocket communication protocol. For a practical demonstration, we�ll create a web page to control the on-board LED of the ESP32. The state of the LED will be displayed on the web page. When someone toggles the LED switch on the page, the LED�s state is instantly updated across all browsers connected to the server.

Ready? Let�s get started! First things first,

What Is WebSocket?

Even though the name �WebSocket� doesn�t seem to make sense at first glance, the concept behind it is actually quite simple, and you can get a basic understanding of it in no time.

WebSocket is the name of a communication protocol that enables bi-directional (full-duplex, to be more precise) communication between a client and a web server. Simply put, WebSocket is a technology that allows both the client and the server to establish a connection through which either side can send messages to the other at any time.

This is different from a regular HTTP connection, where the client initiates a request, the server sends a response, and then the connection terminates. In fact, WebSocket is a completely different communication protocol�when a client establishes a connection with a server, both ends of the connection can send and receive data. So just as the server is listening for new messages, all connected clients are also actively listening. As a result, the server can send data to a specific client or broadcast it to all clients without requiring a request. On top of that, the connection stays alive until either the client or the server closes it, allowing for continuous communication between them.

http vs websocket protocol

So, WebSocket is fantastic, but that doesn�t mean it should be used everywhere. Implementing WebSocket can add complexity to your project, especially on the server side. So it should not be done unless your project involves data broadcasting (where you want to transmit the same data to multiple clients at the same time); otherwise, polling might be sufficient.

Project Overview

Let�s now create a simple project where we build a WebSocket server with the ESP32 to remotely control the on-board LED of the ESP32 through a websocket connection.

We will design a web page featuring a toggle switch that allows users to change the state of GPIO 2, which corresponds to the on-board LED. The web interface will also display the LED�s current status.

The ESP32 will actively listen on port 80 for incoming WebSocket connections and messages. When a user toggles the LED switch on the web page, a �toggle� message will be sent to the ESP32. When the ESP32 receives this message, it will toggle the LED and immediately notify all connected clients (browsers) by sending either a �1� (indicating LED on) or a �0� (indicating LED off). As a result, all active clients will immediately update the LED�s status on their webpages.

To better illustrate how this project works, we�ve included an animation below that shows multiple clients connecting to the ESP32 through a websocket connection and exchanging messages.

esp32 websocket server project overview

While this project is quite basic, it can serve as the basis for more practical experiments and projects. For instance, it can be used as a starting point for creating a smart home lighting system.

Setting Up the Arduino IDE

We will be using the Arduino IDE to program the ESP32, so please ensure you have the ESP32 add-on installed before you proceed:

To build a WebSocket server, we�ll use the ESPAsyncWebServer library. This library requires the AsyncTCP library to work. Unfortunately, neither of these libraries is available in the Arduino IDE Library Manager for installation. Therefore, you must install them manually.

Click the links below to download the libraries.

Once downloaded, in your Arduino IDE, navigate to Sketch > Include Library > Add .ZIP Library and select the libraries you�ve just downloaded.

Uploading the Code

Copy the code below to your Arduino IDE. But before you upload the code, you need to make one essential modification. Update the following two variables with your network credentials so that the ESP32 can connect to your network.

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

After making these changes, go ahead and upload the code.

// 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>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>ESP32 WebSocket Server</title>
    <style>
    html{font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
    body{margin-top: 50px;}
    h1{color: #444444;margin: 50px auto;}
    p{font-size: 19px;color: #888;}
    #state{font-weight: bold;color: #444;}
    .switch{margin:25px auto;width:80px}
    .toggle{display:none}
    .toggle+label{display:block;position:relative;cursor:pointer;outline:0;user-select:none;padding:2px;width:80px;height:40px;background-color:#ddd;border-radius:40px}
    .toggle+label:before,.toggle+label:after{display:block;position:absolute;top:1px;left:1px;bottom:1px;content:""}
    .toggle+label:before{right:1px;background-color:#f1f1f1;border-radius:40px;transition:background .4s}
    .toggle+label:after{width:40px;background-color:#fff;border-radius:20px;box-shadow:0 2px 5px rgba(0,0,0,.3);transition:margin .4s}
    .toggle:checked+label:before{background-color:#4285f4}
    .toggle:checked+label:after{margin-left:42px}
    </style>
  </head>
  <body>
    <h1>ESP32 WebSocket Server</h1>
    <div class="switch">
      <input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
      <label for="toggle-btn"></label>
    </div>
    <p>On-board LED: <span id="state">%STATE%</span></p>

    <script>
	  window.addEventListener('load', function() {
		var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);
		websocket.onopen = function(event) {
		  console.log('Connection established');
		}
		websocket.onclose = function(event) {
		  console.log('Connection died');
		}
		websocket.onerror = function(error) {
		  console.log('error');
		};
		websocket.onmessage = function(event) {
		  if (event.data == "1") {
			document.getElementById('state').innerHTML = "ON";
			document.getElementById('toggle-btn').checked = true;
		  }
		  else {
			document.getElementById('state').innerHTML = "OFF";
			document.getElementById('toggle-btn').checked = false;
		  }
		};
		
		document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });
	  });
	</script>
  </body>
</html>
)rawliteral";

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;
      ws.textAll(String(ledState));
    }
  }
}

void eventHandler(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);
      digitalWrite(ledPin, ledState);
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

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

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.println("Connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  ws.onEvent(eventHandler);
  server.addHandler(&ws);

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

  // Start server
  server.begin();
}

void loop() {
  ws.cleanupClients();
}

Demonstration

After uploading the code, open the Serial Monitor and set the baud rate to 115200. Then, press the EN button on the ESP32. It may take a few moments to connect to your network, after which it will display the dynamic IP address obtained from your router.

esp32 websocket server started message

Next, launch a web browser and navigate to the IP address displayed on the serial monitor. The ESP32 should display a web page with the current status of the on-board LED and a toggle switch to control it.

esp32 websocket interactive web application

Now, grab your phone or tablet and navigate to the same IP address, making sure it�s connected to the same network as your computer. Click on the toggle switch to toggle the LED. You�ll observe that not only does the on-board LED toggle, but the LED state automatically updates across all web browsers.

At the same time, you can keep an eye on the Serial Monitor to see which clients are connecting to and disconnecting from the ESP32.

esp32 websocket list of connected clients and ip address

Detailed Code Explanation

Let�s break this code down.

Importing Required Libraries

The sketch begins by including the following libraries:

  • WiFi.h: offers ESP32-specific WiFi methods we use to connect to the network.
  • ESPAsyncWebServer.h: is used to create an HTTP server that supports websocket endpoints.
  • AsyncTCP.h: the ESPAsyncWebServer library relies on this library.
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

Defining Constants and Variables

Next, the network credentials (SSID and password) are specified, which you should replace with your own Wi-Fi network credentials.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

In that same global area, two variables are defined: one for tracking the current GPIO state (ledState) and another for specifying the GPIO pin connected to the LED (ledPin). In this case, we�ll control the on-board LED (that is connected to GPIO 2).

bool ledState = 0;
const int ledPin = 2;

Initializing Web Server and WebSocket

Next, an object of the AsyncWebServer class named server is created. The constructor of this class accepts as an argument the port number on which the HTTP server will listen for incoming requests. In this case, we�ll use the default HTTP port, which is port 80.

AsyncWebServer server(80);

Then, to set up the WebSocket endpoint, an object of the AsyncWebSocket class named ws is created. The path of the websocket endpoint must be passed as an argument to the constructor of this class. This path is defined as a string, and in our code, it�s set to /ws.

So that the WebSocket will listen for connections on the path: ws://[esp ip]/ws

AsyncWebSocket ws("/ws");

Creating the Web Page

The index_html constant is then defined. It holds a raw string literal containing the HTML code for displaying a toggle switch on a web page for controlling the ESP32�s on-board LED, as well as CSS for styling the web page and JavaScript to establish a WebSocket connection and monitor changes in the LED�s state. This entire code will execute on the client side.

Note that the PROGMEM macro is used, which stores the content in the ESP32�s program memory (flash memory) rather than the SRAM, to optimize memory.

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>ESP32 WebSocket Server</title>
    <style>
    html{font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
    body{margin-top: 50px;}
    h1{color: #444444;margin: 50px auto;}
    p{font-size: 19px;color: #888;}
    #state{font-weight: bold;color: #444;}
    .switch{margin:25px auto;width:80px}
    .toggle{display:none}
    .toggle+label{display:block;position:relative;cursor:pointer;outline:0;user-select:none;padding:2px;width:80px;height:40px;background-color:#ddd;border-radius:40px}
    .toggle+label:before,.toggle+label:after{display:block;position:absolute;top:1px;left:1px;bottom:1px;content:""}
    .toggle+label:before{right:1px;background-color:#f1f1f1;border-radius:40px;transition:background .4s}
    .toggle+label:after{width:40px;background-color:#fff;border-radius:20px;box-shadow:0 2px 5px rgba(0,0,0,.3);transition:margin .4s}
    .toggle:checked+label:before{background-color:#4285f4}
    .toggle:checked+label:after{margin-left:42px}
    </style>
  </head>
  <body>
    <h1>ESP32 WebSocket Server</h1>
    <div class="switch">
      <input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
      <label for="toggle-btn"></label>
    </div>
    <p>On-board LED: <span id="state">%STATE%</span></p>

    <script>
      window.addEventListener('load', function() {
        var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);
        websocket.onmessage = function () {
          if (event.data == "1") {
            document.getElementById('state').innerHTML = "ON";
            document.getElementById('toggle-btn').checked = true;
          }
          else {
            document.getElementById('state').innerHTML = "OFF";
            document.getElementById('toggle-btn').checked = false;
          }
        };
        document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });
      });
    </script>
  </body>
</html>
)rawliteral";

The CSS

Between the <style></style> tags is the CSS code used to style the web page. Feel free to change it to make the website look the way you want it to.

<style>
html{font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
body{margin-top: 50px;}
h1{color: #444444;margin: 50px auto;}
p{font-size: 19px;color: #888;}
#state{font-weight: bold;color: #444;}
.switch{margin:25px auto;width:80px}
.toggle{display:none}
.toggle+label{display:block;position:relative;cursor:pointer;outline:0;user-select:none;padding:2px;width:80px;height:40px;background-color:#ddd;border-radius:40px}
.toggle+label:before,.toggle+label:after{display:block;position:absolute;top:1px;left:1px;bottom:1px;content:""}
.toggle+label:before{right:1px;background-color:#f1f1f1;border-radius:40px;transition:background .4s}
.toggle+label:after{width:40px;background-color:#fff;border-radius:20px;box-shadow:0 2px 5px rgba(0,0,0,.3);transition:margin .4s}
.toggle:checked+label:before{background-color:#4285f4}
.toggle:checked+label:after{margin-left:42px}
</style>

The HTML

Between the <body></body> tags is the actual web page content.

<h1>ESP32 WebSocket Server</h1>
<div class="switch">
  <input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
  <label for="toggle-btn"></label>
</div>
<p>On-board LED: <span id="state">%STATE%</span></p>

The first line of code sets the page header using the <h1> tag; you can change this text to whatever suits your application.

<h1>ESP32 WebSocket Server</h1>

Next block of code creates a toggle switch.

This is actually a checkbox with some styling to make it look like a toggle switch.

<div class="switch">
  <input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
  <label for="toggle-btn"></label>
</div>

Finally, there�s a paragraph that displays the state of the on-board LED. The actual state of the LED (�ON� or �OFF�) is shown within a <span> element.

<p>On-board LED: <span id="state">%STATE%</span></p>

The placeholders %CHECK% and %STATE% are essentially variables that the ESP32 will replace with the appropriate values when serving the web page, based on the LED�s current state. The placeholders ensure that the LED�s state remains synchronized across all users. If one user modifies the LED�s state, the next user who connects to the ESP32 should see the LED�s updated state, right?

The %STATE% placeholder will be replaced with the appropriate text (either �ON� or �OFF�) based on the LED�s current status. And the %CHECK% placeholder determines whether the checkbox should be checked or unchecked (toggle switch is either on or off) when the page loads.

Note that the placeholders come into play when a client connects to the ESP32 for the very first time. Once the websocket connection is established, JavaScript takes care of all the updating. That�s why each element with a placeholder has an id attribute. The id attribute gives each element a unique identifier. This allows JavaScript to access and manipulate these elements whenever the WebSocket receives a message from the server.

The JavaScript

The JavaScript code is placed within the <script></script> tags. It is responsible for initializing a WebSocket connection with the server and for managing data exchange through WebSockets, as well as updating all web page elements accordingly.

<script>
  window.addEventListener('load', function() {
	var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);
	websocket.onopen = function(event) {
	  console.log('Connection established');
	}
	websocket.onclose = function(event) {
	  console.log('Connection died');
	}
	websocket.onerror = function(error) {
	  console.log('error');
	};
	websocket.onmessage = function(event) {
	  if (event.data == "1") {
		document.getElementById('state').innerHTML = "ON";
		document.getElementById('toggle-btn').checked = true;
	  }
	  else {
		document.getElementById('state').innerHTML = "OFF";
		document.getElementById('toggle-btn').checked = false;
	  }
	};
	
	document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });
  });
</script>

First, an event listener is attached to the window object to ensure that the entire JavaScript code executes only once the page has fully loaded.

window.addEventListener('load', function() {

Upon loading, a new WebSocket connection is established to the server using the current hostname (ESP32�s IP address) and the special protocol ws:// in the URL. FYI, there�s also encrypted wss:// protocol; it�s like HTTPS for websockets.

var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);

Once the connection is established, several events are fired on the WebSocket instance on the client side. And, we should listen to these events. There are four events in total:

  • onopen � Fired when a connection with a WebSocket is established.
  • onclose � Fired when a connection with a WebSocket is closed.
  • onerror � Fired when a connection with a WebSocket has been closed because of an error, such as when some data couldn�t be sent.
  • onmessage � Fired when the WebSocket receives data from the server.

When the connection is opened, closed, or encounters an error, we simply print a message to the web console. However, you can use these events to add more functionality if needed.

websocket.onopen = function(event) {
  console.log('Connection established');
}
websocket.onclose = function(event) {
  console.log('Connection died');
}
websocket.onerror = function(error) {
  console.log('error');
};

Now, we need to handle what happens when the WebSocket receives data from the server, i.e. when the onmessage event fires. The server (your ESP32) will either broadcast a �1� or a �0� message depending on the LED�s current state. Based on the received message, we need to take appropriate action.

  • If �1� is received: the text within the element with the ID �state� should be set to �ON� and the checkbox with the ID �toggle-btn� should be marked as checked.
  • If �0� is received: the text within the element with the ID �state� should be set to �OFF� and the checkbox with the ID �toggle-btn� should remain unchecked.
websocket.onmessage = function(event) {
  if (event.data == "1") {
	document.getElementById('state').innerHTML = "ON";
	document.getElementById('toggle-btn').checked = true;
  }
  else {
	document.getElementById('state').innerHTML = "OFF";
	document.getElementById('toggle-btn').checked = false;
  }
};

Finally, an event listener is attached to our toggle switch. When the switch is toggled on or off (i.e., when the state of the �toggle-btn� checkbox changes), a message �toggle� is sent over the WebSocket connection, informing the ESP32 to toggle the LED.

document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });

The setup() Function

You�ve seen how to handle the WebSocket connection on the client side (browser) up to this point. Let�s now look at how to handle it on the server side.

In setup(), the Serial Monitor is initialized for debugging purposes.

Serial.begin(115200);

The LED pin is then configured to behave as an OUTPUT and set to LOW.

pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);

After that, the ESP32 attempts to connect to the WiFi network. The connection progress and the obtained IP address are printed to the serial monitor.

Serial.print("Connecting to ");
Serial.println(ssid);

// Connect to Wi-Fi
WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
}

Serial.println("");
Serial.println("Connected..!");
Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

The next line sets up an event listener for the WebSocket. This means that whenever an event related to the WebSocket (ws object) occurs, the eventHandler() function will be triggered to handle that event. The eventHandler() function contains logic to determine what type of WebSocket event has occurred (like a new client connection, data received, client disconnected, etc.) and how to respond to that event. We�ll look into that later.

ws.onEvent(eventHandler);

The next line attaches or registers the WebSocket (ws object) to the web server (server object). This tells the server that it has a WebSocket handler, and any incoming WebSocket connections or requests should be directed to this handler. The &ws is a pointer to the WebSocket object, allowing the server to know where to send and from where to receive WebSocket-related data.

server.addHandler(&ws);

The next line sets up an endpoint or route on the web server. In this case, the root path (�/�) is being defined. When a client sends an HTTP_GET request to this root path, the lambda function (inside the curly braces { � }) gets executed.

Within this function, there�s a call to request->send_P(...). This function sends a response back to the client.

  • 200 is the HTTP status code, indicating a successful request.
  • "text/html" tells the client that the server is sending back HTML content.
  • index_html is the content to be sent, which is your HTML web page stored in the program memory. More on this later.
  • processor is a function that processes the HTML content before sending it (to replace placeholders, for example). More on this later.
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html, processor);
});

Finally, the web server is started. Once executed, the server starts listening for incoming client requests on the previously defined routes (like the root path we set up).

server.begin();

The loop() Function

In the case of a synchronous web server, you will normally have a call server.handleClient(); within the loop, which checks to see if there are any client requests to be handled. If there are any, the code is expected to process these requests and send responses before returning control to the rest of the loop.

In the case of an asynchronous web server, nothing is required in the loop. The AsyncWebServer is set up with event handlers and also uses the AsyncTCP library. With this, it can actively listen for connections in the background, parse incoming requests, and trigger the appropriate event handler. This is why, when creating an asynchronous web server, the loop() function is generally left empty.

However, in our case, the cleanupClients() method is called. This method is a built-in method of the AsyncWebSocket class. When working with websockets, clients may frequently connect and disconnect, or their connections may drop unexpectedly. Over time, you may have WebSocket clients that are no longer active but still occupy resources. This will eventually exhaust the web server�s resources, causing the server to crash.

The cleanupClients() method iterates through all the connected WebSocket clients and removes those that are no longer active or connected. Periodically calling the cleanClients() function from the main loop() ensures that your ESP32 is not burdened with old, unused client connections and remains efficient when handling new or active clients.

void loop() {
  ws.cleanupClients();
}

Handling Server-side WebSocket Events

If you recall, in the setup() function, we set up an event listener for the WebSocket using the ws.onEvent(eventHandler) method, so that whenever an event related to the WebSocket (ws object) occurs, the eventHandler() function gets triggered to handle that event.

The eventHandler() function simply checks the type argument to determine the type of event that has occurred and responds to that event. The type argument represents the event that occurs. It can take the following values:

  • WS_EVT_CONNECT � when a new client connects to the WebSocket server
  • WS_EVT_DISCONNECT � when a client disconnects from the WebSocket server
  • WS_EVT_DATA � when the WebSocket server receives data from a client
  • WS_EVT_PONG � when the WebSocket receives a ping request
  • WS_EVT_ERROR � when the client reports an error
void eventHandler(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);
      digitalWrite(ledPin, ledState);
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

As you can see, when the event type is WS_EVT_CONNECT or WS_EVT_DISCONNECT, the Serial monitor displays the IP address and unique ID of the clients as they connect or disconnect. Whereas no actions are taken for the WS_EVT_PONG and WS_EVT_ERROR event types, though they could be further developed if needed.

The most important event type is WS_EVT_DATA (when the WebSocket server receives data from a client): the received data is passed to the handleWebSocketMessage() function for further processing and the LED�s state is updated with the latest ledState value.

Reading WebSocket Messages and Broadcasting

The function handleWebSocketMessage() is defined to handle incoming WebSocket messages. When a valid �toggle� message is received, this function toggles the LED, and notifies all connected clients of this change using the textAll() method.

The textAll() method of the AsyncWebSocket class allows the ESP32 to send the same message to all connected clients at the same time, ensuring that the LED�s state remains synchronized across all clients.

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;
      ws.textAll(String(ledState));
    }
  }
}

Processing the web page Placeholders

If you recall, our web page has two placeholders: %CHECK% and %STATE%. The processor() function is responsible for searching for such placeholders within the HTML text and replacing them with the appropriate values before sending the web page to the browser.

The %STATE% placeholder will be replaced with either �ON� or �OFF� based on the LED�s current state. And the %CHECK% placeholder determines whether the checkbox should be checked or unchecked (toggle switch is either on or off) when the page loads.

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

Login
ADS CODE