ESP8266 NodeMCU Web Server: Display Sensor Readings in Gauges

Learn how to build a web server with the ESP8266 NodeMCU 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.

ESP8266 NodeMCU Web Server Display Sensor Readings in Gauges

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

Project Overview

This project will build a web server with the ESP8266 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.

ESP8266 NodeMCU Gauges Web Server Overview

Server-Sent Events

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

ESP8266 NodeMCU 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 ESP8266 Board in Arduino IDE

We’ll program the ESP8266 using Arduino IDE. So, you must have the ESP8266 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 ESP8266:

2. Filesystem Uploader Plugin

To upload the HTML, CSS, and JavaScript files to the ESP8266 filesystem (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 ESPAsynTCP 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 ESP8266 using PlatformIO, you should add the following lines to the platformio.ini file to include the libraries, change the Serial Monitor speed to 115200, and use LittleFS for the filesystem:

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, or display any other values that are 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 ESP8266 SCL (GPIO 5) and SDA (GPIO 4) pins, as shown in the following schematic diagram.

ESP8266 NodeMCU BME280 Sensor Temperature Humidity Pressure Wiring Diagram Circuit

Recommended reading: ESP8266 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.
ESP8266 organizing your Files arduino sketch index html style css script js

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 ESP8266 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" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="style.css">
    <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 ESP8266 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
  Complete instructions at https://RandomNerdTutorials.com/esp8266-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 <ESP8266WiFi.h>
#include <ESPAsyncTCP.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 = 30000;

// 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 initFS() {
  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.begin(115200);
  initBME();
  initWiFi();
  initFS();

  // 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 30 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 ESP8266WiFi, ESPAsyncWebServer, and ESPAsyncTCP libraries are used to create the web server.

#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.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 ESP8266 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();
  initFS();

Handle Requests

When you access the ESP8266 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 Open Sketch Folder to create data folder

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

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

Upload Arduino code

After uploading the code, you need to upload the files. Go to Tools ESP8266 LittleFS Data Upload and wait for the files to be uploaded.

ESP8266 Tools LittleFS Data Upload Arduino IDE

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

Demonstration

Open your browser and type the ESP8266 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 ESP8266 with our resources:

Thank you for reading.



Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »

Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our newsletter!

14 thoughts on “ESP8266 NodeMCU Web Server: Display Sensor Readings in Gauges”

  1. I just was done converting the esp32 code tp esp8266 …and then your article popped up 🙂
    Anyway great project and an interesting library. I need to check that library out a bit more as for now it seems to have problems with negative value. Yes, it is possible to add a ‘major tick’ of say “-10”, but then a value of +3 degrees, starts counting from ‘-10’ and is shown on the thermometer at the “-7” tick. I am pretty sure it can be done but I need to dive in it a bit more

    Reply
  2. Not all BME280 sensors work for me with this sketch.
    With the BME280I2C.h lib they are recognized on the 8266 or esp32 but
    with Adafruit_BME280.h not.
    All sensors I have have the same i2c address (0x76) but still
    the 6 pin BME280 sensor is not recognized ( VIN-GND-SCL-SDA-CSB-SDO).
    But they do work on an Arduino uno, for example.
    In your sketch only the model with ( VIN-GND-SCL-SDA) connections works.

    Reply
    • That is kinda odd. Does the adafruit libraru maybe accedentally expect an SPI device or is your BME SPI only perhaps?
      On a different note, the adafruit BME280 library is in fact very inefficient. It reads the temperature, humidity and pressure with 3 seperate readings,but in order to read humidity and pressure one needs to start a conversion by reading the temperature. So if you read all 3, the library does 5 readings and discards 2. There are some advantages in that coz if you just want the humidity, one always has a fresh reading but imho it would be better to have a ‘start conversion’ command and then pick whatever fresh value you want from the results

      Reply
  3. Thank you for another great tutorial. I live in North America where we do not use the metric system. Are these the lines I need to change for frahenheit readings? Are there any other lines that need changing?

    Thanks

    Reply
    • // 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;
      }

      Reply
      • Hi.
        Yes, you need to convert to Fahrenheit as follows:
        readings[“temperature”] = String(1.8*bme.readTemperature()+32);

        Then, you need to change the script.js file to adjust the temperature range. It is explained in the tutorial how to change the range.
        Regards,
        Sara

        Reply
  4. // 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;
    }

    Reply
  5. I would like to use AHT10 instead of the expensive BME, but I get lost in the “Get Sensor Readings and return JSON object” section. Could I count on help?

    Reply
  6. I am ot sure what library you use for the AHT10 but it will probably do a reading like:
    hum=aht10.readHumidity(AHTXX_USE_READ_DATA).
    The BME280 has a similar instruction:
    hum=BME280.readHumidity, so it is just a matter of substitution

    Reply
    • Thank you! For those in need:

      String getSensorReadings(){
      sensors_event_t humidity, temp;
      aht.getEvent(&humidity, &temp);
      readings[“temperature”] = String(temp.temperature);
      readings[“humidity”] = String(humidity.relative_humidity);
      String jsonString = JSON.stringify(readings);
      return jsonString;

      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.