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.
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.
Server-Sent Events
The readings are updated automatically on the web page using Server-Sent Events (SSE).
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:
- Adafruit_BME280 (Arduino Library Manager)
- Adafruit_Sensor library (Arduino Library Manager)
- Arduino_JSON library by Arduino version 0.1.0 (Arduino Library Manager)
- ESPAsyncWebServer (.zip folder);
- AsyncTCP (.zip folder).
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.
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.
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>
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
}
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);
}
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();
}
}
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.
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.
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.
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.
You can also check your gauges using your smartphone (the web page is mobile responsive).
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:
- ESP32/ESP8266 Plot Sensor Readings in Real Time Charts – Web Server
- ESP32 Web Server using Server-Sent Events (Update Sensor Readings Automatically)
Learn more about the ESP32 with our resources:
- Build Web Servers with ESP32 and ESP8266 eBook
- Learn ESP32 with Arduino IDE (eBook + video course)
- More ESP32 tutorials and projects…
Thank you for reading.
It’s a really useful tutorial. Thanks
Sara e Rui,
Parabéns pelo excelente Tutorial.
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!
Hi.
I think you can do that.
You save it as a regular js file on the ESP32 filesystem, and serve it as you serve the other files (CSS and script.js files)
Regards,
Sara
I just tried saving the gauge.min.js file directly to the data folder and then edit the index.html page from :
http://cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.7/all/gauge.min.js
to http://gauge.min.js
and it doesn’t work, anyone else try this, kinda a deal breaker for my application if it has to see the internet
Hi.
You need to reference the file path.
Take a look at this discussion: https://stackoverflow.com/questions/16677095/what-is-the-right-way-to-write-my-script-src-url-for-a-local-development-envir
I hope this helps.
Regards,
Sara
it should be enough to write
http://gauge.min.js
as path
if you have gauge.min.js
located in your data folder
hope nothing is left out
since I use the word script
Jeg prøver lige igen.
as some text was omitted.
it should be enough to write
src=”gauge.min.js”
between the two script words
script src=”gauge.min.js”></script
as path
if you have gauge.min.js
located in your data folder
hope nothing is left out
since I use the word script
last time.
I’ll try again
as some text was omitted.
it should be enough to write
src=”gauge.min.js”
between the two script words
script src=”gauge.min.js”></script
as path
if you have gauge.min.js
located in your data folder
hope nothing is left out
since I use the word script
Otherwise, take a look here.
https://drive.google.com/drive/folders/1wEd4xZ7TWrce5oODrdGBoH7UJNJuJ__5
under ESP32_gauges_all
in my country 20C is a… heat wave 😉
Ahahah.
It’s autumn time here. But, that’s the temperature inside the house.
Outside it’s about 15C during the day.
Regards,
Sara
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.
All is Ok, but the server says me no page found at address 192.168.1.22
Renzo
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
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.
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
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.
How about taking full advantage of the bme280 to show barometric pressure also.
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
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.
Hi.
If you downloaded all the project files, you don’t need to copy anything to the downloaded files.
The files will work straight away.
Regards,
Sara
Thank you for your quick response.
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
Solved!
Did some Googling…. found the link for the data file upload tool…
https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/
Everything is working now!
Geoff
Great!
Everything is explained in the tutorial.
You may have missed the Prerequisites section.
Regards,
Sara
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
Hi.
That’s probably because you didn’t insert your network credentials in the Arduino sketch.
Regards,
Sara
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.
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
This is message I get every time I look for the IP address:
E (76) psram: PSRAM ID read error 0xffffffff
Connecting to WIFi…………………………………………………..
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.
That’s ok.
You can learn more here about the differences: https://randomnerdtutorials.com/esp32-access-point-ap-web-server/
Regards,
Sara
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?
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
Hi.
That is an issue related to the browser.
I don’t know how to fix that.
It works well with Google Chrome.
Regards,
Sara
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.
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());
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?
Hi.
The DHT22 is a slow sensor, so it may help if you increase the delay time between readings.
Regards,
Sara
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
Oi Sara,
Me tire uma duvida, qualquer placa dev esp-32 irá funcionar ?
Obrigado Geraldo Cartolano
Olá.
À partida, sim. Qualquer placa funcionará desde que consiga connectar o sensor por I2C.
Cumprimentos.
Sara
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
Obrigado Sara
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.
That’s awesome!
Thanks for sharing your work.
Regards,
Sara
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.
Thanks 😀
Great!
Would it be possible to do something similar but with MQTT instead of HTTP?
https://randomnerdtutorials.com/esp8266-nodemcu-mqtt-publish-bme280-arduino/
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.
Hi Tom.
That’s great!
To share your code, I recommend using a link to GitHub gist, or google drive, for example.
Regards,
Sara
Here is a pointer to the modified code on github. Please feel free to use it as you wish.
https://github.com/Tomw3115/weatherstation
Thank you for sharing.
Regards,
Sara
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!
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
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.
Did you remember to tell the web server that its root and other pages are on the SD card?
Check here for reference:
https://randomnerdtutorials.com/esp32-web-server-microsd-card/
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”
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.
Hi.
Check this BME280 troubleshooting guide: https://randomnerdtutorials.com/solved-could-not-find-a-valid-bme280-sensor/
Regards,
Sara
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?
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
I try with the AsyncWebServer project, and it works by itself, but when I want to combine them it will work one or the other.
Hi.
Did you check this tutorial to learn how to get input data from the browser: https://randomnerdtutorials.com/esp32-esp8266-input-data-html-form/
Regards,
Sara
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.
Hi.
I’m sorry. Are you having issues writing comments?
What happens exactly?
Regards,
Sara
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.
Hi again.
Please note that sometimes the comments are not automatically accepted.
So, it may take some time until they are published.
Regards,
Sara
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;
}
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.
Hi.
I’m not sure how to do that.
You can see all the possible configurations here: https://canvas-gauges.com/documentation/user-guide/configuration
I hope this helps.
Regards,
Sara
Thanks.
I’ll have to look into that.
Hi.
If anyone is interested.
Here is a link to a slightly modified version of Rui and Sara’s project,
with all three readings, temperature, humidity and pressure in radial gauges.
can also be used in Ap Mode.
https://drive.google.com/drive/folders/1wEd4xZ7TWrce5oODrdGBoH7UJNJuJ__5?usp=sharing
By the way, I have configured the temperature gauge
to show from minus 20 degrees to 50 degrees.
fantastic gauges from Mikhus.
https://github.com/Mikhus/canvas-gauges
Great!
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);
I should just add that I found this out by monitoring the variable “var myObj”.
by the inspect option in the browser.
console.log(myObj);
Great!
Do you need more help?
Regards,
Sara
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.
if anyone is interested i have added an
animated trend function along with the resize function
just set the offset with setoffset call
after a while just click or touch one of the gauges
and you will see animated trend.
https://drive.google.com/drive/folders/1wEd4xZ7TWrce5oODrdGBoH7UJNJuJ__5
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 .
Hi.
On the web browser window, open the JavaScript console (CTRL + SHIFT + J) and check if you’re getting any errors.
Regards,
Sara
Superb. Again… 🙂
Dive deep into it and you can do something like this
youtu.be/o-mRYGyaaGM
Thanks for your great
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?
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
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
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…
I reset the min value to 32, added higher values, and a new highlight section. Displaying as expected.
Great.
I’m glad the issue is solved.
Regards,
Sara
Hi.
You may need to adjust the range of the gauge.
Regards,
Sara
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);
}
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);
}
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
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
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
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.
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!
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?
Hi.
We have many examples of web server tutorials with explanations that you can follow to try to learn how to combine components:https://randomnerdtutorials.com/?s=web+server
We also have an eBook that explains in great detail how to build web servers from scratch including the Javascript part: https://randomnerdtutorials.com/build-web-servers-esp32-esp8266-ebook/
I hope this helps.
REgards,
Sara
As the ESP32 Filesystem Uploader plugin is not supported on Arduino 2.0, temperature cannot update to data file. How to fix this problem?
Hi.
You can use an older version of Arduino IDE, you can use VS Code with PlatformIO. Or you can use esptool to upload the .bin files for the filesystem: https://arduino.stackexchange.com/questions/51577/what-is-the-tool-to-send-file-to-spifss-on-esp8266-32
Regards,
Sara
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
Specs for the sensor say humidity readings can take 1 second….that might be the problem.
Dave K.
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.
Interesting just noticed….the highlighting start 2 d early too!
Dave K.
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
Hi.
You probably didn’t upload the filesystem image.
Make sure you created the data folder on the right place.
Then, make sure you upload the filesystem image.
Check the section called “Uploading Code and Files”.
Regards,
Sara
ok thanks a lot
Igot it
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?
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
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?
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
Check out this link:
https://canvas-gauges.com/
It has a User Guide, Developer’s API Docs, and Examples
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.
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
Hi.
I have posted a version of Highcharts gauges, with simple
trend function.
drive.google.com/drive/folders/1wEd4xZ7TWrce5oODrdGBoH7UJNJuJ__5
Hi Sara
Thought a question prior to starting down this road.
I have this project, while modified to suit me, working well with server showing as I like on the phone.
What my next add on is incorporating this into LORA. So adding a second ESP into the mix therefore sensor->esp32 sender => via Lora => esp32 receiver ( and now web server) => client (cell phone.)
As it is now sender=> via syncwebsever => client. So kinda putting a second esp in the mix.
Is it possible to marry these two ideas?
Hi.
Yes.
It is possible.
We have the following tutorial that might be a good starting point: https://randomnerdtutorials.com/esp32-lora-sensor-web-server/
I hope this helps.
Regards,
Sara
Saw it, thanks Sara
Sara,
Is there a tutorial you can point me to, about adding more than one ESP server/sensor (to show multiple readings/gauges on app, or scroll thru each gauge)