ESP32: Real-time web dashboard

Introduction

In this tutorial we are going to learn how to use the ESP-DASH library to implement a web-based dashboard that will be updated in real time with sensor measurements (temperature and humidity). Under the hood, the update mechanism will be based in websockets. We will be using the ESP32 and program it with the Arduino core.

First of all, it is important to clarify that the term real-time used here is a simplification of language. We are using it to indicate that the measurements will be updated as fast as possible in the dashboard, after they are obtained from the sensor. In the world of embedded systems, real-time has a different meaning (you can read more about it here).

Like already mentioned, we are going to use the ESP-DASH library to build our dashboard. Although this approach is much less flexible than creating our own Frontend code to render the dashboard (check an example here), it is also much simpler and accessible if we are not comfortable with Frontend development. It also allows to obtain very clean and good looking results, which may be more than enough for many applications and POCs.

You can check the library installation instructions here. Note that it depends on the Async HTTP web server library and on ArduinoJson.

The Async HTTP server library is used under the hood to serve the HTML file containing the dashboard and also to support a client websocket connection, which will be used to notify the dashboard when its values need to be updated. Nonetheless, all of this is hidden from us, meaning we just need to worry about creating a web server instance and initialize it.

We will divide the code in two sections. In the first one we will focus on the usage of the library without worrying about any sensor. As such, we will be generating some random fake measurements. In the second section, we will wire the ESP32 to a temperature and humidity sensor (DHT22). I’ll be using this module.

The tests shown below were performed on a ESP32-E FireBeetle board from DFRobot. The Arduino core version used was 2.0.0 and the Arduino IDE version was 1.8.15, working on Windows 8.1

A simple example

In this section we will analyze a simple example with two cards in our dashboard. To focus on the library, and confirm it works as expected, we won’t be connecting the ESP32 to the sensor yet, but instead generate random fake data for the measurements.

We will start our code by the library includes:

  • WiFi.h: Allows to connect the ESP32 to a WiFi network.
  • ESPAsyncWebServer.h: Allows to setup a HTTP web server on the ESP32.
  • ESPDash.h: Exposes the functionality needed to setup a dashboard and update its values in real-time.
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <ESPDash.h>

Given that we will connect the ESP32 to a WiFi network, we will need the credentials of that network (SSID and password). We will store these in two global variables. Note that below I’m using placeholders that you should replace by the actual credentials of your network.

const char* ssid = "YourNetworkName";
const char* password =  "yourNetworkPass;

Then we will need an object of class AsyncWebServer, which the ESPDash.h library will use under the hood for its functionality. The constructor of this class receives as input the number of the port where the HTTP server will be listening for requests. We will use port 80, which is the default HTTP port.

AsyncWebServer server(80);

Then we will create an object of class ESPDash. The ESPDash class is responsible for handling all the network part of the dashboard [1].

As input, the constructor of this class receives a pointer to an AsyncWebServer object. Naturally, we will pass the address of our previously created server object.

ESPDash dashboard(&server); 

Now that we have our dashboard object, we will setup two cards to be displayed there. We will have one simulating temperature measurements and another simulating humidity measurements.

For this we will create two Card class objects. The constructor of this class receives the following inputs:

  • A pointer to an ESPDash object. We will pass the address of our dashboard.
  • The type of card. The type should be a value from this enum. For the temperature card we will pass the value TEMPERATURE_CARD and for the humidity the value HUMIDITY_CARD.
  • The name of the card, which will be displayed on the UI.
  • The symbol of the unit that we are displaying. Note that we will set this below, but this parameter is optional.
Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C");
Card humidity(&dashboard, HUMIDITY_CARD, "Humidity", "%");

In the setup function, we will open a serial connection and then connect the ESP32 to the WiFi network, using the previously defined credentials. Note that we are printing the IP address assigned to the ESP32, so we can later reach the server.

Serial.begin(115200);

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

To finalize the Arduino setup we will start our HTTP server with a call to the begin method on our server object. The full setup, already with this call, can be seen below.

