ESP32/ESP8266: Firebase Data Logging Web App (Gauges, Charts, and Table)

In this project, you’ll create a Firebase Web App that displays all the sensor readings saved on the Firebase Realtime Database. We’ll create a web interface with gauges, charts, and a table to display all your data records. We’ll also add a button that allows you to delete all data from the database and checkboxes to customize the user interface. This web application will be protected with authentication (using email and password) and all the data is restricted to the user using database rules.

ESP32 ESP8266 NodeMCU Firebase Data Logging Web App with Gauges Charts and Table

This project is Part 2 of the following tutorial (there is a version for ESP32 and a version for ESP8266):

You must follow one of those tutorials first, before proceeding

Here’s a summary of the web app features:

  • login with email and password
  • displays time of the last update
  • cards to display the last sensor readings
  • gauges to display the last sensor readings
  • charts that display data history with timestamps
  • select how many readings to display on charts
  • checkboxes to enable/disable the different display options
  • table that displays all readings saved on the database
  • button to delete database data

Project Overview

In this tutorial (Part 2), you’ll create a web app to display the sensor readings logged with timestamps on the Firebase Realtime Database (read this previous tutorial – ESP32 version / ESP8266 version).

The following video shows the web app project we’ll build—programming the ESP32/ESP8266 and setting up the Firebase Project was done in Part 1 (ESP32 Part 1; ESP8266 Part 1).


  • Firebase hosts your web app over a global CDN using Firebase Hosting and provides an SSL certificate. You can access your web app from anywhere using the Firebase-generated domain name.
  • When you first access the web app, you need to authenticate with an authorized email address and password. You already set up that user and the authentication method in Part 1.
  • After authentication, you can access a web app page that shows the sensor readings. The sensor readings are displayed in cards, gauges, charts and table. You can select how many readings you want to show on the charts and you can also choose how you can view your data.
  • There is a button to show/hide all readings saved on the database on a table with timestamps.
  • There’s also a Delete button that allows you to delete all data from the database.
  • All the data is restricted using database rules.

Prerequisites

Before start creating the Firebase Web App, you need to check the following prerequisites:

Creating a Firebase Project

You should have followed one of the next tutorials first:

The ESP32/ESP8266 must be running the code provided in that tutorial. The realtime database and authentication must be set up also as shown in the tutorial.

Install Required Software

Before getting started you need to install the required software to create the Firebase Web App. Here’s a list of the software you need to install (click on the links for instructions):


1) Add an App to Your Firebase Project

1) Go to your Firebase project Console and add an app to your project by clicking on the +Add app button.

Firebase Add App to Project

2) Select the web app icon.

Firebase Add Web App to Project

3) Give your app a name. Then, check the box next to √ Also set up Firebase Hosting for this App. Click Register app.

Firebase Add Web App to Project Hosting

4) Then, copy the firebaseConfig object and save it because you’ll need it later.

firebaseConfig object configuration copy save

After this, you can also access the firebaseConfig object if you go to your Project settings in your Firebase console.

5) Click Next on the proceeding steps, and finally on Continue to console.


2) Setting Up a Firebase Web App Project (VS Code)

Follow the next steps to create a Firebase Web App Project using VS Code.

1) Creating a Project Folder

1) Create a folder on your computer where you want to save your Firebase project—for example, Firebase-Project on the Desktop.

2) Open VS Code. Go to File > Open Folder… and select the folder you’ve just created.

3) Go to Terminal > New Terminal. A new Terminal window should open on your project path.

Install Firebase Tools 2

2) Firebase Login

4) On the previous Terminal window, type the following:

firebase login

5) You’ll be asked to collect CLI usage and error reporting information. Enter “n” and press Enter to deny.

Login Firebase VS Code Terminal Window

Note: If you are already logged in, it will show a message saying: “Already logged in as [email protected]”.

6) After this, it will pop up a new window on your browser to login into your firebase account.

Login Firebase Account

7) Allow Firebase CLI to access your google account.

Login Firebase Account allow Firebase CLI

8) After this, Firebase CLI login should be successful. You can close the browser window.

Login Firebase Account allow Firebase CLI Login Successful

3) Initializing Web App Firebase Project

9) After successfully login in, run the following command to start a Firebase project directory in the current folder.

firebase init

10) You’ll be asked if you want to initialize a Firebase project in the current directory. Enter Y and hit Enter.

Login Firebase Account allow Firebase CLI firebase init

11) Then, use up and down arrows and the Space key to select the options. Select the following options:

  • Realtime Database: Configure security rules file for Realtime Database and (optionally) provision default instance.
  • Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys

The selected options will show up with a green asterisk. Then, hit Enter.

Login Firebase Account allow Firebase CLI configure directory

12) Select the option “Use an existing project”—it should be highlighted in blue—then, hit Enter.

Firebase Project Setup VS Code

13) After that, select the Firebase project for this directory—it should be the project created in this previous tutorial. In my case, it is called esp-firebase-demo. Then hit Enter.

Firebase Project VS Code create project

14) Press Enter on the following question to select the default database security rules file: “What file should be used for Realtime Database Security Rules?

15) Then, select the hosting options as shown below:

  • What do you want to use as your public directory? Hit Enter to select public.
  • Configure as a single-page app (rewrite urls to /index.html)? No
  • Set up automatic builds and deploys with GitHub? No
Firebase initialization complete

16) The Firebase project should now be initialized successfully. Notice that VS code created some essential files under your project folder.

Firebase Project Files Created successfully

The index.html file contains some HTML text to build a web page. For now, leave the default HTML text. The idea is to replace that with your own HTML text to build a custom web page for your needs. We’ll do that later in this tutorial.

17) To check if everything went as expected, run the following command on the VS Code Terminal window.

firebase deploy
Firebase App First Deploy Testing

You should get a Deploy complete! message and an URL to the Project Console and the Hosting URL.

18) Copy the hosting URL and paste it into a web browser window. You should see the following web page. You can access that web page from anywhere in the world.

Firebase test page hosting setup complete

The web page you’ve seen previously is built with the HTML file placed in the public folder of your Firebase project. By changing the content of that file, you can create your own web app. That’s what we’re going to do in the next section.


3) Creating Firebase Web App

Now that you’ve created a Firebase project app successfully on VS Code, follow the next steps to customize the app to display the sensor readings on a login-protected web page.

index.html

