Lab 4: Databases for IoT - Firebase

In this lab assignment we'll explore hot to leverage online databases to write and read sensor data from an IoT application and access this data from anywhere in the world. We'll use two similar DB platforms: InfluxDB and Firebase.

Firebase

Firebase is Google’s set of hosting services for mobile applications, which comprises a wide range of solutions to manage data from Android, iOS or web applications. We will use the real-time database solution from Firebase (RTDB) and we will store and read values from the DB using our ESP32 boards.

We can send and receive data with the ESP32 board to/from Firebase from anywhere in the world, as long as we have an Internet connection. This, coupled with the fact that we can create a web app that can be accessed anywhere with an Internet connection makes Firebase a very powerful tool, as you can manage, monitor, automate and send commands to an IoT application remotely.

First step is to create a Firebase project with a real-time database and use the ESP32 to write and read data from this database.

Set Up New Firebase Account

  • Sign in to Firebase with your Google account
  • Click Get Started, and then Create Project to create a new project
  • Give a name to your project, for example: ESP32 Firebase
  • Click to Disable Google Analytics (we don't need it for this simple demo)

If everything went well, you should be able to see your project page displayed in a few seconds.

Set Authentication Method

We need to set an authentication method for our app.

In the left sidebar, click on Build → Authentication → Get Started

There are several authentication methods from different sign-in providers. For the purpose of this app where the ESP32 board connects directly to the Firebase app, we can select to have no authentication. Select Anonymous from the list, click to enable and the Save.

Create Real-Time Database

In the left sidebar, click on Build → Realtime Database → Create Database. In the pop-up window that appears, select the DB location (recommended to select the Belgium site) and “Start in Test Mode” in the next tab, then click Enable.

Your database is now created, you will need to copy the URL in the web page, as we will be using it from the ESP32 side to connect to our database.

You'll also need to get the API key, by clicking on the cog next to Project Overview:

With this, you're all set up on the DB side!

Program ESP32 to Connect to the DB

We will use the Firebase-ESP-Client library, which we will need to install.

  1. Go to Sketch > Include Library > Manage Libraries
  2. Search for Firebase ESP Client and install the Firebase Arduino Client Library for ESP8266 and ESP32 by Mobitz.
Writing to the DB

Let's first write an application that can connect to the Firebase DB and send some data.

Use the code below, replace the credentials at the beginning of the code (WiFi AP, password, DB URL and API Key) and the upload it to your ESP32 Sparrow board.

firebaseWrite.ino
#include <Arduino.h>
#include <WiFi.h>
#include <Firebase_ESP_Client.h>
 
//Provide the token generation process info.
#include "addons/TokenHelper.h"
//Provide the RTDB payload printing info and other helper functions.
#include "addons/RTDBHelper.h"
 
// Insert your network credentials
#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"
 
// Insert Firebase project API Key
#define API_KEY "REPLACE_WITH_YOUR_FIREBASE_PROJECT_API_KEY"
 
// Insert RTDB URLefine the RTDB URL */
#define DATABASE_URL "REPLACE_WITH_YOUR_FIREBASE_DATABASE_URL" 
 
//Define Firebase Data object
FirebaseData fbdo;
 
FirebaseAuth auth;
FirebaseConfig config;
 
unsigned long sendDataPrevMillis = 0;
int count = 0;
bool signupOK = false;
 
void setup(){
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED){
    Serial.print(".");
    delay(300);
  }
  Serial.println();
  Serial.print("Connected with IP: ");
  Serial.println(WiFi.localIP());
  Serial.println();
 
  /* Assign the api key (required) */
  config.api_key = API_KEY;
 
  /* Assign the RTDB URL (required) */
  config.database_url = DATABASE_URL;
 
  /* Sign up */
  if (Firebase.signUp(&config, &auth, "", "")){
    Serial.println("ok");
    signupOK = true;
  }
  else{
    Serial.printf("%s\n", config.signer.signupError.message.c_str());
  }
 
  /* Assign the callback function for the long running token generation task */
  config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h
 
  Firebase.begin(&config, &auth);
  Firebase.reconnectWiFi(true);
}
 
void loop(){
  if (Firebase.ready() && signupOK && (millis() - sendDataPrevMillis > 15000 || sendDataPrevMillis == 0)){
    sendDataPrevMillis = millis();
    // Write an Int number on the database path test/int
    if (Firebase.RTDB.setInt(&fbdo, "test/int", count)){
      Serial.println("PASSED");
      Serial.println("PATH: " + fbdo.dataPath());
      Serial.println("TYPE: " + fbdo.dataType());
    }
    else {
      Serial.println("FAILED");
      Serial.println("REASON: " + fbdo.errorReason());
    }
    count++;
 
    // Write an Float number on the database path test/float
    if (Firebase.RTDB.setFloat(&fbdo, "test/float", 0.01 + random(0,100))){
      Serial.println("PASSED");
      Serial.println("PATH: " + fbdo.dataPath());
      Serial.println("TYPE: " + fbdo.dataType());
    }
    else {
      Serial.println("FAILED");
      Serial.println("REASON: " + fbdo.errorReason());
    }
  }
}

