ESP32 Web Server: Display Sensor Readings in Gauges

Learn how to build a web server with the ESP32 to display sensor readings in gauges. As an example, we’ll display temperature and humidity from a BME280 sensor in two different gauges: linear and radial. You can easily modify the project to plot any other data. To build the gauges, we’ll use the canvas-gauges JavaScript library.

ESP32 Web Server Display Sensor Readings in Gauges Arduino IDE

We have a similar tutorial for the ESP8266 board: Web Server – Display Sensor Readings in Gauges

Project Overview

This project will build a web server with the ESP32 that displays temperature and humidity readings from a BME280 sensor. We’ll create a linear gauge that looks like a thermometer to display the temperature, and a radial gauge to display the humidity.

ESP32 Gauges Web Server Overview

Server-Sent Events

The readings are updated automatically on the web page using Server-Sent Events (SSE).

ESP32 Gauges Web Server Overview Server-Sent Events

To learn more about SSE, you can read:

Files Saved on the Filesystem

To keep our project better organized and easier to understand, we’ll save the HTML, CSS, and JavaScript files to build the web page on the board’s filesystem (LittleFS).

Prerequisites

Make sure you check all the prerequisites in this section before continuing with the project.

1. Install ESP32 Board in Arduino IDE

We’ll program the ESP32 using Arduino IDE. So, you must have the ESP32 add-on installed. Follow the next tutorial if you haven’t already:

If you want to use VS Code with the PlatformIO extension, follow the next tutorial instead to learn how to program the ESP32:

2. Filesystem Uploader Plugin

To upload the HTML, CSS, and JavaScript files to the ESP32 flash memory (LittleFS), we’ll use a plugin for Arduino IDE: LittleFS Filesystem uploader. Follow the next tutorial to install the filesystem uploader plugin:

If you’re using VS Code with the PlatformIO extension, read the following tutorial to learn how to upload files to the filesystem:

3. Installing Libraries

To build this project, you need to install the following libraries:

You can install the first three libraries using the Arduino Library Manager. Go to Sketch Include Library > Manage Libraries and search for the libraries’ names.

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

Installing Libraries (VS Code + PlatformIO)

If you’re programming the ESP32 using PlatformIO, you should add the following lines to the platformio.ini file to include the libraries and set the default filesystem to LittleFS (also change the Serial Monitor speed to 115200):

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
board_build.filesystem = littlefs

Parts Required

To follow this tutorial you need the following parts:

You can use any other sensor that is useful for your project. If you don’t have the sensor, you can also experiment with random values to learn how the project works.

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematic Diagram

We’ll send temperature and humidity readings from a BME280 sensor. 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 Circuit to BME280 Schematic Diagram

Recommended reading: ESP32 Pinout Reference: Which GPIO pins should you use?

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 that handles the web server;
  • index.html: to define the content of the web page;
  • sytle.css: to style the web page;
  • script.js: to program the behavior of the web page—handle web server responses, events, create the gauges, etc.
Organizing Your Files Arduino sketch index html css javascript

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 (LittleFS).

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">
    <script src="http://cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.7/all/gauge.min.js"></script>
  </head>
  <body>
    <div class="topnav">
      <h1>ESP WEB SERVER GAUGES</h1>
    </div>
    <div class="content">
      <div class="card-grid">
        <div class="card">
          <p class="card-title">Temperature</p>
          <canvas id="gauge-temperature"></canvas>
        </div>
        <div class="card">
          <p class="card-title">Humidity</p>
          <canvas id="gauge-humidity"></canvas>
        </div>
      </div>
    </div>
    <script src="script.js"></script>
  </body>
</html>

View raw code

The HTML file for this project is very simple. It includes the JavaScript canvas-gauges library in the head of the HTML file:

<script src="https://cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.7/all/gauge.min.js"></script>

There is a <canvas> tag with the id gauge-temperature where we’ll render the temperature gauge later on.

<canvas id="gauge-temperature"></canvas>

There is also another <canvas> tag with the id gauge-humidity, where we’ll render the humidity gauge later on.

<canvas id="gauge-humidity"></canvas>

CSS File

Copy the following styles to your style.css file. It styles the web page with simple colors and styles.

html {
  font-family: Arial, Helvetica, sans-serif; 
  display: inline-block; 
  text-align: center;
}
h1 {
  font-size: 1.8rem; 
  color: white;
}
p { 
  font-size: 1.4rem;
}
.topnav { 
  overflow: hidden; 
  background-color: #0A1128;
}
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.2rem;
  font-weight: bold;
  color: #034078
}

View raw code

JavaScript File (creating the gauges)

Copy the following to the script.js file.

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

// Create Temperature Gauge
var gaugeTemp = new LinearGauge({
  renderTo: 'gauge-temperature',
  width: 120,
  height: 400,
  units: "Temperature C",
  minValue: 0,
  startAngle: 90,
  ticksAngle: 180,
  maxValue: 40,
  colorValueBoxRect: "#049faa",
  colorValueBoxRectEnd: "#049faa",
  colorValueBoxBackground: "#f1fbfc",
  valueDec: 2,
  valueInt: 2,
  majorTicks: [
      "0",
      "5",
      "10",
      "15",
      "20",
      "25",
      "30",
      "35",
      "40"
  ],
  minorTicks: 4,
  strokeTicks: true,
  highlights: [
      {
          "from": 30,
          "to": 40,
          "color": "rgba(200, 50, 50, .75)"
      }
  ],
  colorPlate: "#fff",
  colorBarProgress: "#CC2936",
  colorBarProgressEnd: "#049faa",
  borderShadowWidth: 0,
  borders: false,
  needleType: "arrow",
  needleWidth: 2,
  needleCircleSize: 7,
  needleCircleOuter: true,
  needleCircleInner: false,
  animationDuration: 1500,
  animationRule: "linear",
  barWidth: 10,
}).draw();
  