Copy the following to your index.html file (it is inside the public folder).

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ESP Datalogging Firebase App</title>

    <!-- include Firebase SDK -->
    <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-app.js"></script>

    <!-- include only the Firebase features as you need -->
    <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-database.js"></script>

    <script>
      // Replace with your app config object
      const firebaseConfig = {
        apiKey: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
        authDomain: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
        databaseURL: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
        projectId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
        storageBucket: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
        messagingSenderId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
        appId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION"
      };

      // Initialize firebase
      firebase.initializeApp(firebaseConfig);

      // Make auth and database references
      const auth = firebase.auth();
      const db = firebase.database();

    </script>

    <!-- include highchartsjs to build the charts-->
    <script src="https://code.highcharts.com/highcharts.js"></script>
    <!-- include to use jquery-->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <!--include icons from fontawesome-->
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
    <!-- include Gauges Javascript library-->
    <script src="https://cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.7/all/gauge.min.js"></script>
    <!--reference for favicon-->
    <link rel="icon" type="image/png" href="favicon.png">
    <!--reference a stylesheet-->
    <link rel="stylesheet" type="text/css" href="style.css">

  </head>

  <body>

    <!--TOP BAR-->
    <div class="topnav">
      <h1>Sensor Readings App <i class="fas fa-clipboard-list"></i></h1>
    </div>

    <!--AUTHENTICATION BAR (USER DETAILS/LOGOUT BUTTON)-->
    <div id="authentication-bar" style="display: none;">
      <p><span id="authentication-status">User logged in</span>
        <span id="user-details">USEREMAIL</span>
        <a href="/" id="logout-link">(logout)</a>
      </p>
    </div>

    <!--LOGIN FORM-->
    <form id="login-form" style="display: none;">
      <div class="form-elements-container">
        <label for="input-email"><b>Email</b></label>
        <input type="text" placeholder="Enter Username" id="input-email" required>

        <label for="input-password"><b>Password</b></label>
        <input type="password" placeholder="Enter Password" id="input-password" required>

        <button type="submit" id="login-button">Login</button>
        <p id="error-message" style="color:red;"></p>
      </div>
    </form>

    <!--CONTENT (SENSOR READINGS)-->
    <div class="content-sign-in" id="content-sign-in" style="display: none;">

      <!--LAST UPDATE-->
      <p><span class ="date-time">Last update: <span id="lastUpdate"></span></span></p>
      <p>
        Cards: <input type="checkbox" id="cards-checkbox" name="cards-checkbox" checked>
        Gauges: <input type="checkbox" id="gauges-checkbox" name="gauges-checkbox" checked>
        Charts: <input type="checkbox" id="charts-checkbox" name="charts-checkbox" unchecked>
      </p>
      <div id="cards-div">
        <div class="cards">
          <!--TEMPERATURE-->
          <div class="card">
            <p><i class="fas fa-thermometer-half" style="color:#059e8a;"></i> TEMPERATURE</p>
            <p><span class="reading"><span id="temp"></span> &deg;C</span></p>
          </div>
          <!--HUMIDITY-->
          <div class="card">
            <p><i class="fas fa-tint" style="color:#00add6;"></i> HUMIDITY</p>
            <p><span class="reading"><span id="hum"></span> &percnt;</span></p>
          </div>
          <!--PRESSURE-->
          <div class="card">
            <p><i class="fas fa-angle-double-down" style="color:#e1e437;"></i> PRESSURE</p>
            <p><span class="reading"><span id="pres"></span> hPa</span></p>
          </div>
        </div>
      </div>
      <!--GAUGES-->
      <div id ="gauges-div">
        <div class="cards">
          <!--TEMPERATURE-->
          <div class="card">
            <canvas id="gauge-temperature"></canvas>
          </div>
          <!--HUMIDITY-->
          <div class="card">
            <canvas id="gauge-humidity"></canvas>
          </div>
        </div>
      </div>

      <!--CHARTS-->
      <div id="charts-div" style="display:none">
        <!--SET NUMBER OF READINGS INPUT FIELD-->
        <div>
          <p> Number of readings: <input type="number" id="charts-range"></p>
        </div>
        <!--TEMPERATURE-CHART-->
        <div class="cards">
          <div class="card">
            <p><i class="fas fa-thermometer-half" style="color:#059e8a;"></i> TEMPERATURE CHART</p>
            <div id="chart-temperature" class="chart-container"></div>
          </div>
        </div>
        <!--HUMIDITY-CHART-->
        <div class="cards">
          <div class="card">
            <p><i class="fas fa-tint" style="color:#00add6;"></i> HUMIDITY CHART</p>
            <div id="chart-humidity" class="chart-container"></div>
          </div>
        </div>
        <!--PRESSURE-CHART-->
        <div class="cards">
          <div class="card">
            <p><i class="fas fa-angle-double-down" style="color:#e1e437;"></i> PRESSURE CHART</p>
            <div id="chart-pressure" class="chart-container"></div>
          </div>
        </div>
      </div>

    <!--BUTTONS TO HANDLE DATA-->
    <p>
      <!--View data button-->
      <button id="view-data-button">View all data</button>
      <!--Hide data button-->
      <button id="hide-data-button" style= "display:none;">Hide data</button>
      <!--Delete data button-->
      <button id="delete-button" class="deletebtn">Delete data</button>
    </p>
    <!--Modal to delete data-->
    <div id="delete-modal" class="modal" sytle="display:none">
      <span onclick = "document.getElementById('delete-modal').style.display='none'" class="close" title="Close Modal">×</span>
      <form id= "delete-data-form" class="modal-content" action="/">
        <div class="container">
          <h1>Delete Data</h1>
          <p>Are you sure you want to delete all data from database?</p>
          <div class="clearfix">
            <button type="button" onclick="document.getElementById('delete-modal').style.display='none'" class="cancelbtn">Cancel</button>
            <button type="submit" onclick="document.getElementById('delete-modal').style.display='none'" class="deletebtn">Delete</button>
          </div>
        </div>
      </form>
    </div>

    <!--TABLE WITH ALL DATA-->
    <div class ="cards">
      <div class="card" id="table-container" style= "display:none;">
        <table id="readings-table">
            <tr id="theader">
              <th>Timestamp</th>
              <th>Temp (ºC)</th>
              <th>Hum (%)</th>
              <th>Pres (hPa)</th>
            </tr>
            <tbody id="tbody">
            </tbody>
        </table>
        <p><button id="load-data" style= "display:none;">More results...</button></p>
      </div>
    </div>

  </div>

    <!--INCLUDE JS FILES-->
    <script src="scripts/auth.js"></script>
    <script src="scripts/charts-definition.js"></script>
    <script src="scripts/gauges-definition.js"></script>
    <script src="scripts/index.js"></script>

  </body>

</html>

View raw code

Important: you need to modify the code with your own firebaseConfig object—the one you’ve got in this step.

const firebaseConfig = {
  apiKey: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  authDomain: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  databaseURL: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  projectId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  storageBucket: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  messagingSenderId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  appId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION"
};

style.css

Inside the public folder create a file called style.css. To create the file, select the public folder, and then click on the +file icon at the top of the File Explorer. Call it style.css.

Create CSS File VS Code

Then, copy the following to the style.css file

html {
    font-family: Verdana, Geneva, Tahoma, sans-serif;
    display: inline-block;
    text-align: center;
}

body {
    margin: 0;
    width: 100%;
}

.topnav {
    overflow: hidden;
    background-color: #049faa;
    color: white;
    font-size: 1rem;
    padding: 5px;
}

