Friday, November 11, 2022

ESP webpage with linechart

For an index to all my stories click this text

This is the third installment on putting Gauges and Charts on a webpage that is hosted on an ESP8266 or ESP32. The first story showed how to build gauges and linecharts with Javascript on a PC. Re-read that story here:
http://lucstechblog.blogspot.com/2019/11/gauges-and-linecharts-in-javascript.html
The second story showed how to put Gauges on a webpage to represent data from an ESP8266 or ESP32. This is a more appealing representation of your data as just plain text. Re-read that story here:
http://lucstechblog.blogspot.com/2019/11/esp-webpage-with-gauges.html

A gauge however gives you just a momentarily representation of your data. As soon as the data changes the pointer of the gauge changes to the new value. There is no registration of the old values.

A linechart gives you both the last value of your sensor and a historical view. In this story I am going to show you how to put a linechart on a webpage with your ESP8266 or ESP32 and have it show data from a sensor.

The program relies heavily on the ESP webserver library which I covered in some previous stories. Look in my index at the ESP8266 or ESP32 entries to find these stories and study them well if you are not familiar with the ESP webserver.
I also urge you to read the previous 2 stories in this series to get to know how the linechart is build with a Javascript library.

What's the drill.

I am going to attach a Dallas DS18B20 thermometer to an ESP8266 and an ESP32. Next I am going to show you the program that builds a webserver which displays the values from the thermometer on a webpage.

If you do not own a Dallas DS18B20 you can easily replace it with an LDR or a potmeter or any other sensor that will give a variety of readings.



The program builds a line-chart which displays the last 10 temperature values. As usual I will give you the program for the ESP8266 and after that the (minor) changes you need to make for the ESP32 version. There are only 3 lines that have to be altered for the ESP32 so the program is almost the same. The explanation which follows is therefore suited for both the ESP8266 and the ESP32.

I have noticed though that the ESP32 version is significant faster as the ESP8266 version. With the ESP8266 you will have to be a bit patient before the page is shown.

The programs are written in Arduino language (C++). As you can see I am abandoning ESP-Basic as it is not further developped or even maintained.

ESP8266 Breadboard setup

For this example I am going to use the usual breadboard setup for a Dallas DS18B20 thermometer with a Wemos D1 mini.



The Dallas DS18B20 Thermometer does not use a lot of current so can be powered directly from the ESP8266. The sensor is connected to D4 of the ESP8266 and uses a 4.7k pull-up resistor. You can use any other IO pin as long as you adjust the program.

ESP32 breadboard setup

The ESP32 breadboard setup is taken from my book about the ESP32. You can find a link to my book at the bottom of this page.



The Dallas DS18B20 is attached to IO pin 23 (D23) but you may attach it to another pin if that is more suitable for your own project. Just do not forget to adjust the program accordingly.

ESP8266 Program.

This is a lengthy program and as usual I am giving it in full and an explanation after the complete program. You can copy it and past it in the Arduino IDE and modify iy to your own needs.

// ======================================================
// Webserver program that gets temperature info
// from a Dallas DS18B20 and puts that in a linegraph
// on a webpage with a update every x seconds
// ======================================================

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

ESP8266WebServer server(80);

#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS D4

// Setup a oneWire instance 
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

// Your routers credentials
const char* ssid = "XXXXXXXXXXXXXXXX";
const char* password = "YYYYYYYYYYYYYYYY";

int temparray[] = {0,0,0,0,0,0,0,0,0,0};
String timarray[] = {"0","0","0","0","0","0","0","0","0","0"};
int temptemp = 0;
unsigned long oldmil = 0;
unsigned long newmil = 0;
const long interval = 10000;

