This shows you the differences between two versions of the page.
iothings:laboratoare:2022:lab4 [2022/10/27 15:36] dan.tudose [Firebase] |
iothings:laboratoare:2022:lab4 [2025/03/20 18:30] (current) andreea.miu [Exercise 1] email authorisation info |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ==== Lab 4: Databases for IoT ==== | + | ==== 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. | + | In this lab assignment we'll explore how 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 ==== | ||
Line 60: | Line 60: | ||
{{ :iothings:laboratoare:2022:firebase8.png?600 |}} | {{ :iothings:laboratoare:2022:firebase8.png?600 |}} | ||
- | 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. | + | Use the code below, replace the credentials at the beginning of the code (WiFi AP, password, DB URL and API Key) and then upload it to your ESP32 Sparrow board. |
<code C firebaseWrite.ino> | <code C firebaseWrite.ino> | ||
Line 275: | Line 275: | ||
==== Exercise 1 ==== | ==== Exercise 1 ==== | ||
<note> 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.</note> | <note> 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.</note> | ||
+ | |||
+ | === 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 Authentication tab, select Sign-in method > Sign-in providers > Add new provider. From this tab, select Email/Password provider, which will allow logging in the database through email. To authorise your email for logging in, from the same tab select Users > Add user, and input your email and password. This action will generate a user UID associated with your data. | ||
+ | |||
+ | 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. | ||
+ | |||
+ | <code> | ||
+ | // 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" | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | 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//. | ||
+ | |||
+ | <code C 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; | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | ==== Exercise 2 ==== | ||
+ | <note> 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.</note> | ||
+ | |||
+ | ==== 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: | ||
+ | |||
+ | - Install [[https://randomnerdtutorials.com/esp32-firebase-web-app/#install-vs-code | Visual Studio Code IDE]] | ||
+ | - Install [[https://randomnerdtutorials.com/esp32-firebase-web-app/#install-nodejs | Node.js]] (version 18.x LTS) and the [[https://randomnerdtutorials.com/esp32-firebase-web-app/#install-nodejs-extension-pack | Node.js extension pack]] for VS Code | ||
+ | - Install [[https://randomnerdtutorials.com/esp32-firebase-web-app/#install-firebase-tools | Firebase Tools]] | ||
+ | |||
+ | Follow the tutorial [[https://randomnerdtutorials.com/esp32-esp8266-firebase-gauges-charts/ | here]] to build a fully functional web app that can be accessed from anywhere in the world. | ||
+ |