Display Sensor Readings in Gauges with ESP32 Web Server

In this user guide, we will learn to build ESP32 Gauges Web server using Server-Sent Events (SSE). Using this technique, we will build an ESP32 controlled web server that will automatically update sensor readings such as temperature and humidity to all connected web clients and display them in linear and radial gauges. Any appropriate sensor can be used such as DS18B20, BME680, LM35, and MPU6050 but for this article, we will use a BME280 sensor which is used to measure temperature, pressure, and humidity. We will use Arduino IDE to program our ESP32 board which will be connected to a BME280 sensor.

Our client will receive automatic updates from the ESP32 board with SSE through an HTTP connection. This will be highly useful to automatically send new sensor readings to the web client whenever they will be available. Without making any additional requests, the webpage will spontaneously update.

Moreover, to make our ESP32 web server project more convenient and practical we will use the feature of ESP32 SPI flash file system (SPIFFS). We will save the HTML, CSS, and JavaScript files on the ESP32 SPIFFS that will be used to build the web server. We can use SPIFFS to store files in SPI flash without having to use any external memory with our ESP module.

ESP32 Web Server Display Sensor Readings in Gauges Arduino IDE

We have a similar guide with ESP8266 NodeMCU:

ESP32 Gauges Web Server Project Overview

We aim to build a webpage that will display sensor readings in gauges obtained from BME280 connected with the ESP32 module. We will create a web server based on the Server-Sent Events (SSE) protocol that will display two types of gauges to show the sensor readings. It will consist of a title, “ESP Gauges Web server,” and two cards. The first card will contain the heading ‘Humidity’ and a radial gauge to display humidity readings from BME280 sensor. Likewise, the second card will contain the heading ‘Temperature’ and a linear gauge to display temperature readings from BME280 sensor.

This transmission of data from the server (ESP32) to the client (web page) will occur through Server-Sent Events. These readings will automatically update after every 10 seconds to new values when the web page receives them. Through SSE protocol, it will be very easy to update the sensor readings on our web page without adding any additional requests.

Note: We will use the canvas-gauges JavaScript library to help us create the two gauges.

Working Process

  1. The client starts the SSE connection. It sends an HTTP request to the ESP32 module. This will cause the module to send events to the web page.
  2. ESP32 connected with BME280 receives sensor readings after every 10 seconds.
  3. The ‘new_readings’ events consist of the JSON string consisting of temperature and pressure readings.
  4. These events sent by the module have event listeners associated with them on the client-side. All the updated readings are received by the client on the said events.
  5. As in the case with SSE, only the ESP32 board will be able to send events to the webpage. The web page gets updated accordingly and the process continues.
ESP32 Gauges Web Server Project Overview
ESP32 Gauges Web Server Project Overview

Recommneded Reading: ESP32 Server-Sent Events (SSE) Web Server (Arduino IDE)

Introduction to BME280 sensor

Recommended Readings:
MicroPython: BME280 Web Server with ESP32/ESP8266 (Weather Station)
MicroPython: BME280 with ESP32 and ESP8266 – Measure Temperature, Humidity, and Pressure
ESP32 Send Sensor Readings to ThingSpeak using Arduino IDE (BME280)

The BME280 sensor is used to measure readings regarding ambient temperature, barometric pressure, and relative humidity. It is mostly used in web and mobile applications where low power consumption is key. This sensor uses I2C or SPI to communicate data with the micro-controllers. Although there are several different versions of BME280 available in the market, the one we will be studying uses the I2C communication protocol and SPI.

Connecting BME280 sensor with the ESP32 development board

The connection of BME280 with the ESP32 boards is very easy. We have to connect the VCC terminal with 3.3V, ground with the ground (common ground), SCL of the sensor with SCL of the module, and SDA of the sensor with the SDA pin of the ESP modules.

The I2C pin in ESP32 for SDA is GPIO21 and for SCL is GPIO22.

Required Components:

We will need the following components to connect our ESP32 board with the BME280 sensor.

  • ESP32 board
  • BME280 Sensor
  • Connecting Wires
  • Breadboard