#authentication-bar{
    background-color:mintcream;
    padding-top: 10px;
    padding-bottom: 10px;
}

#user-details{
    color: cadetblue;
}

.content {
    padding: 20px;
}

.card {
    background-color: white;
    box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
    padding: 5%;
}

.cards {
    max-width: 800px;
    margin: 0 auto;
    margin-bottom: 10px;
    display: grid;
    grid-gap: 2rem;
    grid-template-columns: repeat(auto-fit, minmax(200px, 2fr));
}

.reading {
    color: #193036;
}

.date-time{
    font-size: 0.8rem;
    color: #1282A2;
}

button {
    background-color: #049faa;
    color: white;
    padding: 14px 20px;
    margin: 8px 0;
    border: none;
    cursor: pointer;
    border-radius: 4px;
}
button:hover {
   opacity: 0.8;
}
.deletebtn{
    background-color: #c52c2c;
}

.form-elements-container{
    padding: 16px;
    width: 250px;
    margin: 0 auto;
}

input[type=text], input[type=password] {
    width: 100%;
    padding: 12px 20px;
    margin: 8px 0;
    display: inline-block;
    border: 1px solid #ccc;
    box-sizing: border-box;
}

table {
    width: 100%;
    text-align: center;
    font-size: 0.8rem;
}   
tr, td {
    padding: 0.25rem;
}
tr:nth-child(even) {
    background-color: #f2f2f2
}
tr:hover {
    background-color: #ddd;
}
th {
    position: sticky;
    top: 0;
    background-color: #50b8b4;
    color: white;
}

/* The Modal (background) */
.modal {
    display: none; /* Hidden by default */
    position: fixed; /* Stay in place */
    z-index: 1; /* Sit on top */
    left: 0;
    top: 0;
    width: 100%; /* Full width */
    height: 100%; /* Full height */
    overflow: auto; /* Enable scroll if needed */
    background-color: #474e5d;
    padding-top: 50px;
}
  
/* Modal Content/Box */
.modal-content {
    background-color: #fefefe;
    margin: 5% auto 15% auto; /* 5% from the top, 15% from the bottom and centered */
    border: 1px solid #888;
    width: 80%; /* Could be more or less, depending on screen size */
}
  
/* Style the horizontal ruler */
hr {
    border: 1px solid #f1f1f1;
    margin-bottom: 25px;
}

/* The Modal Close Button (x) */
.close {
    position: absolute;
    right: 35px;
    top: 15px;
    font-size: 40px;
    font-weight: bold;
    color: #f1f1f1;
}

.close:hover,
.close:focus {
    color: #f44336;
    cursor: pointer;
}

/* Clear floats */
.clearfix::after {
    content: "";
    clear: both;
    display: table;
}

/* Change styles for cancel button and delete button on extra small screens */
@media screen and (max-width: 300px) {
    .cancelbtn, .deletebtn {
        width: 100%;
    }
}

View raw code

The CSS file includes some simple styles to make our webpage look better. We won’t discuss how CSS works in this tutorial.

JavaScript Files

We’ll create four JavaScript files (auth.js, index.js, charts-definition.js, and gauges-definition.js) inside a scripts folder inside the public folder.

  • Select the public folder, then click on the +folder icon to create a new folder. Call scripts to that new folder.
  • Then, select the scripts folder and click on the +file icon. Create a file called auth.js. Then, repeat the previous steps to create the index.js, charts-definition.js, and gauges-definition.js files.

The following image shows what your web app project folder structure should look like.

Firebase Project VS Code Folder File Structure

auth.js

Copy the following to the auth.js file you created previously.

document.addEventListener("DOMContentLoaded", function(){
    // listen for auth status changes
    auth.onAuthStateChanged(user => {
        if (user) {
          console.log("user logged in");
          console.log(user);
          setupUI(user);
          var uid = user.uid;
          console.log(uid);
        } else {
          console.log("user logged out");
          setupUI();
        }
    });

    // login
    const loginForm = document.querySelector('#login-form');
    loginForm.addEventListener('submit', (e) => {
        e.preventDefault();
        // get user info
        const email = loginForm['input-email'].value;
        const password = loginForm['input-password'].value;
        // log the user in
        auth.signInWithEmailAndPassword(email, password).then((cred) => {
            // close the login modal & reset form
            loginForm.reset();
            console.log(email);
        })
        .catch((error) =>{
            const errorCode = error.code;
            const errorMessage = error.message;
            document.getElementById("error-message").innerHTML = errorMessage;
            console.log(errorMessage);
        });
    });

    // logout
    const logout = document.querySelector('#logout-link');
    logout.addEventListener('click', (e) => {
        e.preventDefault();
        auth.signOut();
    });
});  

View raw code

Then, save the file. This file takes care of everything related to the login and logout of the user.

index.js

The index.js file handles the UI—it shows the right content depending on the user authentication status. When the user is logged in, this file gets new readings from the database whenever there’s a change and displays them in the right places.

Copy the following to the index.js file.

// convert epochtime to JavaScripte Date object
function epochToJsDate(epochTime){
  return new Date(epochTime*1000);
}

// convert time to human-readable format YYYY/MM/DD HH:MM:SS
function epochToDateTime(epochTime){
  var epochDate = new Date(epochToJsDate(epochTime));
  var dateTime = epochDate.getFullYear() + "/" +
    ("00" + (epochDate.getMonth() + 1)).slice(-2) + "/" +
    ("00" + epochDate.getDate()).slice(-2) + " " +
    ("00" + epochDate.getHours()).slice(-2) + ":" +
    ("00" + epochDate.getMinutes()).slice(-2) + ":" +
    ("00" + epochDate.getSeconds()).slice(-2);

  return dateTime;
}

// function to plot values on charts
function plotValues(chart, timestamp, value){
  var x = epochToJsDate(timestamp).getTime();
  var y = Number (value);
  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);
  }
}

// DOM elements
const loginElement = document.querySelector('#login-form');
const contentElement = document.querySelector("#content-sign-in");
const userDetailsElement = document.querySelector('#user-details');
const authBarElement = document.querySelector('#authentication-bar');
const deleteButtonElement = document.getElementById('delete-button');
const deleteModalElement = document.getElementById('delete-modal');
const deleteDataFormElement = document.querySelector('#delete-data-form');
const viewDataButtonElement = document.getElementById('view-data-button');
const hideDataButtonElement = document.getElementById('hide-data-button');
const tableContainerElement = document.querySelector('#table-container');
const chartsRangeInputElement = document.getElementById('charts-range');
const loadDataButtonElement = document.getElementById('load-data');
const cardsCheckboxElement = document.querySelector('input[name=cards-checkbox]');
const gaugesCheckboxElement = document.querySelector('input[name=gauges-checkbox]');
const chartsCheckboxElement = document.querySelector('input[name=charts-checkbox]');

