ESP32/ESP8266 : application Web d’enregistrement de données Firebase (jauges, graphiques et tableau)

ESP32/ESP8266 : application Web d'enregistrement de données Firebase (jauges, graphiques et tableau)

Dans ce projet, vous allez créer une application Web Firebase qui affiche toutes les lectures de capteurs enregistrées sur la base de données en temps réel Firebase. Nous créerons une interface Web avec des jauges, des graphiques et un tableau pour afficher tous vos enregistrements de données. Nous ajouterons également un bouton qui vous permet de supprimer toutes les données de la base de données et des cases à cocher pour personnaliser l’interface utilisateur. Cette application Web sera protégée par une authentification (à l’aide d’un e-mail et d’un mot de passe) et toutes les données sont limitées à l’utilisateur à l’aide de règles de base de données.

ESP32 ESP8266 Application Web d'enregistrement de données Firebase NodeMCU avec graphiques et tableau de jauges

Ce projet est la partie 2 du tutoriel suivant (il existe une version pour ESP32 et une version pour ESP8266) :

Vous devez d’abord suivre l’un de ces tutoriels, avant de continuer

Voici un résumé des fonctionnalités de l’application Web :

  • connectez-vous avec email et mot de passe
  • affiche l’heure de la dernière mise à jour
  • cartes pour afficher les dernières lectures de capteur
  • jauges pour afficher les dernières lectures du capteur
  • des graphiques qui affichent l’historique des données avec des horodatages
  • sélectionner le nombre de lectures à afficher sur les graphiques
  • cases à cocher pour activer/désactiver les différentes options d’affichage
  • tableau qui affiche toutes les lectures enregistrées dans la base de données
  • bouton pour supprimer les données de la base de données

Aperçu du projet

Dans ce didacticiel (partie 2), vous allez créer une application Web pour afficher les lectures de capteurs enregistrées avec des horodatages sur la base de données en temps réel Firebase (lisez ce didacticiel précédent – version ESP32 / version ESP8266).

La vidéo suivante montre le projet d’application Web que nous allons créer. La programmation de l’ESP32/ESP8266 et la configuration du projet Firebase ont été effectuées dans la partie 1 (ESP32 partie 1 ; ESP8266 partie 1).

  • Firebase héberge votre application Web sur un CDN mondial à l’aide de Firebase Hosting et fournit un certificat SSL. Vous pouvez accéder à votre application Web de n’importe où en utilisant le nom de domaine généré par Firebase.
  • Lorsque vous accédez pour la première fois à l’application Web, vous devez vous authentifier avec une adresse e-mail et un mot de passe autorisés. Vous avez déjà configuré cet utilisateur et la méthode d’authentification dans la partie 1.
  • Après l’authentification, vous pouvez accéder à une page d’application Web qui affiche les lectures du capteur. Les lectures des capteurs sont affichées sur des cartes, des jauges, des graphiques et des tableaux. Vous pouvez sélectionner le nombre de lectures que vous souhaitez afficher sur les graphiques et vous pouvez également choisir comment vous pouvez afficher vos données.
  • Il y a un bouton pour afficher/masquer toutes les lectures enregistrées sur la base de données sur une table avec des horodatages.
  • Il y a aussi un bouton Supprimer qui vous permet de supprimer toutes les données de la base de données.
  • Toutes les données sont restreintes à l’aide de règles de base de données.

Conditions préalables

Avant de commencer à créer l’application Web Firebase, vous devez vérifier les prérequis suivants :

Créer un projet Firebase

Vous devriez d’abord avoir suivi l’un des tutoriels suivants :

L’ESP32/ESP8266 doit exécuter le code fourni dans ce tutoriel. La base de données en temps réel et l’authentification doivent également être configurées comme indiqué dans le didacticiel.

Installer le logiciel requis

Avant de commencer, vous devez installer le logiciel requis pour créer l’application Web Firebase. Voici une liste des logiciels que vous devez installer (cliquez sur les liens pour obtenir des instructions) :


1) Ajoutez une application à votre projet Firebase

1) Accédez à la console de votre projet Firebase et ajoutez une application à votre projet en cliquant sur le bouton + Ajouter une application.

Firebase Ajouter une application au projet

2) Sélectionnez l’icône de l’application Web.

Firebase Ajouter une application Web au projet