Follow the schematic diagram below for the ESP32 module and connect them accordingly.

BME280 with ESP32 MicroPython
Connection Diagram

In some BME280 sensors as seen in the above connection diagram, the SCK terminal means the SCL pin and is connected with its respective GPIO pin on the ESP board. Likewise, the SDI terminal means the SDA pin and is connected with its respective GPIO pin on the board. Vin is connected with a 3.3V pin on the module and both the ESP board and the sensor is commonly grounded.

Setting up Arduino IDE

We will use Arduino IDE to program our ESP32 development board. Thus, you should have the latest version of Arduino IDE. Additionally, you also need to install the ESP32 plugin.

If your IDE does not have the plugin installed you can visit the link below: Installing ESP32 library in Arduino IDE and upload code.

Also, you will have to download and install the ESP32 Filesystem Uploader Plugin in your Arduino IDE so that you are able to upload the SPIFFS files on your ESP32 board. If you haven’t, you can follow this tutorial:

Installing BME280 Libraries

As we are connecting the BME280 sensor with ESP32 so we will have to install BME280 libraries to our module. We will require two libraries for this project:

  1. Adafruit BME280 library
  2. Adafruit Unified Sensor library

We will use the Library Manager in our Arduino IDE to install the latest versions of the libraries. Open your Arduino IDE and go to Sketch > Include Libraries > Manage Libraries. Type each library name in the search bar and install them both.

Adafruit BME280 library Arduino IDE
Adafruit unified sensor library install

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. AsyncTCP 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 ESP32 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 Async TCP library for free, click here to download. You will download the library as a .zip folder which you will extract and rename as ‘AsyncTCP.’ 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.

Installing Arduino_JSON Library

You will also have to install the Arduino_JSON library as we will be dealing with JSON script. Open your Arduino Library Manager by clicking Sketch > Include Library > Manage Libraries. Type ‘Arduino_JSON’ in the search tab and press enter. Install the library which is highlighted below.

Arduino_Json library by arduino install

After installation of the libraries, restart your IDE.

Creating Files for SPIFFS

In most of our web server projects with ESP32, we have included HTML 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 an HTTTP request, we send this string as a response which is basically a web page. On the contrary, with SPIFFS, we can save HTML, CSS, and JavaScript files in ESP32 flash file system. Whenever a web client makes a request, we can serve these files directly from SPIFFS. It is convenient to have separate HTML, CSS, and JavaScript files saved on the ESP32 SPIFFS. All of these three files will later be linked together. The SPIFFS will help us access the flash memory of the ESP32 core. We will store our files in the flash memory of the ESP32 board and serve them in our web server.

To build an ESP32 web server using SPIFFS, we will create three different files: HTML, CSS, JavaScript and Arduino sketch and organize them in a project folder like shown below:

ESP Gauges Web Server creating files for SPIFFS

Note: You should place HTML, CSS and JavaScript files inside the data folder. Otherwise, SPIFFS library will not be able to read these files.

You can read our previous SPIFFS web server tutorial with ESP32:

Creating HTML file

Inside our HTML file, we will specify the title of the web page and the two cards to display the gauges.

Now, create an index.html file and replicate the code given below in that file.

<!DOCTYPE html>
<html>
  <head>
    <title>ESP Gauges Web Server</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/png" href="favicon.png">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="style.css">
    http://cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.7/all/gauge.min.js
  </head>
  <body>
    <div class="topnav">
      <h1>ESP Gauges Web Server</h1>
    </div>
    <div class="content">
      <div class="card-grid">
        <div class="card">
          <p class="card-title">Humidity</p>
          <canvas id="gauge-humidity"></canvas>
        </div>
        <div class="card">
          <p class="card-title">Temperature</p>
          <canvas id="gauge-temperature"></canvas>
        </div>
      </div>
    </div>
    http://script.js
  </body>
</html>

Building the Web page

We will start with the title of the web page. The tag will indicate the beginning of the <title> and the </title> tag will indicate the ending. In between these tags, we will specify “ESP Gauges Web Server” which will be displayed in the browser’s title bar.

    <title>ESP Gauges 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">