// DOM elements for sensor readings
const cardsReadingsElement = document.querySelector("#cards-div");
const gaugesReadingsElement = document.querySelector("#gauges-div");
const chartsDivElement = document.querySelector('#charts-div');
const tempElement = document.getElementById("temp");
const humElement = document.getElementById("hum");
const presElement = document.getElementById("pres");
const updateElement = document.getElementById("lastUpdate")

// MANAGE LOGIN/LOGOUT UI
const setupUI = (user) => {
  if (user) {
    //toggle UI elements
    loginElement.style.display = 'none';
    contentElement.style.display = 'block';
    authBarElement.style.display ='block';
    userDetailsElement.style.display ='block';
    userDetailsElement.innerHTML = user.email;

    // get user UID to get data from database
    var uid = user.uid;
    console.log(uid);

    // Database paths (with user UID)
    var dbPath = 'UsersData/' + uid.toString() + '/readings';
    var chartPath = 'UsersData/' + uid.toString() + '/charts/range';

    // Database references
    var dbRef = firebase.database().ref(dbPath);
    var chartRef = firebase.database().ref(chartPath);

    // CHARTS
    // Number of readings to plot on charts
    var chartRange = 0;
    // Get number of readings to plot saved on database (runs when the page first loads and whenever there's a change in the database)
    chartRef.on('value', snapshot =>{
      chartRange = Number(snapshot.val());
      console.log(chartRange);
      // Delete all data from charts to update with new values when a new range is selected
      chartT.destroy();
      chartH.destroy();
      chartP.destroy();
      // Render new charts to display new range of data
      chartT = createTemperatureChart();
      chartH = createHumidityChart();
      chartP = createPressureChart();
      // Update the charts with the new range
      // Get the latest readings and plot them on charts (the number of plotted readings corresponds to the chartRange value)
      dbRef.orderByKey().limitToLast(chartRange).on('child_added', snapshot =>{
        var jsonData = snapshot.toJSON(); // example: {temperature: 25.02, humidity: 50.20, pressure: 1008.48, timestamp:1641317355}
        // Save values on variables
        var temperature = jsonData.temperature;
        var humidity = jsonData.humidity;
        var pressure = jsonData.pressure;
        var timestamp = jsonData.timestamp;
        // Plot the values on the charts
        plotValues(chartT, timestamp, temperature);
        plotValues(chartH, timestamp, humidity);
        plotValues(chartP, timestamp, pressure);
      });
    });

    // Update database with new range (input field)
    chartsRangeInputElement.onchange = () =>{
      chartRef.set(chartsRangeInputElement.value);
    };

    //CHECKBOXES
    // Checbox (cards for sensor readings)
    cardsCheckboxElement.addEventListener('change', (e) =>{
      if (cardsCheckboxElement.checked) {
        cardsReadingsElement.style.display = 'block';
      }
      else{
        cardsReadingsElement.style.display = 'none';
      }
    });
    // Checbox (gauges for sensor readings)
    gaugesCheckboxElement.addEventListener('change', (e) =>{
      if (gaugesCheckboxElement.checked) {
        gaugesReadingsElement.style.display = 'block';
      }
      else{
        gaugesReadingsElement.style.display = 'none';
      }
    });
    // Checbox (charta for sensor readings)
    chartsCheckboxElement.addEventListener('change', (e) =>{
      if (chartsCheckboxElement.checked) {
        chartsDivElement.style.display = 'block';
      }
      else{
        chartsDivElement.style.display = 'none';
      }
    });

    // CARDS
    // Get the latest readings and display on cards
    dbRef.orderByKey().limitToLast(1).on('child_added', snapshot =>{
      var jsonData = snapshot.toJSON(); // example: {temperature: 25.02, humidity: 50.20, pressure: 1008.48, timestamp:1641317355}
      var temperature = jsonData.temperature;
      var humidity = jsonData.humidity;
      var pressure = jsonData.pressure;
      var timestamp = jsonData.timestamp;
      // Update DOM elements
      tempElement.innerHTML = temperature;
      humElement.innerHTML = humidity;
      presElement.innerHTML = pressure;
      updateElement.innerHTML = epochToDateTime(timestamp);
    });

    // GAUGES
    // Get the latest readings and display on gauges
    dbRef.orderByKey().limitToLast(1).on('child_added', snapshot =>{
      var jsonData = snapshot.toJSON(); // example: {temperature: 25.02, humidity: 50.20, pressure: 1008.48, timestamp:1641317355}
      var temperature = jsonData.temperature;
      var humidity = jsonData.humidity;
      var pressure = jsonData.pressure;
      var timestamp = jsonData.timestamp;
      // Update DOM elements
      var gaugeT = createTemperatureGauge();
      var gaugeH = createHumidityGauge();
      gaugeT.draw();
      gaugeH.draw();
      gaugeT.value = temperature;
      gaugeH.value = humidity;
      updateElement.innerHTML = epochToDateTime(timestamp);
    });

    // DELETE DATA
    // Add event listener to open modal when click on "Delete Data" button
    deleteButtonElement.addEventListener('click', e =>{
      console.log("Remove data");
      e.preventDefault;
      deleteModalElement.style.display="block";
    });

    // Add event listener when delete form is submited
    deleteDataFormElement.addEventListener('submit', (e) => {
      // delete data (readings)
      dbRef.remove();
    });

    // TABLE
    var lastReadingTimestamp; //saves last timestamp displayed on the table
    // Function that creates the table with the first 100 readings
    function createTable(){
      // append all data to the table
      var firstRun = true;
      dbRef.orderByKey().limitToLast(100).on('child_added', function(snapshot) {
        if (snapshot.exists()) {
          var jsonData = snapshot.toJSON();
          console.log(jsonData);
          var temperature = jsonData.temperature;
          var humidity = jsonData.humidity;
          var pressure = jsonData.pressure;
          var timestamp = jsonData.timestamp;
          var content = '';
          content += '<tr>';
          content += '<td>' + epochToDateTime(timestamp) + '</td>';
          content += '<td>' + temperature + '</td>';
          content += '<td>' + humidity + '</td>';
          content += '<td>' + pressure + '</td>';
          content += '</tr>';
          $('#tbody').prepend(content);
          // Save lastReadingTimestamp --> corresponds to the first timestamp on the returned snapshot data
          if (firstRun){
            lastReadingTimestamp = timestamp;
            firstRun=false;
            console.log(lastReadingTimestamp);
          }
        }
      });
    };

    // append readings to table (after pressing More results... button)
    function appendToTable(){
      var dataList = []; // saves list of readings returned by the snapshot (oldest-->newest)
      var reversedList = []; // the same as previous, but reversed (newest--> oldest)
      console.log("APEND");
      dbRef.orderByKey().limitToLast(100).endAt(lastReadingTimestamp).once('value', function(snapshot) {
        // convert the snapshot to JSON
        if (snapshot.exists()) {
          snapshot.forEach(element => {
            var jsonData = element.toJSON();
            dataList.push(jsonData); // create a list with all data
          });
          lastReadingTimestamp = dataList[0].timestamp; //oldest timestamp corresponds to the first on the list (oldest --> newest)
          reversedList = dataList.reverse(); // reverse the order of the list (newest data --> oldest data)

          var firstTime = true;
          // loop through all elements of the list and append to table (newest elements first)
          reversedList.forEach(element =>{
            if (firstTime){ // ignore first reading (it's already on the table from the previous query)
              firstTime = false;
            }
            else{
              var temperature = element.temperature;
              var humidity = element.humidity;
              var pressure = element.pressure;
              var timestamp = element.timestamp;
              var content = '';
              content += '<tr>';
              content += '<td>' + epochToDateTime(timestamp) + '</td>';
              content += '<td>' + temperature + '</td>';
              content += '<td>' + humidity + '</td>';
              content += '<td>' + pressure + '</td>';
              content += '</tr>';
              $('#tbody').append(content);
            }
          });
        }
      });
    }

    viewDataButtonElement.addEventListener('click', (e) =>{
      // Toggle DOM elements
      tableContainerElement.style.display = 'block';
      viewDataButtonElement.style.display ='none';
      hideDataButtonElement.style.display ='inline-block';
      loadDataButtonElement.style.display = 'inline-block'
      createTable();
    });

    loadDataButtonElement.addEventListener('click', (e) => {
      appendToTable();
    });

    hideDataButtonElement.addEventListener('click', (e) => {
      tableContainerElement.style.display = 'none';
      viewDataButtonElement.style.display = 'inline-block';
      hideDataButtonElement.style.display = 'none';
    });

  // IF USER IS LOGGED OUT
  } else{
    // toggle UI elements
    loginElement.style.display = 'block';
    authBarElement.style.display ='none';
    userDetailsElement.style.display ='none';
    contentElement.style.display = 'none';
  }
}

