ESP8266 NodeMCU WebSocket Server: Control Outputs (Arduino IDE)

In this tutorial you’ll learn how to build a web server with the ESP8266 using WebSocket communication protocol. As an example, we’ll show you how to build a web page to control the ESP8266 outputs remotely. The output state is displayed on the web page and it updates automatically in all clients.

ESP8266 NodeMCU WebSocket Server Control Outputs Arduino IDE

The ESP8266 will be programmed using Arduino IDE and the ESPAsyncWebServer. We also have a similar WebSocket guide for the ESP32.

If you’ve been following some of our previous web server projects like this one, you may have noticed that if you have several tabs (in the same or on different devices) opened at the same time, the state doesn’t update in all tabs automatically unless you refresh the web page. To solve this issue, we can use WebSocket protocol – all clients can be notified when a change occurs and update the web page accordingly.

This tutorial was based on a project created and documented by one of our readers (Stéphane Calderoni). You can read his excellent tutorial here.

Introducing WebSocket

A WebSocket is a persistent connection between a client and a server that allows bidirectional communication between both parties using a TCP connection. This means you can send data from the client to the server and from the server to the client at any given time. 

ESP32 ESP8266 WebSocket Server How it Works

The client establishes a WebSocket connection with the server through a process known as WebSocket handshake. The handshake starts with an HTTP request/response, allowing servers to handle HTTP connections as well as WebSocket connections on the same port. Once the connection is established, the client and the server can send WebSocket data in full duplex mode.

Using the WebSockets protocol, the server (ESP8266 board) can send information to the client or to all clients without being requested. This also allows us to send information to the web browser when a change occurs.

This change can be something that happened on the web page (you clicked a button) or something that happened on the ESP8266 side like pressing a physical button on a circuit.

Project Overview

Here’s the web page we’ll build for this project.

ESP32 WebSocket Server Toggle Outputs Project Overview
  • The ESP8266 web server displays a web page with a button to toggle the state of GPIO 2;
  • For simplicity, we’re controlling GPIO 2 – the on-board LED. You can use this example to control any other GPIO;
  • The interface shows the current GPIO state. Whenever a change occurs on the GPIO state, the interface is updated instantaneously;
  • The GPIO state is updated automatically in all clients. This means that if you have several web browser tabs opened on the same device or on different devices, they are all updated at the same time.

How it Works?

The following image describes what happens when click on the “Toggle” button.

ESP8266 NodeMCU WebSocket Web Server Update All Clients How it Works

Here’s what happens when you click on the “Toggle” button:

  1. Click on the “Toggle” button;
  2. The client (your browser) sends data via WebSocket protocol with the “toggle” message;
  3. The ESP8266 (server) receives this message, so it knows it should toggle the LED state. If the LED was previously off, turn it on;
  4. Then, it sends data with the new LED state to all clients also through WebSocket protocol;
  5. The clients receive the message and update the led state on the web page accordingly. This allows us to update all clients almost instantaneously when a change happens.

Preparing Arduino IDE

We’ll program the ESP8266 board using Arduino IDE, so make sure you have it installed in your Arduino IDE.

Installing Libraries – Async Web Server

To build the web server we’ll use the ESPAsyncWebServer library. This library needs the ESPAsyncTCP library to work properly. Click the links below to download the libraries.

These libraries aren’t available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation Libraries folder. Alternatively, in your Arduino IDE, you can go to Sketch Include Library > Add .zip Library and select the libraries you’ve just downloaded.

Code for ESP8266 NodeMCU WebSocket Server

Copy the following code to your Arduino IDE.

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp8266-nodemcu-websocket-server-arduino/
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*********/

// Import required libraries
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.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:hover {background-color: #0f8b8d}*/
   .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; // <-- add this line
  }
  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";
    }
  }
}

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_P(200, "text/html", index_html, processor);
  });

  // Start server
  server.begin();
}

void loop() {
  ws.cleanupClients();
  digitalWrite(ledPin, ledState);
}

View raw code

Insert your network credentials in the following variables and the code will work straight away.

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

How the Code Works

Continue reading to learn how the code works or skip to the Demonstration section.

Importing Libraries

Import the necessary libraries to build the web server.

#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

Network Credentials

Insert your network credentials in the following variables:

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

GPIO Output

Create a variable called ledState to hold the GPIO state and a variable called ledPin that refers to the GPIO you want to control. In this case, we’ll control the on-board LED (that is connected to GPIO 2).

bool ledState = 0;
const int ledPin = 2;

AsyncWebServer and AsyncWebSocket

Create an AsyncWebServer object on port 80.