// =========================================
// This is where we build the HTML page
// =========================================
String getPage()
  {
  sensors.requestTemperatures();
  String page = "<!DOCTYPE HTML>";
  page += "<html>";
  page += "<head>";
  page += "<meta name = \"viewport\" content = \"width = device-width, initial-scale = 1.0 maximum-scale = 2.5, user-scalable=1\">"; 
  page += "<title>Luc's Linechart demo</title>";
  page += "<body style='background-color:powderblue;'>";
  //page += "<style>";
  page += "</style>";
  page += "</head>";
  page += "<body>";
  
  page += "<h1 style='color:red'>Hobby room";
  page += "<br>";
  page += "temperature</h1>";
  
  page += "<div id='temp_chart' style='width:600px; height: 400px;'></div>";
  page += "<br>";
  
  page += "<FORM action=\"/\" method=\"post\">";
  page += "<button type=\"submit\" name=\"button1\" id=\"button1\" value=\"button1\" onclick=\'update()\'>CLICK FOR UPDATE</button>";
  page += "</form>";
  
  page += "<script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>";
  page += "<script type='text/javascript'>";
  page += "google.charts.load('current', {'packages':['corechart']});";
  page += "google.charts.setOnLoadCallback(drawChart);";

  page += "function drawChart() {";
  page += "var chartData = google.visualization.arrayToDataTable([";
  page += "['Time' , 'Temp'],";
  
  page += "[";
  page += String(timarray[0]);
  page += ",";
  page += String(int(temparray[0]));
  page += "],";

  page += "[";
  page += String(timarray[1]);
  page += ",";
  page += String(int(temparray[1]));
  page += "],";

  page += "[";
  page += String(timarray[2]);
  page += ",";
  page += String(int(temparray[2]));
  page += "],";
  
  page += "[";
  page += String(timarray[3]);
  page += ",";
  page += String(int(temparray[3]));
  page += "],";

  page += "[";
  page += String(timarray[4]);
  page += ",";
  page += String(int(temparray[4]));
  page += "],";

  page += "[";
  page += String(timarray[5]);
  page += ",";
  page += String(int(temparray[5]));
  page += "],";

  page += "[";
  page += String(timarray[6]);
  page += ",";
  page += String(int(temparray[6]));
  page += "],";

  page += "[";
  page += String(timarray[7]);
  page += ",";
  page += String(int(temparray[7]));
  page += "],";
      
  page += "[";
  page += String(timarray[8]);
  page += ",";
  page += String(int(temparray[8]));
  page += "],";

  page += "[";
  page += String(timarray[9]);
  page += ",";
  page += String(int(temparray[9]));
  page += "]";
  
  page += "]);";

  page += "var chartoptions ="; 
  page += "{";
  page += "title: 'Mancave temperature',";
  page += "legend: { position: 'bottom' },";
  page += "width: 600,";
  page += "height: 400,";
  page += "chartArea: {width: '90%', height: '75%'}";
  page +="}; ";

  page += "var chart = new google.visualization.LineChart(document.getElementById('temp_chart'));";
  page += "chart.draw(chartData, chartoptions);";
  page += "}";

  page += "function update() {";
  page += "chart.draw(chartData, chartoptions);";
  page += "}";
 
  page += "</script>";  
  page += "</body>";
  page += "</html>";
  return page;
  }

// ==================================================
// Handle for page not found
// ==================================================
void handleNotFound()
{
  server.send(200, "text/html", getPage());
}

// ==================================================
// Handle submit form
// ==================================================
void handleSubmit()
{   
   if (server.hasArg("button1"))
      {
      Serial.print("The temperature is: ");
      Serial.print(int(sensors.getTempCByIndex(0)));
      Serial.println(" degrees C");
      } 
   server.send(200, "text/html", getPage());       //Response to the HTTP request
}  

// ===================================================
// Handle root
// ===================================================
void handleRoot() 
{   
  if (server.args() ) 
    {
    handleSubmit();
    } 
  else 
    {
    server.send(200, "text/html", getPage());  
    }
}

// ===================================================
// Setup
// ===================================================
void setup()
{
  Serial.begin(115200);

  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid,password);
  while (WiFi.status() != WL_CONNECTED) 
  {
   delay(500);
   Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  
  server.begin();
  server.on("/", handleRoot);
  server.onNotFound(handleNotFound);
  server.begin();

  // Start up the library
  sensors.begin();
}

// ===================================================
// Loop
// ===================================================
void loop()
{  
  server.handleClient(); 
  server.send(200, "text/html", getPage());
  unsigned long newmil = millis();
 
  if(newmil - oldmil >= interval) 
  {
      oldmil = newmil;
      Serial.print("The temperature in ticker is: ");
      temptemp = (int(sensors.getTempCByIndex(0)));
      Serial.print(temptemp);
      Serial.println(" degrees C");
      for (int i = 0; i<9; i++)
      {
        temparray[i] = temparray[i+1];
        timarray[i] = timarray[i+1];
      }
      temparray[9] = temptemp;
      timarray[9] = String(int(millis()/1000));
  }   
}