3) Donnez un nom à votre application. Ensuite, cochez la case à côté de √ Configurer également l’hébergement Firebase pour cette application. Cliquez sur Enregistrer l’application.

Firebase Ajouter une application Web à l'hébergement de projet

4) Ensuite, copiez l’objet firebaseConfig et enregistrez-le car vous en aurez besoin plus tard.

sauvegarde de la copie de la configuration de l'objet firebaseConfig

Après cela, vous pouvez également accéder à l’objet firebaseConfig si vous accédez aux paramètres de votre projet dans votre console Firebase.

5) Cliquez sur Suivant dans les étapes suivantes, et enfin sur Continuer vers la console.


2) Configuration d’un projet d’application Web Firebase (code VS)

Suivez les étapes suivantes pour créer un projet d’application Web Firebase à l’aide de VS Code.

1) Création d’un dossier de projet

1) Créez un dossier sur votre ordinateur dans lequel vous souhaitez enregistrer votre projet Firebase, par exemple, Firebase-Project sur le bureau.

2) Ouvrez le code VS. Allez dans Fichier > Ouvrir le dossier… et sélectionnez le dossier que vous venez de créer.

3) Allez dans Terminal > Nouveau terminal. Une nouvelle fenêtre Terminal devrait s’ouvrir sur le chemin de votre projet.

Installer les outils Firebase 2

2) Connexion Firebase

4) Dans la fenêtre Terminal précédente, tapez ce qui suit :

firebase login

5) Il vous sera demandé de collecter des informations sur l’utilisation de la CLI et les rapports d’erreurs. Entrez « n » et appuyez sur Entrée pour refuser.

Connexion Firebase VS Code Terminal Fenêtre

Remarque : Si vous êtes déjà connecté, un message indiquant : « Déjà connecté en tant que [email protected]”.

6) Après cela, une nouvelle fenêtre apparaîtra sur votre navigateur pour vous connecter à votre compte Firebase.

Se connecter au compte Firebase

7) Autorisez Firebase CLI à accéder à votre compte Google.

Connexion au compte Firebase autoriser Firebase CLI

8) Après cela, la connexion Firebase CLI devrait réussir. Vous pouvez fermer la fenêtre du navigateur.

Connexion au compte Firebase autoriser la connexion CLI Firebase réussie

3) Initialisation du projet Web App Firebase

9) Après vous être connecté avec succès, exécutez la commande suivante pour démarrer un répertoire de projet Firebase dans le dossier actuel.

firebase init

10) Il vous sera demandé si vous souhaitez initialiser un projet Firebase dans le répertoire actuel. Entrez Y et appuyez sur Entrée.

Connexion au compte Firebase autoriser Firebase CLI firebase init

11) Ensuite, utilisez les flèches haut et bas et la touche Espace pour sélectionner les options. Sélectionnez les options suivantes :

  • Base de données en temps réel : configurez le fichier de règles de sécurité pour la base de données en temps réel et (éventuellement) provisionnez l’instance par défaut.
  • Hébergement : configurez les fichiers pour l’hébergement Firebase et (éventuellement) configurez les déploiements d’action GitHub

Les options sélectionnées s’afficheront avec un astérisque vert. Ensuite, appuyez sur Entrée.

Connectez-vous au compte Firebase pour autoriser le répertoire de configuration de la CLI Firebase

12) Sélectionnez l’option « Utiliser un projet existant » – il doit être surligné en bleu – puis appuyez sur Entrée.

Configuration du projet Firebase VS Code

13) Après cela, sélectionnez le projet Firebase pour ce répertoire – il devrait s’agir du projet créé dans ce didacticiel précédent. Dans mon cas, cela s’appelle esp-firebase-demo. Appuyez ensuite sur Entrée.

Firebase Project VS Code créer un projet

14) Appuyez sur Entrée à la question suivante pour sélectionner le fichier de règles de sécurité de la base de données par défaut : « Quel fichier doit être utilisé pour les règles de sécurité de la base de données en temps réel ? »

15) Ensuite, sélectionnez les options d’hébergement comme indiqué ci-dessous :

  • Que voulez-vous utiliser comme répertoire public ? Appuyez sur Entrée pour sélectionner public.
  • Configurer en tant qu’application d’une seule page (réécrire les URL dans /index.html) ? Non
  • Configurer des builds et des déploiements automatiques avec GitHub ? Non