AsyncWebServer server(80);

The ESPAsyncWebServer library includes a WebSocket plugin that makes it easy to handle WebSocket connections. Create an AsyncWebSocket object called ws to handle the connections on the /ws path.

AsyncWebSocket ws("/ws");

Building the Web Page

The index_html variable contains the HTML, CSS and JavaScript needed to build and style the web page and handle client-server interactions using WebSocket protocol.

Note: we’re placing everything needed to build the web page on the index_html variable that we use on the Arduino sketch. Note that it may be more practical to have separated HTML, CSS and JavaScript files that then you upload to the ESP8266 filesystem and reference them on the code.

Recommended reading: ESP8266 Web Server using SPIFFS (SPI Flash File System)

Here’s the content of the index_html variable:

<!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;
  function initWebSocket() {
    console.log('Trying to open a WebSocket connection...');
    websocket = new WebSocket(gateway);
    websocket.onopen    = onOpen;
    websocket.onclose   = onClose;
    websocket.onmessage = onMessage; // <-- add this line
  }
  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;
  }
  window.addEventListener('load', onLoad);
  function onLoad(event) {
    initWebSocket();
    initButton();
  }

  function initButton() {
    document.getElementById('button').addEventListener('click', toggle);
  }
  function toggle(){
    websocket.send('toggle');
  }
</script>
</body>
</html>

CSS

Between the <style> </style> tags we include the styles to style the web page using CSS. Feel free to change it to make the web page look as you wish. We won’t explain how the CSS for this web page works because it is not relevant for this WebSocket tutorial.

<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>

HTML

Between the <body> </body> tags we add the web page content that is visible to the user.

<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>

There’s a heading 1 with the text “ESP WebSocket Server”. Feel free to modify that text.

<h1>ESP WebSocket Server</h1>

Then, there’s a heading 2 with the “Output – GPIO 2” text.

<h2>Output - GPIO 2</h2>

After that, we have a paragraph that displays the current GPIO state.

<p class="state">state: <span id="state">%STATE%</span></p>

The %STATE% is a placeholder for the GPIO state. It will be replaced with the current value by the ESP8266 at the time of sending the web page. The placeholders on the HTML text should go between % signs. This means that this %STATE% text is like a variable that will then be replaced with the actual value.

After sending the web page to the client, the state needs to change dynamically whenever there’s a change in the GPIO state. We’ll receive that information via WebSocket protocol. Then, JavaScript handles what to do with the information received to update the state accordingly. To be able to handle that text using JavaScript, the text must have an id that we can reference. In this case the id is state ( <span id=”state”>).

Finally, there’s a paragraph with the button to toggle the GPIO state.

<p><button id="button" class="button">Toggle</button></p>

Note that we’ve given an id to the button ( id=”button”).

JavaScript – Handling WebSockets

The JavaScript goes between the <script> </script> tags. It is responsible for initializing a WebSocket connection with the server as soon the web interface is fully loaded in the browser and handling data exchange through WebSockets.

<script>
  var gateway = `ws://${window.location.hostname}/ws`;
  var websocket;
  function initWebSocket() {
    console.log('Trying to open a WebSocket connection...');
    websocket = new WebSocket(gateway);
    websocket.onopen    = onOpen;
    websocket.onclose   = onClose;
    websocket.onmessage = onMessage; // <-- add this line
  }
  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;
  }

  window.addEventListener('load', onLoad);

  function onLoad(event) {
    initWebSocket();
    initButton();
  }

  function initButton() {
    document.getElementById('button').addEventListener('click', toggle);
  }

  function toggle(){
    websocket.send('toggle');
  }
</script>

Let’s take a look at how this works.

The gateway is the entry point to the WebSocket interface.

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

window.location.hostname gets the current page address (the web server IP address).

Create a new global variable called websocket.

var websocket;

Add an event listener that will call the onload function when the web page loads.

window.addEventListener('load', onload);

The onload() function calls the initWebSocket() function to initialize a WebSocket connection with the server and the initButton() function to add event listeners to the buttons.

function onload(event) {
  initWebSocket();
  initButton();
}

The initWebSocket() function initializes a WebSocket connection on the gateway defined earlier. We also assign several callback functions for when the WebSocket connection is opened, closed or when a message is received.

function initWebSocket() {
  console.log('Trying to open a WebSocket connection…');
  websocket = new WebSocket(gateway);
  websocket.onopen    = onOpen;
  websocket.onclose   = onClose;
  websocket.onmessage = onMessage;
}