The first part just loads the libraries for wifi, the webserver and for the DS18B20. Just do not forget to put in your own routers credentials at the XXXXX and YYYY.

int temparray[] = {0,0,0,0,0,0,0,0,0,0};
String timarray[] = {"0","0","0","0","0","0","0","0","0","0"};

Then there are two arrays defined. The first is for storing the temperatures the second for storing the time. They are both filled with 0's initially.

Besides these there are a few variables defined which are needed further on in the program.

In the getpage() routine the webpage is build. The first part is pretty obvious. The background color is defined (my favorite: powderblue) and the text at the top of the page is set.

  page += "<div id='temp_chart' style='width:600px; height: 400px;'></div>";
  page += "<br>";

This line reserves a space on the webpage where the chart will be put. The width and height are defined and the space gets the name 'temp_Chart'

  page += "<FORM action=\"/\" method=\"post\">";
  page += "<button type=\"submit\" name=\"button1\" id=\"button1\" value=\"button1\" onclick=\'update()\'>CLICK FOR UPDATE</button>";
  page += "</form>";

These lines put a button below the chart. Pressing the button (onclick) will call the Javascript function 'update'

  page += "<script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>";
  page += "<script type='text/javascript'>";
  page += "google.charts.load('current', {'packages':['corechart']});";
  page += "google.charts.setOnLoadCallback(drawChart);";

This part loads the Javascript library for making charts from the Google site. When loaded it calls the function drawChart()

  page += "function drawChart() {";
  page += "var chartData = google.visualization.arrayToDataTable([";
  page += "['Time' , 'Temp'],";

And here is the Javascript function drawChart(). For drawing a linechart the library needs a datatable containing the data for the x axis and the y axis. For this example I used the names Time for the X axis and Temp for the Y axis. You can alter them for your own purposes.

  page += "[";
  page += String(timarray[0]);
  page += ",";
  page += String(int(temparray[0]));
  page += "],";

These lines create the first entry in the data-table.
Actually an entry in the table is simple. It looks like [ x , y ], Here I replaced the x with the first entry in my defined timarray (element 0) and make sure it is a String. The timarray will contain the time slots. The y component is replaced with the first entry in the temparray (element 0). This will contain the measured temperature at the defined time slot.

When the program starts all timarray and temparray elements are set to 0 as you have seen in the beginning of the program.

  page += "[";
  page += String(timarray[9]);
  page += ",";
  page += String(int(temparray[9]));
  page += "]";

This is the last entry in the data table. It contains the last elements from the timarray and temparray. This brings the total of entries to 10 (from 0 to 9). You can add more entries if you like. Just make sure you also add more array elements and adjust the involved loops that update the array.

  page += "var chartoptions ="; 
  page += "{";
  page += "title: 'Mancave temperature',";
  page += "legend: { position: 'bottom' },";
  page += "width: 600,";
  page += "height: 400,";
  page += "chartArea: {width: '90%', height: '75%'}";
  page +="}; ";

These lines define some specifics for the Chart to be drawn. The Chart will have a name and a legend and the position of the legend is defined. The width and height of the Chart are defined and ythe width and height (in percentages) of the space the Chart occupies in the defined area. If you do not define these last two percentages there will be a lot of whitespace at each side of the chart. So alter these to your own needs.

  page += "var chart = new google.visualization.LineChart(document.getElementById('temp_chart'));";
  page += "chart.draw(chartData, chartoptions);";
  page += "}";

Now the datatable is filled and the charts parameters are defined the chart is drawn and put into the space we defined in the HTML code with the name temp_chart. Actually the complete web-page is now defined and drawn.

  page += "function update() {";
  page += "chart.draw(chartData, chartoptions);";
  page += "}";

This last piece of Javascript is the function that is called when the button on the screen is pressed. It just updates the chart.

// ==================================================
// Handle for page not found
// ==================================================
void handleNotFound()
{
  server.send(200, "text/html", getPage());
}

This piece of code is just a safety net for when the webpage is called with an unknown parameter. The page will just be re-drawn.