View raw code

charts-definition.js

Copy the following to the charts-definition.js file. This file creates the different charts using the highcharts javascript library.

// Create the charts when the web page loads
window.addEventListener('load', onload);

function onload(event){
  chartT = createTemperatureChart();
  chartH = createHumidityChart();
  chartP = createPressureChart();
}

// Create Temperature Chart
function createTemperatureChart() {
  var chart = new Highcharts.Chart({
    chart:{ 
      renderTo:'chart-temperature',
      type: 'spline' 
    },
    series: [
      {
        name: 'BME280'
      }
    ],
    title: { 
      text: undefined
    },
    plotOptions: {
      line: { 
        animation: false,
        dataLabels: { 
          enabled: true 
        }
      }
    },
    xAxis: {
      type: 'datetime',
      dateTimeLabelFormats: { second: '%H:%M:%S' }
    },
    yAxis: {
      title: { 
        text: 'Temperature Celsius Degrees' 
      }
    },
    credits: { 
      enabled: false 
    }
  });
  return chart;
}

// Create Humidity Chart
function createHumidityChart(){
  var chart = new Highcharts.Chart({
    chart:{ 
      renderTo:'chart-humidity',
      type: 'spline'  
    },
    series: [{
      name: 'BME280'
    }],
    title: { 
      text: undefined
    },    
    plotOptions: {
      line: { 
        animation: false,
        dataLabels: { 
          enabled: true 
        }
      },
      series: { 
        color: '#50b8b4' 
      }
    },
    xAxis: {
      type: 'datetime',
      dateTimeLabelFormats: { second: '%H:%M:%S' }
    },
    yAxis: {
      title: { 
        text: 'Humidity (%)' 
      }
    },
    credits: { 
      enabled: false 
    }
  });
  return chart;
}

// Create Pressure Chart
function createPressureChart() {
  var chart = new Highcharts.Chart({
    chart:{ 
      renderTo:'chart-pressure',
      type: 'spline'  
    },
    series: [{
      name: 'BME280'
    }],
    title: { 
      text: undefined
    },    
    plotOptions: {
      line: { 
        animation: false,
        dataLabels: { 
          enabled: true 
        }
      },
      series: { 
        color: '#A62639' 
      }
    },
    xAxis: {
      type: 'datetime',
      dateTimeLabelFormats: { second: '%H:%M:%S' }
    },
    yAxis: {
      title: { 
        text: 'Pressure (hPa)' 
      }
    },
    credits: { 
      enabled: false 
    }
  });
  return chart;
}

View raw code

gauges-definition.js

In our web app, we’ll display a gauge for the temperature and another for the humidity. The gauges-definition.js file contains functions to create the gauges.

// Create Temperature Gauge
function createTemperatureGauge() {
    var gauge = new LinearGauge({
        renderTo: 'gauge-temperature',
        width: 120,
        height: 400,
        units: "Temperature C",
        minValue: 0,
        startAngle: 90,
        ticksAngle: 180,
        maxValue: 40,
        colorValueBoxRect: "#049faa",
        colorValueBoxRectEnd: "#049faa",
        colorValueBoxBackground: "#f1fbfc",
        valueDec: 2,
        valueInt: 2,
        majorTicks: [
            "0",
            "5",
            "10",
            "15",
            "20",
            "25",
            "30",
            "35",
            "40"
        ],
        minorTicks: 4,
        strokeTicks: true,
        highlights: [
            {
                "from": 30,
                "to": 40,
                "color": "rgba(200, 50, 50, .75)"
            }
        ],
        colorPlate: "#fff",
        colorBarProgress: "#CC2936",
        colorBarProgressEnd: "#049faa",
        borderShadowWidth: 0,
        borders: false,
        needleType: "arrow",
        needleWidth: 2,
        needleCircleSize: 7,
        needleCircleOuter: true,
        needleCircleInner: false,
        animationDuration: 1500,
        animationRule: "linear",
        barWidth: 10,
    });
    return gauge;
}

// Create Humidity Gauge
function createHumidityGauge(){
    var gauge = new RadialGauge({
        renderTo: 'gauge-humidity',
        width: 300,
        height: 300,
        units: "Humidity (%)",
        minValue: 0,
        maxValue: 100,
        colorValueBoxRect: "#049faa",
        colorValueBoxRectEnd: "#049faa",
        colorValueBoxBackground: "#f1fbfc",
        valueInt: 2,
        majorTicks: [
            "0",
            "20",
            "40",
            "60",
            "80",
            "100"
    
        ],
        minorTicks: 4,
        strokeTicks: true,
        highlights: [
            {
                "from": 80,
                "to": 100,
                "color": "#03C0C1"
            }
        ],
        colorPlate: "#fff",
        borderShadowWidth: 0,
        borders: false,
        needleType: "line",
        colorNeedle: "#007F80",
        colorNeedleEnd: "#007F80",
        needleWidth: 2,
        needleCircleSize: 3,
        colorNeedleCircleOuter: "#007F80",
        needleCircleOuter: true,
        needleCircleInner: false,
        animationDuration: 1500,
        animationRule: "linear"
    });
    return gauge;
}

View raw code

Favicon File

