ESP8266 NodeMCU WebSocket Server using Arduino IDE and LittleFS – Control GPIOs

In this tutorial, we will create an ESP8266 NodeMCU web server by using a WebSocket communication protocol and Arduino IDE that will control the onboard LED of ESP8266 NodeMCU. Through WebSocket communication protocol, several clients can be connected to the server concurrently without blocking each other. We will see that unlike HTTP, a websocket server updates the state of LED to all clients automatically.

ESP8266 WebSocket Server control GPIO and relays remotely

We have a similar guide with ESP32:

Link to article: ESP32 WebSocket Server using Arduino IDE – Control GPIOs and Relays

What is a WebSocket?

A WebSocket follows a two-way communication protocol using a TCP connection whereby the client and the server can transfer data at the same time. It uses the handshaking mechanism to ensure that the server is in synchronization with all the clients and a successful connection is established.

It offers extremely fast response rate by using two-way communication between the client-server as compared to the simple HTTP protocol. Therefore, in this guide, our objective is to show you the responsiveness of a web server created with WebSocket. The server will update all the connected clients (smartphones, tablets, computers, laptops, etc.) instantaneously when the state of the onboard LED will change.

WebSocket communciation between Server and clients

The simple WebSocket handshake process is shown in the picture above. Firstly, the client sends an HTTP request to the server. Through an upgraded header, the server gets to know that the request deals with a WebSocket connection. The servers can handle both the HTTP and the WebSocket connections on the same port by sending data to all the clients.

key features of WebSocket Communication Protocol

  • Two-way communication over a single Transmission Control Protocol (TCP) connection.
  • Real-time client-server communication is achievable.
  • A big step forward in attaining high speed for real-time web technology.

In the following project, we have also created a web server to control the GPIOs of ESP8266 NodeMCU. But in this project, we have used an HTTP protocol for communication between server and client. But one of the main disadvantages of HTTP for web server projects is that a server cannot notify all connected clients about the state of GPIO pins whenever the state of GPIO is modified by any client or by a user manually through a physical push button. 

WebSocket Server Project Overview

We will create a web server based on the WebSocket protocol. It will consist of a title, “ESP8266 NodeMCU WebSocket Server,” the state of the GPIO (ON or OFF) and a toggle button. We aim to build a web page through which the user will click the button to toggle the onboard LED on the ESP8266 NodeMCU board. Whenever the LED’s state will be changed, it will automatically get updated on the web page immediately. This will occur for all the clients who will be connected with the ESP8266 NodeMCU (server).

ESP8266 NodeMCU WebSocket Server Control Outputs (Arduino IDE) Demo

Working Process

  • First, the user will click the Toggle button.
  • The web server will send the “toggle” request message through the WebSocket communication protocol.
  • Our ESP8266 NodeMCU board will receive the “toggle” request and it performs the particular action.
  • The on-board LED will turn on if it was previously off and vice versa. Next, the ESP8266 NodeMCU will send the new LED state to all the connected clients with the help of WebSocket.
  • Immediately all the clients will receive the new message and their respective web pages will update to the new current state.
ESP8266 NodeMCU WebSocket Server Control Outputs Arduino IDE
Working Process

Setting up Arduino IDE

Make sure you have the latest version of Arduino IDE and the ESP8266 NodeMCU plugin installed on your computer.

Installing ESPAsyncWebServer Library and Async TCP Library

We will need two additional libraries to build our web server. The ESPAsyncWebServer library will help us in creating our web server easily. With this library, we will set up an asynchronous HTTP server. ESPAsyncTCP is another library that we will be incorporating as it a dependency for the ESPAsyncWebServer library. Both of these libraries are not available in the Arduino library manager so we will have to download and load them in our ESP8266 NodeMCU board ourselves.

  • To install the ESPAsyncWebServer library for free, click here to download. You will download the library as a .zip folder which you will extract and rename as ‘ESPAsyncWebServer.’ Then, transfer this folder to the installation library folder in your Arduino IDE.
  • To install the ESPAsyncTCP library for free, click here to download. You will download the library as a .zip folder which you will extract and rename as ‘ESPAsyncTCP.’ Then, transfer this folder to the installation library folder in your Arduino IDE.

