ESP32 WebSocket Server: Display Sensor Readings

Table of Contents

ESP32 WebSocket Server Display Sensor Readings Arduino IDE

Throughout this tutorial, we’ll cover the complying with major topics:

We have a similar tutorial for the ESP8266 board:

A WebSocket is a persistent link between a customer and a web server that enables bidirectional interaction in between both events making use of a TCP connection. Adhere to the following tutorial to install the filesystem uploader plugin if you haven’t currently:

If you’re using VS Code with the PlatformIO extension, read the following tutorial to find out how to submit data to the filesystem:

Introducing WebSocket Protocol

To construct this task, you need to set up the following collections:

ESP32 ESP8266 WebSocket Server How it Works

You can mount the very first two libraries utilizing the Arduino Library Manager.

To find out even more about building web servers with the ESP32, we truly suggest taking a look at our e-book:

Learn more about the ESP32 with our sources:

Thank you for analysis.

Project Overview

Table of Contents

ESP32 Websocket Server Sensor Readings
  • We’ll create a web page that displays temperature, humidity, and pressure.
  • The web page displays the latest sensor readings when you open or refresh the web page.
  • The sensor readings update automatically whenever there’s a new reading available on the ESP32 without the need to refresh the webpage.
  • The web server works perfectly on multiple clients (multiple web browser tabs on the same device or different devices).

How does it work?

  • The ESP hosts a web server that displays a web page with three cards for the sensor readings.
  • When you open the webpage, it sends a message (getReadings) to the ESP via WebSocket protocol. The server (ESP) receives that message. When that happens, it gets new readings from the sensors and sends them back to the client (web browser), also via web socket protocol. This way, whenever you open a new tab, it always shows the current and updated values.
ESP32 Websocket server how it works
  • Every 30 seconds, the ESP gets new readings and sends them to all connected clients (all web browser tabs opened) via WebSocket protocol. The client receives that message and displays the readings on the web page.
ESP32 Websocket server how it works

Prerequisites

Throughout this tutorial, we’ll cover the following major subjects:

1) Parts Required

We have a comparable tutorial for the ESP8266 board:

  • – read
  • – check the

A WebSocket is a consistent connection between a client and also a server that enables bidirectional interaction in between both celebrations making use of a TCP connection. Follow the next tutorial to install the filesystem uploader plugin if you haven’t currently:

If you’re utilizing VS Code with the PlatformIO extension, checked out the complying with tutorial to discover just how to post files to the filesystem:

To build this job, you need to set up the adhering to collections:

2) Arduino IDE and ESP32 Boards Add-on

You can set up the very first 2 libraries using the Arduino Library Manager.

3) Filesystem Uploader Plugin

Copy the complying with to the script.js file.

4) Libraries

Here’s a listing of what this code does:

  •  (Arduino Library Manager)
  • (Arduino Library Manager)
  •  (.zip folder)
  •  (.zip folder)

Let’s take an appearance at this JavaScript code to see exactly how it works.

To discover even more concerning building web servers with the ESP32, we actually suggest taking a look at our book:

Installing Libraries (VS Code + PlatformIO)

Learn more concerning the ESP32 with our resources:

platform = [email protected]
board = esp32doit-devkit-v1
framework = arduino
monitor_speed = 115200
lib_deps = ESP Async WebServer
    arduino-libraries/Arduino_JSON @ 0.1.0
    adafruit/Adafruit BME280 Library @ ^2.1.0
    adafruit/Adafruit Unified Sensor @ ^1.1.4

Building the Circuit

Thank you for reading.

Schematic Diagram

We’re going to use I2C communication with the BME280 sensor module. For that, wire the sensor to the default ESP32 SCL (GPIO 22) and SDA (GPIO 21) pins, as shown in the following schematic diagram.

ESP32 Wiring to BME280 Schematic Diagram

Recommended reading:

Organizing Your Files

To keep the project organized and make it easier to understand, we’ll create four files to build the web server:

  • Arduino sketch: to get the sensor readings and handle the web server;
  • index.html: to define the content of the web page to display the sensor readings;
  • style.css: to style the web page;
  • script.js: to program the behavior of the web page—handle what happens when you open the web page and display the readings received via WebSocket protocol.
ESP32 Web Server with HTML, CSS and JavasScript files on data folder