// Create Humidity Gauge
var gaugeHum = new RadialGauge({
  renderTo: 'gauge-humidity',
  width: 300,
  height: 300,
  units: "Humidity (%)",
  minValue: 0,
  maxValue: 100,
  colorValueBoxRect: "#049faa",
  colorValueBoxRectEnd: "#049faa",
  colorValueBoxBackground: "#f1fbfc",
  valueInt: 2,
  majorTicks: [
      "0",
      "20",
      "40",
      "60",
      "80",
      "100"

  ],
  minorTicks: 4,
  strokeTicks: true,
  highlights: [
      {
          "from": 80,
          "to": 100,
          "color": "#03C0C1"
      }
  ],
  colorPlate: "#fff",
  borderShadowWidth: 0,
  borders: false,
  needleType: "line",
  colorNeedle: "#007F80",
  colorNeedleEnd: "#007F80",
  needleWidth: 2,
  needleCircleSize: 3,
  colorNeedleCircleOuter: "#007F80",
  needleCircleOuter: true,
  needleCircleInner: false,
  animationDuration: 1500,
  animationRule: "linear"
}).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;
      gaugeTemp.value = temp;
      gaugeHum.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);
    gaugeTemp.value = myObj.temperature;
    gaugeHum.value = myObj.humidity;
  }, false);
}

View raw code

Here’s a summary of what this code does:

  • initializing the event source protocol;
  • adding an event listener for the new_readings event;
  • creating the gauges;
  • getting the latest sensor readings from the new_readings event and display them in the corresponding gauges;
  • making an HTTP GET request for the current sensor readings when you access the web page for the first time.

Get Readings

When you access the web page for the first time, we’ll request the server to get the current sensor readings. Otherwise, we would have to wait for new sensor readings to arrive (via Server-Sent Events), which can take some time depending on the interval that you set on the server.

Add an event listener that calls the getReadings function when the web page loads.

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

The window object represents an open window in a browser. The addEventListener() method sets up a function to be called when a certain event happens. In this case, we’ll call the getReadings function when the page loads (‘load’) to get the current sensor readings.

Now, let’s take a look at the getReadings function. Create a new XMLHttpRequest object. Then, send a GET request to the server on the /readings URL using the open() and send() methods.

function getReadings() {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/readings", true);
  xhr.send();
}

When we send that request, the ESP will send a response with the required information. So, we need to handle what happens when we receive the response. We’ll use the onreadystatechange property that defines a function to be executed when the readyState property changes. The readyState property holds the status of the XMLHttpRequest. The response of the request is ready when the readyState is 4, and the status is 200.

  • readyState = 4 means that the request finished and the response is ready;
  • status = 200 means “OK”

So, the request should look something like this:

function getStates(){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      … DO WHATEVER YOU WANT WITH THE RESPONSE …
    }
  };
  xhr.open("GET", "/states", true);
  xhr.send();
}

The response sent by the ESP is the following text in JSON format (those are just arbitrary values).

{
  "temperature" : "25.02",
  "humidity" : "64.01",
}

We need to convert the JSON string into a JSON object using the parse() method. The result is saved on the myObj variable.

var myObj = JSON.parse(this.responseText);

The myObj variable is a JSON object that contains the temperature and humidity readings. We want to update the gauges values with the corresponding readings.

Updating the value of a gauge is straightforward. For example, our temperature gauge is called gaugeTemp (as we’ll see later on), to update a value, we can simply call: gaugeTemp.value = NEW_VALUE. In our case, the new value is the temperature reading saved on the myObj JSON object.

gaugeTemp.value = myObj.temperature;

It is similar for the humidity (our humidity gauge is called gaugeHum).

gaugeHum.value = myObj.humidity;

Here’s the complete getReadings() function.

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;
      gaugeTemp.value = temp;
      gaugeHum.value = hum;
    }
  }; 
  xhr.open("GET", "/readings", true);
  xhr.send();
}

Creating the Gauges

The canvas-charts library allows you to build linear and radial gauges to display your readings. It provides several examples, and it is very simple to use. We recommend taking a look at the documentation and exploring all the gauges functionalities:

Temperature Gauge

The following lines create the gauge to display the temperature.

// Create Temperature Gauge
var gaugeTemp = new LinearGauge({
  renderTo: 'gauge-temperature',
  width: 120,
  height: 400,
  units: "Temperature C",
  minValue: 0,
  startAngle: 90,
  ticksAngle: 180,
  maxValue: 40,
  colorValueBoxRect: "#049faa",
  colorValueBoxRectEnd: "#049faa",
  colorValueBoxBackground: "#f1fbfc",
  valueDec: 2,
  valueInt: 2,
  majorTicks: [
      "0",
      "5",
      "10",
      "15",
      "20",
      "25",
      "30",
      "35",
      "40"
  ],
  minorTicks: 4,
  strokeTicks: true,
  highlights: [
      {
          "from": 30,
          "to": 40,
          "color": "rgba(200, 50, 50, .75)"
      }
  ],
  colorPlate: "#fff",
  colorBarProgress: "#CC2936",
  colorBarProgressEnd: "#049faa",
  borderShadowWidth: 0,
  borders: false,
  needleType: "arrow",
  needleWidth: 2,
  needleCircleSize: 7,
  needleCircleOuter: true,
  needleCircleInner: false,
  animationDuration: 1500,
  animationRule: "linear",
  barWidth: 10,
}).draw();

To create a new linear gauge, use the new LinearGauge() method and pass as an argument the properties of the gauge.

var gaugeTemp = new LinearGauge({

In the next line, define where you want to put the chart (it must be a <canvas> element). In our example, we want to place it in the <canvas> HTML element with the gauge-temperature id—see the HTML file section.

renderTo: 'gauge-temperature',

Then, we define other properties to customize our gauge. The names are self-explanatory, but we recommend taking a look at all possible configurations and changing the gauge to meet your needs.

In the end, you need to apply the draw() method to actually display the gauge on the canvas.

}).draw();

Special attention that if you need to change the gauge range, you need to change the minValue and maxValue properties:

minValue: 0,
maxValue: 40,

You also need to adjust the majorTicks values for the values displayed on the axis.

majorTicks: [
    "0",
    "5",
    "10",
    "15",
    "20",
    "25",
    "30",
    "35",
    "40"
],

Humidity Gauge

Creating the humidity gauge is similar, but we use the new RadialGauge() function instead and it is rendered to the <canvas> with the gauge-humidity id. Notice that we apply the draw() method on the gauge so that it is drawn on the canvas.

