Post

Implementing Firebase RTDB Streaming with REST API on ESP32

Introduction

In this guide, we will learn how to connect to Firebase Realtime Database (RTDB) streaming using the REST API and also how to perform PUT operations with an ESP32.


Step: 1. Set Up Firebase RTDB

  • Go to the Firebase Console and create a project.
  • Creating a User and Obtaining UID with Email and Password Authentication.
  • Enable the Realtime Database and set basic rules for testing:
    1
    2
    3
    4
    5
    6
    
    {
      "rules": {
        ".read": "auth != null && auth.uid === 'USER_UID'",
        ".write": "auth != null && auth.uid === 'USER_UID'"
      }
    }
    

    Update rules for production security.


Step: 2. Set UP api key, email, password, path

1
2
3
4
5
6
7
8
9
10
11
// api key, auth settings
#define API_KEY "Web_API_KEY"
#define USER_EMAIL "USER_EMAIL"
#define USER_PASSWORD "USER_PASSWORD"

// variable settings
String Version = "1.0.0";

String firebaseHost = "DATABASE_HOST_URL"; // rtdb host url
String firebaseStreamingPath = "DATABASE_STREAMING_PATH_UTL"; // streaming url
String firebasePutPath = "DATABASE_PUT_PATH_UTL"// put url

Step: 3. Code Implementation

Include Libraries

1
2
3
4
5
6
// library settings
#include <Arduino.h>
#include <ArduinoJson.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <WiFiManager.h>

Firebase authetincate & get id token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
bool authenticateFirebase(const char* email, const char* password){
    String authUrl = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=" + String(API_KEY);

    // create JSON Body 
    String requestBody;
    StaticJsonDocument<200> doc;
    doc["email"] = email;
    doc["password"] = password;
    doc["returnSecureToken"] = true;
    serializeJson(doc, requestBody);

    // Connect HTTPS
    authClient.setInsecure();
    if (!authClient.connect("identitytoolkit.googleapis.com", 443)) {
        Serial.println("Connection to Firebase Auth failed!");
        return false;
    }

    authClient.println("POST " + authUrl + " HTTP/1.1");
    authClient.println("Host: identitytoolkit.googleapis.com");
    authClient.println("Content-Type: application/json");
    authClient.println("Content-Length: " + String(requestBody.length()));
    authClient.println();
    authClient.print(requestBody);

    // set Response
    String response = "";
    while (authClient.connected()) {
        if (authClient.available()) {
            response = authClient.readString();
            break;
        }
    }

    // check HTTP Status Code
    if (!response.startsWith("HTTP/1.1 200")) {
        Serial.println("Non-200 HTTP response received:");
        return false;
    }

    // remove Header
    int bodyStartIndex = response.indexOf("\r\n\r\n");
    if (bodyStartIndex != -1) {
        response = response.substring(bodyStartIndex + 4);
    }

    // remove chunked data
    int chunkSizeEndIndex = response.indexOf("\r\n");
    if (chunkSizeEndIndex != -1) {
        response = response.substring(chunkSizeEndIndex + 2);
    }

    // Parsing JSON response
    StaticJsonDocument<500> responseDoc;
    DeserializationError error = deserializeJson(responseDoc, response);
    if (error) {
        Serial.print("JSON parse error: ");
        Serial.println(error.c_str());
        return false;
    }

    if (responseDoc.containsKey("idToken") && responseDoc.containsKey("refreshToken")) {
        firebaseAuth = responseDoc["idToken"].as<String>();
        firebaseRefreshToken = responseDoc["refreshToken"].as<String>();
        tokenExpiryTime = millis() + (responseDoc["expiresIn"].as<unsigned long>() * 1000); 
        Serial.println("ID Token: " + firebaseAuth);
        Serial.println("Refresh Token: " + firebaseRefreshToken);
        return true;
    } else {
        Serial.println("Failed to authenticate. Response:");
        Serial.println(response);
        return false;
    }
}

refresh Firebase token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
bool refreshFirebaseToken(){
    String refreshUrl = "https://securetoken.googleapis.com/v1/token?key=" + String(API_KEY);

    // create JSON Body 
    String requestBody;
    StaticJsonDocument<200> doc;
    doc["grant_type"] = "refresh_token";
    doc["refresh_token"] = firebaseRefreshToken;
    serializeJson(doc, requestBody);

    // Connect HTTPS
    authClient.setInsecure();
    if (!authClient.connect("securetoken.googleapis.com", 443)) {
        Serial.println("Connection to Firebase Token Refresh failed!");
        return false;
    }

    authClient.println("POST " + refreshUrl + " HTTP/1.1");
    authClient.println("Host: securetoken.googleapis.com");
    authClient.println("Content-Type: application/json");
    authClient.println("Content-Length: " + String(requestBody.length()));
    authClient.println();
    authClient.print(requestBody);

    // check HTTP Status Code
    if (!response.startsWith("HTTP/1.1 200")) {
        Serial.println("Non-200 HTTP response received:");
        return false;
    }

    // remove Header
    int bodyStartIndex = response.indexOf("\r\n\r\n");
    if (bodyStartIndex != -1) {
        response = response.substring(bodyStartIndex + 4);
    }

    // remove chunked data
    int chunkSizeEndIndex = response.indexOf("\r\n");
    if (chunkSizeEndIndex != -1) {
        response = response.substring(chunkSizeEndIndex + 2);
    }

    // set Response
    String response = "";
    while (authClient.connected()) {
        if (authClient.available()) {
            response = authClient.readString();
            break;
        }
    }

    // Parsing JSON response
    StaticJsonDocument<500> responseDoc;
    DeserializationError error = deserializeJson(responseDoc, response);
    if (error) {
        Serial.print("JSON parse error: ");
        Serial.println(error.c_str());
        return false;
    }

    if (responseDoc.containsKey("id_token")) {
        firebaseAuth = responseDoc["id_token"].as<String>();
        tokenExpiryTime = millis() + (responseDoc["expires_in"].as<unsigned long>() * 1000);
        Serial.println("Refreshed ID Token: " + firebaseAuth);
        return true;
    } else {
        Serial.println("Failed to refresh token. Response:");
        Serial.println(response);
        return false;
    }
}