When the connection is opened, we simply print a message in the console and send a message saying “hi”. The ESP8266 receives that message, so we know that the connection was initialized.

function onOpen(event) {
  console.log('Connection opened');
  websocket.send('hi');
}

If for some reason the web socket connection is closed, we call the initWebSocket() function again after 2000 milliseconds (2 seconds).

function onClose(event) {
  console.log('Connection closed');
  setTimeout(initWebSocket, 2000);
} 

The setTimeout() method calls a function or evaluates an expression after a specified number of milliseconds.

Finally, we need to handle what happens  when we receive a new message. The server (your ESP board) will either send a “1” or a “0” message. Accordingly to the received message, we want to display an “ON” or a “OFF” message on the paragraph that displays the state. Remember that <span> tag with id=”state”? We’ll get that element and set its value to ON or OFF.

function onMessage(event) {
  var state;
  if (event.data == "1"){
    state = "ON";
  }
  else{
    state = "OFF";
  }
  document.getElementById('state').innerHTML = state;
}

The initButton() function gets the button by its id (button) and adds an event listener of type ‘click’.

function initButton() {
  document.getElementById('button').addEventListener('click', toggle);
}

This means that when you click the button, the toggle function is called.

The toggle function sends a message using the WebSocket connection with the ‘toggle’ text.

function toggle(){
  websocket.send('toggle');
}

Then, the ESP8266 should handle what happens when it receives this message – toggle the current GPIO state.

Handling WebSockets – Server

Previously, you’ve seen how to handle the WebSocket connection on the client side (browser). Now, let’s take a look on how to handle it on the server side.

Notify All Clients

The notifyClients() function notifies all clients with a message containing whatever you pass as a argument. In this case, we’ll want to notify all clients of the current LED state whenever there’s a change.

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

The AsyncWebSocket class provides a textAll() method for sending the same message to all clients that are connected to the server at the same time.

Handle WebSocket Messages

The handleWebSocketMessage() function is a callback function that will run whenever we receive new data from the clients via WebSocket protocol.

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();
    }
  }
}

If we receive the “toggle” message, we toggle the value of the ledState variable. Additionally, we notify all clients by calling the notifyClients() function. This way, all clients are notified of the change and update the interface accordingly.

if (strcmp((char*)data, "toggle") == 0) {
  ledState = !ledState;
  notifyClients();
}

Configure the WebSocket server

Now we need to configure an event listener to handle the different asynchronous steps of the WebSocket protocol. This event handler can be implemented by defining the onEvent() as follows:

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;
  }
}

The type argument represents the event that occurs. It can take the following values:

  • WS_EVT_CONNECT when a client has logged in;
  • WS_EVT_DISCONNECT when a client has logged out;
  • WS_EVT_DATA when a data packet is received from the client;
  • WS_EVT_PONG in response to a ping request;
  • WS_EVT_ERROR when an error is received from the client.

Initialize WebSocket

Finally, the initWebSocket() function initializes the WebSocket protocol.

void initWebSocket() {
  ws.onEvent(onEvent);
  server.addHandler(&ws);
}

processor()

The processor() function is responsible for searching for placeholders on the HTML text and replace them with whatever we want before sending the web page to the browser. In our case, we’ll replace the %STATE% placeholder with ON if the ledState is 1. Otherwise, replace it with OFF.

String processor(const String& var){
  Serial.println(var);
  if(var == "STATE"){
    if (ledState){
      return "ON";
    }
    else{
      return "OFF";
    }
  }
}

setup()

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

Serial.begin(115200);

Set up the ledPin as an OUTPUT and set it to LOW when the program first starts.

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

Initialize Wi-Fi and print the ESP8266 IP address on the Serial Monitor.

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());

Initialize WebSocket protocol by calling the initWebSocket() function created previously.

initWebSocket();

Handle Requests

Serve the text saved on the index_html variable when you receive a request on the root / URL – you need to pass the processor function as an argument to replace the placeholders with the current GPIO state.

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

Finally, start the server.

server.begin();

loop()

The LED will be physically controlled on the loop().

void loop() {
  ws.cleanupClients();
  digitalWrite(ledPin, ledState);
}

Note that we all call the cleanupClients() method. Here’s why (explanation from the ESPAsyncWebServer library GitHub page):

Browsers sometimes do not correctly close the WebSocket connection, even when the close() function is called in JavaScript. This will eventually exhaust the web server’s resources and will cause the server to crash. Periodically calling the cleanupClients() function from the main loop()limits the number of clients by closing the oldest client when the maximum number of clients has been exceeded. This can be called every cycle, however, if you wish to use less power, then calling as infrequently as once per second is sufficient.