Initialisation Firebase terminée

16) Le projet Firebase devrait maintenant être initialisé avec succès. Notez que le code VS a créé des fichiers essentiels dans votre dossier de projet.

Fichiers de projet Firebase créés avec succès

Le fichier index.html contient du texte HTML pour créer une page Web. Pour l’instant, laissez le texte HTML par défaut. L’idée est de le remplacer par votre propre texte HTML pour créer une page Web personnalisée selon vos besoins. Nous le ferons plus tard dans ce tutoriel.

17) Pour vérifier si tout s’est passé comme prévu, exécutez la commande suivante dans la fenêtre VS Code Terminal.

firebase deploy
Premier test de déploiement de l'application Firebase

Vous devriez obtenir un déploiement complet ! message et une URL vers la console de projet et l’URL d’hébergement.

18) Copiez l’URL d’hébergement et collez-la dans une fenêtre de navigateur Web. Vous devriez voir la page Web suivante. Vous pouvez accéder à cette page Web de n’importe où dans le monde.

Configuration de l'hébergement de la page de test Firebase terminée

La page Web que vous avez vue précédemment est construite avec le fichier HTML placé dans le dossier public de votre projet Firebase. En modifiant le contenu de ce fichier, vous pouvez créer votre propre application Web. C’est ce que nous allons faire dans la section suivante.


3) Créer une application Web Firebase

Maintenant que vous avez créé une application de projet Firebase avec succès sur VS Code, suivez les étapes suivantes pour personnaliser l’application afin d’afficher les lectures des capteurs sur une page Web protégée par connexion.

index.html

Copiez ce qui suit dans votre fichier index.html (il se trouve dans le dossier public).

<!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>

Afficher le code brut

Important : vous devez modifier le code avec votre propre objet firebaseConfig, celui que vous avez dans cette étape.

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

Dans le dossier public, créez un fichier appelé style.css. Pour créer le fichier, sélectionnez le dossier public, puis cliquez sur l’icône +fichier en haut de l’explorateur de fichiers. Appelez-le style.css.

Créer un fichier CSS VS Code

Ensuite, copiez ce qui suit dans le fichier style.css

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%;
    }
}

Afficher le code brut

Le fichier CSS comprend quelques styles simples pour améliorer l’apparence de notre page Web. Nous n’aborderons pas le fonctionnement de CSS dans ce didacticiel.

Fichiers JavaScript

Nous allons créer quatre fichiers JavaScript (auth.js, index.js, charts-definition.js et gauges-definition.js) dans un dossier scripts du dossier public.

  • Sélectionnez le dossier public, puis cliquez sur l’icône +dossier pour créer un nouveau dossier. Appelez les scripts dans ce nouveau dossier.
  • Ensuite, sélectionnez le dossier des scripts et cliquez sur l’icône +fichier. Créez un fichier appelé auth.js. Ensuite, répétez les étapes précédentes pour créer les fichiers index.js, charts-definition.js et gauges-definition.js.

L’image suivante montre à quoi devrait ressembler la structure de dossiers de votre projet d’application Web.

Structure du fichier du dossier Firebase Project VS Code

auth.js

Copiez ce qui suit dans le fichier auth.js que vous avez créé précédemment.

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();
    });
});  

Afficher le code brut

Ensuite, enregistrez le fichier. Ce fichier prend en charge tout ce qui concerne la connexion et la déconnexion de l’utilisateur.

index.js

Le fichier index.js gère l’interface utilisateur : il affiche le bon contenu en fonction de l’état d’authentification de l’utilisateur. Lorsque l’utilisateur est connecté, ce fichier obtient de nouvelles lectures de la base de données chaque fois qu’il y a un changement et les affiche aux bons endroits.

Copiez ce qui suit dans le fichier index.js.

// 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';
  }
}

Afficher le code brut

charts-definition.js

Copiez ce qui suit dans le fichier charts-definition.js. Ce fichier crée les différents graphiques en utilisant le bibliothèque javascript highcharts.

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

Afficher le code brut

gauges-definition.js

Dans notre application Web, nous afficherons une jauge pour la température et une autre pour l’humidité. Le fichier gauges-definition.js contient des fonctions pour créer les jauges.

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

Afficher le code brut

Fichier d’icône de favori