Connect to a Firebase Stream

This code establishes a streaming connection to a REST API using the event-stream method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
bool connectToFirebaseStream(){
  if (millis() - lastReconnectAttempt < 5000) {
      return false;
  }

  lastReconnectAttempt = millis();

  // Create Firebase stream URL 
  String url = String("https://") + firebaseHost.c_str() + firebaseStreamingPath + "?auth=" + firebaseAuth.c_str();

  // Connect Firebase
  Serial.print("Connecting to Firebase...");
  if (!streamClient.connect(firebaseHost.c_str(), 443)) {
      Serial.println("Connection failed!");
      return false;
  }
  Serial.println("Connected!");

  // Write HTTP streamClient
  streamClient.println("GET " + url + " HTTP/1.1");
  streamClient.println("Host: " + String(firebaseHost.c_str()));
  streamClient.println("Accept: text/event-stream");
  streamClient.println("Connection: keep-alive");
  streamClient.println();

  String locationHeader = ""; 
  while (streamClient.connected()) {
    if (streamClient.available()) {
      String line = streamClient.readStringUntil('\n');
      line.trim(); 

      Serial.print("Line Length: ");
      Serial.println(line.length());
      Serial.print("Raw Line: ");
      Serial.println(line);

      if (line.startsWith("Location: ")) {
          String extractedSubstring = line.substring(10);
          locationHeader = extractedSubstring; 
          locationHeader.trim(); 
          Serial.println("Location Header Found: " + locationHeader);
      }

      if (line == "") {
          break;
      }
    }
  }

  if (!locationHeader.isEmpty()) {
      Serial.println("Redirecting to: " + locationHeader);
      streamClient.stop();
      return connectToRedirectedStream(locationHeader);
  }

  Serial.println("Failed to receive HTTP headers.");
  return false;
}

Observing Streaming Data

This code observes streaming data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void handleFirebaseStream(){
  while (streamClient.available()) {
    String line = streamClient.readStringUntil('\n');
    line.trim();

    // event
    if (line.startsWith("event:")) {
        String eventType = line.substring(6); 
        eventType.trim();
        Serial.println("Event: " + eventType);
    }

    // data
    if (line.startsWith("data:")) {
      String eventData = line.substring(5);
      eventData.trim();
      Serial.println("Data: " + eventData);

      if (eventData == "null") {
        Serial.println("Keep-alive received.");
        continue;
      }{
        handlePathAndData(eventData);
      }
    }
  }
}

Putting Data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Firebase PUT Data
bool putData(const String& path, int value){
  WiFiClientSecure putClient;
  putClient.setInsecure();

  String url = String("https://") + firebaseHost.c_str() + path + "?auth=" + firebaseAuth.c_str();

  Serial.print("Connecting to Firebase for PUT...");
  if (!putClient.connect(firebaseHost.c_str(), 443)) {
      Serial.println("Connection failed!");
      return false;
  }
  Serial.println("Connected!");

  String jsonPayload = String(value);

  // Write HTTP PUT 
  putClient.println("PUT " + url + " HTTP/1.1");
  putClient.println("Host: " + String(firebaseHost.c_str()));
  putClient.println("Content-Type: application/json");
  putClient.print("Content-Length: ");
  putClient.println(jsonPayload.length());
  putClient.println("Connection: close");
  putClient.println();
  putClient.println(jsonPayload);

  while (putClient.connected()) {
      String line2 = putClient.readStringUntil('\n');
      line2.trim();
      if (line2.startsWith("HTTP/1.1") && line2.indexOf("200")) {
          Serial.println("Response: " + line2);
          cnt_error = 0;
          return true;
      }
  }

  Serial.println("No response or failed to put data.");
  cnt_error++;
  return false;
}

Conclusion

Today, I learned how to use Firebase RTDB streaming and perform PUT operations with an ESP32 in the Arduino IDE.

Recently, I tried using the FirebaseClient library in the Arduino IDE but kept encountering errors. It seemed to be related to SSL and authentication issues, so I decided to implement the functionality myself. I’m sharing this guide for anyone who might face similar issues and need an alternative solution.

Thank you for reading, and happy blogging! 🚀

References

This post is licensed under CC BY 4.0 by the author.