Demonstration

After inserting your network credentials on the ssid and password variables, you can upload the code to your board. Don’t forget to check if you have the right board and COM port selected.

After uploading the code, open the Serial Monitor at a baud rate of 115200 and press the on-board EN/RST button. The ESP IP address should be printed.

Open a browser on your local network and insert the ESP8266 IP address. You should get access to the web page to control the output.

ESP32 WebSocket Server Toggle Outputs Project Overview

Click on the button to toggle the LED. You can open several web browser tabs at the same time or access the web server from different devices and the LED state will be update automatically in all clients whenever there’s a change.


Wrapping Up

In this tutorial you’ve learned how to set up a WebSocket server with the ESP8266. The WebSocket protocol allows a full duplex communication between the client and the server. After initializing, the server and the client can exchange data at any given time.

This is very useful because the server can send data to the client whenever something happens. For example, you can add a physical button to this setup that when pressed notifies all clients to update the web interface.

In this example, we’ve shown you how to control one GPIO of the ESP8266. You can use this method to control more GPIOs. You can also use the WebSocket protocol to send sensor readings or notifications at any given time.

We hope you’ve found this tutorial useful. We intend to create more tutorials and examples using the WebSocket protocol. So, stay tuned.

Learn more about the ESP8266 with our resources:



Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »

Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our weekly newsletter!

17 thoughts on “ESP8266 NodeMCU WebSocket Server: Control Outputs (Arduino IDE)”

  1. Hi Rui and Sara, Another well presented tutorial where I have again expanded my knowledge, especially in the web server setup side of things and now sockets, thank you. But like the other ESP server tutorials I’m finding it too hard to expand the sketch to more than one button. In one of your first ESP8266 server tutorials you had two buttons, that was great as it allowed someone to see what portions of the code needed to be duplicated/expanded out and what parts were common. Also I noticed that source project idea mentioned at the top of this page, in VSCode, had a physical button as well but you have not included this here. It would be great to see your next tutorial in this theme with more than one input and output. Many thanks.

    Reply
  2. Hello,
    When I compiling the sketch, I get the following message:
    “class AsyncWebSocket’ has no member named ‘cleanupClients'”
    In line: “ws.cleanupClients();”

    Kind Regards
    Juergen B.

    Reply
  3. Good Afternoon,
    how do I have to extend the JavaScript functions to display the values from a sensor (e.g. DHT11)?
    websocket.onmessage = onTemperature; ?
    etc.
    Kind Regards
    Juergen B.

    Reply
    • Hi.
      You can do that on the onMessage function.
      For example, you need to have a paragraph to display the reading with a specific id (for example “reading”).
      Then, the onMessage function would be something like this:

      function onMessage(event) {
      var reading = event.data;
      document.getElementById(“reading”).innerHTML=reading;
      }

      Regards,
      Sara

      Reply
      • Good Afternoon,
        Many thanks for the answer.
        But how is the sensor value passed to event.data?
        In the existing sketch, the value ‘0/1’ is passed to event.data.
        Kind regards
        Juergen B.

        Reply
        • Hello,
          with: void notifyTemperature () {
          ws.textAll (String (t));
          }
          can I send the temperature value to event.data.
          As a test, I entered the “notifyTemperature ()” function in the “handleWebSocketMessage” function.
          The “handleWebSocketMessage” function is only executed when I press the toggle button (WS_EVT_DATA = 1).
          Which WS_? would I have to use?
          Kind regards
          Juergen

          Reply
          • Hi.
            Yes, with
            with:
            void notifyTemperature () {
            ws.textAll (String (t));
            }
            You can send the temperature to the client.

            The handleWebSocketMessage() is a callback function that is called when the server receives a message, which is not the case. You want to send data to the client.

            So, use ws.textAll().

            Regards,
            Sara

  4. Hello,
    but how can I call the “notifyTemperature ()” function so that the sensor value is continuously displayed?

    Kind Regards

    Juergen B.

    Reply
  5. Hello,
    my next step is to display two sensor values (temperature, humidity).
    Since the two sensor values have the same value range (T = 25 ° C, H = 25%), “Temperature = 25” and Humidity = 25 “should be sent as the event.
    These events must then be separated in the “onMessage (event)” function.

    Kind Regards

    Juergen B.

    Reply
    • Hello,
      with the Javascript command “split ()” I separated the sensor string and was able to display the two sensor values separately.
      Thank you very much for the tutorial, so I learned something about “WebSocket” and “Javascript” again.

      Kind Regards
      Juergen B.

      Reply

Leave a Comment

Download our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.