Likewise, you can also go to Sketch > Include Library > Add .zip Library inside the IDE to add the libraries as well. After installation of the libraries, restart your IDE.

WebSocket Server Arduino Sketch

Open your Arduino IDE and go to File > New. A new file will open. Copy the code given below in that file and save it as ‘ESP8266 NodeMCU WebSocket Webserver.’

// Importing necessary libraries
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Replace network credentials with your own WiFi details
const char* ssid = "Enter_Your_WiFi_Name";
const char* password = "Enter_Your_WiFi_Password";

bool GPIO_State = 0;
const int Led_Pin = 2;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

const char html_page[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP8266 NodeMCU Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <style>
  html {
    font-family: New Times Roman;
    text-align: center;
  }
  h1 {
    font-size: 1.8rem;
    color: white;
  }
  h2{
    font-size: 1.5rem;
    font-weight: bold;
    color: #612b78;
  }
  .topnav {
    overflow: hidden;
    background-color: #612b78;
  }
  body {
    margin: 0;
  }
  .content {
    padding: 30px;
    max-width: 600px;
    margin: 0 auto; 
  }
  .button {
    padding: 15px 50px;
    font-size: 24px;
    text-align: center;
    outline: none;
    color: #D3D3D3;
    background-color:#130ffa;  //green
    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:#000000 ; 
     transform: translateY(2px);
   }
   .state {
     font-size: 1.5rem;
     color:#120707;
     font-weight: bold;
   }
  </style>
<title>ESP8266 NodeMCU Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
</head>
<body>
  <div class="topnav">
    <h1>ESP8266 NodeMCU WebSocket Server</h1>
  </div>
  <div class="content">
      <h2>GPIO2</h2>
       <p><button id="button" class="button">Click to Toggle</button></p>
      <p class="state">State: <span id="state">%STATE%</span></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 = "OFF";
    }
    else{
      state = "ON";
    }
    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(GPIO_State));
}

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) {
      GPIO_State = !GPIO_State;
      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 (GPIO_State){
      return "ON";
    }
    else{
      return "OFF";
    }
  }
}

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

  pinMode(Led_Pin, OUTPUT);
  digitalWrite(Led_Pin, 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", html_page, processor);
  });

  // Start server
  server.begin();
}

void loop() {
  ws.cleanupClients();
  digitalWrite(Led_Pin, GPIO_State);
}

How the Code Works?

Importing Libraries

Firstly, we will import the necessary libraries. For this project, we are using three of them. WiFi.h, ESPAsyncWebServer.h and AsyncTCP.h. As we have to connect our ESP8266 NodeMCU to a wireless network hence we need WiFi.h library for that purpose. The other two libraries are the ones that we recently downloaded and will be required for the building of the asynchronous web server.

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

Setting Network Credentials

Next, we will create two global variables, one for the SSID and the other for the password. These will hold our network credentials which will be used to connect to our wireless network. Replace both of them with your credentials to ensure a successful connection.

// Replace network credentials with your own WiFi details
const char* ssid = "Enter_Your_WiFi_Name";
const char* password = "Enter_Your_WiFi_Password";

GPIO Output Symbolic Names

Then, we will define the variable Led_Pin to save the GPIO pin through which the on-board LED will be connected i.e., GPIO2. The ‘GPIO_State’ variable will be used to store the current LED state. This will be used later on in the program code. We will initialize it with 0 because we start with an off LED.

bool GPIO_State = 0;
const int Led_Pin = 2;

Creating AsyncWebServer and Socket Objects

The AsyncWebServer object will be used to set up the ESP8266 NodeMCU web server. We will pass the default HTTP port which is 80, as the input to the constructor. This will be the port where the server will listen to the requests. We will also create an object for the AsyncWebSocket called ‘ws’. This will be used on the /ws path to handle connections appropriately.

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

Creating Web Page

