ESP32/ESP8266 Plot Sensor Readings in Real Time Charts – Web Server

Learn how to plot sensor readings (temperature, humidity, and pressure) on a web server using the ESP32 or ESP8266 with Arduino IDE. The ESP will host a web page with three real-time charts that have new readings added every 30 seconds.

ESP32 ESP8266 Plot Data Chart Web Server BME280 Temperautre Arduino IDE

Project Overview

In this tutorial we’ll build an asynchronous web server using the ESPAsyncWebServer library.

The HTML to build the web page will be stored on the ESP32 or ESP8266 Filesystem (LittleFS).

We’ll display temperature, humidity and pressure readings from a BME280 sensor on a chart, but you can modify this project to display sensor readings from any other sensor. To learn more about the BME280, read our guides:

To build the charts, we’ll use the Highcharts library. We’ll create three charts: temperature, humidity and pressure over time. The charts display a maximum of 40 data points, and a new reading is added every 30 seconds, but you change these values in your code.

Watch the Video Demonstration

To see how the project works, you can watch the following video demonstration:

Prerequisites

Make sure you check all the prerequisites in this section before continuing with the project in order to compile the code.

1. Install ESP Board in Arduino IDE

We’ll program the ESP32 and ESP8266 using Arduino IDE. So, you must have the ESP32 or ESP8266 add-on installed. Follow one of the next tutorials to install the ESP add-on:

2. Filesystem Uploader Plugin

To upload the HTML file to the ESP32 and ESP8266 flash memory, we’ll use a plugin for Arduino IDE: Filesystem uploader. Follow one of the next tutorials to install the filesystem uploader plugin. The uploader is compatible with both boards.

3. Installing Libraries

To build the asynchronous web server, you need to install the following libraries.

These libraries aren’t available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation folder.

To get readings from the BME280 sensor module you need to have the next libraries installed:

You can install these libraries through the Arduino Library Manager.

Parts Required

ESP32 wit BME280 sensor module temperature humidity pressure

To follow this tutorial you need the following parts:

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

The BME280 sensor module we’re using communicates via I2C communication protocol, so you need to connect it to the ESP32 or ESP8266 I2C pins.

BME280 wiring to ESP32

BME280ESP32
SCK (SCL Pin) GPIO 22
SDI (SDA pin) GPIO 21

So, assemble your circuit as shown in the next schematic diagram.

BME280 Sensor Module wiring to ESP32 board

Recommended reading: ESP32 Pinout Reference Guide

BME280 wiring to ESP8266

BME280ESP8266
SCK (SCL Pin) GPIO 5
SDI (SDA pin) GPIO 4

Assemble your circuit as in the next schematic diagram if you’re using an ESP8266 board.

BME280 Sensor Module wiring to ESP8266 board

Recommended reading: ESP8266 Pinout Reference Guide

Organizing your Files

To build the web server you need two different files. The Arduino sketch and the HTML file. The HTML file should be saved inside a folder called data inside the Arduino sketch folder, as shown below:

LittleFS Web Server to Display Charts File Organization

Creating the HTML File

Create an index.html file with the following content or download all project files here:

<!DOCTYPE HTML><html>
<!-- Rui Santos - Complete project details at https://RandomNerdTutorials.com

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. -->
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script src="https://code.highcharts.com/highcharts.js"></script>
  <style>
    body {
      min-width: 310px;
    	max-width: 800px;
    	height: 400px;
      margin: 0 auto;
    }
    h2 {
      font-family: Arial;
      font-size: 2.5rem;
      text-align: center;
    }
  </style>
</head>
<body>
  <h2>ESP Weather Station</h2>
  <div id="chart-temperature" class="container"></div>
  <div id="chart-humidity" class="container"></div>
  <div id="chart-pressure" class="container"></div>
</body>
<script>
var chartT = new Highcharts.Chart({
  chart:{ renderTo : 'chart-temperature' },
  title: { text: 'BME280 Temperature' },
  series: [{
    showInLegend: false,
    data: []
  }],
  plotOptions: {
    line: { animation: false,
      dataLabels: { enabled: true }
    },
    series: { color: '#059e8a' }
  },
  xAxis: { type: 'datetime',
    dateTimeLabelFormats: { second: '%H:%M:%S' }
  },
  yAxis: {
    title: { text: 'Temperature (Celsius)' }
    //title: { text: 'Temperature (Fahrenheit)' }
  },
  credits: { enabled: false }
});
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var x = (new Date()).getTime(),
          y = parseFloat(this.responseText);
      //console.log(this.responseText);
      if(chartT.series[0].data.length > 40) {
        chartT.series[0].addPoint([x, y], true, true, true);
      } else {
        chartT.series[0].addPoint([x, y], true, false, true);
      }
    }
  };
  xhttp.open("GET", "/temperature", true);
  xhttp.send();
}, 30000 ) ;

var chartH = new Highcharts.Chart({
  chart:{ renderTo:'chart-humidity' },
  title: { text: 'BME280 Humidity' },
  series: [{
    showInLegend: false,
    data: []
  }],
  plotOptions: {
    line: { animation: false,
      dataLabels: { enabled: true }
    }
  },
  xAxis: {
    type: 'datetime',
    dateTimeLabelFormats: { second: '%H:%M:%S' }
  },
  yAxis: {
    title: { text: 'Humidity (%)' }
  },
  credits: { enabled: false }
});
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var x = (new Date()).getTime(),
          y = parseFloat(this.responseText);
      //console.log(this.responseText);
      if(chartH.series[0].data.length > 40) {
        chartH.series[0].addPoint([x, y], true, true, true);
      } else {
        chartH.series[0].addPoint([x, y], true, false, true);
      }
    }
  };
  xhttp.open("GET", "/humidity", true);
  xhttp.send();
}, 30000 ) ;

var chartP = new Highcharts.Chart({
  chart:{ renderTo:'chart-pressure' },
  title: { text: 'BME280 Pressure' },
  series: [{
    showInLegend: false,
    data: []
  }],
  plotOptions: {
    line: { animation: false,
      dataLabels: { enabled: true }
    },
    series: { color: '#18009c' }
  },
  xAxis: {
    type: 'datetime',
    dateTimeLabelFormats: { second: '%H:%M:%S' }
  },
  yAxis: {
    title: { text: 'Pressure (hPa)' }
  },
  credits: { enabled: false }
});
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var x = (new Date()).getTime(),
          y = parseFloat(this.responseText);
      //console.log(this.responseText);
      if(chartP.series[0].data.length > 40) {
        chartP.series[0].addPoint([x, y], true, true, true);
      } else {
        chartP.series[0].addPoint([x, y], true, false, true);
      }
    }
  };
  xhttp.open("GET", "/pressure", true);
  xhttp.send();
}, 30000 ) ;
</script>
</html>

View raw code

Let’s take a quick look at the relevant parts to build a chart.

First, you need to include the highcharts library:

<script src="https://code.highcharts.com/highcharts.js"></script>

You need to create a <div> section for each graphic with a unique id. In this case: chart-temperature, chart-humidity and chart-pressure.

<div id="chart-temperature" class="container"></div>
<div id="chart-humidity" class="container"></div>
<div id="chart-pressure" class="container"></div>

To create the charts and add data to the charts, we use javascript code. It should go inside the <script> and </script> tags.

The following spinet creates the temperature chart. You define the chart id, you can set the title, the axis labels, etc…

var chartT = new Highcharts.Chart({
  chart:{ renderTo : 'chart-temperature' },
  title: { text: 'BME280 Temperature' },
  series: [{
    showInLegend: false,
    data: []
  }],
  plotOptions: {
    line: { animation: false,
      dataLabels: { enabled: true }
    },
    series: { color: '#059e8a' }
  },
  xAxis: { type: 'datetime',
    dateTimeLabelFormats: { second: '%H:%M:%S' }
  },
  yAxis: {
    title: { text: 'Temperature (Celsius)' }
    //title: { text: 'Temperature (Fahrenheit)' }
  },
  credits: { enabled: false }
});

Then, the setInterval() function adds points to the charts. Every 30 seconds it makes a request to the /temperature URL to get the temperature readings from your ESP32 or ESP8266.

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var x = (new Date()).getTime(),
          y = parseFloat(this.responseText);
      //console.log(this.responseText);
      if(chartT.series[0].data.length > 40) {
        chartT.series[0].addPoint([x, y], true, true, true);
      } else {
        chartT.series[0].addPoint([x, y], true, false, true);
      }
    }
  };
  xhttp.open("GET", "/temperature", true);
  xhttp.send();
}, 30000 ) ;

The other graphics are created in a similar way. We make a request on the /humidity and /pressure URLs to get the humidity and pressure readings, respectively.

In the Arduino sketch, we should handle what happens when we receive those requests: we should send the corresponding sensor readings.

Arduino Sketch

Copy the following code to the Arduino IDE or download all project files here. Then, you need to type your network credentials (SSID and password) to make it work.

/*********
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-plot-chart-web-server/
  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.
*********/
#ifdef ESP32
  #include <WiFi.h>
  #include <ESPAsyncWebServer.h>
  #include <LittleFS.h>
#else
  #include <Arduino.h>
  #include <ESP8266WiFi.h>
  #include <Hash.h>
  #include <ESPAsyncTCP.h>
  #include <ESPAsyncWebServer.h>
  #include <LittleFS.h>
  #include <FS.h>
#endif
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