void setup() {
  Serial.begin(115200);

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

We will take care of updating the dashboard values periodically on the Arduino main loop. First, we will generate two measurements (one for temperature and another for humidity) using the Arduino random function.

int temp = random(10, 40);
int hum = random(0, 100);

Then we will update our Card objects with these new values. We do so with a call to the update method, passing as input our new measurements.

temperature.update(temp);
humidity.update(hum);

Note that the update call doesn’t immediately send the values to the dashboard. To do so, we still need to call the sendUpdates method in our ESPDash object. This method will take care of sending to the web page all the updated measurements of each card of the dashboard. This is done using a websocket under the hood.

dashboard.sendUpdates();

We will introduce a small 1 second delay between each iteration of the Arduino main loop. The full loop is shown below, already containing this delay.

void loop() {

  int temp = random(10, 40);
  int hum = random(0, 100);
  
  temperature.update(temp);
  humidity.update(hum);

  dashboard.sendUpdates();

  delay(1000);
}

The complete code is available below.

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

const char* ssid = "YourNetworkName";
const char* password =  "yourNetworkPass;

AsyncWebServer server(80);
ESPDash dashboard(&server); 

Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C");
Card humidity(&dashboard, HUMIDITY_CARD, "Humidity", "%");

void setup() {
  Serial.begin(115200);

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

void loop() {

  int temp = random(10, 40);
  int hum = random(0, 100);
  
  temperature.update(temp);
  humidity.update(hum);

  dashboard.sendUpdates();

  delay(1000);
}

To test the code, compile it and upload it to your ESP32 using the Arduino IDE. When the procedure is finished, open the IDE serial monitor. You should see an IP address getting printed. This IP address corresponds to the IP assigned to the ESP32 on your local network. Copy it, since we will need it to reach the web page with the dashboard.

Then, open a web browser of your choice. On the address bar, type the following address, changing #YourIP# by the IP you have just copied:

http://#YourIP#/

You should get a result similar to figure 1. As can be seen, we have obtained a dashboard with two cards: one for humidity and another for temperature. The measurements should be updated approximately every second.

You can also explore the “Statistics” tab, which contains some information about the ESP32: SDK version, chip ID, Free heap, etc…

Web page with real-time dashboard.
Figure 1 – Web page with real-time dashboard.

Dashboard with real sensor measurements

Now that we had the opportunity to understand the basics on how to build a dashboard with some cards and update its values, we will do it with real measurements obtained from a DHT22 temperature and humidity sensor (I will be using this module, which is ready to connect to a microcontroller without additional electronics).

To interact with the sensor we will be using an auxiliary library, which will expose to us higher level functions that abstract the details of communicating with the sensor. For an explanation on how to install the library, please check this post. The mentioned post also contains the electric diagram to connect the ESP32 to the sensor module.

To further enhance our previous example and leverage some knowledge from previous tutorials, we will be using the Ticker library to setup the periodic execution of a function that will read the sensor and update the cards, rather than doing this from the main loop. For a detailed explanation on how to use the Ticker library, please consult this post.

As usual, we will start by the library includes. Besides the 3 libs we have already seen in the previous example, we will need the following ones:

  • DHTesp.h: Allows to interact with the DHT22 using high level functions.
  • Ticker.h: Allows to very easily setup a function to run periodically.
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <ESPDash.h>
#include <DHTesp.h>
#include <Ticker.h>

After this we will define a variable to hold the number of the ESP32 GPIO that will be connected to the sensor. In my case, I’ll be using pin 17.

const int sensorPin = 17;

Then we will create an object of class DHTesp. It will expose to us the methods to obtain both temperature and humidity measurements.

DHTesp dht;

We will also create an object of class Ticker, which we will use to setup the periodic execution of the function that will get the measurements and update them on the cards.

Ticker periodicTicker;

The rest of the global definitions are exactly the same we have seen in the previous section.

const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPass";

AsyncWebServer server(80);
ESPDash dashboard(&server); 

Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C");
Card humidity(&dashboard, HUMIDITY_CARD, "Humidity", "%");

The Arduino setup will be exactly the same as before, except for two new method calls. The first one will be calling the setup method on our dht object, passing as input the number of the ESP32 GPIO connected to the sensor. This method call will take care of initializing the sensor interface.

dht.setup(sensorPin);

We will also call the attach_ms method on our Ticker object. As first input we pass the number of milliseconds between executions of the function and as second input we will pass the function to be executed. It will be called updateCards and we will check its implementation later.

We are going to set an interval of 5 seconds (5000 milliseconds), for illustration purposes.

periodicTicker.attach_ms(5000, updateCards);

The complete setup is shown below.

void setup() {
  Serial.begin(115200);

  dht.setup(sensorPin);

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

  periodicTicker.attach_ms(5000, updateCards);

}

Since we are going to write the code to update the cards with new measurements on a callback function, we can leave the main loop empty.

void loop() {}

Finally, we will analyze the implementation of our updateCards function.

void updateCards() {
 
  // function implementation
}

First we will read both the temperature and the humidity from the sensor.

float temp = dht.getTemperature();
float hum = dht.getHumidity();

Then we will update our Card objects with these new values.

temperature.update(temp);
humidity.update(hum);

Finally we will send the updates to the dashboard.

dashboard.sendUpdates();

The complete code is available below.

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <ESPDash.h>
#include <DHTesp.h>
#include <Ticker.h>

const int sensorPin = 17;
DHTesp dht;

Ticker periodicTicker;

const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPass";

AsyncWebServer server(80);
ESPDash dashboard(&server); 

Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C");
Card humidity(&dashboard, HUMIDITY_CARD, "Humidity", "%");

void updateCards() {
 
  float temp = dht.getTemperature();
  float hum = dht.getHumidity();
  
  temperature.update(temp);
  humidity.update(hum);

  dashboard.sendUpdates();
}

void setup() {
  Serial.begin(115200);

  dht.setup(sensorPin);

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

  periodicTicker.attach_ms(5000, updateCards);

}

void loop() {}

Upon testing the code, you should obtain a result similar to the one described in the previous section. Naturally, now with real sensor measurements, it is expected that you see less variations in the values of the cards, as we are polling the sensor every 5 seconds and it is likely that the values won’t vary so regularly.

Note that, for the sake of simplicity, we are doing all the update work inside the callback. This should be fine for this example as we have a 5 seconds delay between each execution. Nonetheless, for a more robust code, specially with shorter intervals, you should consider using this callback to notify a task (using some FreeRTOS mechanism) to do the actual update work. You can read more about some considerations when using the Ticker library here.

Regarding the rate of updates, it is also important to mention that the Async HTTP web server websockets implementation (used under the hood) has a limitation regarding the maximum number of pending messages it can accumulate. I did not check the whole implementation of the ESP-DASH library to analyze if there is some sort of mechanism to prevent this, neither found any reference to it in the documentation. As such, if you try a very high rate of updates and run into some issue, it may be related with that limitation.

Suggested ESP32 readings

References

[1] https://ayushsharma82.github.io/ESP-DASH/concept.html

Leave a Reply