We will create an html_page variable to store all the HTML, CSS, and JavaScript text which will be required to build our web page. It is more convenient to have separate HTML, CSS, and JavaScript files saved on the ESP8266 NodeMCU LittleFS. All of these three files will later be linked together. For this article, we will not be using this approach but include everything inside our Arduino sketch instead.

We will start with the title of the web page. The <title> tag will indicate the beginning of the title and the </tile> tag will indicate the ending. In between these tags, we will specify “ESP8266 NodeMCU WEB SERVER” which will be displayed in the browser’s title bar. You can use any title according to your preference.

<title>ESP8266 NodeMCU WEB SERVER</title>

Next, we will create a meta tag to make sure our web server is available for all browsers e.g., smartphones, laptops, computers etc.

<meta name="viewport" content="width=device-width, initial-scale=1">

CSS styling

Inside the html_page variable we have the <style> </style> tags which mark the beginning and end of the CSS styling file.

 We will set the display text to font type Times New Roman and align it in the centre of the webpage. For all the different texts/button, the font size, font type, colour, positioning and everything relating to the overall visuals of the web page will be is specified. This section of code shows the CSS styling which we will incorporate in our web page.


  <style>
  html {
    font-family: New Times Roman;
    text-align: center;
  }
  h1 {
    font-size: 1.8rem;
    color: white;
  }
  h2{
    font-size: 1.5rem;
    font-weight: bold;
    color: #612b78;
  }
  .topnav {
    overflow: hidden;
    background-color: #612b78;
  }
  body {
    margin: 0;
  }
  .content {
    padding: 30px;
    max-width: 600px;
    margin: 0 auto;
   
  }
 
  .button {
    padding: 15px 50px;
    font-size: 24px;
    text-align: center;
    outline: none;
    color: #fff;
    background-color:#130ffa;  
    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:#000000; 
     transform: translateY(2px);
   }
   .state {
     font-size: 1.5rem;
     color:#120707;
     font-weight: bold;
   }
  </style>


HTML Body

The next step will be to define the HTML web page body. This will go inside the <body></body> tags which mark the beginning and the ending of the script. This part will include the heading of the web page and the buttons. We will include the heading of our webpage inside the <h1></h1> tags and it will be the same as that of the web browser title i.e., ESP8266 NodeMCU WebSocket Server.

<h1>ESP8266 NodeMCU WEB SERVER</h1>

Then, we will include the second heading in <h2> </h2> tags as ‘ONBOARD LED GPIO2’.

<h2>ONBOARD LED GPIO2</h2>

Next, we will include two paragraphs to display texts. One for the button and another for the current GPIO state. The button will consist of the text: ‘Click to Toggle.’ We will use a placeholder for the GPIO state and place it inside %% signs like %STATE%. This will act as a variable and its value will always get replaced by the current GPIO state. This state will change dynamically whenever the toggle button is clicked.

The WebSocket protocol will handle this smoothly. Also, JavaScript will be in charge of handling the received data so that the state will be updated correctly. This will be accomplished by using an id (<span id= “state”>) which we will reference in our script. The toggle button will also have an id (id= “button”) associated with it. This will help in handling the text using JavaScript.

<p><button id="button" class="button">Click to Toggle</button></p>
<p class="state">State: <span id="state">%STATE%</span></p>

JavaScript Handling WebSockets

Inside the <script></script> tags, we will include the JavaScript to handle the WebSocket. Data exchange as well as initiating the two-way connection between the client-server will be accomplished through this script when the webpage will be formed.

Firstly, we will create a gateway that will act as the entrance to the WebSocket interface. We will specify the webserver IP address (window.location.hostname) for the gateway.

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

Then we will create a new variable of type ‘var’ named ‘websocket’. Through the window.addwindowListener()function we will call the onload function. This will occur when the webserver will be loaded.

var websocket;
window.addEventListener('load', onload);

Next, we will define the onload() function. This takes in the parameter ‘event’ and calls the initWebSocket() function and the initButton() function. The initWebsocket() function will be used to initialize the WebSocket. Likewise the initButton() function will add the event listeners to the button.

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

Initializing WebSocket