Between the <head></head> tags, we will reference the CSS file as we’ll be creating different files for both HTML and CSS by using the <link> tag. This tag will be used so that we can link with an external style sheet which we will specify as a CSS file. It will consist of three attributes. The first one is rel which shows the relationship between the current file and the one which is being linked.

We will specify “stylesheet” which will change the visuals of the web page. The second attribute is type which we will specify as “text/css” as we will be using the CSS file for styling purpose. The third attribute is href and it will specify the location of the linked file. Both of the files (HTML & CSS) will be saved in the same folder (data) so we will just specify the name of the CSS file that is “style.css.”

<link rel="stylesheet" type="text/css" href="style.css">

Additionally, we will reference the canvas-gauges library as well to create the gauges.

http://cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.7/all/gauge.min.js
Body

Inside the HTML web page body, we will include the heading and the cards that will display the gauges with the sensor readings. This will go inside the <body></body> tags which will mark the beginning and the ending of the script.

We will include the heading of our webpage inside the <h1></h1> tags and it will be “ESP Gauges Web Server”.

<h1>ESP Gauges Web Server</h1>

Next, the following lines of code will be used to create the two gauges. We have included two cards which we will use to display the gauges. The first card has the title ‘Humidity’ and has a <canvas> id ‘gauge-humidity’ that will be used later on to display the radial gauge. Likewise, the second card has the title ‘Temperature’ and has a <canvas> id ‘gauge-temperature’ that will be used later on to display the linear gauge.

    </div>
    <div class="content">
      <div class="card-grid">
        <div class="card">
          <p class="card-title">Humidity</p>
          <canvas id="gauge-humidity"></canvas>
        </div>
        <div class="card">
          <p class="card-title">Temperature</p>
          <canvas id="gauge-temperature"></canvas>
        </div>
      </div>
    </div>

Creating CSS File

Next, create another file “style.css” in the data folder and copy the code given below in that file. CSS is used to give styles to a web page.

html {
  font-family: New Times Roman; 
  display: inline-block; 
  text-align: center;
}
h1 {
  font-size: 2.0rem; 
  color: white;
}
p { 
  font-size: 1.6rem;
}
.topnav { 
  overflow: hidden; 
  background-color: #111d88;
}
body {  
  margin: 0;
}
.content { 
  padding: 5%;
}
.card-grid { 
  max-width: 1200px; 
  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.4rem;
  font-weight: bold;
  color: #034078
}

To add CSS files in head tags, we will use <style></style> tags to mark the beginning and the end. We will set the display text to font type Times New Roman and align it in the center of the webpage. The font size, color and positioning of the heading, paragraphs and the cards will also be set.

Creating JavaScript File

Now create another file in the data folder and name it ‘script.js.’ The JavaScript file will be responsible for handling the SSE events and the creation of the gauges using the canvas-gauges library.

// Get current sensor readings when the page loads  
window.addEventListener('load', getReadings);

// Create Temperature Gauge
var gaugeTemperature = new LinearGauge({
  renderTo: 'gauge-temperature',
    width: 120,
    height: 400,
    units:"Degree Celsius",
    minValue: 0,
    maxValue: 50,
    majorTicks: [
        "0",
        "10",
        "20",
        "30",
        "40",
        "50",
    ],
    minorTicks: 10,
    strokeTicks: true,
    highlights: [
        {
            "from": 25,
            "to": 50,
            "color": "rgba(200, 50, 50, .75)"
        }
    ],
    colorPlate: "transparent",
    borderShadowWidth: 0,
    borders: false,
    needleType: "arrow",
    needleWidth: 2,
    animationDuration: 1500,
    animationRule: "linear",
    tickSide: "both",
    numberSide: "left",
    needleSide: "both",
    barStrokeWidth: 7,
    barBeginCircle: false,
}).draw();
  