The code sends two types of variables, an integer and a float repeatedly in the loop() function. As specified in the library documentation, the API allows the following functions: set, setInt, setFloat, setDouble, setString, setJSON, setArray, setBlob, and setFile.

For faster sending data, non-waits or async mode functions are available e.g. setAsync, setIntAsync, setFloatAsync, setDoubleAsync, setBoolAsync, setStringAsync, setJSONAsync, setArrayAsync, setBlobAsync and setFileAsync.

For generic set, use Firebase.RTDB.set(&fbdo, <path>, <any variable or value>).

These async functions will ignore the server responses.

You can also view the sent values from the project page of the DB, every 15 seconds they will update with new values.

Reading from the DB

In this example, we will read the data saved in the previous exercise in the text/int and test/float paths. We can do this with the same board, of with a totally different board/application connected to the Internet.

Upload the following code to your board:

firebaseRead.ino
#include <Arduino.h>
#include <WiFi.h>
#include <Firebase_ESP_Client.h>
 
//Provide the token generation process info.
#include "addons/TokenHelper.h"
//Provide the RTDB payload printing info and other helper functions.
#include "addons/RTDBHelper.h"
 
// Insert your network credentials
#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"
 
// Insert Firebase project API Key
#define API_KEY "REPLACE_WITH_YOUR_FIREBASE_PROJECT_API_KEY"
 
// Insert RTDB URLefine the RTDB URL */
#define DATABASE_URL "REPLACE_WITH_YOUR_FIREBASE_DATABASE_URL" 
 
//Define Firebase Data object
FirebaseData fbdo;
 
FirebaseAuth auth;
FirebaseConfig config;
 
unsigned long sendDataPrevMillis = 0;
int intValue;
float floatValue;
bool signupOK = false;
 
void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(300);
  }
  Serial.println();
  Serial.print("Connected with IP: ");
  Serial.println(WiFi.localIP());
  Serial.println();
 
  /* Assign the api key (required) */
  config.api_key = API_KEY;
 
  /* Assign the RTDB URL (required) */
  config.database_url = DATABASE_URL;
 
  /* Sign up */
  if (Firebase.signUp(&config, &auth, "", "")) {
    Serial.println("ok");
    signupOK = true;
  }
  else {
    Serial.printf("%s\n", config.signer.signupError.message.c_str());
  }
 
  /* Assign the callback function for the long running token generation task */
  config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h
 
  Firebase.begin(&config, &auth);
  Firebase.reconnectWiFi(true);
}
 
void loop() {
  if (Firebase.ready() && signupOK && (millis() - sendDataPrevMillis > 15000 || sendDataPrevMillis == 0)) {
    sendDataPrevMillis = millis();
    if (Firebase.RTDB.getInt(&fbdo, "/test/int")) {
      if (fbdo.dataType() == "int") {
        intValue = fbdo.intData();
        Serial.println(intValue);
      }
    }
    else {
      Serial.println(fbdo.errorReason());
    }
 
    if (Firebase.RTDB.getFloat(&fbdo, "/test/float")) {
      if (fbdo.dataType() == "float") {
        floatValue = fbdo.floatData();
        Serial.println(floatValue);
      }
    }
    else {
      Serial.println(fbdo.errorReason());
    }
  }
}

We can use the following functions to read data from the DB: get, getInt, getFloat, getDouble, getBool, getString, getJSON, getArray, getBlob, getFile.

If you don't know the type of data returned by the get (or you use a generic Firebase.RTDB.set(&fbdo, <path>, <any variable or value>) ) then you can use the fbdo.getDataType() function to determine the type of the payload.

The database data’s payload (response) can be read or access through the following Firebase Data object’s functions: fbdo.intData, fbdo.floatData, fbdo.doubleData, fbdo.boolData, fbdo.stringData, fbdo.jsonString, fbdo.jsonObject, fbdo.jsonObjectPtr, fbdo.jsonArray, fbdo.jsonArrayPtr, fbdo.jsonData (for keeping parse/get result), and fbdo.blobData.

Exercise 1

Modify the example above to send sensor data to your database. Use both LTR308 and BME680 sensors to send integer and float data corresponding to the sensor values read every 15 seconds.

Setting Up DB Security Rules

As you can probably notice in the previous exercise, you can only save the last recording of your data. If we want to log multiple readings for our data and also guarantee that our data is more or less secure, we will need to configure the Firebase DB.

On the Realtime Database tab, select the Rules tab at the top. Then, click on Edit rules, copy the following rules and then click Publish.

// These rules grant access to a node matching the authenticated
// user's ID from the Firebase auth token
{
  "rules": {
    "UsersData": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    }
  }
}