To display a favicon in your web app, you need to move the picture you want to use as favicon to the public folder. The picture should be called favicon.png. You can simply drag the favicon file from your computer into the public folder in VS Code.

We’re using the following icon as a favicon for our web app:

Deploy your App

After saving the HTML, CSS, and JavaScript files, deploy your app on VS Code by running the following command on the Terminal window.

firebase deploy

The Terminal should display something as follows:

Firebase Deploy web app

Firebase offers a free hosting service to serve your assets and web apps. Then, you can access your web app from anywhere.

You can use the Hosting URL provided to access your web app from anywhere.

Demonstration

Congratulations! You successfully deployed your app. It is now hosted on a global CDN using Firebase hosting. You can access your web app from anywhere on the Hosting URL provided. In my case, it is https://esp-firebase-demo.web.app.

The web app is responsive, and you can access it using your smartphone, computer, or tablet.

When you first access the web app, you’ll see a form to insert the email username and password.

Firebase Web App Login Page

Insert the email and password of the authorized user you added in the Firebase Authentication methods. If the form doesn’t show up at first, refresh the web page. After that, you can access the web page with the readings.

Firebase Web app Sensor readings cards and gauges

The readings are displayed in cards, gauges, charts, and a table. You can also select which interfaces you want to see by checking/unchecking the checkboxes.

You can also check the readings displayed on charts. You can select the charts range, but keep in mind that selecting more than 30 readings will take some time.

Firebase Web app Sensor readings table with all data

Finally, if you want to see all the readings. You can open the readings table. At the end of the table, there’s a button to load more readings until all readings are displayed.

Firebase Web app Sensor readings table with all data

There is also a button to delete all data if you want to remove all readings from the database.

Firebase Web app Sensor readings table with all data delete data

Here’s a video showing how the web app works.


Wrapping Up

In this tutorial, you created a Firebase Web App with login/logout authentication that displays sensor readings in many different ways. The sensor readings are saved on the realtime database. The database is protected using database rules (that you’ve already set up in a previous tutorial).

You can apply what you learned here to display any other type of data, and you can change the files in the public folder to add different functionalities and features to your project.

We didn’t explain how the javascript files work because the project is quite long. However, if there is enough interest in this subject, we can split this application into smaller projects so that you understand how to handle data using queries and how to display it in different ways. Let us know what you think in the comments below.

If you want to learn more about Firebase, we recommend taking a look at our new eBook, exclusively dedicated to this subject:

We have other resources related to ESP32 and ESP8266 that you may like:

Thanks for reading.



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

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


Enjoyed this project? Stay updated by subscribing our newsletter!