// Create Humidity Gauge
var gaugeHumidity = new RadialGauge({
    renderTo: 'gauge-humidity',
    width: 300,
    height: 300,
    units: "%",
    title: "Humidity",
    minValue: 0,
    maxValue: 100,
    majorTicks: [
        0,
        10,
        20,
        30,
        40,
        50,
        60,
        70,
        80,
        90,
        100,
        
    ],
    minorTicks: 2,
    strokeTicks: true,
    highlights: [
        {
            "from": 0,
            "to": 50,
            "color": "rgba(0,0, 255, .3)"
        },
        {
            "from": 50,
            "to": 100,
            "color": "rgba(255, 0, 0, .3)"
        }
    ],
    ticksAngle: 225,
    startAngle: 67.5,
    colorMajorTicks: "#ddd",
    colorMinorTicks: "#ddd",
    colorTitle: "#eee",
    colorUnits: "#ccc",
    colorNumbers: "#eee",
    colorPlate: "#222",
    borderShadowWidth: 0,
    borders: true,
    needleType: "arrow",
    needleWidth: 2,
    needleCircleSize: 7,
    needleCircleOuter: true,
    needleCircleInner: false,
    animationDuration: 1500,
    animationRule: "linear",
    colorBorderOuter: "#333",
    colorBorderOuterEnd: "#111",
    colorBorderMiddle: "#222",
    colorBorderMiddleEnd: "#111",
    colorBorderInner: "#111",
    colorBorderInnerEnd: "#333",
    colorNeedleShadowDown: "#333",
    colorNeedleCircleOuter: "#333",
    colorNeedleCircleOuterEnd: "#111",
    colorNeedleCircleInner: "#111",
    colorNeedleCircleInnerEnd: "#222",
    valueBoxBorderRadius: 0,
    colorValueBoxRect: "#222",
    colorValueBoxRectEnd: "#333"
}).draw();

// Function to get current readings on the webpage when it loads for the first time
function getReadings(){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var myObj = JSON.parse(this.responseText);
      console.log(myObj);
      var temp = myObj.temperature;
      var hum = myObj.humidity;
      gaugeTemperature.value = temp;
      gaugeHumidity.value = hum;
    }
  }; 
  xhr.open("GET", "/readings", true);
  xhr.send();
}

if (!!window.EventSource) {
  var source = new EventSource('/events');
  
  source.addEventListener('open', function(e) {
    console.log("Events Connected");
  }, false);

  source.addEventListener('error', function(e) {
    if (e.target.readyState != EventSource.OPEN) {
      console.log("Events Disconnected");
    }
  }, false);
  
  source.addEventListener('message', function(e) {
    console.log("message", e.data);
  }, false);
  
  source.addEventListener('new_readings', function(e) {
    console.log("new_readings", e.data);
    var myObj = JSON.parse(e.data);
    console.log(myObj);
    gaugeTemperature.value = myObj.temperature;
    gaugeHumidity.value = myObj.humidity;
  }, false);
}

The following line calls the function getReadings when the web page loads.

window.addEventListener('load', getReadings);

Create the Gauges

To build the gauges we will use canvas-gauges library. We will display two type of gauges: linear and radial. The temperature reading will be displayed in a linear gauge that will look like a thermometer. Similarly, the humidity reading will be displayed in a radial gauge.

To create a linear gauge for the temperature, we will use the following lines of code:

The ‘gaugeTemperature’ var will be used to create the linear gauge using the LinearGauge() method. This method takes in several parameters that involve the dimensions, minimum/maximum value, colors and overall visuals of the gauge. Moreover, we specify the ‘gauge-temperature’ id that will be used to properly place this gauge in the <canvas> HTML element with the appropriate id. Use the draw() method to display the gauge.

// Create Temperature Gauge
var gaugeTemperature = new LinearGauge({
  renderTo: 'gauge-temperature',
    width: 120,
    height: 400,
    units:"Degree Celsius",
    minValue: 0,
    maxValue: 50,
    majorTicks: [
        "0",
        "10",
        "20",
        "30",
        "40",
        "50",
    ],
    minorTicks: 10,
    strokeTicks: true,
    highlights: [
        {
            "from": 25,
            "to": 50,
            "color": "rgba(200, 50, 50, .75)"
        }
    ],
    colorPlate: "transparent",
    borderShadowWidth: 0,
    borders: false,
    needleType: "arrow",
    needleWidth: 2,
    animationDuration: 1500,
    animationRule: "linear",
    tickSide: "both",
    numberSide: "left",
    needleSide: "both",
    barStrokeWidth: 7,
    barBeginCircle: false,
}).draw();