For the initWebSocket() function, we will use it to initialize the WebSocket connection. Three different callback functions will also be included to cater when a message will be received or the connection is opened/closed. Next, we will define each callback function separately.

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

Now, we will define the onOpen() function in which ‘event’ is passed as an argument . When the WebSocket connection is opened, the console.log() function is called. This will write the message which is passed as an argument into the browser console. In our case, the message is ‘Connection opened.’

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

For the onClose() function, event will be passed as argument. When the WebSocket connection is closed, console.log() function is called which will log ‘Connection closed’ in the browser log. Additionally, the initWebSocket() function will be called after every 2 seconds to initiate the WebSocket connection.

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

The onMessage() function is called whenever a new message will be received. The message will either a 1 or 0 depending on the state of the GPIO2. Thus, we will use an if-else statement to check whether the message is 1 or 0. If it is 1 then the state will be updated with the string “ON” else it will be updated with the string “OFF”. This will be saved on the id string which we initiated with the <span> tag previously.

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

Initializing Button

Inside the initButton() function, we will obtain the button through its id which we defined earlier. An event listener will also be added to the button. We will add the ‘click’ event listener. This is because our web page works on the principle of clicking the toggle button.

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

Next, inside the toggle() function, we will call the websocket.send() function. As an argument, we will pass ‘toggle’ inside it. This will state that whenever the button is clicked the toggle function will be called. This function will send the ‘toggle’ message through the WebSocket.

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

Handling WebSocket on the Server side (ESP8266 NodeMCU)

This section will deal with how our ESP8266 NodeMCU board will handle the Websocket requests.

We will start by defining the notifyClients() function. Through this function, we want to notify all the connected clients of the updated state of the onboard LED when subjected to a change. We will achieve this through the textAll() method which will send the same string to all the clients on board.

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

Handling WebSocket Message

Next, we will define the handleWebSocketMessage(). This will act as a callback function. When new information is received by our ESP8266 NodeMCU through the WebSocket, this callback function will run. Likewise, if the ‘toggle’ message is received by our ESP8266 NodeMCU then notifyClients() will be called.

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

The following section of code will define the onEvent() function. This will enable an event listener which will handle the Websocket connection properly. There are five different values that the event (type) can take up. These are shown below:

  1. WS-EVT-CONNECT: when a client will log in
  2. WS_EVT_DISCONNECT: when the client will log out
  3. WS_EVT_DATA: when data will be received from the client
  4. WS_EVT_PONG: when a ping request will come up
  5. WS_EVT_ERROR: when an error will be received from the client
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;
  }
}

Initializing WebSocket (for ESP8266 NodeMCU)

This function will initialize our WebSocket protocol for the server side.

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

Inside our processor() function, we will replace the placeholder (%STATE%) with either ON or OFF depending on the value of the GPIO_State. If the GPIO_State is 1 then the placeholder will be replaced by “ON”. Otherwise, it will be replaced by “OFF”.

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

Setup() function

Inside the setup() function, we will open a serial connection at a baud rate of 115200. By using the pinMode() function, the Led_Pin (GPIO2) will be passed as a parameter inside the function which will be configured as an output pin. We will initialize this pin to a LOW state so that initially the onboard LED will be off.

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

We will connect our ESP8266 NodeMCU board with the local network whose network credentials we already specified above through WiFi.begin(). After the connection will be established, the IP address of the ESP8266 NodeMCU board will get printed on the serial monitor. This will help us in making a request to the server.

WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Connecting to WiFi..");
}


Serial.println(WiFi.localIP());

Also, we will call the initWebSocket() to initialize the WebSocket communication protocol.

initWebSocket();

Handling Requests

We will configure the root / URL where our server will listen to HTTP GET requests.

The handling function will respond to the client by using the send_P() method on the request object. This method will take in four parameters. The first is 200 which is the HTTP status code for ‘ok’. The second is “text/html” which will correspond to the content type of the response. The third input is the text saved on the html_page variable. Finally, the last parameter is the processor function which will be sent as the response.

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

To start the server, we will call the begin() on our server object.

server.begin();

Infinite loop()