You should save the HTML, CSS, and JavaScript files inside a folder called data inside the Arduino sketch folder, as shown in the previous diagram. We’ll upload these files to the ESP32 filesystem (SPIFFS).

You can download all project files:

HTML File

Copy the following to the index.html file.

<!DOCTYPE html>
<html>
    <head>
        <title>ESP IOT DASHBOARD</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="icon" type="image/png" href="favicon.png">
        <link rel="stylesheet" type="text/css" href="style.css">
    </head>
    <body>
        <div class="topnav">
            <h1>SENSOR READINGS (WEBSOCKET)</h1>
        </div>
        <div class="content">
            <div class="card-grid">
                <div class="card">
                    <p class="card-title"><i class="fas fa-thermometer-threequarters" style="color:#059e8a;"></i> Temperature</p>
                    <p class="reading"><span id="temperature"></span> &deg;C</p>
                </div>
                <div class="card">
                    <p class="card-title"> Humidity</p>
                    <p class="reading"><span id="humidity"></span> &percnt;</p>
                </div>
                <div class="card">
                    <p class="card-title"> Pressure</p>
                    <p class="reading"><span id="pressure"></span> hpa</p>
                </div>
            </div>
        </div>
        <script src="script.js"></script>
    </body>
</html>

We won’t go into much detail about the content of the HTML file. Just the relevant parts.

The following lines display a card for the temperature.

<div class="card">
    <p class="card-title"><i class="fas fa-thermometer-threequarters" style="color:#059e8a;"></i> Temperature</p>
    <p class="reading"><span id="temperature"></span> &deg;C</p>
</div>

The temperature will show up in the following paragraph between the <span> tags. Notice that you need a unique id for that HTML tag so that later we know how to refer to this HTML element. In this case, the unique id is temperature.

<span id="temperature"></span>

We do a similar procedure for the humidity and pressure. The unique ids for the HTML elements where we’ll display the humidity and pressure are humidity and pressure.

<div class="card">
      <p class="card-title"> Humidity</p>
      <p class="reading"><span id="humidity"></span> &percnt;</p>
</div>
<div class="card">
    <p class="card-title"> Pressure</p>
    <p class="reading"><span id="pressure"></span> hpa</p>
</div>

CSS File

Copy the following to the style.css file.  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 tutorial.