Likewise, to create a radial gauge to display the humidity reading, we will use the following lines of code:

The ‘gaugeHumidity’ var will be used to create the linear gauge using the RadialGauge() method. This method takes in several parameters that involve the dimensions, minimum/maximum value, colors and overall visuals of the gauge. Moreover, we specify the ‘gauge-humidity’ id that will be used to properly place this gauge in the <canvas> HTML element with the appropriate id. Use the draw() method to display the gauge.

// Create Humidity Gauge
var gaugeHumidity = new RadialGauge({
    renderTo: 'gauge-humidity',
    width: 300,
    height: 300,
    units: "%",
    title: "Humidity",
    minValue: 0,
    maxValue: 100,
    majorTicks: [
        0,
        10,
        20,
        30,
        40,
        50,
        60,
        70,
        80,
        90,
        100,
        
    ],
    minorTicks: 2,
    strokeTicks: true,
    highlights: [
        {
            "from": 0,
            "to": 50,
            "color": "rgba(0,0, 255, .3)"
        },
        {
            "from": 50,
            "to": 100,
            "color": "rgba(255, 0, 0, .3)"
        }
    ],
    ticksAngle: 225,
    startAngle: 67.5,
    colorMajorTicks: "#ddd",
    colorMinorTicks: "#ddd",
    colorTitle: "#eee",
    colorUnits: "#ccc",
    colorNumbers: "#eee",
    colorPlate: "#222",
    borderShadowWidth: 0,
    borders: true,
    needleType: "arrow",
    needleWidth: 2,
    needleCircleSize: 7,
    needleCircleOuter: true,
    needleCircleInner: false,
    animationDuration: 1500,
    animationRule: "linear",
    colorBorderOuter: "#333",
    colorBorderOuterEnd: "#111",
    colorBorderMiddle: "#222",
    colorBorderMiddleEnd: "#111",
    colorBorderInner: "#111",
    colorBorderInnerEnd: "#333",
    colorNeedleShadowDown: "#333",
    colorNeedleCircleOuter: "#333",
    colorNeedleCircleOuterEnd: "#111",
    colorNeedleCircleInner: "#111",
    colorNeedleCircleInnerEnd: "#222",
    valueBoxBorderRadius: 0,
    colorValueBoxRect: "#222",
    colorValueBoxRectEnd: "#333"
}).draw();

Get Readings

The getReadings() will be responsible to obtain the current BME280 sensor readings.


function getReadings(){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var myObj = JSON.parse(this.responseText);
      console.log(myObj);
      var temp = myObj.temperature;
      var hum = myObj.humidity;
      gaugeTemperature.value = temp;
      gaugeHumidity.value = hum;
    }
  }; 
  xhr.open("GET", "/readings", true);
  xhr.send();
}

Through this function, the HTTP GET request will be made to the ESP module regarding sending the BME280 sensor readings.

Inside this function we use the XMLHttpRequest. This will allow us to make an HTTP request in JavaScript. To make the HTTP GET request to we will follow three steps:
Firstly, we will create an XMLHttpRequest as follows:

var xhr = new XMLHttpRequest();

Secondly, we will initialize the request by using the xhr.open() method. Inside it we will pass on three arguments. The first argument specifies the type of HTTP method which is GET in our case. The second argument is the URL to which are ESP32 will request upon. In our case, it will be /readings. The last argument is true which specifies that the request is asynchronous.

 xhr.open("GET", "/readings", true);

Lastly, we will use xhr.send() to open the connection. Our server (ESP32) will now be able to receive the HTTP GET request whenever the page first loads.

xhr.send();