Inside our infinite loop() function we will control the onboard LED of our ESP8266 NodeMCU through the digitalWrite() function. This function takes in the Led_Pin (GPIO2) and the GPIO_State as its input parameters. Notice that we are using the cleanClients() method inside our loop(). When the maximum number of clients is exceeded this function closes the oldest client. As a result, it helps in limiting the number of clients so the server does not exhaust.

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

Demonstration

Make sure you choose the correct board and COM port before uploading your code to the board. Go to Tools > Board and select ESP8266 NodeMCU Dev Module.

selecting ESP8266 NodeMCU in Arduino IDE

Next, go to Tools > Port and select the appropriate port through which your board is connected.

Click on the upload button to upload the code into the ESP8266 NodeMCU development board.

After you have uploaded your code to the ESP8266 NodeMCU development board press its ENABLE button.

ESP8266 NodeMCU reset button

In your Arduino IDE, open up the serial monitor and you will be able to see the IP address of your ESP module.

ESP8266 NodeMCU WebSocket Server IP address
Serial Monitor Demonstration

Copy that address into a web browser and press enter. The web server will look something like this:

ESP8266 NodeMCU WebSocket Server using Arduino IDE
ESP8266 NodeMCU WebSocket Web Page

Now click the toggle button. The onboard LED of the ESP8266 NodeMCU board will light up. At the same moment, on ther clientswebpage, the state will also be updated to ‘ON’. Now keep on clicking the button and the state will automatically update accordingly and the LED will turn on/off. You can also try using multiple devices to open the web page. Whenever the toggle button will be clicked, the LED GPIO state will immediately update in all the clients.

WebSocket Server with LittleFS

In the last section, we have included HTML, Javascript, and CSS files inside our Arduino sketch. We save these HTML documents inside Arduino sketch by converting them into strings. Whenever a web client makes a request, the WebSocket server sends this string as a response which is basically a web page.

On the contrary, with LittleFS, we can save HTML, CSS, and JavaScript files in ESP8266 NodeMCU flash file system. Whenever a web client makes an HTTP request, we can serve these files directly from LittleFS.

Recommended reading: ESP8266 NodeMCU Web Server with LittleFS (SPI Flash File System)

LittleFS Files Organization

To build a WebSocket ESP8266 NodeMCU web server using LittleFS, we will create four different files: HTML, CSS, JavaScript and Arduino sketch and organize them in a project folder like shown below:

ESP8266 NodeMCU websocket littlefs server files organization

Recommended reading: LittleFS Introduction & Install ESP8266 NodeMCU Filesystem Uploader in Arduino IDE

HTML File

Copy the following content to page.html file:

<!DOCTYPE HTML><html>
<head>
  <title>ESP8266 NodeMCU Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <link rel="stylesheet" type="text/css" href="style.css">

<title>ESP8266 NodeMCU Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
</head>
<body>
  <div class="topnav">
    <h1>ESP8266 NodeMCU WebSocket Server</h1>
  </div>
  <div class="content">
      <h2>ONBOARD LED GPIO2</h2>
       <p><button id="button" class="button">Click to Toggle</button></p>
       <p class="state">State: <span id="state">%STATE%</span></p>

  </div>
  </div>
  <script src="script.js"></script>
</body>
</html>

CSS File

Copy the following content to style.css file.

 html {
    font-family: New Times Roman;
    text-align: center;
  }
  h1 {
    font-size: 1.8rem;
    color: white;
  }
  h2{
    font-size: 1.5rem;
    font-weight: bold;
    color: #612b78;
  }
  .topnav {
    overflow: hidden;
    background-color: #612b78;
  }
  body {
    margin: 0;
  }
  .content {
    padding: 30px;
    max-width: 600px;
    margin: 0 auto; 
  }
  .button {
    padding: 15px 50px;
    font-size: 24px;
    text-align: center;
    outline: none;
    color: #fff;
    background-color:#fa0f0f;  //green
    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:#fa0f0f ; 
     transform: translateY(2px);
   }
   .state {
     font-size: 1.5rem;
     color:#120707;
     font-weight: bold;
   }

JavaScript File