These rules grant access to a node matching the authenticated user’s UID. This grants that each authenticated user can only access its own data. This means the user can only access the nodes that are under a node with its corresponding user UID. If there are other data published on the database, not under a node with the users’ UID, that user can’t access that data.

For example, imagine our user UID is RjO3taAzMMXBB2Xmir2LQ. With our security rules, it can read and write data to the database under the node UsersData/RjO3taAzMMXBB2Xmir2LQ.

firebaseLog.ino
#include <Arduino.h>
#include <WiFi.h>
#include <Firebase_ESP_Client.h>
#include <Wire.h>
 
#include "time.h"
 
// Provide the token generation process info.
#include "addons/TokenHelper.h"
// Provide the RTDB payload printing info and other helper functions.
#include "addons/RTDBHelper.h"
 
// Insert your network credentials
#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"
 
// Insert Firebase project API Key
#define API_KEY "REPLACE_WITH_YOUR_PROJECT_API_KEY"
 
// Insert Authorized Email and Corresponding Password
#define USER_EMAIL "REPLACE_WITH_THE_USER_EMAIL"
#define USER_PASSWORD "REPLACE_WITH_THE_USER_PASSWORD"
 
// Insert RTDB URLefine the RTDB URL
#define DATABASE_URL "REPLACE_WITH_YOUR_DATABASE_URL"
 
// Define Firebase objects
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;
 
// Variable to save USER UID
String uid;
 
// Database main path (to be updated in setup with the user UID)
String databasePath;
// Database child nodes
String tempPath = "/temperature";
String timePath = "/timestamp";
 
// Parent Node (to be updated in every loop)
String parentPath;
 
int timestamp;
FirebaseJson json;
 
const char* ntpServer = "pool.ntp.org";
 
float temperature = 0.0;
 
// Timer variables (send new readings every three minutes)
unsigned long sendDataPrevMillis = 0;
unsigned long timerDelay = 180000;
 
// Initialize WiFi
void initWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
  Serial.println();
}
 
// Function that gets current epoch time
unsigned long getTime() {
  time_t now;
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    //Serial.println("Failed to obtain time");
    return(0);
  }
  time(&now);
  return now;
}
 
void setup(){
  Serial.begin(115200);
 
 
  initWiFi();
  configTime(0, 0, ntpServer);
 
  // Assign the api key (required)
  config.api_key = API_KEY;
 
  // Assign the user sign in credentials
  auth.user.email = USER_EMAIL;
  auth.user.password = USER_PASSWORD;
 
  // Assign the RTDB URL (required)
  config.database_url = DATABASE_URL;
 
  Firebase.reconnectWiFi(true);
  fbdo.setResponseSize(4096);
 
  // Assign the callback function for the long running token generation task */
  config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h
 
  // Assign the maximum retry of token generation
  config.max_token_generation_retry = 5;
 
  // Initialize the library with the Firebase authen and config
  Firebase.begin(&config, &auth);
 
  // Getting the user UID might take a few seconds
  Serial.println("Getting User UID");
  while ((auth.token.uid) == "") {
    Serial.print('.');
    delay(1000);
  }
  // Print user UID
  uid = auth.token.uid.c_str();
  Serial.print("User UID: ");
  Serial.println(uid);
 
  // Update database path
  databasePath = "/UsersData/" + uid + "/readings";
}
 
void loop(){
 
  // Send new readings to database
  if (Firebase.ready() && (millis() - sendDataPrevMillis > timerDelay || sendDataPrevMillis == 0)){
    sendDataPrevMillis = millis();
 
    //Get current timestamp
    timestamp = getTime();
    Serial.print ("time: ");
    Serial.println (timestamp);
 
    parentPath= databasePath + "/" + String(timestamp);
 
    json.set(tempPath.c_str(), String(temperature));
    json.set(timePath, String(timestamp));
    Serial.printf("Set json... %s\n", Firebase.RTDB.setJSON(&fbdo, parentPath.c_str(), &json) ? "ok" : fbdo.errorReason().c_str());
 
    temperature += 0.1;
  }
}

Exercise 2

The code above sends fake temperature readings and also appends a timestamp to them, so they can be logged under a UUID. Modify the above code to send real temperature readings from the BME680 sensor with the added timestamps

Firebase Web App

Firebase does not only offer the backend solution for storing your data, but we can also use it to build a hosted frontend complete with authentication and data visualization.

However, this will require more than what the online Firebase configuration interface can handle, so we'll have to install Firebase on our local computer, together with Node.js and a (better) IDE than Arduino to write code in.

You'll need to follow the next tutorials and install the necessary programs and tools:

  1. Install Node.js and the Node.js extension pack for VS Code

Follow the tutorial here to build a fully functional web app that can be accessed from anywhere in the world.

iothings/laboratoare/2022/lab4.txt · Last modified: 2022/11/17 14:08 by dan.tudose
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0