/*#include <SPI.h>
#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/

Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI

// Replace with your network credentials
const char* ssid = "REPLACE_IWTH_YOUR_SSID";
const char* password = "REPLACE_IWTH_YOUR_PASSWORD";

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

String readBME280Temperature() {
  // Read temperature as Celsius (the default)
  float t = bme.readTemperature();
  // Convert temperature to Fahrenheit
  //t = 1.8 * t + 32;
  if (isnan(t)) {    
    Serial.println("Failed to read from BME280 sensor!");
    return "";
  }
  else {
    Serial.println(t);
    return String(t);
  }
}

String readBME280Humidity() {
  float h = bme.readHumidity();
  if (isnan(h)) {
    Serial.println("Failed to read from BME280 sensor!");
    return "";
  }
  else {
    Serial.println(h);
    return String(h);
  }
}

String readBME280Pressure() {
  float p = bme.readPressure() / 100.0F;
  if (isnan(p)) {
    Serial.println("Failed to read from BME280 sensor!");
    return "";
  }
  else {
    Serial.println(p);
    return String(p);
  }
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);
  
  bool status; 
  // default settings
  // (you can also pass in a Wire library object like &Wire2)
  status = bme.begin(0x76);  
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }

  // Initialize LittleFS
  if(!LittleFS.begin()){
    Serial.println("An Error has occurred while mounting LittleFS");
        return;
  }

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP32 Local IP Address
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(LittleFS, "/index.html");
  });
  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readBME280Temperature().c_str());
  });
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readBME280Humidity().c_str());
  });
  server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readBME280Pressure().c_str());
  });

  // Start server
  server.begin();
}
 
void loop(){
  
}

View raw code

How the code works

Let’s take a quick look at the code and see how it works.

Including libraries

First, include the necessary libraries. You include different libraries depending on the board you’re using. If you’re using an ESP32, the code loads the following libraries:

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

If you’re using an ESP8266, the code loads these libraries:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <Hash.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

Create an instance to communicate with the BME280 sensor using I2C:

Adafruit_BME280 bme; // I2C

Insert your network credentials in the following variables:

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

Read Temperature, Humidity and Pressure

Then, we create three functions readBME280Temperature(), readBME280Humidity() and readBME280Pressure(). These functions request the temperature, humidity and pressure from the BME280 sensor and return the readings as a String type.

String readBME280Temperature() {
  // Read temperature as Celsius (the default)
  float t = bme.readTemperature();
  // Convert temperature to Fahrenheit
  //t = 1.8 * t + 32;
  if (isnan(t)) {
    Serial.println("Failed to read from BME280 sensor!");
    return "";
  }
  else {
    Serial.println(t);
    return String(t);
  }
}

Init BME280

In the setup(), initialize the sensor:

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

Init LittleFS

Initialize the filesystem (LittleFS):

if(!LittleFS.begin()){
  Serial.println("An Error has occurred while mounting LittleFS");
  return;
}

Connect to Wi-Fi

Connect to Wi-Fi and print the IP address in the Serial Monitor:

WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Connecting to WiFi..");
}

// Print ESP32 Local IP Address
Serial.println(WiFi.localIP());

Handle requests

Then, we need to handle what happens when the ESP receives a request.

When it receives a request on the root URL, we send the HTML text that is saved in LittleFS under the index.html name:

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

When we receive a request on the /temperature, /humidity or /pressure URLs, call the functions that return the sensor readings.

For example, if we receive a request on the /temperature URL, we call the readBME280Temperature() function that returns the temperature.

server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/plain", readBME280Temperature().c_str());
});

The same happens for the other readings.

Finally, start the server:

server.begin();

Because this is an asynchronous web server we don’t need to write anything in the loop().

void loop(){

}

Uploading Code and Files

Save the code as ESP_Chart_Web_Server or download all project files here. Go to Sketch > Show Sketch Folder, and create a folder called data. Inside that folder you should save the HTML file created previously.

Arduino IDE Open Sketch Folder to create data folder

Now you need to upload the HTML file to the ESP32 or ESP8266 filesystem.

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

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

ESP32/ESP8266 Data Sketch Upload filesystem Uploader

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

Then, upload the code to your board. Make sure you have the right board and COM port selected. Also, make sure you’ve inserted your networks credentials in the code.

Arduino IDE 2 Upload Button

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

ESP32 ESP8266 IP Address Serial Monitor

Demonstration

Open a browser on your local network and type the ESP32 or ESP8266 IP address. You should see three charts. A new data point is added every 30 seconds to a total of 40 points. New data keeps being displayed on the charts as long as you have your web browser tab open.

Here is an example of the humidity chart:

ESP32 ESP8266 Chart Web Server BME280 Humidity Arduino IDE

You can select each point to see the exact timestamp.

ESP32 ESP8266 BME280 Temperature Pressure Humidity Plot Sensor readings chart web server

Wrapping Up

In this tutorial you’ve learned how to create charts to display data in your web server. You can modify this project to create as many charts as you want and using any other sensors.

Next, we recommend building a project that displays charts from data stored on your database. Here are other tutorials that you might like:

If you would like to learn more about building web servers with the ESP32 and ESP8266 boards, we have an eBook dedicated to that subject:

Learn more about the ESP32 and ESP8266 with our resources:

Thank you for reading.



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

Recommended Resources

Build a Home Automation System from Scratch » With Raspberry Pi, ESP8266, Arduino, and Node-RED.

Home Automation using ESP8266 eBook and video course » Build IoT and home automation projects.

Arduino Step-by-Step Projects » Build 25 Arduino projects with our course, even with no prior experience!

What to Read Next…


Enjoyed this project? Stay updated by subscribing our newsletter!

281 thoughts on “ESP32/ESP8266 Plot Sensor Readings in Real Time Charts – Web Server”

  1. Thank you for making all of these free tutorials. They are well done easy to follow and the explanations of how it works are very good. Again thanks.

    Reply
  2. Hi Rui, hi Sara, thanks for this other amazing tutorial!!!

    It would be really cool to have the opportunity to generate a log file in .csv file from the web page, the web server should be password protected.

    I see wen we make the web server the ESP8266 is set in AP and Station mode simultaneously and I see on my network another network with different IP classe. Could it be possible to use this network to set the SSID and WiFi password in order to avoid everytime to get connected to the laptop the device and fix the credentials data?

    Think about it, it will be the maximum!!!!

    Reply
  3. Hi Ruis.
    Thanks for this good tutorial. You can use any JS library chart guys.

    I compiled all this using recommended libraries with my ESP32 DEVKIT V1, sont if you get errors when compiling do not remove ESPAsyncTCP library because it is used for ESP8266 MCUs as Ruis said. if you get errors during code compilation using ESP32 MCU Below are some changes to make in AsyncTCP library :

    Rename ESPAsyncTCP.cpp and ESPAsyncTCP.h files to AsyncTCP.cpp
    AsyncTCP.h files

    Rename ESPAsyncTCPbuffer.cpp and ESPAsyncTCPbuffer.h files to AsyncTCPbuffer.cpp
    AsyncTCPbuffer.h files

    In AsyncPrinter.cpp and SyncClient.cpp files, change the library ESPAsyncTCP.h to AsyncTCP.h.

    Once done, Inside every files correct ESPAsync to Async in order to get everything linked properly

    Also commenting panic() method helped me.

    The only update you have to make it’s regarding Arduino OTA. If you upgrade to the Arduino ESP32 latest library, make sure to update the Arduino OTA also to the latest.

    Thanks guys

    Reply
  4. Hi;
    get a failure after installation on ESP32-WROOM:

    E (446) SPIFFS: mount failed, -10025
    An Error has occurred while mounting SPIFFS
    Van you help me?

    Reply
  5. Hi Rui and Sara, thanks for this great tutorial.
    I need to change the time zone for + 2 hours.
    Can you show us what to add in code.
    Thanks in advance.

    Reply
    • Hi.

      One of our readers suggested the following to fix that:

      I fixed my time zone problems by adding this in the start of the script.
      // Highcharts.setOptions({
      // global: {
      // useUTC: false
      // }
      // });
      By adding the code above it will use the time zone of the browser.”

      I hope this helps.
      Regards,
      Sara

      Reply
  6. Hello

    The temperature is always 26.09 C. The humidity 47.89%. The air pressure always 1006.34. The serial monitor shows no values. The BME280 runs correctly with another program.
    Where could the problem be? I am using an ESP32.

    Mfg Andreas

    Reply
  7. Unfortunately the time is not correct. Difference 2 hours. I live in Germany. How can I change this?

    Kind regards
    Andreas

    Reply
    • Hi Andreas

      One of our readers suggested the following to fix that:

      I fixed my time zone problems by adding this in the start of the script.
      // Highcharts.setOptions({
      // global: {
      // useUTC: false
      // }
      // });
      By adding the code above it will use the time zone of the browser.”

      I hope this helps.
      Regards,
      Sara

      Reply
  8. Really enjoying your tutorials and learning a lot in the process. Wondering how to use the ESP32-CAM for this project. I can’t seem to figure out how to connect the BME280 to I2C to get this to work. Any help would be appreciated.

    Thank you!

    Reply
    • Hi Greg.
      Do you want to use an ESP32-CAM to do this project?
      The I2C pins of the ESP32-CAM are internally connected to the camera, so I don’t think you can use the camera with the BME280 at the same time. Unless, you configure your code to use other pins as I2C. For example, GPIO 12 and GPIO 13 if you’re not using SD card. However, I don’t know if this I2C will conflict with the camera I2C.

      Regards,
      Sara

      Reply
  9. Hi Rui and Sara.
    I fixed my time zone problems by adding this in the start of the script.
    // Highcharts.setOptions({
    // global: {
    // useUTC: false
    // }
    // });
    By adding the code above it will use the time zone of the browser.

    Reply
  10. Hi, thanks for the great tutorial.
    I installed and it was working very well. But after trying to change the intervall time in the html file and some header text changes i don’t get this again on the server. I changes into the data folder within the Sketch Folder and reloaded with the ESP8266 Sketch Data Upload , but no change on the Server output.
    Do you have an idea what can be wrong on my changes.
    Thanks in advance

    Reply
    • Now I found out the issue. The ESP8286 Sketch Data Upload do not use the html file in the folder I created under Arduino/data instead it used the data folder I downloaded unter Download like your description.
      So i changed the data in this folder and it works fine.
      Regards Hans.

      Reply
      • Hello Hans
        ESP32/ESP8266 Plot Sensor Readings in Real Time Charts – Web Server

        bmp is recognized and the ip address can be connected to but blank page.
        Is this the problem you had?
        Yes it looks like the index.html file not used.
        Not sure what to do

        Reply
        • Hi.
          You probably didn’t upload the HTML file to the filesystem.
          Check the section “Uploading Code and Files” in detail. You probably missed one of the steps.
          Regards,
          Sara

          Reply
  11. Hello,
    Thank you for sharing your work, this looks very interesting, particularly the use of the charting libraries. However, I cannot access the code, GitHub returns an error 500 each time, even if I try to access your repo directly from my account.
    Is there something wrong at Github, or did you remove your code?
    Thank you.

    Reply
  12. Another cracking tutorial…..well done guys.
    Had it up and running in a jiffy. I’ve registered with a DDNS supplier and have configured remote access so that I can access from anywhere on the internet.
    Waiting for the DB access that you mentioned in the write up as I have an SQL database set up for the same readings as per your last tutorial.
    See below my URL address so you can check my live readings.

    Keep up the great work……..
    Regards
    Alan

    Reply
  13. Hi Rui and Sara.
    it is also possible to change the time zone by adding this in the start of the script.
    the minus sign is necessary to add the 2 hours.

    // Highcharts.setOptions({
    // time: {
    // timezoneOffset: -2 * 60
    // }
    // });

    Regards J.B.

    Reply
  14. Hi again.
    I think this should take care of daylight saving time crossover if the other options don’t
    place this where the other scripts are declared without the //
    // https://code.highcharts.com/highcharts.js
    // https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js
    // https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.13/moment-timezone-with-data-2012-2022.min.js

    and replace the Highcharts.setOptions with this, without the //

    // Highcharts.setOptions({
    // time: {
    // /**
    // * Use moment-timezone.js to return the timezone offset for individual
    // * timestamps, used in the X axis labels and the tooltip header.
    // */
    // getTimezoneOffset: function (timestamp) {
    // var zone = ‘Europe/Oslo’,
    // timezoneOffset = -moment.tz(timestamp, zone).utcOffset();
    //
    // return timezoneOffset;
    // }
    // }
    // });

    Reply
  15. hello,
    my code does’nt work with an esp32. message : Could not find a valid BME280 sensor, check wiring!

    I checked the wiring and it’s ok

    any idea ?
    Thanks,

    marc,

    Reply
  16. Guys, you’re just too good! I always follow this site and I have never being disappointed, in fact, I think I just need to buy one of your ebooks now.
    Please keep up the good job

    Reply
  17. This is a great example. I got it running in no time. I made a couple of changes. I added FTP ( handle in loop) and added a few additional lines in loop to write the data to a file in SPIFFS, tmp.csv. I would like index.html to use the data in the csv file rather than the async data so the data is non-volatile. I have looked all over the web for an example of how to use Highcharts in this manner with no success.

    Can you give me enough hints to get me started?

    Reply
    • Bill,
      write sensor data to a csv (which i believe you already did)
      File sensordata = SPIFFS.open(“/sensorresults.csv,”a”);
      sensordata.print(unixtime);
      sensordata.print(‘,’);
      sensordata.println(temperature);
      sensordata.close();

      read the csv file
      while (sensordata.available())
      {
      String T += char(configurations.read());
      }

      and separate the lines into a JSON (by looking for the EOL character
      or do it directly with a library such as ‘boost’ or ‘jsoncons’

      send the JSON to the client
      server.on(“/temperature”, HTTP_GET, [](AsyncWebServerRequest *request){
      String payload = “{YOUR JSON}”;
      request->send(200, “text/plain”, payload.c_str());
      });

      use JSON as input to e.g. HIGHCHARTS, use the time with every sensorreading for x axis

      Reply
  18. seems I am the first to have a problem compiling. lots of errors:

    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In function ‘void _handle_async_event(lwip_event_packet_t*)’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:166:68: error: no matching function for call to ‘AsyncClient::_s_dns_found(const char*&, ip_addr_t*, void*&)’
    AsyncClient::_s_dns_found(e->dns.name, &e->dns.addr, e->arg);
    ^
    In file included from /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:24:0:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.h:137:17: note: candidate: static void AsyncClient::_s_dns_found(const char*, ip_addr*, void*)
    static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg);
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.h:137:17: note: no known conversion for argument 2 from ‘ip_addr_t* {aka _ip_addr*}’ to ‘ip_addr*’
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In function ‘bool _start_async_task()’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:204:141: error: ‘xTaskCreateUniversal’ was not declared in this scope
    xTaskCreateUniversal(_async_service_task, “async_tcp”, 8192 * 2, NULL, 3, &_async_service_task_handle, CONFIG_ASYNC_TCP_RUNNING_CORE);
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In function ‘void _tcp_dns_found(const char*, ip_addr*, void*)’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:303:55: error: invalid application of ‘sizeof’ to incomplete type ‘ip_addr’
    memcpy(&e->dns.addr, ipaddr, sizeof(struct ip_addr));
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: At global scope:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:328:32: error: field ‘call’ has incomplete type ‘tcpip_api_call_data’
    struct tcpip_api_call_data call;
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:328:12: note: forward declaration of ‘struct tcpip_api_call_data’
    struct tcpip_api_call_data call;
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In function ‘esp_err_t _tcp_output(tcp_pcb*, AsyncClient*)’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:368:70: error: invalid conversion from ‘err_t (*)(tcpip_api_call_data*) {aka signed char (*)(tcpip_api_call_data*)}’ to ‘tcpip_api_call_fn {aka signed char (*)(tcpip_api_call*)}’ [-fpermissive]
    tcpip_api_call(_tcp_output_api, (struct tcpip_api_call_data*)&msg);
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:368:70: error: cannot convert ‘tcpip_api_call_data*’ to ‘tcpip_api_call*’ for argument ‘2’ to ‘err_t tcpip_api_call(tcpip_api_call_fn, tcpip_api_call*)’
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:328:12: note: class type ‘tcpip_api_call_data’ is incomplete
    struct tcpip_api_call_data call;
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In function ‘esp_err_t _tcp_write(tcp_pcb*, const char*, size_t, uint8_t, AsyncClient*)’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:391:69: error: invalid conversion from ‘err_t (*)(tcpip_api_call_data*) {aka signed char (*)(tcpip_api_call_data*)}’ to ‘tcpip_api_call_fn {aka signed char (*)(tcpip_api_call*)}’ [-fpermissive]
    tcpip_api_call(_tcp_write_api, (struct tcpip_api_call_data*)&msg);
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:391:69: error: cannot convert ‘tcpip_api_call_data*’ to ‘tcpip_api_call*’ for argument ‘2’ to ‘err_t tcpip_api_call(tcpip_api_call_fn, tcpip_api_call*)’
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:328:12: note: class type ‘tcpip_api_call_data’ is incomplete
    struct tcpip_api_call_data call;
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In function ‘esp_err_t _tcp_recved(tcp_pcb*, size_t, AsyncClient*)’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:413:70: error: invalid conversion from ‘err_t (*)(tcpip_api_call_data*) {aka signed char (*)(tcpip_api_call_data*)}’ to ‘tcpip_api_call_fn {aka signed char (*)(tcpip_api_call*)}’ [-fpermissive]
    tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call_data*)&msg);
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:413:70: error: cannot convert ‘tcpip_api_call_data*’ to ‘tcpip_api_call*’ for argument ‘2’ to ‘err_t tcpip_api_call(tcpip_api_call_fn, tcpip_api_call*)’
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:328:12: note: class type ‘tcpip_api_call_data’ is incomplete
    struct tcpip_api_call_data call;
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In function ‘esp_err_t _tcp_close(tcp_pcb*, AsyncClient*)’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:433:69: error: invalid conversion from ‘err_t (*)(tcpip_api_call_data*) {aka signed char (*)(tcpip_api_call_data*)}’ to ‘tcpip_api_call_fn {aka signed char (*)(tcpip_api_call*)}’ [-fpermissive]
    tcpip_api_call(_tcp_close_api, (struct tcpip_api_call_data*)&msg);
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:433:69: error: cannot convert ‘tcpip_api_call_data*’ to ‘tcpip_api_call*’ for argument ‘2’ to ‘err_t tcpip_api_call(tcpip_api_call_fn, tcpip_api_call*)’
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:328:12: note: class type ‘tcpip_api_call_data’ is incomplete
    struct tcpip_api_call_data call;
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In function ‘esp_err_t _tcp_abort(tcp_pcb*, AsyncClient*)’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:453:69: error: invalid conversion from ‘err_t (*)(tcpip_api_call_data*) {aka signed char (*)(tcpip_api_call_data*)}’ to ‘tcpip_api_call_fn {aka signed char (*)(tcpip_api_call*)}’ [-fpermissive]
    tcpip_api_call(_tcp_abort_api, (struct tcpip_api_call_data*)&msg);
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:453:69: error: cannot convert ‘tcpip_api_call_data*’ to ‘tcpip_api_call*’ for argument ‘2’ to ‘err_t tcpip_api_call(tcpip_api_call_fn, tcpip_api_call*)’
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:328:12: note: class type ‘tcpip_api_call_data’ is incomplete
    struct tcpip_api_call_data call;
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In function ‘esp_err_t _tcp_connect(tcp_pcb*, ip_addr_t*, uint16_t, tcp_connected_fn)’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:472:71: error: invalid conversion from ‘err_t (*)(tcpip_api_call_data*) {aka signed char (*)(tcpip_api_call_data*)}’ to ‘tcpip_api_call_fn {aka signed char (*)(tcpip_api_call*)}’ [-fpermissive]
    tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call_data*)&msg);
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:472:71: error: cannot convert ‘tcpip_api_call_data*’ to ‘tcpip_api_call*’ for argument ‘2’ to ‘err_t tcpip_api_call(tcpip_api_call_fn, tcpip_api_call*)’
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:328:12: note: class type ‘tcpip_api_call_data’ is incomplete
    struct tcpip_api_call_data call;
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In function ‘esp_err_t _tcp_bind(tcp_pcb*, ip_addr_t*, uint16_t)’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:490:68: error: invalid conversion from ‘err_t (*)(tcpip_api_call_data*) {aka signed char (*)(tcpip_api_call_data*)}’ to ‘tcpip_api_call_fn {aka signed char (*)(tcpip_api_call*)}’ [-fpermissive]
    tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call_data*)&msg);
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:490:68: error: cannot convert ‘tcpip_api_call_data*’ to ‘tcpip_api_call*’ for argument ‘2’ to ‘err_t tcpip_api_call(tcpip_api_call_fn, tcpip_api_call*)’
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:328:12: note: class type ‘tcpip_api_call_data’ is incomplete
    struct tcpip_api_call_data call;
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In function ‘tcp_pcb* _tcp_listen_with_backlog(tcp_pcb*, uint8_t)’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:508:70: error: invalid conversion from ‘err_t (*)(tcpip_api_call_data*) {aka signed char (*)(tcpip_api_call_data*)}’ to ‘tcpip_api_call_fn {aka signed char (*)(tcpip_api_call*)}’ [-fpermissive]
    tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data*)&msg);
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:508:70: error: cannot convert ‘tcpip_api_call_data*’ to ‘tcpip_api_call*’ for argument ‘2’ to ‘err_t tcpip_api_call(tcpip_api_call_fn, tcpip_api_call*)’
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:328:12: note: class type ‘tcpip_api_call_data’ is incomplete
    struct tcpip_api_call_data call;
    ^
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp: In member function ‘void AsyncClient::_dns_found(ip_addr*)’:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:926:33: error: invalid use of incomplete type ‘struct ip_addr’
    connect(IPAddress(ipaddr->u_addr.ip4.addr), _connect_port);
    ^
    In file included from /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.cpp:24:0:
    /home/dave/Arduino/libraries/AsyncTCP-master/src/AsyncTCP.h:53:8: note: forward declaration of ‘struct ip_addr’
    struct ip_addr;
    ^
    Multiple libraries were found for “WiFi.h”
    Used: /home/dave/Arduino/hardware/espressif/esp32/libraries/WiFi
    Not used: /home/dave/Documents/arduino-1.8.8/libraries/WiFi
    Multiple libraries were found for “Adafruit_BME280.h”
    Used: /home/dave/Arduino/libraries/Adafruit_BME280_Library
    Not used: /home/dave/Arduino/libraries/BME280-I2C-ESP32-master
    exit status 1
    Error compiling for board ESP32 Dev Module.

    Reply
      • ESP32 Dev Module is selected

        MH-ET Live -ESP32 DEVKIT
        has a VROOM-32

        using the chip on a different sketch, it does run my OLED and display the pair of DS18B20 sensors and the pair of BME280 sensors.

        Reply
      • Arduino IDE 1.8.8
        preferences boards ; separated by a comma
        arduino.esp8266.com/stable/package_esp8266com_index.json, dl.espressif.com/dl/package_esp32_index.json

        ESP32 sketch data upload is shown in tools
        MHET LIVE32devKIT board is selected

        AsyncTCP-master.zip in my library folder and added as a zip
        ESPAsyncWebServer-master.zip also present and added as zip

        since all the errors are from AsyncTCP-master
        I am not sure if that is getting bad data, or not handling things.

        Reply
      • I added the ESPAsyncTCP.h for an ESP8266
        changed the board to an ESP8266 and it complied.

        then, changed the board back to the ESP32
        and it compiled without errors.

        ===============
        E (440) SPIFFS: mount failed, -10025
        An Error has occurred while mounting SPIFFS
        ===============

        now I just have to play with the unit to figure out what is going on.

        Reply
  19. Hi, Sara and Rui,

    Thanks for uploading this nice tutorial, I wanted to plot the waveform of current but problem is that this code is updates the values after every 30 seconds which is not suitable for my case. So, please can you inform what I have to change to plot my current wave with zero delay?

    Reply
  20. Thanks for the tutorial!
    I am using ESP8266-01 and the BME280 sensor did not return any data. To correct, I called the function Wire.begin (0,2);
    If anyone else has this problem, the tip is

    Reply
  21. Hi Rui,

    Is it possible to store the data first in SPIFFS or on a SD card and plot the data within a selected period into the webbrowser, whenever you want to check it?

    Thx

    Wim

    Reply
    • I hoped to do the same thing you describe. I made some progress, got frustrated and set it aside for a while. Here’s what I have got so far. This is my sketch:

      https://1drv.ms/u/s!AmXKqAwyCrbxiMArt67eeBAKWoy9Bg?e=ikLvVL

      I have made little progress on the HighChart side. I was not able to make HighCharts grab data from a local file. Using the sketch I shared the data can be downloaded by Highcharts using something like: xmlhttp.open(“GET”, “http://your externalshare:port/temp.csv”, true); Just using file://temp.csv does not appear possible. I guess that there is a security issue of a Java script exposing a local file. Even to make the working example function, I had to put an extension in Chrome ‘Allow-Control-Allow-Origin:’ because I was serving /temp.csv via http and not https. That’s where I gave up. I hope some of this help. Perhaps Rui can shine a light on a better way.

      Bill

      Reply
      • Wouldnt it be possible that if you make a request as in e.g. in Rui’s example, that you let the server.on”/xxxxxx ” routine read data from an SDcard?

        Reply
        • Sure Ed, that works. As Dave suggested the Sketch side can be modified to provide more data. The trick is how to modify index.html.

          Reply
          • ah OK, then i misunderstood.
            About changing the index file, I think I replied that in tw posts below.
            Adding lines check arduinodiy.wordpress.com/2019/08/05/esp8266-plot-sensor-readings-in-real-time-charts-on-a-web-server/
            Using the date: var x = (new Date()).getTime(),
            in which ‘getTime()’ is your function of reading in the time, but it could e.g. be:
            var x=(newDate()).UTC(hh,min,sec)

    • in order to fill in a chart with stored data, you may be able to send a post/get request to the ESP. something like sending a command to turn on a switch.
      but, when you set the flag the ESP would open the spiff and read the last 20 saved data sets, then post one every second.
      that would use the ESP in the same way this is being done for you now.
      my need is for a garden temp , soil, rain. so I only want one reading a day.

      Reply
  22. Dave, that is a good idea. Should work. Even nicer, I can use most of the code that I have already written. The only drawback to this approach that I can see is the limited life of EEPROM. That’s a lot of reading and writing. My weather station using Wview and a Raspberry PI uses a somewhat similar data structure. I have worn out a couple of uSDs.

    That being said, I don’t have any better ideas.

    Reply
    • Just occurred to me, If multiple points are sent then the data will also have to include the time and date. I can revise the sketch to create the three responses, temp, humidity and pressure..

      Example, /temp could yield:

      08/06/2019, 08:09, 78.8
      08/06/2019, 08:10, 78.7
      08/06/2019, 08:11, 80.1

      Now, does any one know how to revise index.html to work with that data and is what I wrote above the easiest format to work with?

      Reply
      • in Every setInterval function, you will find this section
        if (this.readyState == 4 && this.status == 200) {
        var x = (new Date()).getTime(),
        y = parseFloat(this.responseText);

        The value ‘x’, being the date. Instead of ‘getTime()), you need to supply the date that you save

        Reply
  23. Makes sense to me. Now to figure out how to make HighCharts plot a series.

    For testing, I wrote:

    String hTest = “38 50.3 30.3 28.5 50.5”;

    server.on(“/humidity”, HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, “text/plain”, hTest.c_str());
    });

    As I expected Highchart only display the first value, 38.

    Reply
  24. Thanks Ed. Now I am closer.

    That example does not address retaining the data but it does describe how to encode the data in the sketch in the familiar JSON string and how to read it to generate multiple lines. In that it address bringing multiple point into HighCharts, that’s pretty close. I think, however, the JSON string would need to be brought into HighCharts as a Series so that an unknown number of points could be plotted.

    This get me a bit closer but not all the way. I may have to read up on HighCharts. Sadly, I know a fair amount of C but not much Java Script.

    Reply
  25. Hello, I find many of your tutorials very useful.

    I’m trying to modify this tutorial to work with an AD8232 Heart Rate Monitor and my ESP32. Before I tried to implement the web server, I was able to get the AD8232 working and displaying the output in the serial monitor and serial plotter. So I adjusted all of the Arduino code and the html file in this tutorial and was able to connect to the web server. The problem is that the graph only shows one value (4095).

    Do you have any idea how to get the proper values to display?

    Reply
      • I am actually using ADC2 pins. I will reconfigure it and try again. Thank you very much for responding. I’ll post back and let you know if the issue is resolved.

        Reply
      • Hello, so I changed the pins to ADC1 and that get the proper values showing. Thank you. Now, since it’s a heart rate monitor that I’m using, I need to increase the number of data points showing as well as increase the rate at which the data is displayed. I’ve tried changing the polling interval in the index.html file, but the rate of display hasn’t changed. Also, I thought I knew which line to change the number of data points shown, but when I changed it

        (if(chartH.series[0].data.length > 40),

        nothing much happened. Can you point me in the right direction to do those two things, please,

        Reply
  26. Does anyone know how to change the script to plot the first reading immediately on load and then after every 30 seconds.
    It would be nice to be able to plot the first reading immediately, if you increase the time interval to more than 30 sec.
    Hope someone can show this in code.

    Reply
  27. If anyone is interested, I have solved my problem plotting the first reading immediately on load and then after every 30 seconds, by adding this on top of the new chart section. without //

    // var myVar = setInterval(myTimer, 1000);
    // function myTimer() {
    // var xhttp = new XMLHttpRequest();
    // xhttp.onreadystatechange = function() {
    // if (this.readyState == 4 && this.status == 200) {
    // var x = (new Date()).getTime(),
    // y = parseFloat(this.responseText);
    // //console.log(this.responseText);
    // chartT.series[0].addPoint([x, y], true, false, true);
    // }
    // };
    // xhttp.open(“GET”, “/temperature”, true);
    // xhttp.send();
    // clearInterval(myVar);
    //};

    Jørgen B

    Reply
  28. here is the code for the three readouts if anyone has trouble implementing it in the code.
    And of course you have to keep the original set interval functions.

    // var myVar = setInterval(myTimer, 1000);
    // function myTimer() {
    // var xhttp = new XMLHttpRequest();
    // xhttp.onreadystatechange = function() {
    // if (this.readyState == 4 && this.status == 200) {
    // var x = (new Date()).getTime(),
    // y = parseFloat(this.responseText);
    // //console.log(this.responseText);
    // chartT.series[0].addPoint([x, y], true, false, true);
    //
    // }
    // };
    // xhttp.open(“GET”, “/temperature”, true);
    // xhttp.send();
    //
    // var xhttp = new XMLHttpRequest();
    // xhttp.onreadystatechange = function() {
    // if (this.readyState == 4 && this.status == 200) {
    // var x = (new Date()).getTime(),
    // y = parseFloat(this.responseText);
    // //console.log(this.responseText);
    // chartH.series[0].addPoint([x, y], true, false, true);
    //
    // }
    // };
    // xhttp.open(“GET”, “/humidity”, true);
    // xhttp.send();
    //
    // var xhttp = new XMLHttpRequest();
    // xhttp.onreadystatechange = function() {
    // if (this.readyState == 4 && this.status == 200) {
    // var x = (new Date()).getTime(),
    // y = parseFloat(this.responseText);
    // //console.log(this.responseText);
    // chartP.series[0].addPoint([x, y], true, false, true);
    //
    // }
    // };
    // xhttp.open(“GET”, “/pressure”, true);
    // xhttp.send(); clearInterval(myVar);
    // clearInterval(myVar);
    // };

    Reply
    • This is a better way to get first reading immediately, the first time you are loading the page.

      var myFnc = function() {
      var xhttp = new XMLHttpRequest();
      xhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
      var x = (new Date()).getTime(),
      y = parseFloat(this.responseText);
      //console.log(this.responseText);
      if(chartT.series[0].data.length > 40) {
      chartT.series[0].addPoint([x, y], true, true, true);
      } else {
      chartT.series[0].addPoint([x, y], true, false, true);
      }
      }
      };
      xhttp.open(“GET”, “/temperature”, true);
      xhttp.send();
      }
      setInterval(myFnc, 30000);
      myFnc();

      var myFnc = function() {
      var xhttp = new XMLHttpRequest();
      xhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
      var x = (new Date()).getTime(),
      y = parseFloat(this.responseText);
      //console.log(this.responseText);
      if(chartH.series[0].data.length > 40) {
      chartH.series[0].addPoint([x, y], true, true, true);
      } else {
      chartH.series[0].addPoint([x, y], true, false, true);
      }
      }
      };
      xhttp.open(“GET”, “/humidity”, true);
      xhttp.send();
      }
      setInterval(myFnc, 30000);
      myFnc();

      var myFnc = function() {
      var xhttp = new XMLHttpRequest();
      xhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
      var x = (new Date()).getTime(),
      y = parseFloat(this.responseText);
      //console.log(this.responseText);
      if(chartP.series[0].data.length > 40) {
      chartP.series[0].addPoint([x, y], true, true, true);
      } else {
      chartP.series[0].addPoint([x, y], true, false, true);
      }
      }
      };
      xhttp.open(“GET”, “/pressure”, true);
      xhttp.send();
      }
      setInterval(myFnc, 30000);
      myFnc();

      Reply
  29. The reading on /temperature URL is working, but just the graph is not showing up. It was actually working fine a few days ago and I dont remember changing anything in the code

    Reply
  30. Hello,

    Thank you for sharing this very helpful post.

    I tried to use the same code but for the DHT22 and it doesn’t work.
    Could you help me ? I used this code:

    (…)

    Thank you very much !

    Reply
    • check the ‘Beginners guide for ESP8266’ chapter 16.
      I did not try it myself but I think that describes what you need tttapa.github.io/ESP8266/Chap16%20-%20Data%20Logging.html

      Reply
    • You do not need to leave it empty, you can put anything in there that you like, you just do not need it for the described functionality in here.
      In the setup() function you create the webpage. That page contains javascript. The javascript makes 30 sec calls to routines in your program file. That is all you ‘need’.
      But if you want you could just read sensors in your loop and have the Javascript use those, there is just no need to do that

      Reply
  31. Dear Sara and Rui

    Is there a way to implement the project without an internet access, I mean, the graphs entirely genereated on chip?

    Best regards.
    Carlos.

    Reply
    • Hi Carlos.
      This approach uses the highcharts library from the internet. So, I don’t think you can do this without internet access.
      There should be another way to do that, but we don’t have any tutorial about it.
      Regards,
      Sara

      Reply
    • Please read this comment from one of our readers. That should do the trick.


      If anyone needs to run in access point mode, offline.
      Then add this
      /highcharts.js
      to the index.html
      and remove this
      https://code.highcharts.com/highcharts.js
      from the index.html

      Download the highcharts.js file, and put it in the data folder, together with index html.
      Remember to upload data to spiffs.

      Add this to your sketch.
      server.on(“/highcharts.js”, HTTP_GET, [](AsyncWebServerRequest *request){
      request->send(SPIFFS, “/highcharts.js”, “text/javascript”);
      });

      that’s how I did it, to make it work offline in access point mode.”
      I hope this helps.
      Regards,
      Sara

      Reply
      • Hey Sara thank you for your tutorials!!

        I have downloaded the highcharts zip file. But know i am unsure what i have to put in the data folder on spiffs.
        Do you have any advice for me?

        Thank you in andvance and best regards,
        Fabian

        Reply
  32. A question: If I would like to host a JSON file that takes care of plotting the points on a graph and the arduino uses that instead of high charts, how would I do that? I am creating a project that is supposed to last a long time in a remote location and am worried that if for whatever reason high charts removes that JSON file from their server, then my project will stopping plotting points.

    Reply
    • Hi. Vlad.
      Please read this comment from one of our readers. That should do the trick.


      If anyone needs to run in access point mode, offline.
      Then add this
      /highcharts.js
      to the index.html
      and remove this
      https://code.highcharts.com/highcharts.js
      from the index.html

      Download the highcharts.js file, and put it in the data folder, together with index html.
      Remember to upload data to spiffs.

      Add this to your sketch.
      server.on(“/highcharts.js”, HTTP_GET, [](AsyncWebServerRequest *request){
      request->send(SPIFFS, “/highcharts.js”, “text/javascript”);
      });

      that’s how I did it, to make it work offline in access point mode.”
      I hope this helps.
      Regards,
      Sara

      Reply
  33. If anyone needs to run in access point mode, offline.
    Then add this
    /highcharts.js
    to the index.html
    and remove this
    https://code.highcharts.com/highcharts.js
    from the index.html

    Download the highcharts.js file, and put it in the data folder, together with index html.
    Remember to upload data to spiffs.

    Add this to your sketch.
    server.on(“/highcharts.js”, HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, “/highcharts.js”, “text/javascript”);
    });

    that’s how I did it, to make it work offline in access point mode.

    Reply
  34. Hi I am posting it again.
    I can see there is missing some of my text in my post.
    Remove the slashes when inserting the code.

    If anyone needs to run in access point mode, offline.
    Then add this
    // /highcharts.js
    to the index.html
    and remove this
    //https://code.highcharts.com/highcharts.js
    from the index.html

    Download the highcharts.js file, and put it in the data folder, together with index html.
    Remember to upload data to spiffs.

    Add this to your sketch.
    // server.on(“/highcharts.js”, HTTP_GET, [](AsyncWebServerRequest *request){
    // request->send(SPIFFS, “/highcharts.js”, “text/javascript”);
    // });

    that’s how I did it, to make it work offline in access point mode.

    Reply
    • Thanks this worked great, also nice to know this code will still run even when the Highcharts will stop.
      Small note is that copy pasting your code resulted in bad ” and I had to replace them.

      Reply
  35. I can see that some of my text is still missing.
    I don’t know if it is for security reasons that some of my text has been removed from my post.
    I don’t know how to post my code to avoid some of the text being removed.
    Can you help me solve this.
    Regards, J. Bredahl.

    Reply
    • Hi.
      Some text is omitted for security reasons.
      You can use pastebin to show your code.
      However, I think it was posted correctly now.
      Regards,
      Sara

      Reply
  36. hi Sara.Thank you very much for your response.
    I can see that some of my comment is still missing.
    I hope your readers can figure out where to put the code and what to remove from the s c r i p t part in the index.html.
    //src=”/highcharts.js
    best regards to you
    J. Bredahl.

    Reply
  37. As my BME280 sometimes sends corrupted data, e.g.
    temperature -280° or humidity 0% or 100% or pressure 0, all from my living-room, wich results in a nearly straight line with one or two spikes,
    I added the following to avoid these spikes –
    for the temperature:
    if (t>50) {t = 20;
    }
    if (t98) {h = 50;
    }
    if (h<10) {h = 50;
    }

    for pressure:
    if (p1200) {p = 1000;
    }

    To get a wide chart over about 30 hours, in the index.html file I changed

    max-width: 1900px;

    if(chartP.series[0].data.length > 180)

    and interval to 600000 (= 10 min.)

    For better overview I added to title: { text: “… Interval 10 min.”

    For the time zone problem I added
    Highcharts.setOptions({
    global: {
    useUTC: false
    }
    });
    as Sara suggested in July. It works fine.

    Regards,
    Siegfried

    Reply
  38. Hi Sarah and Rui !
    With your i2c scanner sketch, I find my BME280 at address x076. So far so good!
    But with ESP_chart_web_server.ino I get this error message.
    Could not find a valid BME280 sensor, check wiring!
    Problem with Adafruit_BME280 library (not used by i2c scanner) ? I am stuck…
    I use an ESP32
    Alain

    Reply
    • Hi Sarah and Rui !
      With your i2c scanner sketch, I find my BME280 at address x076. So far so good!
      But with ESP_chart_web_server.ino I get this error message.
      Could not find a valid BME280 sensor, check wiring!
      Problem with Adafruit_BME280 library (not used by i2c scanner) ? I am stuck…
      I use an ESP32
      Alain
      same problem with a ESP8266
      Alain

      Reply
  39. I really love this tutorial! I made a PID slowcooker!
    I know there is also a slow-cooker version. but that was not for the esp32 version, and rewriting it was way to difficult!
    This one I understand and now can mostly modify it myself.
    I just have issues with the Highchart, was someone able to add the lines into one graph?
    I would like to add the setpoint to the graph.

    Reply
    • var value1 = ;
      var value2 = ;
      var value3 = ;
      var reading_time = ;
      window.addEventListener(‘load’, reading_time);

      // Create Temperature Chart
      var chartT = new Highcharts.Chart({
      chart:{
      renderTo:’chart-temperature’
      },
      series: [
      {
      name: ‘Temperature’,
      type: ‘line’, animation: {duration: 5000},
      color: ‘red’,
      data:value1,
      dataLabels: { enabled: true },
      marker: {
      symbol: ‘circle’,
      radius: 3,
      fillColor: ‘#101D42’,

      }
      },
      {
      name: ‘Himidity’,
      type: ‘line’, animation: {duration: 5000},
      color: ‘blue’,
      data:value2,
      dataLabels: { enabled: true },
      marker: {
      symbol: ‘square’,
      radius: 3,
      fillColor: ‘#00A6A6’,

      }
      },
      {
      name: ‘Pressure’,
      type: ‘line’, animation: {duration: 5000},
      color: ‘lightblue’,
      data:value3,
      dataLabels: { enabled: true },
      marker: {
      symbol: ‘triangle’,
      radius: 3,
      fillColor: ‘#8B2635’,
      }
      },

      ],
      title: {
      text: undefined
      },
      xAxis: {
      type: ‘reading_time’,
      categories: reading_time

      },
      yAxis: {
      title: {
      text: ‘Temperature \u00B0C Humidity % Pressure hPa’
      }
      },
      credits: {
      enabled: false
      }
      });

      //Plot temperature in the temperature chart
      function plotTemperature(jsonValue) {

      var keys = Object.keys(jsonValue);
      console.log(keys);
      console.log(keys.length);

      for (var i = 0; i 40) {
      chartT.series[i].addPoint([x, y], true, true, true);
      } else {
      chartT.series[i].addPoint([x, y], true, false, true);
      }

      }
      }

      // 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);
      plotTemperature(myObj);
      }
      };
      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);
      plotTemperature(myObj);
      }, false);
      }

      Reply
  40. Thank you for the tutorial!

    How can i display the current temperature reading on the top if the chart updates?

    Something just like:

    Tempereature: 21°C

    In html file it would be:

    Temperature Celsius %TEMPERATUREC% °C

    Reply
  41. This is awesome and i love. were in lock down and the thermostat has broken so were having to manualy control the central heating, fortunately i have all the bits lying around here. what i dont have is the experience of coding html. im trying to splice this project in to this:
    Input Data on HTML Form ESP32/ESP8266 Web Server using Arduino IDE

    so we can set the temperature we want online. Its really not going well. I have figured out how to get the input boxes to display, but everything falls apart after that if i press submit. Has anyone done this? or know where i can find some code that will do it, im just bumbling around in the dark here trying not to break things.

    Thanks

    Reply
  42. Hi,
    I had used Nodemcu instead of ESP8266. Everything is perfect also I could visualize the graph in the web server but that doesn’t print any output data on the Chart. It is still showing empty graph. please anybody have an idea about it? please help me to find out the solution for this problem.

    Reply
  43. Hello!
    Thank you very much for the tutorial!
    However, there’s been a few things that are still confusing me:
    First of, you never declare any pins and you also only create a bme variable in your code, but don’t initialize it.
    Since I only have a bmp280 and no bme280 yet, I’ve tweaked your code slightly by adding
    #include <Adafruit_BMP280.h> to the imports and
    Adafruit_BMP280 bme;
    further below, commenting out the original lines for bme.
    Compiling and uploading the code works, but throws the following exception:
    17:31:38.454 -> ————— CUT HERE FOR EXCEPTION DECODER —————
    17:31:38.454 ->
    17:31:38.454 -> Soft WDT reset
    17:31:38.454 ->
    17:31:38.454 -> >>>stack>>>
    17:31:38.454 ->
    17:31:38.454 -> ctx: cont
    17:31:38.454 -> sp: 3ffffde0 end: 3fffffc0 offset: 01a0
    17:31:38.454 -> 3fffff80: 3fffdad0 3ffeec5c 3ffeec5c 40201430
    17:31:38.454 -> 3fffff90: feefeffe feefeffe feefeffe feefeffe
    17:31:38.454 -> 3fffffa0: 3fffdad0 00000000 3ffeed4c 40209eb0
    17:31:38.454 -> 3fffffb0: feefeffe feefeffe 3ffe84f4 40100fb5
    17:31:38.488 -> <<<stack<<<
    17:31:38.488 ->
    17:31:38.488 -> ————— CUT HERE FOR EXCEPTION DECODER —————
    17:31:38.488 ->
    17:31:38.488 -> ets Jan 8 2013,rst cause:2, boot mode:(3,6)
    17:31:38.488 ->
    17:31:38.488 -> load 0x4010f000, len 3664, room 16
    17:31:38.488 -> tail 0
    17:31:38.488 -> chksum 0xee
    17:31:38.488 -> csum 0xee
    17:31:38.523 -> v39c79d9b
    17:31:38.523 -> ~ld
    17:31:38.592 -> Could not find a valid BME280 sensor, check wiring!

    I am quite confused how the code should ever work? Maybe I just misunderstood something, but I must say I find it somewhat weird that you’ve added pin declariations in your code, but commented it out without ever explaining why it’s there.
    Also, my ESP8266 looks different than the one you’ve shown – it has less GPIOs. Why do you only use 2 of the 4 GPIOs from the BME?
    I’d really appreciate an explanation, thanks again for the tutorial!
    Best regards,
    Stefan

    Reply
    • Hi.
      The pins declared are used for SPI communication. Because we are not using that communication protocol, we’re using I2C instead, we commented those lines. Those lines are in the code commented so that people can see how to use SPI if they wanted.
      When you call Adafruit_BME280 bme; this creates an Adafruit_BME280 object called bme on the ESP32 default I2C pins (GPIO 21 and GPIO 22). This is how the library works.
      I hope my explanation is clear.
      Regards,
      Sara

      Reply
      • Hello!

        Thank you very much for the explanation – after searching around a bit more I figured that it had something to do with that. I got it to work now 🙂

        I have another question though if you don’t mind.
        Is it possible to save and display more than 40 points?
        Now bear with me, I already found someone else in the comments explaining how to do it. I noticed though, that the graphs are not saved anywhere, so if I refresh the site, I start with 0 dots on each graph.
        How would I go on to write an algorithm that saves all of the data every few seconds and instead of calling for new input, the website just takes a, for example csv I’m generating on the Sketch-part of the programm and uses that to feed all the dots into the graph?

        Best regards,
        Stefan

        Reply
  44. Hello everyone,
    First of all, I admire those tutorials which are very helpful,
    However, I want to ask for help for my project.
    I am working on a load detection algorithm so that I can display in real time the power consumption curve and the list of running equipments. If I am going to use ESP32 and the webserver where should I put my algorithm,
    Thank you very much !

    Reply
  45. How do I change the time format to show 21:45 no seconds. Currently it is:
    xAxis: { type: ‘datetime’,
    dateTimeLabelFormats: { second: ‘%H:%M:%S’ }
    },

    I have tried { minute: ‘%H:%M:’}
    and
    xAxis: { type: ‘datetime’,
    labels: {
    format: ‘{value:%Y-%b-%e %H:%M}’
    }

    Reply
  46. I want the Temperatur Chart to only show one decimal point, so in index.html i changed line 64 from y = parseFloat(this.responseText); to y = parseFloat(this.responseText).ToFixed(1);
    But now the Temperature Chart stopped showing the numbers! How can change the code to only show one decimal point in Celsius degrees?

    Reply
    • All right, i managed to change de decimals length by adding the format property in index.html, line 52:

      plotOptions: {
      line: { animation: false,
      dataLabels: {
      enabled: true,
      format: ‘{y:.1f}’
      }
      },
      series: { color: ‘#059e8a’ }
      },

      Plus i update the tooltip text to change this same format, by adding this in line 40:

      tooltip: {
      valueDecimals: 1,
      pointFormat: ‘{point.y} °C’
      },

      Reply
    • But i think the highcharts can see the second decimal, because 31.4 and another 31.4 in the temperature chart are not in the same line!
      Anyone knows how to send only one decimal to the chart, or to make the same number appears in the same line?

      Reply
  47. Hi Rui and Sara, I hope you are well.
    If anyone is interested in having all three readings in
    same chart container, window.
    Here’s how I did it.

    Highcharts.setOptions({
    colors: [‘#059e8a’, ‘#5DADE2’, ‘#ED561B’]
    });

    Highcharts.setOptions({
    global: {
    useUTC: false
    }
    });

    var chart = new Highcharts.Chart({
    chart:{ renderTo : ‘chart-weather’,
    },
    tooltip: {
    crosshairs: true,
    shared: true,

    },

    plotOptions: {
    line: { animation: false,
    dataLabels: { enabled: true }
    },
    },
    xAxis: { type: ‘datetime’,
    dateTimeLabelFormats: { second: ‘%H:%M:%S’ }
    },

    credits: {
    enabled: false
    },

    title: { text: ‘Weather data’,
    margin: 0,
    style: {
    fontSize: ’15px’,
    }
    },

    yAxis: [{
    title: {
    enabled:false,
    text: ‘Temperature (Celsius)’,
    }
    },

    {
    opposite: true,
    title: {
    enabled:false,
    text: 'Temperature (Celsius)',
    }
    },

    {
    opposite: true,
    title: {
    enabled:false,
    text: 'Temperature (Celsius)',
    }

    }],

    series: [
    {
    yAxis: 0,
    // data: []
    name: ‘Temperature’
    },
    {
    yAxis: 1,
    // data: []
    name: ‘Humidity’,
    marker: {
    symbol: ’round’
    }
    },
    {
    yAxis: 2,
    // data: []
    name: ‘Pressure’,
    marker: {
    symbol: ’round’
    }
    }],

    });

    var myFnc = function() {
    var time = (new Date()).getTime();

    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    var x = time,
    y = parseFloat(this.responseText);
    //console.log(this.responseText);
    if(chart.series[0].data.length > 40) {
    chart.series[0].addPoint([x, y], true, true, true);
    } else {
    chart.series[0].addPoint([x, y], true, false, true);
    }
    }
    };
    xhttp.open(“GET”, “/temperature”, true);
    xhttp.send();

    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    var x = time,
    y = parseFloat(this.responseText);
    //console.log(this.responseText);
    if(chart.series[1].data.length > 40) {
    chart.series[1].addPoint([x, y], true, true, true);
    } else {
    chart.series[1].addPoint([x, y], true, false, true);
    }
    }
    };
    xhttp.open(“GET”, “/humidity”, true);
    xhttp.send();

    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    var x = time,
    y = parseFloat(this.responseText);
    //console.log(this.responseText);
    if(chart.series[2].data.length > 40) {
    chart.series[2].addPoint([x, y], true, true, true);
    } else {
    chart.series[2].addPoint([x, y], true, false, true);
    }
    }
    };
    xhttp.open(“GET”, “/pressure”, true);
    xhttp.send();
    }
    setInterval(myFnc, 30000);
    myFnc();

    Reply
  48. Great job, Rui and Sara.
    I had a question: are the data, after each request / reading, written to the flash (….When it receives a request on the root URL, we send the HTML text that is saved in SPIFFS….)? If this is true, there is a usage limit, as the flash must accept around 100k burns, right?

    Reply
  49. I managed to fix my problem….
    The text editor was saving the file as index.html.txt (with the .txt bit not displayed)
    I had to run example SPIFFS sketch to see the file directory, and simply had to rename the file (using a line from the same SPIFFS sketch – It basically demonstrates name changes etc.)

    How I narrowed it down to this before even scratching is the IP address followed by /temperature (yes I have slightly different variables due to using BME680), it would display temperature etc… only the root / would give 404, or calling for /index.html explicitly gives server error 500.

    So if you can access the parameters directly, your problem like mine might be simply wrong name of file in SPIFFS.

    Reply
  50. Hi great tutorial

    I have added a ccs811 air quality sensor. Every thing is working fine if i replace the temperature and pessure graphic by the CO2 and VOC graphics respectively.

    If for some reason, i try to add the new graphics instead ( a forth and a fifth graphic). I get the additional graphic frames but no data updates ???

    Is there a limit to the number of graphics in the webpage or some details that i could have forgot ??

    Reply
    • Hi.
      I don’t think there is a limit of graphics.
      You should have something wrong in your code.
      Check that you are using the right events for the sensor readings and corresponding charts.
      Regards,
      Sara

      Reply
  51. Hi, that is a great tutorial!

    How can I use the plots with an accsess point on the esp8266? I want to use it in my sons school as an co2 warner. There is no wifi network. 🙁

    Reply
    • Go through the comments, there is a solution for that described. It involves downloading the highcharts.js library, putting that in spiffs and making a few code alterations

      Reply
  52. Hello guys. Thanks for this great tutorial.

    Everything working fine but I have some question.
    My ESP8266 working for over 48 hours right now.
    When I woke up and check my phone I can’t see any data. I have to wait until next data entry. But I like to see what happened last night.
    I’m editing index.html to setup 20minutes for updates but when I close my browser or refresh it everything gone. I just see empty graphics not old data.

    Reply
    • Hi.
      This particular example doesn’t save the readings.
      Old readings are lost when you close the browser.
      If you want to permanently save your readings, it is better to save the readings on a file (in the filesystem or on an SD card) and then, load that file to build the charts.
      Regards.
      Sara

      Reply
      • Well I don’t need 5 days old data. I just want to see last 40 entry.
        for example my setup was 20 minutes. for temp & humidty. 5 minutes for pressure but when I closed my computer or change my phone screen all datas are gone.
        will try another one I guess

        Reply
        • That’s because the data is not being saved.
          You can save the last data point in a moving array, for example.
          Regards,
          Sara

          Reply
  53. Amazing tutorial.
    You think it would be possible for 2 ESP32s to post to one database? And then have 6 charts instead of 3?
    In this case is it enough if I change the location value in one of them?
    Do I need 2 users? Or 2 API keys?

    Reply
  54. Hi, so I wanted to ask about the high chart function that makes the height of the graph change. For example, when you have a reading that’s abnormally high the whole graph gets taller. Is there any way to set your graph to always be at a certain height? (Like 100 max)

    Reply
    • Hi.
      Yes.
      You can edit the axis properties with a certain maximum and minimum value.
      You can use the floor and ceiling properties to set a minimum and a maximum: https://api.highcharts.com/highcharts/yAxis
      For example, the yAxis would look like this if you wanted a minimum of 0 and a maximum of 100:


      yAxis: {
      floor: 0,
      ceiling: 100,
      title: { text: 'Temperature (Celsius)' }
      //title: { text: 'Temperature (Fahrenheit)' }
      },

      I hope this helps.
      Regards,
      Sara

      Reply
  55. Please Can I get code Using LDR light sensor. I dont have BME sensor and incooperating code for light is a bit difficult

    Reply
  56. Hey!
    Great Project, funcionality is perfect.
    Is there a way to save the Sensordata to an Excel sheet?

    Best regards
    Niklas

    Reply
  57. So let’s say i want to update the readings of temperature at an interval of 10sec and humidity at 30 seconds then how do i do that

    Reply
  58. Hello, I need your help please. I am making a similar project, a battery monitor that can send data from esp8266 to graph on my phone – all without internet. Can you point me in the right direction?

    Reply
  59. Hi thank for the tutorial.
    I encountered a problem when i followed the tutorial. It says error compiling for DOIT ESP32 DEVKITV1. Is there any problems with the libraries like ESP32AsyncWebServer.h and AsyncTCP.h and SPIFFS.h ?

    Reply
  60. Hi!! great tutorial!! Is there any easy way to store historical data on SPIFFS (let’s say the 10 last measurements) and load them each time the website is requested?

    Reply
  61. Oi Sara!! Parabéns pelo projeto. Eu consegui com 1 sensor bme280. Você conseguiria me ajudar a colocar 2x bme280 para fazer a leitura? O que preciso modificar no código?

    Obrigado,
    Mateus

    Reply
  62. Hi.
    Thank you for this tutorial Rui and Sara.

    Setting chart timezone.
    For those who wonder (as I did), where to put the before mentioned configuration script in order to change chart time zone correct. Here is the answer.
    Open index.html file and add text after tag.

    <

    script>
    Highcharts.setOptions({
    global: {
    useUTC: false
    }
    });
    var chartT = new Highcharts.Chart({

    Now you have correct time (your computer system time) in your charts.

    Reply
    • Hi! I substract a value of the variable x

      var x = ((new Date()).getTime() – 10800000),

      My time zone is -3h transform in 3000
      3000 * 60 * 60 = 10800000

      A Macgyver solution kkkkk

      Reply
  63. Hey, really useful tutorial. Saving me hours of time.
    By the way, it looks like this entire webpage is being ripped off here:
    XXXXXXXXXXXXXXXXXXXXXXX
    It looks like someone that’s trying to plagiarize by rewriting some text around and redrawing the graphs. But the code is basically the same, and the whole flow is as well.

    Reply
    • Hi.
      That website plagiarizes almost all our tutorials.
      It’s really annoying, but there’s nothing we can do about it.
      I hope people can tell the difference and understand that ours are the original tutorials.
      Thanks for telling us.
      Regards,
      Sara

      Reply
  64. As seen on random nerd tutorials –

    thanks to Rui & Sara Santos. I left all your credentials in the code.

    https://randomnerdtutorials.com/esp32-esp8266-plot-chart-web-server/

    I just added security, logging, registering new user, pagination system, live searcher for database, php file to change room temperature for cron job. Results sent on two email adresses. One treshold is set in php file (email 1) other one thru website php page (email 2)…

    Supreme work Rui & Sara. I hope you don’t mind for tweeking your code

    javascript file is only edited to set the time zone gmt+2.

    Reply
  65. As seen on random nerd tutorials – randomnerdtutorials.com

    thanks to Rui & Sara Santos. I left all your credentials in the code. I strongly recommend your website and knowledge to anyone… Everyone… =)

    I just added security as you mentioned on your channel and website it is recommended to add some kind of security. So i added logging, registering new user, added pagination system for database, live searcher for database – search by ***… Also added php file to change room temperature. Results sent on two email adresses. One treshold is set in php file (email 1 – set to 45 degrees celsius – urgent – trigger whatever with cron) other one thru website php page (email 2)… Third action is added through cron job (cron job checks every minute if treshold is reached) and accordingly triggers relay (second microcontroller), which turns on big fan in room. As long as tempreture treshold is reached fan is turned on otherwise cron job turns off the fan.

    Thank you so very much and keep up with great even Supreme work. 😉

    and my link on utube

    Reply
  66. Hi! Thanks for the best project of ESP WebServer of the internet!
    If is not much, you can help me to make the graphic no resize de axis Y? I have show a scale 0 to 10 always.

    Reply
  67. I love your tutorials! Question: what would I change to remove the value of the reading from being plotted on the chart? I want to be able to hover and see that data still, just not have any text on the point otherwise. Thx!

    Reply
  68. Hi! I love this code. I am using it in my thesis about energy consumption and protection. I would like to make 2 plotlines in the chart, to set maximum and minimum values. I did it manually in HTML code, but I would like to transfer the variable of maximum and minimum to HTML automatically as we do with the readings.
    Sorry, but my knowledge about HTML and Jave Script is inverse of my knowledge about c++.

    var chartH = new Highcharts.Chart({
    chart:{ renderTo:’chart-corrente’ },
    title: { text: ‘Corrente em Amperes’ },
    series: [{
    showInLegend: false,
    data: []
    }],
    plotOptions: {
    line: { animation: false,
    dataLabels: { enabled: true }
    }
    },
    xAxis: {
    type: ‘datetime’,
    dateTimeLabelFormats: { second: ‘%H:%M:%S’ }
    },
    yAxis: {
    title: { text: ‘Corrente’ },

    plotLines: [{
    value: “0”,
    color: ‘green’,
    dashStyle: ‘shortdash’,
    width: 2,
    label: {
    text: ‘Corrente minima’
    }
    }, {
    value: “1.0”, //<== I would like to put “correnteMax” varieble here.
    color: ‘red’,
    dashStyle: ‘shortdash’,
    width: 2,
    label: {
    text: ‘Corrente maxima’
    }
    }]
    },
    credits: { enabled: false }
    });
    setInterval(function ( ) {
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    var x = (new Date()).getTime(),
    y = parseFloat(this.responseText);
    //console.log(this.responseText);
    if(chartH.series[0].data.length > 40) {
    chartH.series[0].addPoint([x, y], true, true, true);
    } else {
    chartH.series[0].addPoint([x, y], true, false, true);
    }
    }
    };
    xhttp.open(“GET”, “/corrente”, true);
    xhttp.send();
    }, 1000 ) ;

    // I tried to make this code to read the variable, but don’t work.
    setInterval(function ( ) {
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    var correnteMax = parseFloat(this.responseText);

    }

    };
    xhttp.open(“GET”, “/correnteMax”, true);
    xhttp.send();
    }, 1000 ) ;

    Reply
  69. Thank you for the wonderful tutorial, it was really helpful. I have modified the codes a bit so that I display values from the dht11 and soil moisture sensor. The code is compiling but whenever I access the web server so that I can view the charts the esp8266 gets into a continuous loop/ continuously restarts. I get the following error in the serial monitor : ets Jan 8 2013, rst cause: 2, boot mode: (3,6) and this followed by a very long code which comprises of
    what I think are addresses. May you please kindly assist, what could be the problem?

    Reply
  70. Good day, I was about to use it to monitor my sensor’s data. I’m planning to leave my prototype overnight then check the previous record data in the morning. But I noticed that when the webserver being refresh, the previous data that has been plotted will vanish.

    Is it possible in this project of yours that even I refresh the webserver the previous data will still be there?

    Reply
  71. Hello Rui and Sara, I hope you are doing well!

    First off, I want to say thanks so much for uploading your tutorials – they have been of great help to myself and everyone else here.

    I’m trying to get the multiple graphs working. I am using a DS18b20 sensor and am successfully able to load the HTML code to SPIFFS and can visualize this single graph in real-time. But just now I have tried to add a second graph (readings from an ADS1115 converter) and have tried for hours but cannot get these readings to plot. BUT: The values are indeed being read and updated by the HTML or ESP32 code (I’m not sure exactly which does this) which can be seen when I go to the corresponding “/ads” URL.

    Do you have any ideas? Thanks so much!

    Reply
    • Hi.
      Without checking the code, it is very difficult to find out what might be wrong.
      If you want, share your code using a link to github gist or pastebin, for example.
      Regards,
      Sara

      Reply
  72. I want to plot second sensor data on x axis… Instate of time… Like voltage vs current graph….. Please help me to modify html file…

    Reply
  73. Hi, I have a problem regarding multiple clients accessing the IP Adress to view the charts, All charts works fine on all of the clients but they do not display the same readings, What I wanted was, what is seen on one client it is also seen on the other clients. THank you very much!

    Reply
  74. Excellent stepping stone for my project. Thank you.
    I’ve downloaded highcharts.js and uploaded it to the Flash using LittleFS.
    I would like to change the line:
    https://code.highcharts.com/highcharts.js
    so that I can access it locally on the ESP8266.
    I’ve tried everything like
    http://highcharts.js
    /data/highcharts.js
    /highcharts.js
    It works with
    script src=”https://code.highcharts.com/highcharts.js”>
    anything else, and I get this error:
    Loading failed for the with source “http://192.168.1.55/highcharts.js”.
    Can I use this file from local storage? How?
    Thanks.

    Reply
    • Hi Mai. I was also unable to use a local copy of highcharts.js despite trying all the suggests made in comments on this tutorial webpage and verifying that the file was stored in the SPIFFS of my ESP8266.

      What finally worked for me was to modify the sketch C code so that it explicitly served the highcharts.js file when requested. The code snippet to do that is pasted here : https://pastebin.com/DmtWwByM

      Reply
  75. First, thank you for a great tutorial.
    I have a problem running this without ESP8266 crashing.
    I’ve located the error to be these 2 lines.
    server.on(“/temperature”, HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, “text/plain”, readBMPTemperature().c_str());
    });
    server.on(“/pressure”, HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, “text/plain”, readBMPPressure().c_str());
    });
    If I comment them out, no crash and I get the index.html showing up (no data of course) !
    I have removed Humidity portions since I’m using a BMP180 and have also removed humidity from index.html. So to only process Temperature and Pressure.
    I’ve tried everything I can think of and don’t know what else to try.
    Tested every other part of the code, including values returned and they all works.
    For example;
    String readBMPTemperature() {
    // Read temperature as Celsius (the default)
    //T = bmp.readTemperature(); commented out
    //T is declared as float globally.
    T=random(10,30);
    return String(T);
    }
    Plots the random points!
    !!! H E L P !!!

    Reply
    • Hi.
      Save the readings on a global variable and get the readings on the loop().
      Then, instead of calling the function readBMPPressure() inside the server.on(), you’ll use the variable value.
      Regards,
      Sara

      Reply
      • After days of trying to figure out what is wrong, I find that request->send_P(200, “text/plain”, readBMPPressure().c_str()); is not valid. This function does not accept a function call.
        I had to read the Temperature and pressure in the loop section into global variables, then pass the string of the number to request->send_P
        But you knew that, and didn’t bother updating your page.
        The code the way it is, will not work for anyone.
        Thanks for wasting days of my life. I was going to buy your ESP webserver book, but I’m glad I didn’t. It too is probably full of errors.

        Reply
        • Hi. I’m sorry for that issue.
          The code works fine as it is, with the BME280 sensor.
          However, if you’re using another sensor, depending on the library you’re using, you might need to get the readings on the loop() as I suggested.
          I guess you’re not using the BME280 sensor (because you changed the name of the function).
          Regards,
          Sara

          Reply
  76. Hi there,

    Thank you for the great tutorial! I am always learning new things from your website tutorial!
    But I have a question to ask, seem like this graph can only be visualised when there is internet, it there by any chance I could do it with access point?

    If so can you guide me with similar post so I could understand and work on it!

    Thank you

    Reply
      • Hi Sara!

        Thank you! i managed to make it work offline! But i meet with a problem is that when the data reached the end of the graph, the data is not displaying anymore, it just showed a full graph, is this normal?

        thanks!

        Reply
        • What do you mean?
          Older readings are replaced with newer readings is the normal behavior of this example.
          Regards,
          Sara

          Reply
          • hi sara,

            What i mean is that data was displaying perfectly until it reaches a point the data is not displaying anymore. For eg, it only manage to log the data from 19:34 to 19:36 and then I stopped right there

            thanks!

    • That’s a very interesting project.
      Thanks for sharing.
      It’s interesting that you’re sharing this right now as we are currently preparing a tutorial about load cells. Your project is a great application of how they can be used.
      Regards,
      Sara

      Reply
  77. I’ve got this working with a radiation detector.
    However, I am storing the time/date and other data, on to a SPIFFS file, every 10 minutes.
    Is there a way to draw the graph on previous data, every time the browser opens?
    The way it is, the chart will chart, only if the browser stays open.
    40 samples per chart is good enough for me.
    Is there a way to process the array of records like these;
    typedef struct {
    int lcnt;
    String ltime;
    String temp;
    String pres;
    unsigned short radcount;
    } record_type;
    record_type sensor_data[record_size]; // Define the data array

    I’ve tried ESP32WebServer library to see if it works. It does, but unfortunately it doesn’t play well with AsynWebServer, AsynTCP and ElegentOTA libraries. I assume conflict of port 80.

    Any guidance would be greatly appreciated.

    Reply
  78. Hi Sara !!

    Your tutorial is phenomenal!!

    I think of making an asynchronous server like the one shown in this tutorial and at the same time I want to save the data on a micro sd, I wanted to ask if there would be any interference between the server that contains the graphics and the NTP server since I want to save the data data with the time in which they were recorded in the micro sd.

    Reply
      • Hi Sara !

        I have made my code and I have my page working and saving data but without the time on the microSD, that is why I wanted to see if you send me your email and so I can share my code with you to see what my mistake is, please.

        Reply
  79. Hello friends, I have a problem with the time of the graph.
    Despite adding the lines,
    //Highcharts.setOptions({
    //global: {
    //useUTC:false
    //}
    //});
    due to the summer time change, there is 1 hour difference to the real one. how can I solve it ?

    Reply
  80. Hello,
    How do i upload the index.html file by hand in the new arduino ide 2.0.3? I am not eable to find the “ESP32/ESP8266 Data Sketch Upload”-Button.

    Reply
    • Hi.
      You can save the values (timestamp and reading) in a JSON file in the ESP filesystem or on a microSD card and then load all the points when you open the web browser.
      Unfortunately, at the moment, we don’t have any tutorials showing that specific scenario.
      But, you should look for how to save and load data from a file.
      Regards,
      Sara

      Reply
  81. Hello, great tutorial! Is there any way to decrease the time data updates? Maybe to see the charts update by second or less? I’m asking this so I don’t use a websocket server for my project, thanks!!

    Reply
  82. What a great tutorial! Thank you.
    I have the code that calculates the number of ticks produced by a GM radiation tube.
    I therefore don’t need the setInterval(function ( ) ) since it will read at wrong times.
    How do I pass a number to the chart. Basically the same as what goes on inside each setInterval(function ( )) but without the timer part.
    I’ve tried this instead of setInterval();
    function updateTicks( ) {
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    var x = (new Date()).getTime(), y = parseFloat(this.responseText);
    console.log(“ticks:”);
    console.log(this.responseText);
    if(chartT.series[0].data.length > 60) {
    chartT.series[0].addPoint([x, y], true, true, true);
    } else {
    chartT.series[0].addPoint([x, y], true, false, true);
    }
    }
    };
    }
    /*added this to the setup /
    source.addEventListener(‘ticks’, function(e) {
    console.log(“ticks”, e.data);
    update_ticks();
    }, false);

    There must be a way that I could pass real-time data to the charts plotter as they come it.
    Please help if you know how. The charts docs are very cryptic and hard to understand.

    Reply
  83. There are a lot of improvements that can be done.. for example you can easily combine the readings data in one single request instead to have multiple endpoints, even better, use WebSockets instead of Ajax so it’s the server that send the data to client = less network overhead and more responsive updates. Also it’s better to include all the files (js/css) in your LittleFS (SPIFFS is deprecated) so it can work without any internet connection.

    Reply
    • Hi.
      Thanks for your feedback.
      Yes. I know, our most recent tutorials (like the ones in the Web Servers eBook), already use those improvements.
      We’ll also have to update the tutorials in our website.
      Regards,
      Sara

      Reply
  84. Thank you so much for this project guide.
    I’d like to also have a line on the server where it shows us the max pressure so fsr and im struggling to do so. could you help me by any chance?

    Reply
  85. Hey, this is really cool! I’m just getting started with web servers and sensors, and I was wondering if you could explain a bit more about the BME280 sensor. What exactly does it do?

    Reply
  86. Hello,

    I have tried following every step in this tutorial and can’t seem to get the code working. I ahve two issues.

    The first issue is with the <LittleFS.h> library. In Step 3, you don’t include this library as one to download, implying that it already comes with the standard Arduino library package, but that is not the case for me. I have Arduino version 2.3.2. I found a <LittleFS.h> library on GitHub from author earlephilhower, but it doesn’t seem to work. I also noticed that in your YouTube video you don’t include <LittleFS.h>, but in the code you do. Can you give me any advice on where I can find a <LittleFS.h> library that works?

    The second issue is with the Filesystem uploader. I get a message “ERROR: No filesystem specified, check flash size menu”. Do you know why this is happening?

    Thanks,

    Denis

    Reply
    • Hi.
      The LittleFS library is installed by default.
      Make sure you have the latest version of the ESP32 boards installed in your Arduino IDE and that you have an ESP32 board selected in Tools > Board before compiling the code.
      Regards,
      Sara

      Reply

Leave a Reply to Rui Santos Cancel reply

Download Our Free eBooks and Resources

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