101 thoughts on “ESP32/ESP8266: Firebase Data Logging Web App (Gauges, Charts, and Table)”

  1. Do you have maybe an example of Modbus RTU communication? For example: communication with HMI – reading of digital outputs, temperature measurement…

    Reply
  2. Good Afternoon,
    thank you for your new project.
    I was able to show with almost no problems.
    But I have a problem with the time between the read sensor time (e.g. 17:30:54) and the time in the chart display (15:30:54), i.e. a time difference of two hours.
    In which file can I set my time zone?

    Kind Regards
    Juergen

    Reply
    • Hi.
      Yes, you just need to make sure that those devices login as an authorized user.
      Then, you can organize your database in a different way to organize the data by device.
      Regards,
      Sara

      Reply
  3. Very good project. I have almost no knowledge about Firebase and java scripts, so I had to copy everything from this web. Now I try to learn what is doing what.
    I see the first part that is collecting data and writhing to the database is the easy part and the part I know about. To be able to store the data and see it form the rest of the world has been my dream for several years. Thank you. Now I feel it is within reach to make data collection from my boat, cottage and house. The next will be to turn on heat on the cottage from my phone.

    Reply
      • Hello Svein,

        I have the same problem. Using the shift+ctl+J function in the browser I found out, that the var chartRange contains 0. This is not accepted by the function limitToLast. Something seems to be wrong around the databasepath

        var chartRef = firebase.database().ref(chartPath);

        I keep on searching. Please let me know, if or how You solved the problem.

        regards Ulli

        Reply
      • Hi Svein,

        for me the problem was solved, when I entered a number in the “number of readings” field. I simply had forgotten to do this. Now everything works fine!

        Regards Ulli

        Reply
  4. Hello
    I like your tutorials and have usually no problem with them because they are well detailed.
    But this time, I have no success with the latest tutorial.
    Following the first part of the Firebase tutorial (ESP32 Data Logging to Firebase Realtime Database), I can read the values given by the sensor on the browser page, but the second part (ESP32/ESP8266: Firebase Data Logging Web App (Gauges, Charts, and Table)) does not show any values for the sensor nor time stamps or something in the “Last update” field on the browser page.
    I must have missed something but was not able to find what it is . . . .
    Any ideas?

    Reply
  5. Uncaught ReferenceError: chartT is not defined
    at index.js:86:9
    at o (Reference.ts:253:16)
    at za.onValue (EventRegistration.ts:56:27)
    at Reference_impl.ts:850:30
    at Be (util.ts:546:5)
    at EventQueue.ts:160:7
    at ha (EventQueue.ts:128:9)
    at la (EventQueue.ts:108:3)
    at ga (Repo.ts:406:3)
    at tn.a.server_ [as onDataUpdate_] (Repo.ts:256:9)
    util.ts:556 Uncaught ReferenceError: chartT is not defined
    at index.js:86:9
    at o (Reference.ts:253:16)
    at za.onValue (EventRegistration.ts:56:27)
    at Reference_impl.ts:850:30
    at Be (util.ts:546:5)
    at EventQueue.ts:160:7
    at ha (EventQueue.ts:128:9)
    at la (EventQueue.ts:108:3)
    at ba (Repo.ts:541:3)
    at as (Reference_impl.ts:677:3)

    Reply
    • Hi.
      Make sure you’ve created all the necessary Javascript files and in the right place.
      Hard refresh your web browser after deploying your app.
      Regards,
      Sara

      Reply
  6. My problem is solved now. I followed the tutorial “ESP32 Data Logging to Firebase Realtime Database” a couple of days after it was published on the Random Nerd web site. But I found that the main.ccp file has been modified in the mean time by addition of #include “time.h”, several tempPath, time stamp, etc. . . . When using this modified main.ccp file, everything is now working.

    Reply
  7. As with Juergen above, when looking at the readings, the time stamp in the readings is correct, but the time shown on the charts is one hour less (= GMT, I live in a GMT+1 zone). Could it be that the firebase server (the one assigned to me is Europe-west1) is using GMT as basis, not taking account of the timestamp sent to it? How can I modify this to have the correct time on the charts?

    Reply
  8. Is there an easy way to show more than one value on a chart? I have adjusted the programme to read three DS18B20 sensors and would like to display all three temperatures on one graph. I am guessing it is something to do with the plotValues function in index.js?

    Reply
  9. Great project – I’m new to VS and Firebase. It’s all working for me except gauges. I realized that my gauges-definition.js file is missing from Firebase, but running “Firebase deploy” again in VS doesn’t seem to upload the missing file. Any suggestions or hints? Thanks.

    Reply
    • Hi.
      Make sure your charts-definition.js file is located in the scripts folder.
      Hard refresh your web browser after deploying.
      Regards,
      Sara

      Reply
  10. Is there an easy way to show multiple lines on one chart. I have modified the programme to sense 3 x DS18B20 and want to show them on one graph? I am guessing the plotValues function in index.js will need modifying.

    Reply
        • In charts-definition.js

          // Create Temperature Chart
          function createTemperatureChart() {
          var chart = new Highcharts.Chart({
          chart:{
          renderTo:’chart-temperature’,
          type: ‘spline’
          },
          series: [
          {
          name: ‘DS18B20 2’,
          marker: {
          symbol: ‘square’
          }
          },
          {
          name: ‘DS18B20 3’,
          marker: {
          symbol: ‘diamond’
          }
          },
          {name: ‘DS18B20 1’,
          marker: {
          symbol: ‘triangle’
          }
          }
          ],
          title: {
          text: undefined
          },
          plotOptions: {
          line: {
          animation: false,
          dataLabels: {
          enabled: true
          }
          }
          },
          xAxis: {
          type: ‘datetime’,
          dateTimeLabelFormats: { second: ‘%H:%M:%S’ }
          },
          yAxis: {
          title: {
          text: ‘Temperature (°C)’
          }
          },
          credits: {
          enabled: false
          }

          });
          return chart;

          In index.js I have added a function to plot multiple values.

          / function to plot multiple values on charts

          function plotValuesMulti(chart, timestamp, value1, value2, value3){
          var x = epochToJsDate(timestamp).getTime();
          var y = Number (value1);
          var y2 = Number (value2);
          var y3 = Number (value3);
          if(chart.series[0].data.length > 40) {
          chart.series[0].addPoint([x, y], true, true, true);
          chart.series[1].addPoint ([x, y2], true, true, true);
          chart.series[2].addPoint ([x, y3], true, true, true);
          } else {
          chart.series[0].addPoint([x, y], true, false, true);
          chart.series[1].addPoint ([x, y2], true, false, true);
          chart.series[2].addPoint ([x, y3], true, false, true);
          }
          }

          Call the function with

          plotValuesMulti(chartT, timestamp, temperature, humidity, pressure);

          Although the variables are called temperature, humidity and pressure, they are in fact all temperature readings as I haven’t got round to changing the actual database yet.

          Reply
  11. Hi Sara
    Another great article, however there is no explanation of the code, I can see that would have made for a much longer article however.

    I am also not able to get the charts to work, I see a problem with index.js line 72
    var chartPath = ‘UsersData/’ + uid.toString() + ‘/charts/range’; as this isn’t my path, I changed it to …+’/readings’; but still no go. The error is
    util.ts:556 Uncaught Error: limitToLast: First argument must be a positive integer.
    Ie it is not getting a valid chartRange, probably coz it is not seeing any data.

    What am I doing wrong?

    I am really pleased to get the data into the cards and gauges, I have been trying for some time with no success until you published this article!
    Regards
    Bruce

    Reply
    • Hi.
      Insert a number in the “Number of readings” field, like 30 for example.
      After that, the chart should load.

      As I explained in the Wrapping Up section, the project is quite long. I’m able to write explanations about each section of the code in different tutorials if there is enough interest in these subjects.
      Regards,
      Sara

      Reply
  12. Werry nice, is it possible for you to ad the funktion to set alarms wen reading sensors?
    Like, if a battery is charging and getting to hot.. a alarm goes off with sound and perhaps red flaching display?
    Many thanks, best regards
    /fredde

    Reply
  13. Nice write up. My first time with Firebase and it all worked as it should (almost)

    When I have run the code, I dont get any gauges. I note that in you instructions you create a gauges.definition.js file. But in the code its referenced as scripts/gauges-definition.js NOTE the hyphen- vs the dot. This causes “createTemperatureGauge is not defined” exception. I fixed the mistake, but Im still unable to see the gauges. EDIT – a hard refresh after deploy fixed the issue (ctrl + shift + R)

    Reply
    • Hi.
      The file should be called gauges-definition.js. There was a typo in the tutorial. It is fixed now.
      After changing javascript files always hard refresh your web browser.
      Regards.
      Sara

      Reply
  14. Buenas tardes
    Desde España
    Felicitaros, por vuestro inmenso trabajo.
    Acabo de realizar este proyecto muy ilusionado, pero la parte de web no me funciona, no me muestra los datos en la app. Lo he realizado 4 veces siempre con el mismo resultado.
    Me podeis ayudar
    GRACIA
    Jesús Cuesta

    Reply
      • Hola Sara
        Este proyecto es lo primero que realice y funciona correctamente esta enviando los datos y generando la tabla de los mismos.
        El problema es que cuando realice el 2º proyecto tal y como te he comentado, no me muestra ningún dato de los que estan almacenados.
        No encuentro el problema, me puede ayudar?
        Saludos cordiales

        Reply
        • Hola.

          Open your javascript console on your web browser and tell me if there are any errors.
          Additionally, double-check that you’re saving all the required javascript files on the scripts folder.

          Regards.
          Sara

          P.S. I can read and understand Spanish, but I can only write the basics.

          Reply
          • 341 / 5.000
            Resultados de traducción
            Hi Sara
            There was an error on my part in the code that entered it wrong and now it shows me the temperature and the values in the table.
            But I get 4 errors in the javascript console and it doesn’t show me the graphics, nor the widgets.
            I am attaching the errors.

            
            esp-cascosta-termico.web.app/:198
            GET https://esp-cascosta-termico.web.app/scripts/gauges-definition.js net::ERR_ABORTED 404
            esp-cascosta-termico.web.app/:198
            GET https://esp-cascosta-termico.web.app/scripts/gauges-definition.js net::ERR_ABORTED 404
            Reference_impl.ts:2020 Uncaught Error: limitToLast: First argument must be a positive integer.
            at Reference_impl.ts:2020:11
            at Cu.limitToLast (Reference.ts:400:59)
            at index.js:95:28
            at o (Reference.ts:253:16)
            at za.onValue (EventRegistration.ts:56:27)
            at Reference_impl.ts:850:30
            at Be (util.ts:546:5)
            at EventQueue.ts:160:7
            at ha (EventQueue.ts:128:9)
            at la (EventQueue.ts:108:3)
            2
            util.ts:556 Uncaught ReferenceError: createTemperatureGauge is not defined
            at index.js:167:22
            at o (Reference.ts:253:16)
            at za.onValue (EventRegistration.ts:56:27)
            at Reference_impl.ts:924:30
            at Be (util.ts:546:5)
            at EventQueue.ts:160:7
            at ha (EventQueue.ts:128:9)
            at la (EventQueue.ts:108:3)
            at ga (Repo.ts:406:3)
            at tn.a.server_ [as onDataUpdate_] (Repo.ts:256:9)

            I await your news.

            P.S. I’m glad you understand Spanish

            Hola Sara
            Ha habido un error por mi parte en el código que lo introducido mal y ahora me muestra la temperatura y los valores en la tabla.
            Pero me salen 2 errores en la consola de javascript y no me muestra los gráficos, tampoco los widgets.
            Te adjunto los errores.
            Quedo a la espera de tus noticias.

            PD. Me alegra que entiendas el español

  15. Good project — a very complex one (6 different files to create and understand, in 4 different languages, and dealing with where everything goes). There are 2 parts to this immense project: creating the code, and knowing the procedures for getting that code where it needs to live (ESP32 and the cloud). The code part is understandable at a high level, assuming one has gone through other eBooks like the webservers one, and the firebase one. The procedure is strung out over many pages and I found it hard to create a similar but different project “from scratch” – i.e. not so much the code writing part, but the procedural part. I wrote up a 3 page procedure that I’d like to share (but can’t figure out how to get a pdf into a gist; maybe there’s another way)

    Reply
    • Hi Joe.
      I already answered your question in the forum.
      To share a PDF, you can upload it to google drive and then share a link to the file, for example.
      Regards,
      Sara

      Reply
  16. Hi Sara
    Many thanks, I got the charts going, my error.

    I would for one be interested in more on the code for these examples and maybe more on different charting etc.
    Thanks
    Bruce

    Reply
  17. hai Sara
    thanks for great tutorials….

    why my ESP8266 can’t conect to firebase,
    i was make correct setup with my credential :

    // Insert Firebase project API Key
    #define API_KEY “******”
    // Insert Authorized Email and Corresponding Password
    #define USER_EMAIL “
    #define USER_PASSWORD “

    // Insert RTDB URLefine the RTDB URL
    #define DATABASE_URL “***”

    but this is error from serial print after conected to wifi, like this :
    06:56:40.573 -> Token info: type = id token, status = on request
    06:56:47.799 -> Token info: type = id token, status = error
    06:56:47.799 -> Token error: code: -1, message: connection refused
    06:56:47.799 -> Getting User UID
    06:56:47.799 -> ……………………………………….

    Reply
    • Hi.
      Wow. Your document looks great. It contains a great summary of all the important steps to create a Firebase project with the ESP32 and ESP8266.
      Thank you so much for sharing this.
      I’m sure this will be helpful for many of our readers.
      Regards,
      Sra

      Reply
  18. 15-03-2022

    Hello dear Mr Randomnerst,
    I want to show you courtesy and decency and I am grateful that I have come to understand the Web App development around the esp8266 and esp32.

    I’ve known you for quite some time, and ran into you again on the side of my mobile phone.

    I will join the Randomnerst community from now on and next week I will buy a book and then take a course with you.

    I am proud of you…

    My thanks is great
    Mr: A.Olieberg from the Netherlands

    Reply
  19. Hi Sara, I am able to upload my sensor readings to FireBase and get them displayed on the cards. But I couldnt get the Gauges update in realtime. Is there a way that I can share you the code and get it fixed?

    Reply
    • Hi.
      What is the error that you’re getting?
      Open the JavaScript console and check if you’re getting any specific errors.
      Regards,
      Sara

      Reply
  20. Solid tutorial. Works like a champ on a laptop but no joy on an Android device, all devices are quite new (S-21 phone). All I get is the top line banner “Sensor Readings App”. Tried refreshing, then reloading the page but again no joy. Tried resizing the screen as well. Might something be missing from the Android device? As I said works great from my laptop.

    Reply
    • Hi.

      I found the issue.
      Basically, you need to make sure that index.js is fully loaded before auth.js.
      So, auth.js will only run after everything has been loaded. I wrapped all auth.js content inside:

      document.addEventListener(“DOMContentLoaded”, function(){
      });

      The auth.js file will be fixed in a few minutes.

      Let me know if this fixes the issue.

      Regards,
      Sara

      Reply
      • Hi Sara,

        I have the same issue but the modification of auth.js does not work for me. Any ideas?

        Regards,

        Stephan

        Reply
        • Hi.
          Make sure you saved the file and deployed the app again.
          Then, add refresh your web browser so that it starts using the newest js file.
          Regards,
          Sara

          Reply
  21. Hi Sara,

    I have integrated an BH1750 into Your project; if interested, I can send You the modified files all together.
    Just one problem: I am living in a GMT+2h Zone, but the Website shows (correct) GMT time which ist transferred from the ESP 32. Can You give me a hint how to change the ESP Code to send the correct timestamp for my City (GMT +2h)

    Regards, Stephan

    Reply
    • Hi. Are you referring to the time on the charts?
      To adjust the time, add the following when defining a chart:
      var chart = new Highcharts.Chart({
      time:{
      useUTC: false
      },

      chart:{ (…)
      Regards,
      Sara

      Reply
  22. How would one have multiple device send data to the firebase database? There is a lot of information going the other way, database push to many devices, but none for this scenario.

    Reply
    • Found the answer to this question in the Firebase Project Docs. They call this multi-tenancy and strongly discourage it as it can lead to serious configuration, privacy and authentication issues.

      Reply
  23. Gracias Sara por ese aporte tan maravilloso. Todo se encuentra perfectamente bien explicado y siguiendo los pasos pude hacer funcionar la aplicación.

    Sólo dos detalles se me presentaron, uno con la zona horaria en el gráfico. El cual logré corregir según menciono en uno de mis comentarios realizados.

    Por otro lado, un segundo detalle que tengo días tratando y no he podido resolver. Se trata que cuando se muestra la tabla de las lecturas, haciendo clic en el botón respectivo, el último valor se duplica cuando se hace una actualización de una nueva lectura.

    Sabes algo de ese error y como se podría resolver ?

    Reply
      • Gracias por responder…

        En el siguiente enlace puedes ver un video del error que se presenta en la carga de datos desde Firebase. Ocurre cuando se muestra y se oculta la tabla con los registros de las lecturas.

        https://youtu.be/xm_GJfEerqc

        Reply
        • Eureka !!! … Problema resuelto !!!

          var tableChildAddedListener = null;

          // Function that creates the table with the first 100 readings
          function createTable() {
          // append all data to the table
          var firstRun = true;

          $('#tbody').empty();

          if (tableChildAddedListener != null) {
          dbRef.off('child_added', tableChildAddedListener);
          }

          tableChildAddedListener = dbRef.orderByKey().limitToLast(10).on('child_added', (snapshot) =>

          Cada vez que se presiona el botón de “View all data” se agrega con el “.on” nuevamente el Listener al evento “child_added”. Eso hace que se repita el registro nuevo. Por lo tanto, lo que hago es eliminar con el “.off” el Listener antes de agregarlo.

          Me ayudó mucho el siguiente enlace:

          https://stackoverflow.com/questions/51902759/remove-off-for-firebase-listeners

          Reply

Leave a Reply to Joshua Orillaneda 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.