Pour afficher un favicon dans votre application Web, vous devez déplacer l’image que vous souhaitez utiliser comme favicon dans le dossier public. L’image doit s’appeler favicon.png. Vous pouvez simplement faire glisser le fichier favicon de votre ordinateur dans le dossier public de VS Code.

Nous utilisons l’icône suivante comme favicon pour notre application Web :

Déployez votre application

Après avoir enregistré les fichiers HTML, CSS et JavaScript, déployez votre application sur VS Code en exécutant la commande suivante dans la fenêtre Terminal.

firebase deploy

Le terminal devrait afficher quelque chose comme suit :

Firebase Déployer l'application Web

Firebase propose un service d’hébergement gratuit pour servir vos actifs et vos applications Web. Ensuite, vous pouvez accéder à votre application Web de n’importe où.

Vous pouvez utiliser l’URL d’hébergement fournie pour accéder à votre application Web de n’importe où.

Manifestation

Toutes nos félicitations! Vous avez déployé votre application avec succès. Il est maintenant hébergé sur un CDN mondial utilisant l’hébergement Firebase. Vous pouvez accéder à votre application Web de n’importe où sur l’URL d’hébergement fournie. Dans mon cas, c’est https://esp-firebase-demo.web.app.

L’application Web est réactive et vous pouvez y accéder à l’aide de votre smartphone, ordinateur ou tablette.

Lorsque vous accédez pour la première fois à l’application Web, vous verrez un formulaire pour insérer le nom d’utilisateur et le mot de passe de l’e-mail.

Page de connexion de l'application Web Firebase

Insérez l’e-mail et le mot de passe de l’utilisateur autorisé que vous avez ajouté dans les méthodes d’authentification Firebase. Si le formulaire ne s’affiche pas au début, actualisez la page Web. Après cela, vous pouvez accéder à la page Web avec les lectures.

Application Web Firebase Cartes et jauges de relevés de capteurs

Les lectures sont affichées sur des cartes, des jauges, des graphiques et un tableau. Vous pouvez également sélectionner les interfaces que vous souhaitez voir en cochant/décochant les cases à cocher.

Vous pouvez également vérifier les lectures affichées sur les graphiques. Vous pouvez sélectionner la plage des graphiques, mais gardez à l’esprit que la sélection de plus de 30 lectures prendra un certain temps.

Application Web Firebase Tableau des lectures de capteurs avec toutes les données

Enfin, si vous voulez voir toutes les lectures. Vous pouvez ouvrir le tableau des mesures. À la fin du tableau, il y a un bouton pour charger plus de lectures jusqu’à ce que toutes les lectures soient affichées.

Application Web Firebase Tableau des lectures de capteurs avec toutes les données

Il existe également un bouton pour supprimer toutes les données si vous souhaitez supprimer toutes les lectures de la base de données.

Application Web Firebase Tableau des lectures de capteurs avec toutes les données de suppression de données

Voici une vidéo montrant le fonctionnement de l’application Web.

Conclusion

Dans ce didacticiel, vous avez créé une application Web Firebase avec une authentification de connexion/déconnexion qui affiche les lectures des capteurs de différentes manières. Les relevés des capteurs sont enregistrés dans la base de données en temps réel. La base de données est protégée à l’aide de règles de base de données (que vous avez déjà configurées dans un didacticiel précédent).

Vous pouvez appliquer ce que vous avez appris ici pour afficher tout autre type de données, et vous pouvez modifier les fichiers dans le dossier public pour ajouter différentes fonctionnalités et caractéristiques à votre projet.

Nous n’avons pas expliqué le fonctionnement des fichiers javascript car le projet est assez long. Cependant, s’il y a suffisamment d’intérêt pour ce sujet, nous pouvons diviser cette application en projets plus petits afin que vous compreniez comment gérer les données à l’aide de requêtes et comment les afficher de différentes manières. Faites-nous savoir ce que vous pensez dans les commentaires ci-dessous.

Si vous souhaitez en savoir plus sur Firebase, nous vous recommandons de jeter un œil à notre nouvel eBook, exclusivement dédié à ce sujet :

Nous avons d’autres ressources liées à ESP32 et ESP8266 qui pourraient vous plaire :

Merci d’avoir lu.

Cette vidéo vous emmène dans l’histoire de Raspberry Pi :

YouTube video