// ==================================================
// Handle submit form
// ==================================================
void handleSubmit()
{   
   if (server.hasArg("button1"))
      {
      Serial.print("The temperature is: ");
      Serial.print(int(sensors.getTempCByIndex(0)));
      Serial.println(" degrees C");
      } 
   server.send(200, "text/html", getPage());       //Response to the HTTP request

This is the code that will be executed when the button is pressed. The actual temperature will be printed in the Serial Monitor and the web-page will re-load with the up-to-date information.

// ===================================================
// Handle root
// ===================================================
void handleRoot() 
{   
  if (server.args() ) 
    {
    handleSubmit();
    } 
  else 
    {
    server.send(200, "text/html", getPage());  
    }
}

Here is the code that is executed when you point your browser to the IP adress of the ESP. The webserver will load the website that is build in the previous discussed getpage() routine.

In the setup() routine the usual things happen.
First the program will wait till a Wifi connection with your router is established. When that is done the ESP will get an IP number from the router and that is printed in the Serial Monitor. Put this IP number in your browser and the handleRoot() routine starts and loads the webpage.

// ===================================================
// Loop
// ===================================================
void loop()

This is where the interesting stuff at the ESP side is done.

  server.handleClient(); 
  server.send(200, "text/html", getPage());

The loop constant tests wether the webserver should be activated. This happens when the webpage is loaded, reloaded or when the button on the page is pressed.

unsigned long newmil = millis();

A new variable is created that is set to the number of milliseconds that have past since the program started.

  if(newmil - oldmil >= interval) 

The interval is at the beginning of the program set to 10.000 which is 10 seconds. The oldmil is at the beginning set to 0. So this line tests if 10 seconds or more have past. If that is so the program in the loop is run.

      oldmil = newmil;

The oldmil variable gets the value of the newmil. That way the sequence repeats as the newmill is updated in the loop. So the loop will run every 10 seconds.

      Serial.print("The temperature in ticker is: ");
      temptemp = (int(sensors.getTempCByIndex(0)));
      Serial.print(temptemp);
      Serial.println(" degrees C");

The temperature is fetched and printed in the Serial Monitor for checking purposes.

      for (int i = 0; i<9; i++)
      {
        temparray[i] = temparray[i+1];
        timarray[i] = timarray[i+1];
      }

This loop moves all values in the array 1 place down. This way the latest time and temperature will get at the end of the array and therefore at the end of the chart when that is drawn.

      temparray[9] = temptemp;
      timarray[9] = String(int(millis()/1000));

And here the latest temperature and time are set in the last array entries.

ESP32 Program

The ESP32 program is almost identical to the ESP8266 version. You only have to alter a few lines.

#include <WiFi.h>
#include <WebServer.h>

The webserver and wifi libraries are different so use the libraries shown here.

#define ONE_WIRE_BUS 23

The Dallas DS18B20 is (as the breadboard shows) attached to pin 23. So this is the modification for the IO port.

That's all. You only have to alter 3 lines in the beginning of the program to get it working on the ESP32.

Brief explanation.

So what happens is that every 10 seconds the temperature is measured. That temperature is put in the last temparray entry and the time is put in the last timarray.

When you reload the webpage or press the 'update' button on the webpage the array values are put in the Javascript data table and send to the Google library which will transfrom the figures into a nice linechart.

Starting the program

After building the breadboard setup, copy the complete program and paste it in the Arduino IDE. Upload it to the ESP32 and open the Serial Monitor


As you can see the program started and then connected to my router. From the router the ESP got an IP number.



Put that in your browser and the chart will show. Just press the update button to get the most recent data. The chart above was made after I held an icecube (wrapped in plastic) against the DS18B20.

Modifications.

This program is easily modified for your own needs.

At the start of the program the variable interval is defined at 10.000 This stands for 10 seconds. So each 10 seconds a new temperature is requested and put in the array.
For demo purposes this is great but for real life purposes an interval of lets say 1000 (1 second) x 60 (1 minute) x 30 = 1.800.000 is half an hour would be more realistic. Then the array and therefore the linechart would be updated each half hour.

Even better is to attach a real-time-clock to the ESP or get the time over internet by conatcting an NTP server. Both these would give you the actual time instead of the amount of miliseconds that passed since starting the program.

Another modification, which has to be done in the HTML code,  is the header of the page and the color. You certainly want to alter Hobby Room Temperature to something that suits your needs better. And changing the background color will definitely also be an option.

Next you can expand or shrink the datatable so more or less entries are displayed.

You can also modify the appearance of the table. For detailed info on how to do that look at the the Google Charts page: https://developers.google.com/chart/interactive/docs/gallery

With the web-page stories for building pages with buttons, sliders etc and these entries to get charts on that same page you are able to build totally customised webpages for your IOT projects.

Till next time
Have fun !!

Luc Volders