Copy the following content to script.js file.

  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 = "OFF";
    }
    else{
      state = "ON";
    }
    document.getElementById('state').innerHTML = state;
  }
  function onLoad(event) {
    initWebSocket();
    initButton();
  }
  function initButton() {
    document.getElementById('button').addEventListener('click', toggle);
  }
  function toggle(){
    websocket.send('toggle');
  }

Arduino Sketch

Finally, copy the following code to Arduino and name it as ESP8266_WebSocket_LittleFS_Server.ino.

// Importing necessary libraries
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "LittleFS.h"

// Replace network credentials with your own WiFi details
const char* ssid = "Enter_Your_Wifi_Name";
const char* password = "Enter_Your_Wifi_Password";

bool GPIO_State = 0;
const int Led_Pin = 2;
String stateled = "OFF";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");


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

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) {
      GPIO_State = !GPIO_State;
      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 (GPIO_State){
      return "ON";
    }
    else{
      return "OFF";
    }
  }
}
void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);
  if (!LittleFS.begin()) {
    Serial.println("An error has occurred while mounting LittleFS");
  }
  else{
   Serial.println("LittleFS mounted successfully");
  }
  pinMode(Led_Pin, OUTPUT);
  digitalWrite(Led_Pin, 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(LittleFS, "/Page.html", "text/html",false,processor);
  });
   server.serveStatic("/", LittleFS, "/");

  // Start server
  server.begin();
}

void loop() {
  ws.cleanupClients();
  digitalWrite(Led_Pin, GPIO_State);
}

Demonstration

After you have saved all three files such as Page.html, script.js and style.css in data folder ‘data’, upload sketch to Arduino.

Make sure you choose the correct board and COM port before uploading your code to the board. Go to Tools > Board and select ESP8266 NodeMCU Dev Module. Next, go to Tools > Port and select the appropriate port through which your board is connected.

selecting ESP8266 NodeMCU in Arduino IDE

Click on the upload button to upload the code into the ESP8266 NodeMCU development board.

Now, we will upload the files into our ESP8266 NodeMCU board. Go to Tools > ESP8266 LittleFS Data Upload. After a few moments, the files will be uploaded.

If you don’t find “ESP8266 LittleFS Data Upload” option in your Arduino IDE that means you have not installed the file system uploaded plugin in Arduino IDE. You can read the following article to install a plugin:

Follow this link: LittleFS Introduction & Install ESP8266 NodeMCU Filesystem Uploader in Arduino IDE

LittleFS sketch data upload option Arduino IDE

After you have uploaded your code and the files to the ESP8266 NodeMCU development board, press its ENABLE button.

ESP8266 NodeMCU reset button

In your Arduino IDE, open up the serial monitor and you will be able to see the IP address of your ESP module.

ESP8266 NodeMCU WebSocket Server IP address

Copy this ESP8266 NodeMCU IP address into a web browser and press enter. The web server will look something like this:

ESP8266 NodeMCU WebSocket Server using Arduino IDE

Conclusion

In summary, in this tutorial, we have learned to create an ESP8266 NodeMCU web server using WebSocket communication protocols which support full-duplex communication between user and client. One of the main advantages of using WebSocket to create ESP8266 NodeMCU and ESP8266 web servers is that the server can notify all clients whenever something changes on the server or any client-side such as LED state, sensor output, etc.

Secondly, we have also created an ESP8266 NodeMCU WebSocket server with LittleFS or file system. We learned how to store CSS, JavaScript, and HTML files in the SPI flash file system of ESP8266 NodeMCU and build a WebSocket web server through those files. Instead of hard coding the HTML as string literals which takes up a lot of memory, the LittleFS will help us access the flash memory of the ESP8266 NodeMCU core.

For a demonstration purpose, we have controlled onboard LED of ESP8266 NodeMCU. But you can also control a relay. You can check this project to see How to control relay with ESP8266 NodeMCU but in the following project, we used HTTP protocol. But you can modify use the above code and modify it according to your requirement.

If you like this ESP8266 NodeMCU web server project, you can read more here:

Leave a Comment