// Create Humidity Gauge
var gaugeHum = new RadialGauge({
  renderTo: 'gauge-humidity',
  width: 300,
  height: 300,
  units: "Humidity (%)",
  minValue: 0,
  maxValue: 100,
  colorValueBoxRect: "#049faa",
  colorValueBoxRectEnd: "#049faa",
  colorValueBoxBackground: "#f1fbfc",
  valueInt: 2,
  majorTicks: [
      "0",
      "20",
      "40",
      "60",
      "80",
      "100"

  ],
  minorTicks: 4,
  strokeTicks: true,
  highlights: [
      {
          "from": 80,
          "to": 100,
          "color": "#03C0C1"
      }
  ],
  colorPlate: "#fff",
  borderShadowWidth: 0,
  borders: false,
  needleType: "line",
  colorNeedle: "#007F80",
  colorNeedleEnd: "#007F80",
  needleWidth: 2,
  needleCircleSize: 3,
  colorNeedleCircleOuter: "#007F80",
  needleCircleOuter: true,
  needleCircleInner: false,
  animationDuration: 1500,
  animationRule: "linear"
}).draw();

Handle events

Update the readings on the gauge when the client receives the readings on the new_readings event

Create a new EventSource object and specify the URL of the page sending the updates. In our case, it’s /events.

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

Once you’ve instantiated an event source, you can start listening for messages from the server with addEventListener().

These are the default event listeners, as shown here in the AsyncWebServer 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);

Then, add the event listener for new_readings.