html {
    font-family: Arial, Helvetica, sans-serif;
    display: inline-block;
    text-align: center;
}
h1 {
    font-size: 1.8rem;
    color: white;
}
.topnav {
    overflow: hidden;
    background-color: #0A1128;
}
body {
    margin: 0;
}
.content {
    padding: 50px;
}
.card-grid {
    max-width: 800px;
    margin: 0 auto;
    display: grid;
    grid-gap: 2rem;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.card {
    background-color: white;
    box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
}
.card-title {
    font-size: 1.2rem;
    font-weight: bold;
    color: #034078
}
.reading {
    font-size: 1.2rem;
    color: #1282A2;
}

JavaScript File

Copy the following to the script.js file.


var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
// Init web socket when the page loads
window.addEventListener('load', onload);

function onload(event) {
    initWebSocket();
}

function getReadings(){
    websocket.send("getReadings");
}

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

// When websocket is established, call the getReadings() function
function onOpen(event) {
    console.log('Connection opened');
    getReadings();
}

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

// Function that receives the message from the ESP32 with the readings
function onMessage(event) {
    console.log(event.data);
    var myObj = JSON.parse(event.data);
    var keys = Object.keys(myObj);

    for (var i = 0; i < keys.length; i++){
        var key = keys[i];
        document.getElementById(key).innerHTML = myObj[key];
    }
}

Here’s a list of what this code does:

  • initializes a WebSocket connection with the server;
  • sends a message to the server to get the current sensor readings;
  • uses the response to update the sensor readings on the web page;
  • handles data exchange through the WebSocket protocol.

Let’s take a look at this JavaScript code to see how it works.

The gateway is the entry point to the WebSocket interface. window.location.hostname gets the current page address (the web server IP address).

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

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.

function onload(event) {
  initWebSocket();
}

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

Note that when the WebSocket connection is open, we’ll call the getReadings function.

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

The getReadings() function sends a message to the server getReadings to get the current sensor readings. Then, we must handle what happens when we receive that message on the server side (ESP32).

function getReadings(){
  websocket.send("getReadings");
}

We handle the messages received via WebSocket protocol on the onMessage() function.

// Function that receives the message from the ESP32 with the readings
function onMessage(event) {
  console.log(event.data);
  var myObj = JSON.parse(event.data);
  var keys = Object.keys(myObj);

  for (var i = 0; i < keys.length; i++){
    var key = keys[i];
    document.getElementById(key).innerHTML = myObj[key];
  }
}

The server sends the readings in JSON format, for example:

{
  temperature: 20;
  humidity: 50;
  pressure: 1023;
}

The onMessage() function simply goes through all the key values (temperature, humidity, and pressure) and places them in the corresponding places on the HTML page. In this case, the keys have the same name as the ids we’ve defined on the HTML page. So, we can simply do something like this:

for (var i = 0; i < keys.length; i++){
  var key = keys[i];
  document.getElementById(key).innerHTML = myObj[key];
}

Code for ESP32 WebSocket Server (Sensor Readings)

Copy the following code to your Arduino IDE.

/*********
  Rui Santos
  Complete instructions at https://RandomNerdTutorials.com/esp32-websocket-server-sensor/

  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/

#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "SPIFFS.h"
#include <Arduino_JSON.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>

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

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Create a WebSocket object
AsyncWebSocket ws("/ws");

// Json Variable to Hold Sensor Readings
JSONVar readings;

// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;

// Create a sensor object
Adafruit_BME280 bme;

// Init BME280
void initBME(){
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
}

// Get Sensor Readings and return JSON object
String getSensorReadings(){
  readings["temperature"] = String(bme.readTemperature());
  readings["humidity"] =  String(bme.readHumidity());
  readings["pressure"] = String(bme.readPressure()/100.0F);
  String jsonString = JSON.stringify(readings);
  return jsonString;
}

// Initialize SPIFFS
void initSPIFFS() {
  if (!SPIFFS.begin(true)) {
    Serial.println("An error has occurred while mounting SPIFFS");
  }
  Serial.println("SPIFFS mounted successfully");
}

// Initialize WiFi
void initWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
}

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

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;
    //String message = (char*)data;
    // Check if the message is "getReadings"
    //if (strcmp((char*)data, "getReadings") == 0) {
      //if it is, send current sensor readings
      String sensorReadings = getSensorReadings();
      Serial.print(sensorReadings);
      notifyClients(sensorReadings);
    //}
  }
}

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

void setup() {
  Serial.begin(115200);
  initBME();
  initWiFi();
  initSPIFFS();
  initWebSocket();

  // Web Server Root URL
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(SPIFFS, "/index.html", "text/html");
  });

  server.serveStatic("/", SPIFFS, "/");

  // Start server
  server.begin();
}

void loop() {
  if ((millis() - lastTime) > timerDelay) {
    String sensorReadings = getSensorReadings();
    Serial.print(sensorReadings);
    notifyClients(sensorReadings);

  lastTime = millis();

  }

  ws.cleanupClients();
}

How the Code Works

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

Including Libraries

The Adafruit_Sensor and Adafruit_BME280 libraries are needed to interface with the BME280 sensor.

#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>

The WiFi, ESPAsyncWebServer and AsyncTCP libraries are used to create the web server.

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

We’ll save the HTML, CSS, and JavaScript files on the ESP32 filesystem, so we also need to include the SPIFFS.h library.

#include "SPIFFS.h"

To create JSON objects, we’ll use the Arduino_JSON library.

#include <Arduino_JSON.h>

Network Credentials

Insert your network credentials in the following variables, so that the ESP32 can connect to your local network using Wi-Fi.

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

AsyncWebServer and AsyncWebSocket

Create an AsyncWebServer object on port 80.

AsyncWebServer server(80);

The following line creates a new websocket object on /ws.

// Create a WebSocket object
AsyncWebSocket ws("/ws");

Timer Variables

The following variables are used to create timers in our code. In our case, we’ll send sensor readings to the client via WebSocket protocol every 30000 milliseconds (30 seconds). You can change the timerDelay variable to any other value that makes sense for your project.

// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;

Initializing the BME280 Sensor

The following line creates an Adafruit_BME280 object to refer to the sensor called bme.

// Create a sensor object
Adafruit_BME280 bme;

The initBME() function initializes the sensor. It will be called later in the setup().

// Init BME280
void initBME(){
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
}

Getting Sensor Readings (JSON String)

The getSensoreadings() function creates a JSON string with the current sensor readings.

// Get Sensor Readings and return JSON object
String getSensorReadings(){
  readings["temperature"] = String(bme.readTemperature());
  readings["humidity"] = String(bme.readHumidity());
  readings["pressure"] = String(bme.readPressure()/100.0F);
  String jsonString = JSON.stringify(readings);
  return jsonString;
}

Initializing the Filesystem

The initSPIFFS() function initializes SPIFFS, the ESP32 filesystem we’re using in this project to save the HTML, CSS, and Javascript files.

// Initialize SPIFFS
void initSPIFFS() {
  if (!SPIFFS.begin(true)) {
    Serial.println("An error has occurred while mounting SPIFFS");
  }
  Serial.println("SPIFFS mounted successfully");
}

Initializing Wi-Fi

The following function initializes Wi-Fi and connects to your network using the credentials you used previously. This function will be called later in the setup().

// Initialize WiFi
void initWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
}

Notifying all Clients Via WebSocket

The notifyClients() function notifies all clients with the current sensor readings. Calling this function is what allows us to notify changes in all clients whenever we get new sensor readings (every 30 seconds).

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

Handling WebSocket Messages

The handleWebSocketMessage(), as the name suggests, handles what happens when the server receives a message from the client via WebSocket protocol. We’ve seen in the JavaScript file, that the server can receive the getReadings message.

When the ESP32 receives the getReadings message, it sends the current sensor readings.

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;
    String message = (char*)data;
    //Check if the message is "getReadings"
    if (strcmp((char*)data, "getReadings") == 0) {
      if it is, send current sensor readings
      String sensorReadings = getSensorReadings();
      Serial.print(sensorReadings);
      notifyClients(sensorReadings);
    }
  }
}

Handling WebSocket Events

The onEvent() function handles other WebSocket events.

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 Protocol

The initWebSocket() function initializes the WebSocket protocol.

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

setup()

In the setup(), we initialize the Serial Monitor, the BME280 sensor, Wi-Fi, the filesystem, and the WebSocket protocol by calling the functions we’ve created previously.

Serial.begin(115200);
initBME();
initWiFi();
initSPIFFS();
initWebSocket();

The following lines will serve the index.html and the other referenced static files saved on SPIFFS (style.css and script.js) when you access the web server.

// Web Server Root URL
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
  request->send(SPIFFS, "/index.html", "text/html");
});

server.serveStatic("/", SPIFFS, "/");

Finally, start the server.

// Start server
server.begin();

loop()

In the loop(), we get and send new sensor readings every 30000 milliseconds (30 seconds).

void loop() {
  if ((millis() - lastTime) > timerDelay) {
    String sensorReadings = getSensorReadings();
    Serial.print(sensorReadings);
    notifyClients(sensorReadings);

    lastTime = millis();

  }

  ws.cleanupClients();
}

Upload Code and Files

After inserting your network credentials, save the code. Go to Sketch > Show Sketch Folder, and create a folder called data.

Arduino IDE Open Sketch Folder to create data folder

Inside that folder, you should place the HTML, CSS, and JavaScript files.

Upload those files to the filesystem: go to Tools ESP32 Data Sketch Upload and wait for the files to be uploaded.

ESP32 Sketch Data Upload SPIFFS Arduino IDE

Then, upload the code to your ESP32 board. Make sure you modified the code with your network credentials.

Upload Arduino code

When everything is successfully uploaded, open the Serial Monitor at a baud rate of 115200. Press the ESP32 EN/RST button, and it should print the ESP32 IP address.

Demonstration

Open a browser on your local network and paste the ESP32 IP address. You should get access to the web server page that displays the sensor readings.

ESP32 Websocket Server Sensor Readings

The readings update automatically on the web page every 30 seconds.

You can have multiple clients on different web browser tabs or devices and it will update automatically on all clients.

ESP32 WebSocket Server Display Sensor Readings Arduino IDE Demonstration

Wrapping Up

In this tutorial, you’ve learned how to build a websocket server with the ESP32 that serves a web page to display sensor readings. The sensor readings update automatically on the web page without the need to manually refresh it.

We hope you learned a lot from this tutorial. Let us know in the comments below if you successfully followed this tutorial and got the project working.

To learn more about building web servers with the ESP32, we really recommend taking a look at our eBook:

Learn more about the ESP32 with our resources:

Thank you for reading.