Additionally, we will define a function that will be executed when the readyState property changes. In our case, we have set the response to be sent when the status is OK (200) and the readyState to ‘4’ that means that the response is ready.

Now, as the response, the ESP32 board sends the sensor readings in a JSON string. This string will be first converted to a JSON object and logged on the console. Moreover, to set the gauge readings we will set the value of gauges with the current sensor readings. In our case, the temperature gauge is called ‘gaugeTemperature’ hence we will set its value by using gaugeTemperature.value to temp (current temperature reading). Similarly, for the humidity gauge we will use gaugeHumidity.value to hum (current humidity reading).

  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var myObj = JSON.parse(this.responseText);
      console.log(myObj);
      var temp = myObj.temperature;
      var hum = myObj.humidity;
      gaugeTemperature.value = temp;
      gaugeHumidity.value = hum;
    }
  }; 

Receive and Handle SSE Events

We will mainly handle four kinds of occurrences:

  1. Connection
  2. Disconnection
  3. Messages sent by the server
  4. events

Firstly, we will define an event source and pass the /events URL inside it. This URL will send the updates to our web page.

if (!!window.EventSource) {
  var source = new EventSource('/events');

Then, we will use the addEventListener() function to listen to incoming events. It will have two arguments. The first will be the event name and the second will be function(e).

The following are listeners for connection, disconnection and incoming messages from the server. We used the default event listeners in this case which were listed in the Documentation.

  source.addEventListener('open', function(e) {
    console.log("Events Connected");
  }, false);

  source.addEventListener('error', function(e) {
    if (e.target.readyState != EventSource.OPEN) {
      console.log("Events Disconnected");
    }
  }, false);
  
  source.addEventListener('message', function(e) {
    console.log("message", e.data);
  }, false);

Next, we will include listener for ‘new_readings’. This event will be sent to the web server. When the client will receive the particular event, it will print the BME280 sensor readings on the browser console. Additionally, the readings will be changed to a JSON object which will then be displayed on their respective gauges.


  source.addEventListener('new_readings', function(e) {
    console.log("new_readings", e.data);
    var myObj = JSON.parse(e.data);
    console.log(myObj);
    gaugeTemperature.value = myObj.temperature;
    gaugeHumidity.value = myObj.humidity;
  }, false);

Arduino Sketch ESP32 Gauges Web Server

Now, open your Arduino IDE and go to File > New to open a new file. Copy the code given below in that file and save it as ‘ESP_Web_Server’.

Remember to replace your network credentials.

#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 = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

AsyncWebServer server(80);
AsyncEventSource events("/events");

JSONVar readings;

unsigned long previous_time = 0;
unsigned long Delay = 10000;

Adafruit_BME280 bme; 

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

void setup() {
  Serial.begin(115200);
  if (!bme.begin(0x76)) {
    Serial.println("Could not find BME280 sensor, check circuit!");
    while (1);
  }

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

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

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

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

  // Request for the latest sensor readings
  server.on("/readings", HTTP_GET, [](AsyncWebServerRequest *request){
    String json = getReadings();
    request->send(200, "application/json", json);
    json = String();
  });

  events.onConnect([](AsyncEventSourceClient *client){
    if(client->lastId()){
      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
    }
    client->send("hello!", NULL, millis(), 10000);
  });
  server.addHandler(&events);
  server.begin();
}

void loop() {
  if ((millis() - previous_time) > Delay) {
    Serial.println(getReadings());
    events.send("ping",NULL,millis());
    events.send(getReadings().c_str(),"new_readings" ,millis());
    previous_time = millis();
  }
}

How the Code Works?

Firstly, we will include the necessary libraries. As we have to connect our ESP32 to a wireless network hence we need WiFi.h library for that purpose. The ESPAsyncWebServer.h and AsyncTCP.h libraries are the ones that we recently downloaded and will be required for the building of the asynchronous web server. The Adafruit_BME280 library is used in implementing the hardware functionalities of the sensor whereas the Adafruit_Sensor is a unified sensor library. Additionally, the Arduino_JSON library will be used as we will use a JSON strings to store the sensor readings. Also, the SPIFFS library will allow us to access the flash memory file system of our ESP32 core.

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

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

const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

Creating an AsyncWebServer Object

The AsyncWebServer object will be used to set up the ESP32 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.

AsyncWebServer server(80);

Defining an EventSource

Next, we will define an event source called ‘/events.’ You can use any appropriate name.

AsyncEventSource events("/events");

Defining Variables

We will create a JSON variable called ‘readings’ to store the sensor readings.

JSONVar readings;

Adding delay

The web server will update sensor readings after every 10 seconds. We will incorporate this value in the ‘Delay’ variable in milliseconds.

unsigned long previous_time = 0;
unsigned long Delay = 10000;

Creating BME280 Object

Additionally, we will also create an object of Adafruit_BME280 called ‘bme’ which we will use later on to initialize the sensor and access the readings.

Adafruit_BME280 bme;

Get Sensor Readings

The following getReadings() function will return a JSON string consisting of the temperature and humidity readings. We will use the bme object on the following methods: readTemperature() and readHumidity() individually to access each reading.

String getReadings(){
  readings["temperature"] = String(bme.readTemperature());
  readings["humidity"] =  String(bme.readHumidity());
  String jsonString = JSON.stringify(readings);
  return jsonString;
}

setup()

Inside the setup() function, we will open a serial connection at a baud rate of 115200.

  Serial.begin(115200);

Next we will initialize the BME280 sensor. If the connection between the module and the sensor is incorrect it will print that message on the serial monitor.

  if (!bme.begin(0x76)) {
    Serial.println("Could not find BME280 sensor, check circuit!");
    while (1);
  }

The following section of code will connect our ESP32 board with the local network whose network credentials we already specified above. After the connection will be established, the IP address of the ESP32 board will get printed on the serial monitor. This will help us to make a request to the server.

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

These lines of code will initialize the SPIFFS.

    if (!SPIFFS.begin()) {
    Serial.println("An error has occurred while mounting SPIFFS");
  }
  Serial.println("SPIFFS mounted successfully");
Handling Requests

We will use the on() method on the server object to listen to the incoming HTTP requests and execute functions accordingly. The send() method uses to return the HTTP response.

The index.html file will send to the client, whenever the server will receive a request on the “/” URL.

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

When the server will receive a request on the /readings URL, the JSON string with the current BME280 sensor readings will be sent.

  server.on("/readings", HTTP_GET, [](AsyncWebServerRequest *request){
    String json = getReadings();
    request->send(200, "application/json", json);
    json = String();
  });

The event source will be set up on our ESP32 board thorough the following lines of code:


  events.onConnect([](AsyncEventSourceClient *client){
    if(client->lastId()){
      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
    }
    client->send("hello!", NULL, millis(), 10000);
  });
  server.addHandler(&events);

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

  server.begin();

loop()

In the loop() function we will check if 10 seconds have passed since the sensor readings were updated. Then we will call the getReadings() function. On the serial monitor, these current sensor readings will get displayed as a JSON string. Lastly, we will send all these updated sensor readings to the web browser. The web server will now get updated to new sensor readings.

void loop() {
  if ((millis() - previous_time) > Delay) {
    Serial.println(getReadings());
    events.send("ping",NULL,millis());
    events.send(getReadings().c_str(),"new_readings" ,millis());
    previous_time = millis();
  }
}

Demonstration

Choose the correct board and COM port before uploading your code to the board.
Go to Tools > Board and select ESP32 Dev Module.

select esp32 board

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

Selecting COM PORT ESP32

Click on the upload button to upload the code into the ESP32 development board. After you have uploaded your code to the ESP32 development board press its ENABLE button.

ESP32 enable reset button

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

ESP32 Gauges Web Server Project serial monitor demo

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

ESP32 Gauges Web Server Project Laptop View

You can view all three sensor readings After every 10 seconds the sensor readings will get updated on both the serial monitor and the web page.

ESP32 Gauges Web Server Project Mobile View

If you finds this ESP32 web server project interesting, you may also like to read:

Leave a Comment