source.addEventListener('new_readings', function(e) {

When new readings are available, the ESP32 sends an event (new_readings) to the client. The following lines handle what happens when the browser receives that event.

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

Basically, print the new readings on the browser console, convert the data into a JSON object and display the readings on the corresponding gauges.

Arduino Sketch

Copy the following code to your Arduino IDE or to the main.cpp file if you’re using PlatformIO.

You can also download all the files here.

/*********
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete instructions at https://RandomNerdTutorials.com/esp32-web-server-gauges/
  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 "LittleFS.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 an Event Source on /events
AsyncEventSource events("/events");

// Json Variable to Hold Sensor Readings
JSONVar readings;

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

// Create a sensor object
Adafruit_BME280 bme; // BME280 connect to ESP32 I2C (GPIO 21 = SDA, GPIO 22 = SCL)

// 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());
  String jsonString = JSON.stringify(readings);
  return jsonString;
}

// Initialize LittleFS
void initLittleFS() {
  if (!LittleFS.begin()) {
    Serial.println("An error has occurred while mounting LittleFS");
  }
  Serial.println("LittleFS 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 setup() {
  // Serial port for debugging purposes
  Serial.begin(115200);
  initBME();
  initWiFi();
  initLittleFS();

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

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

  // Request for the latest sensor readings
  server.on("/readings", HTTP_GET, [](AsyncWebServerRequest *request){
    String json = getSensorReadings();
    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());
    }
    // send event with message "hello!", id current millis
    // and set reconnect delay to 1 second
    client->send("hello!", NULL, millis(), 10000);
  });
  server.addHandler(&events);

  // Start server
  server.begin();
}

void loop() {
  if ((millis() - lastTime) > timerDelay) {
    // Send Events to the client with the Sensor Readings Every 10 seconds
    events.send("ping",NULL,millis());
    events.send(getSensorReadings().c_str(),"new_readings" ,millis());
    lastTime = millis();
  }
}

View raw code

How the code works

Let’s take a look at the code and see how it works to send readings to the client using server-sent events.

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 use LittleFS to save the files to build the web server.

#include "LittleFS.h"

You also need to include the Arduino_JSON library to make it easier to handle JSON strings.

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

Create an AsyncWebServer object on port 80.

AsyncWebServer server(80);

The following line creates a new event source on /events.

AsyncEventSource events("/events");

Declaring Variables

The readings variable is a JSON variable to hold the sensor readings in JSON format.

JSONVar readings;

The lastTime and the timerDelay variables will be used to update sensor readings every X number of seconds. As an example, we’ll get new sensor readings every 30 seconds (30000 milliseconds). You can change that delay time in the timerDelay variable.

unsigned long lastTime = 0;
unsigned long timerDelay = 30000;

Create an Adafruit_BME280 object called bme on the default ESP I2C pins.

Adafruit_BME280 bme;

Initialize BME280 Sensor

The following function can be called to initialize the BME280 sensor.

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

Get BME280 Readings

To get temperature and humidity from the BME280 temperature, use the following methods on the bme object:

  • bme.readTemperature()
  • bme.readHumidity()

The getSensorReadings() function gets the sensor readings and saves them on the readings JSON array.

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

The readings array is then converted into a JSON string variable using the stringify() method and saved on the jsonString variable.

The function returns the jsonString variable with the current sensor readings. The JSON string has the following format (the values are just arbitrary numbers for explanation purposes).

{
  "temperature" : "25",
  "humidity" : "50"
}

setup()

In the setup(), initialize the Serial Monitor, Wi-Fi, filesystem, and the BME280 sensor.

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

Handle Requests

When you access the ESP32 IP address on the root / URL, send the text that is stored on the index.html file to build the web page.

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

Serve the other static files requested by the client (style.css and script.js).

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

Send the JSON string with the current sensor readings when you receive a request on the /readings URL.

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

The json variable holds the return from the getSensorReadings() function. To send a JSON string as response, the send() method accepts as first argument the response code (200), the second is the content type (“application/json”) and finally the content (json variable).

Server Event Source

Set up the event source on the server.

events.onConnect([](AsyncEventSourceClient *client){
  if(client->lastId()){
    Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
  }
  // send event with message "hello!", id current millis
  // and set reconnect delay to 1 second
  client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);

Finally, start the server.

server.begin();

loop()

In the loop(), send events to the browser with the newest sensor readings to update the web page every 30 seconds.

events.send("ping",NULL,millis());
events.send(getSensorReadings().c_str(),"new_readings" ,millis());

Use the send() method on the events object and pass as an argument the content you want to send and the name of the event. In this case, we want to send the JSON string returned by the getSensorReadings() function. The send() method accepts a variable of type char, so we need to use the c_str() method to convert the variable. The name of the events is new_readings.

Usually, we also send a ping message every X number of seconds. That line is not mandatory. It is used to check on the client side that the server is alive.

events.send("ping",NULL,millis());

Uploading 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 show sketch folder

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

Then, upload the code to your ESP32 board. Make sure you have the right board and COM port selected. Also, make sure you’ve added your network credentials.

Arduino IDE 2 Upload Button

After uploading the code, you need to upload the files to the filesystem.

Press [Ctrl] + [Shift] + [P] on Windows or [] + [Shift] + [P] on MacOS to open the command palette. Search for the Upload LittleFS to Pico/ESP8266/ESP32 command and click on it.

If you don’t have this option is because you didn’t install the filesystem uploader plugin. Check this tutorial.

ESP32 Sketch Data Upload LittleFS Arduino IDE

Important: make sure the Serial Monitor is closed before uploading to the filesystem. Otherwise, the upload will fail.

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 your browser and type the ESP32 IP address. You should get access to the web page that shows the gauges with the latest sensor readings.

Gauges Web Server ESP32 ESP8266 Demonstration

You can also check your gauges using your smartphone (the web page is mobile responsive).

Gauges Web Server ESP32 ESP8266 Demonstration Smartphone

Wrapping Up

In this tutorial you’ve learned how to create a web server to display sensor readings in linear and radial gauges. As an example, we displayed temperature and humidity from a BME280 sensor. You can use those gauges to display any other values that may make sense for your project.

You might also like reading:

Learn more about the ESP32 with our resources:

Thank you for reading.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

118 thoughts on “ESP32 Web Server: Display Sensor Readings in Gauges”

  1. Hi,
    This requires the client to have access to the internet in order to download the: http://cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.7/all/gauge.min.js

    Is it possible to have the ESP32 server also host this file? Could you provide an example of how the code would be modified?

    The reason I ask is i would like to have the ESP32 be the access point and the client connected directly to it, however in this case the client would not have internet access anymore. So all files needed would need to be provided.

    Thanks!

    Reply
    • Ahahah.
      It’s autumn time here. But, that’s the temperature inside the house.
      Outside it’s about 15C during the day.
      Regards,

      Sara

      Reply
  2. This is really excellent! Thanks. Instead of using just a local sensor, one could use MQTT to aggregate data from many sensors and have a really beautiful and functional panel with all in one place.

    Reply
    • Hi.
      Double-check that you’ve uploaded all the required files, specially the HTML, CSS and JavaScript.
      That’s probably the source of your issue.
      Regards,
      Sara

      Reply
  3. Very nice example and demo of building a dashboard! Really helpful 🙂 Could it be an idea to extend this with a use of Firebase, that would be very useful.
    Anyway thanks a lot for the effort you put in this lesson and all the others too.

    Cheers,
    Rene.

    Reply
    • Hi.
      Thanks for the suggestion.
      I already thought of that project example with Firebase. I think I’ll create something like that: with gauges to show current readings, and charts to show saved data along the time. What do you think?
      Regards,
      Sara

      Reply
      • I’d say:
        ‘Sara for President’
        How does that sound?

        Seriously, I’m building a thermostat for controlling my home with an ESP32 and an OpenTherm gateway. The idea is build a trend of the room temperature, setpoint and outside temperature. Also when I’m away from home. (in case I’ve forgotten to turn the heating off :-))

        Enjoy the weekend,
        Cheers,
        Rene.

        Reply
  4. Sara e Rui,
    sono molto felice di seguire i vostri suggerimenti e i risultati sono ottimi. Il web sensor temperatura e umidità mi è molto utile Ho messo il BME280 e ESP32 all’esterno della casa e finalmente ho una indicazione precisa dei due valori. Grazie per la vostra guida e pazienza.
    Siete fantastici.
    Gianni

    Reply
  5. The instructions states to “Download All the Arduino Project Files” which includes the following:
    – favicon.png
    – index.html
    – script.js
    – style.css

    Then it instructs to copy code into the index.html file. Is the code added to the existing file and if so, how?
    The same instruction is repeated for the style.css and script.js.

    Can you please clarify what should be done and how.
    Thank you.

    Reply
  6. Hi Sara and Rui,

    Love your work which I have subscribed to for the last few years.

    My question… I have successfully uploaded the sketch to a standard ESP32 WROOM32 DEV board, connected to WiFi and got the IP address, all good so far but, I’m not getting the menu option – Tools > ESP32 Data Sketch Upload – to upload the ‘data’???

    I checked again to make sure folders and files are in the correct place. Can you help?

    Cheers
    Geoff

    Reply
  7. Great tutorial and I’m looking forward to using it. I followed every instruction beginning to end but can’t get the IP address and no wifi shows up. The serial monitor just shows connecting to wifi……….

    Any suggestions? Thank you. Florian

    Reply
      • For my network credentials, this is what I entered:
        const char* ssid = “Greenhouse”;
        const char* password = “12345678”;
        I’ve gone through the entire tutorial three times and still no IP address. Any thoughts? Thank you.

        Reply
        • Hi.
          Are those your network credentials for when you want to connect to your router?
          The ESP32 is set as a station in this tutorial, not as an access point.
          You need to insert the network credentials you need to use when you want to connect to the internet in your local network.
          Regards,
          Sara

          Reply
  8. This is message I get every time I look for the IP address:
    E (76) psram: PSRAM ID read error 0xffffffff
    Connecting to WIFi…………………………………………………..

    Reply
  9. Thank you. That fixed it. The distinction between Station and Access Point was not obvious to me. Thank you for your quick response and help.

    Reply
  10. All supporting sketches are installed in the IDE including AsyncTCP
    When I compile the sketch it gets stuck here
    ResolveLibrary(functional)
    -> candidates: []
    In file included from C:\Users\IAN\Documents\Arduino\ESP32Web_guages\ESP32Web_guages.ino:13:0:
    C:\Users\IAN\Documents\Arduino\libraries\AsyncTCP-master\src/AsyncTCP.h:27:10: fatal error: functional: No such file or directory
    #include
    ^~~~~~~~~~~~
    compilation terminated.
    Any ideas?

    Reply
  11. Hello,
    thank you for the gauges.
    I have a problem if I use the firefox browser (94.0.2 (64 Bit)): the gauges show after the update: postponed (I can send you a screen-shot).
    If I use the edge browser –> the display of the gauges is ok

    Reply
  12. Thanks for the amazing tutorials. I have learnt a great amount just understanding the processes and implementing some.
    I have tried unsuccessfully over many days to change the code to replace the BME280 with the DHT22.
    I have included “DHT.h” and the definitions and run the appropriate String readHumidity and Temperature. which include the usual “float h = dht.readHumidity();” etc but get stuck on this line of code “readings[“humidity”] = String(dht.readHumidity()());” with error “expression cannot be used as a function”

    // Get Sensor Readings and return JSON object
    String getSensorReadings(){
    readings[“temperature”] = String(dht.readTemperature()());
    readings[“humidity”] = String(dht.readHumidity()());
    String jsonString = JSON.stringify(readings);
    return jsonString;
    }
    I have unsuccessfully tried different approaches.

    Reply
    • Why do you have 2 sets of () in these statements?
      readings[“temperature”] = String(dht.readTemperature()());
      readings[“humidity”] = String(dht.readHumidity()());

      Try this:
      readings[“temperature”] = String(dht.readTemperature());
      readings[“humidity”] = String(dht.readHumidity());

      Reply
      • Thank you. I should have seen that as soon as I posted. That was so obvious.
        Having corrected that I get a curious effect. Using Chrome on both laptop and smart phone.

        The temperature displays12.5 in the box but thermometer rises to 19 degrees which is the ambient temperature. Humidity displays 3303 in the box and needle goes full scale.

        It holds readings for random times between 6 to 20 seconds then both drop to 00.00
        This will remain at 00.00 again anywhere between 8 and 30 seconds.
        I know the DHT22 is working correctly and gives readings between 0 and 100% when I test it out in another sketch.
        Any ideas what is happening?

        Reply
          • Thank you. That made me rethink.
            The DHT22 was only initiated but not actually run with:
            void initDHT(){
            }

            It was picking up noise until I added dht.begin:
            String getSensorReadings(){
            dht.begin();
            delay (500);
            It now works correctly

      • The sketch now works with the DHT22 sensor.
        Unlike the BME280, the DHT needed a begin so I included dht.begin() and delay(500)

        String getSensorReadings(){
        dht.begin();
        delay (500);
        readings[“temperature”] = String(dht.readTemperature());
        readings[“humidity”] = String(dht.readHumidity());
        String jsonString = JSON.stringify(readings);
        return jsonString;
        }

        Thanks for the advice

        Reply
  13. I love this project and made a few modifications to it:
    added Barometric Pressure
    made all gauges circular to fit better on the screen
    added monitoring of remote devices by MQTT
    added client ssl for secure MQTT connection, server cannot use ssl
    added ini file for configuration information
    if present at boot time the following files are copied from the SD card to SPIFFS
    iniFileName[] = “/WebServer.ini”;
    htmFileName[] = “/index.htm”;
    jsFileName[] = “/script.js”;
    styFileName[] = “/style.css”;
    icoFileName[] = “/favicon.png”;

    It further inspired me to expand it even further and move the HTML to an IIS server.
    See the results here: https://gauges.mulvey.us/inspiration.htm

    Thanks again for all the work you do.

    Reply
      • Thank you. It probably never would have happened if you and Rui had not published this article. When I first started I spent more time with a logic analyzer trying to figure out how to communicate with various peripheral devices like sensors than I did coding the microcontrollers. Now people like you do all the heavy lifting and I can just have fun with the projects. Again, thank you.

        Reply
  14. Great tutorial and a very practical application. I implemented it with a few minor changes and then added a second esp-32 to measure and send outdoor temperature readings to the server. It then updates all 4 gauge readings on the web display. If outdoor readings are not available it hides the outdoor gauges and just displays the indoor readings. I learned a lot implementing it and would be happy to share my code.

    Reply
  15. Hi, thank you for this tutorial, I love learning about Server-Sent Events. I was also quite surprised when I saw that you were using Canvas-Gauges as I discovered them many years ago and have been using them since.

    I was wondering if you could do a version of this tutorial in MicroPython in particular the Server-Sent Events part or post an example of how Server-Sent Events would work with uPython.

    Keep up the great work! I’m a big fan!
    Merry Christmas and Happy New Year!

    Reply
    • Hi Rick.
      Thanks for your comment.
      I’m not very familiar with these protocols with MicroPython.
      Maybe I’ll take a look at that next year.
      Happy Holidays!
      Regards,
      Sara

      Reply
  16. My original sketch using the SPIFFS was unsuccessful so I implemented the project using DHT22 and SD card (See my notes Dec1)
    I am now trying to use the identical HTML data on the SPIFFS.
    The sketch uploads successfully and then the data as well.
    The server connects to the WiFi but browser says “no page to display” and the Com port freezes.

    Reply
    • I have just returned to this project.
      The SD card worked perfectly as I stated.
      I now want to remove the SD card and revert to the original sketch using the SPIFFS “ESP32 Web Server: Display Sensor Readings in Gauges” of which these comments are part.
      First I uploaded the data files. I used another sketch to prove they were correctly uploaded in the SPIFFS
      The results were confirmed:
      FILE: /Index.html
      FILE: /script.js
      FILE: /style.css

      However, when opening the web browser FireFox displays a blank page
      Chrome displays “This 192.168.0.125 page can’t be found”

      Reply
  17. Hello Sara, I have done all the steps as you have indicated in the tutorial and when I run the code it throws me the next code:

    ets Jun 8 2016 00:22:57

    rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
    flash read err, 1000
    ets_main.c 371
    ets Jun 8 2016 00:22:57

    rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
    configsip: 0, SPIWP:0xee
    clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
    mode:DIO, clock div:1
    load:0x3fff0018,len:4
    load:0x3fff001c,len:1216
    ho 0 tail 12 room 4
    load:0x40078000,len:10944
    load:0x40080400,len:6388
    entry 0x400806b4
    Could not find a valid BME280 sensor, check wiring!

    What is the problem with my code?
    Thank you so much.

    Reply
  18. hi, thanks for this tutorial.
    i need to add a pulse-sensor to a project almost similar to this.
    how best can i include it in the arduino code?

    Reply
  19. This shows how to constantly update the web server with the sensor data, but what I need to do if I also want a input box to insert data and download it to the ESP32?
    I want to be able to read a senor and add an offset or calibration if needed

    Reply
  20. Hi.
    this is just a test to see if it’s the links to onedrive,
    I have tried inserting in the comment field,
    that causes my comments not to appear on this page.

    Reply
      • Hi Sara.
        I don’t know if it was because I pasted a link to some files from
        my onedrive my comment was not accepted.
        never mind.
        Thanks for all your tutorials.

        Reply
        • Hi again.
          Please note that sometimes the comments are not automatically accepted.
          So, it may take some time until they are published.
          Regards,
          Sara

          Reply
  21. Hi.
    If you need to calibrate or remap the humidity sensor values.
    this piece of code might be useful to someone.

    I found the description on youtube.
    Search for “engineer2you” on youtube.

    double map(double x, double in_min, double in_max, double out_min, double out_max) {
    return (x – in_min) * (out_max – out_min) / (in_max – in_min) + out_min;
    }

    // Get Sensor Readings and return JSON object
    String getSensorReadings(){

    // if need to calibrate humidity
    // the humidity sensor has been calibrated, remapped. it was showing 4.50% to high
    double humid = map(bme.readHumidity(),30.00,79.50,30,75);

    readings[“temperature”] = String(bme.readTemperature());
    readings[“humidity”] = humid;
    String jsonString = JSON.stringify(readings);
    return jsonString;
    }

    Reply
  22. Does anyone know how to get two needles in the same gauge.
    where one of the needles only is updated one time,
    during the first load of the page.
    it would be nice to have the ability to see if the value is increasing or decreasing over time.

    Reply
  23. Hi Sara.
    Smart way you have done the reading of the bme280 sensor.
    by putting the reading in a string.
    String(bme.readTemperature());
    In this way, the value is rounded to 2 decimal places.
    I found that you can also round to 1 decimal place like this
    String (bme.readTemperature() , 1);

    Reply
  24. Hello again.
    I can see under inspect, network in the browser,
    that the variable readings,
    is loaded much faster when I replace the SPIFFS file system with the LittleFS file system in the sketch.
    load time with SPIFFS 400 – 500 ms
    load time with LittleFS below 50 to just over 100 ms.
    That’s quite an improvement.
    As I understand it, LittleFS supports ESP32 in arduino IDE now.
    I’ve been using LittleFS with ESP32 and ESP32-C3 for a while.

    Reply
  25. Good day.
    I’m having issues displaying the BME280 values.
    The page Displays a box for the temperature and one for the humidity but no BME values or gauges just the empty boxes.
    Here is some info from the serial monitor:
    entry 0x400805e4
    [ 5][D][esp32-hal-cpu.c:244] setCpuFrequencyMhz(): PLL: 480 / 2 = 240 Mhz, APB: 80000000 Hz
    [ 38][I][esp32-hal-i2c.c:75] i2cInit(): Initialising I2C Master: sda=21 scl=22 freq=100000
    [ 184][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 0 – WIFI_READY
    [ 280][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 2 – STA_START
    Connecting to WiFi …[ 336][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 4 – STA_CONNECTED
    [ 639][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 7 – STA_GOT_IP
    [ 639][D][WiFiGeneric.cpp:991] _eventCallback(): STA IP: 192.168.50.200, MASK: 255.255.255.0, GW: 192.168.50.1
    192.168.50.200
    SPIFFS mounted successfully
    Started Server
    {“temperature”:”28.10″,”humidity”:”35.83″}
    {“temperature”:”28.09″,”humidity”:”35.81″}
    {“temperature”:”28.09″,”humidity”:”35.88″}
    {“temperature”:”28.10″,”humidity”:”35.74″}
    Request lattest readings
    I serial printed the json message to make sure it was being created.
    Your Help would be greatly appreciated .

    Reply
    • Hi.
      On the web browser window, open the JavaScript console (CTRL + SHIFT + J) and check if you’re getting any errors.
      Regards,
      Sara

      Reply
  26. Hi guys, Big thanks to you and the amazing job; i would like to ask you if you encounter any problem connecting more than 2 sensors and display sensor data on the webserver ; in my case i’ve connect a DHT22, a BMP280 and a BH1750; everything works smooth if i connect and display just 1 sensor but problems start when i add more sensors ( data is not display correctly and the behavior slow down for some gauges )
    I already tried powering the esp32 with a power supply 2A but the problem still exist.
    Do you think is a code problem with the webserver regarding handling multiple sensors values or it depends by the main supply?
    Do you have tested with multiple sensors connected?

    Reply
    • Hi.
      It’s probably a problem with your code or the way the sensors are connected.
      In this scenario, I don’t think it is a problem with the power supply.
      Regards.
      Sara

      Reply
      • Hi Sara, thank you for your response; i’ve just check and fix the problem.
        I forgot to insert the “reading” delay value for the DHT22 that takes about 2 sec.
        This was messing up all the data displayed and the behavior on the webpage.
        Now works perfectly 🙂
        Thank you again guys hope you the BEST

        Reply
  27. Any thoughts on having a dual-scale temp gauge for C & F degrees? I have modded the code to send F temperature to the canvas-gauge, but the gauge is off. I think I need to set min value as 32 instead of 30…

    Reply
  28. I added the barometric air pressure:

    // map for float
    float map(float x, float in_min, float in_max, float out_min, float out_max) {
    return (x – in_min) * (out_max – out_min) / (in_max -in_min) + out_min;
    }

    // Get Sensor Readings and return JSON object
    String getSensorReadings() {
    // Gottstedt 294m
    float temp = bme.readTemperature();
    float pressureSea = barometricPressure(294.0F, bme.readPressure() / 100.0F, temp);
    #ifdef DEBUG
    Serial.printf(“%f\n” , pressureSea);
    #endif
    readings[“temperature”] = String(temp);
    // Remapping in comparison ELV-Station
    float Humidity = map(bme.readHumidity(), 10.0, 42.90, 10.0, 50.0);
    readings[“humidity”] = String(Humidity);
    readings[“pressure”] = String(pressureSea);
    String jsonString = JSON.stringify(readings);
    #ifdef DEBUG
    Serial.println(jsonString);
    #endif
    return jsonString;
    }

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

    // Create Temperature Gauge
    var gaugeTemp = new LinearGauge({
    renderTo: ‘gauge-temperature’,
    width: 120,
    height: 400,
    units: “Temperature C”,
    minValue: 0,
    startAngle: 90,
    ticksAngle: 180,
    maxValue: 40,
    colorValueBoxRect: “#049faa”,
    colorValueBoxRectEnd: “#049faa”,
    colorValueBoxBackground: “#f1fbfc”,
    valueDec: 2,
    valueInt: 2,
    majorTicks: [
    “0”,
    “5”,
    “10”,
    “15”,
    “20”,
    “25”,
    “30”,
    “35”,
    “40”
    ],
    minorTicks: 5,
    strokeTicks: true,
    highlights: [
    {
    “from”: 30,
    “to”: 40,
    “color”: “rgba(200, 50, 50, .75)”
    }
    ],
    colorPlate: “#fff”,
    colorBarProgress: “#CC2936”,
    colorBarProgressEnd: “#049faa”,
    borderShadowWidth: 0,
    borders: false,
    needleType: “arrow”,
    needleWidth: 2,
    needleCircleSize: 7,
    needleCircleOuter: true,
    needleCircleInner: false,
    animationDuration: 1500,
    animationRule: “linear”,
    barWidth: 10,
    }).draw();

    // Create Humidity Gauge
    var gaugeHum = new RadialGauge({
    renderTo: ‘gauge-humidity’,
    width: 300,
    height: 300,
    units: “Humidity (%)”,
    minValue: 0,
    maxValue: 100,
    colorValueBoxRect: “#049faa”,
    colorValueBoxRectEnd: “#049faa”,
    colorValueBoxBackground: “#f1fbfc”,
    valueInt: 2,
    majorTicks: [
    “0”,
    “20”,
    “40”,
    “60”,
    “80”,
    “100”

    ],
    minorTicks: 4,
    strokeTicks: true,
    highlights: [
    {
    “from”: 80,
    “to”: 100,
    “color”: “#03C0C1”
    }
    ],
    colorPlate: “#fff”,
    borderShadowWidth: 0,
    borders: false,
    needleType: “line”,
    colorNeedle: “#007F80”,
    colorNeedleEnd: “#007F80”,
    needleWidth: 2,
    needleCircleSize: 3,
    colorNeedleCircleOuter: “#007F80”,
    needleCircleOuter: true,
    needleCircleInner: false,
    animationDuration: 1500,
    animationRule: “linear”
    }).draw();

    // Create Pressure Gauge
    var gaugePress = new RadialGauge({
    renderTo: ‘gauge-pressure’,
    width: 300,
    height: 300,
    units: “Pressure (hPa)”,
    minValue: 950,
    maxValue: 1050,
    colorValueBoxRect: “#049faa”,
    colorValueBoxRectEnd: “#049faa”,
    colorValueBoxBackground: “#f1fbfc”,
    valueInt: 4,
    valueDec: 2,
    majorTicks: [
    “950”,
    “960”,
    “970”,
    “980”,
    “990”,
    “1000”,
    “1010”,
    “1020”,
    “1030”,
    “1040”,
    “1050”
    ],
    minorTicks: 10,
    strokeTicks: true,
    highlights: [
    {
    “from”: 950,
    “to”: 970,
    “color”: “#C10505”
    }
    ],
    colorPlate: “#fff”,
    borderShadowWidth: 0,
    borders: false,
    needleType: “line”,
    colorNeedle: “#007F80”,
    colorNeedleEnd: “#007F80”,
    needleWidth: 2,
    needleCircleSize: 3,
    colorNeedleCircleOuter: “#007F80”,
    needleCircleOuter: true,
    needleCircleInner: false,
    animationDuration: 1500,
    animationRule: “linear”
    }).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;
    var press = myObj.pressure;
    gaugeTemp.value = temp;
    gaugeHum.value = hum;
    gaugePress.value = press;
    }
    };
    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);
    gaugeTemp.value = myObj.temperature;
    gaugeHum.value = myObj.humidity;
    gaugePress.value = myObj.pressure;
    }, false);
    }

    Reply
    • Something is missing:
      // barometricPressure is converted here to sea level

      float barometricPressure(float height, float measurAir, float measurTemp)
      {
      float Kelvin = measurTemp + 273.15F;

      // Temperature gradient = ( Temperature at current altitude – Temperature at target altitude ) / Altitude difference
      // an average value was taken here:

      float tmpGradient = 0.0068;

      // Gottstedt = 294m (my Home)
      // Barometric Altitude Formula:
      // Atmospheric pressure at target altitude = Atmospheric pressure at current altitude * (1-temperature gradient*altitude difference/temperature at current altitude in Kelvin)^(0.03416/temperature gradient)
      // Since weather stations calculate the air pressure at sea level, the altitude of the location must be included as a negative value

      return measurAir * pow(1 – tmpGradient * -height / Kelvin, 0.03416F / tmpGradient);
      }

      Reply
      • Hi.
        Next time, please share the code using Github Gist, so that it doesn’t occupy so much space in the comments section and so that it doesn’t mess up with the formatting and indentation.
        Regards,
        Sara

        Reply
  29. Hi Sara, I tried your project and everything worked fine as long as the ESP32 was connected via USB to the computer.
    Then I wanted to test it outside and tried different power supplies.
    First I tried several USB cell phone chargers, but although the ESP light was on it would not connect to the network; then I tried with a 9 volt battery but even then I could not get the board to work, lastly I tried with a 12 volt power supply with a DNS-mini-360 and starting with 5 volts I increased the wine voltage to 9 volts but although the web page was present the sensor was not sending values.
    How should a system like this in your pregetto be powered?
    Can you help me?

    Renato

    Reply
    • Hi.
      Usually, when I want to test projects not connected to a computer, I use a power bank, and it works straight away. I never had problems with that.

      How are you connecting the battery to the board-? What ESP32 board do you have?

      Regards,
      Sara

      Reply
    • I have several dev boards that have a separate power jack. Most are isolated from the USB port by a diode. Sometimes the voltage drop across the diode requires a higher applied voltage, ususally 6 or 7 volts instead of the usual 5v. The voltage drop is increased if other devices, SD cars, sensors, relays, etc. are using power from the dev board. USB power always works with 5v, for me at least.

      I do have some cheap USB power sources which provide less than 5v. Most of them do not work if I have a display attached to the board.

      Reply
  30. Hello There!

    Very Useful tutorial!

    I want to get the device geolocation and send it to the ESP32. I have used the Javascript Geolocation API and I have tested on the desktop, everything OK.
    When I upload the files to the SPIFFS I can see the webpage with no problems, but I can’t get working geolocation. I’m running a webserver on the ESP32, so I’m connecting to the microcontroller via WiFi from my smartphone with an IP address.

    How can I get working geolocation? (I don’t want to use a GPS module for the ESP32, I think this is possible sending it from my android phone)

    Thank you for your help!

    Reply
  31. Where can I go to learn more about combining some of this code along with a slider to control an l298n with a fan attached? I get confused in how to combine the script.js components?

    Reply
  32. As the ESP32 Filesystem Uploader plugin is not supported on Arduino 2.0, temperature cannot update to data file. How to fix this problem?

    Reply
  33. Hi,

    I want to have the gauge respond quickly, so I have set the timer delay to 100ms. This works for a few minutes but eventually the gauge will stop responding, is there anything that can be done to prevent this?

    -Andrew

    Reply
  34. Hi There,
    Using the original project, I modified the script.js file to increase the range the sensor can report. It displays -40C to +40 C. I increased the minor ticks from 4 to 5, to increase accuracy, but there is the rub! (The gauge sizes were increased and there were no changes to anything else but the webpage title.) Indeed, the text box on the gauge shows a presumably accurate enough value, but the gauge display is now 2 d C too low. (ex; 20 shows 18 on the graphic) Is there someway to make this better? I’d be happy with a 0,5 d variance, but 2 full d is just too much!

    Thanks,
    Dave K.

    Reply
  35. Hello.
    I need help, can’t get web page.
    On serial plot got all info, but can’t get web
    Cod is from your example just change sensor from bme 280 to bmp180

    #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_BMP085.h>
    #include <Adafruit_Sensor.h>

    // Replace with your network credentials
    const char* ssid = “Redmi10”;
    const char* password = “21Duel10”;

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

    // Create an Event Source on /events
    AsyncEventSource events(“/events”);

    // Json Variable to Hold Sensor Readings
    JSONVar readings;

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

    // Create a sensor object
    Adafruit_BMP085 bmp; // BME280 connect to ESP32 I2C (GPIO 21 = SDA, GPIO 22 = SCL)

    // Init BME280
    void initBMP(){
    if (!bmp.begin(0x74)) {
    Serial.println(“Could not find a valid BME180 sensor, check wiring!”);
    while (1);
    }
    }

    // Get Sensor Readings and return JSON object
    String getSensorReadings(){
    readings[“temperature”] = String(bmp.readTemperature());
    readings[“pressure”] = String(bmp.readPressure());
    String jsonString = JSON.stringify(readings);
    return jsonString;
    }

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

    Serial.print(“Temp = “);
    Serial.print(bmp.readTemperature());
    Serial.println(” *C”);

    Serial.print(“Pressure = “);
    Serial.print(bmp.readPressure());
    Serial.println(” Pa”);
    }

    // Initialize WiFi
    void initWiFi() {
    WiFi.mode(WIFI_STA);
    // Set device as a Wi-Fi Station
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(“”);
    Serial.println(“Setup done”);
    Serial.println(“Setting as a Wi-Fi Station..”);
    Serial.println(“Station IP Address: “);
    Serial.println(WiFi.localIP());
    //Serial.println();
    }
    }

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

    // 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 = getSensorReadings();
    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());
    }
    // send event with message “hello!”, id current millis
    // and set reconnect delay to 1 second
    client->send(“hello!”, NULL, millis(), 10000);
    });
    server.addHandler(&events);

    // Start server
    server.begin();
    }

    void loop() {
    if ((millis() – lastTime) > timerDelay) {
    // Send Events to the client with the Sensor Readings Every 10 seconds
    events.send(“ping”,NULL,millis());
    events.send(getSensorReadings().c_str(),”new_readings” ,millis());
    lastTime = millis();
    }
    }
    *******************************************************88
    i have
    doit sp32 devkit v1

    Please help
    Regards
    Dusan

    Reply
  36. hello, i was trying to implement this with my code but i use vsc and platform.io
    is there a way to get the json library in platform.io?

    Reply
    • Hi.
      It’s explained in the tutorial, under the title “Installing Libraries (VS Code + PlatformIO)”.
      Let me know if you need further help.
      Regards,
      Sara

      Reply
  37. Sara
    ESP32 Web Server with BME280 – Advanced Weather Station Works fine
    ESP32 Web Server using Server-Sent Events (Update Sensor Readings Automatically) Works fine
    ESP32 Web Server: Display Sensor Readings in Gauges doesn’t work
    I can’t get (This 192.168.000.00 page can’t be found)
    I didn’t load ESP32_Gauges, could this the culprit!
    If so, how do it from library to esp32-web-server-gauges?

    Reply
  38. Hello Sara

    I am trying in get a compass gauge to read the wind direction.
    Very hard to get basic info.
    I can’t find how the gauge reads and writes to the java script.

    Do you have resources you can point me to?
    Doug

    Reply
  39. I successfully uploaded SPIFFS in ArduinoIDE 2.3.2 (which I originally installed to try the LittleFS uploader), and sucessfully ran the program as described by adding this uploader “https://github.com/espx-cz/arduino-spiffs-upload”. I’m using Ubuntu 22.04.

    There were a couple of other issues involving different versions of the IDE interacting but these were solved by creating a folder named “portable” in the same folder as the arduino-ide application, then installing the sketchbook and libraries in the portable folder. If you want different versions of the IDE or one installed on a removable drive this ensures files are dedicated to each version.

    Reply
  40. Hi everyone.
    Highcharts Gauges with Esp32 and the sensor bme280.
    If anyone is interested how to use
    Highcharts Gauges instead of Mikhus/canvas gauges.

    Here is a link to the code on my google drive,
    with picture showing the appearance.
    in the Gauges Highcharts folder.
    drive.google.com/drive/folders/1wEd4xZ7TWrce5oODrdGBoH7UJNJuJ__5

    Reply
  41. Hi.
    I have posted a version of Highcharts gauges, with simple
    trend function.

    drive.google.com/drive/folders/1wEd4xZ7TWrce5oODrdGBoH7UJNJuJ__5

    Reply

Leave a Comment

Download Our Free eBooks and Resources

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