ESP32からFirebaseのRealtime Databaseを使用する

# はじめに
オリジナルの観葉植物IoTサービスの開発中に、FirebaseのRealtime Databaseを使ってデータを管理したかったのですが、日本語記事がほぼないため今回備忘録の意味も含めご紹介しします。

今回ご紹介するライブラリの日本語記事はありませんでした。

私はESP32を使用しましたが、ESP8266でも使用できるようです。詳しくはリポジトリの[README](https://github.com/mobizt/Firebase-ESP-Client#readme)をご覧ください。

## 該当ライブラリのリポジトリ
https://github.com/mobizt/Firebase-ESP-Client

## 開発環境
Arduino IDE 2.0を使用します

# 準備

## ライブラリのインストール
まずは、IDEのライブラリマネージャーを開きます。
検索欄に **Firebase Arduino** と入力し、**"Firebase Arduino Client Library"** を見つけたら **"インストール"** をクリックしてください。
![Desktop Screenshot 2022.12.22 - 17.54.45.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408220/bb69d3c6-f613-3152-4256-153f8f71549b.png)

## メモリオプション
私はあまり理解できていませんが、メモリの設定を変更するとよくなるらしいです。
PSRAMが搭載されているESP32モジュールでは、PSRAMを有効にして、代わりにこの外部メモリを使用するように設定することができます。
![ESP32-PSRAM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408220/7e141b87-ba5f-c420-85a9-85c395e328c4.png)

## Firebase プロジェクト
Fireabaseのプロジェクトは作成してあることを前提としています。新規に作成する際は、[こちら](https://firebase.google.com/?hl=ja)からプロジェクトを作成してください。

## Firebase Authentication
ユーザー認証を実装し、認証済みのユーザーからしかデータを操作できないようにします。しかし、未認証でもアクセスできるようにする方法もあります。こちらを使用すると、転送時間が大幅に減少し全データのサイズは小さくなりますが安全性に問題があり、データベースを利用するケースでは少数派であると思うので、今回はユーザー認証を実装する方法を紹介します。

Email/Passwordでサインインする方法は以下の画像を参考にしてください。
![Enable_Email_Password_Provider.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408220/18d5ef2d-c6c7-d3fc-9b93-9c251950b682.png)
![Enable_Email_Password_Provider2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408220/da5ca084-8c4e-cd20-5fe6-52555b0be958.png)
プロジェクトの最初のユーザーのメールアドレスとパスワードを入力し、それらを使用してサインインしてください。
![Enable_Email_Password_Provider3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408220/1fe2cf61-6e3b-ac04-2e7a-c728f5ed74c6.png)

## Firebase Realtime Database
以下の画像を参考に、新規でリアルタイムデータベースを作成します。
![Create_New_RTDB.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408220/a2a9d000-1929-8080-b971-6d054a2a8136.png)
ロケーションはシンガポールを選択します。日本サーバーはなく、現状一番近い場所がシンガポールです。
![Desktop Screenshot 2022.12.22 - 18.33.53.41 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408220/1b79f827-9de5-5039-58af-7699bccb650c.png)
![Create_New_RTDB3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408220/b20ba51a-d3bf-a253-608d-256c3e55cf04.png)
最後にルールを、認証済みのアカウント以外は通さないようにします。
![Create_New_RTDB4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408220/975227bb-3814-d290-1490-152f78c4d662.png)
```
{
  "rules": {
    ".read": "auth != null", 
    ".write": "auth != null"
  }
}
```

## その他必要な情報
コードを書く際に必要になるため、それぞれコピーをしておいてください。
### API Key
サインイン時に必要となるAPI Keyを取得
![API_Key.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408220/28ce084e-de40-2e4a-9a8a-7600fe494384.png)

### Database URLを取得
![RTDB_URL.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408220/889dddbf-bf7d-043d-b127-52f9d1d1b271.png)

# 実装
```c++
#include <WiFi.h>
// 今回のライブラリのメイン
#include <Firebase_ESP_Client.h>

// メモリを節約するために不要なクラスを除外する
#define ENABLE_FIRESTORE
#define ENABLE_FCM
#define ENABLE_GC_STORAGE
#define ENABLE_FB_FUNCTIONS

// WiFi credentials
const char *ssid = "wifi ssid";
const char *password = "wifi password";

// define the api key
const String apiKey = "コピーしたAPI Key";

// define the RTDB URL
const String databaseURL = "コピーしたDatabase URL";

// define the user Email and password tha alreadey registrerd or added in my project
const String userEmail = "email address";
const String userPassword = "account password";

// Firebase data object
FirebaseData fbdo;

FirebaseAuth auth;
FirebaseConfig config;

// アカウントへのサインインを確認する Firebase.ready() を loop() 行うので、
// 永遠とデータを送受信してしまわないようにする。
bool taskCompleted = false;

void setup() {
  Serial.begin(115200);

  // WiFiへ接続
  bool done = true;
  WiFi.begin(ssid, password);
  while (done) {
    Serial.print("WiFi connecting");
    auto last = millis();
    while (WiFi.status() != WL_CONNECTED && last + 1000 > millis()) {
      delay(500);
      Serial.print(".");
    }
    if (WiFi.status() == WL_CONNECTED) {
      done = false;
    } else {
      Serial.println("retry");
      WiFi.disconnect();
      WiFi.reconnect();
    }
  }
  Serial.println("\nWiFi connected.");

  // Firebaseの初期化をする
  // APIKeyを割り当てる 
  config.api_key = apiKey;

  // ユーザー認証情報を割り当てる 
  auth.user.email = userEmail;
  auth.user.password = userPassword;

  // Real time data baseのURLを割り当てる 
  config.database_url = databaseURL;

  Firebase.begin(&config, &auth);

  // これを設定すると、切断されたら自動的に再接続する
  Firebase.reconnectWiFi(true);
}

void loop() {
  // Firebase.ready()は認証タスクを処理するために呼び出す必要がある
  if (Firebase.ready() && !taskCompleted) {
    taskCompleted = true;
    
    Serial.printf("Set timestamp... %s\n", Firebase.RTDB.setTimestamp(&fbdo, "/test/timestamp") ? "ok" : fbdo.errorReason().c_str());

    if (fbdo.httpCode() == FIREBASE_ERROR_HTTP_CODE_OK)
    {
      // ミリ秒単位で保存されたタイムスタンプを、intで取得する
      Serial.print("TIMESTAMP (Seconds): ");
      Serial.println(fbdo.to<int>());

      // もしくはdoubleでミリ秒の合計を表示する
      // Serial.printではバグがあるのでprintfでdoubleを表示する
      printf("TIMESTAMP (milliSeconds): %lld\n", fbdo.to<uint64_t>());
    }

    Serial.printf("Get timestamp... %s\n", Firebase.RTDB.getDouble(&fbdo, "/test/timestamp") ? "ok" : fbdo.errorReason().c_str());
    if (fbdo.httpCode() == FIREBASE_ERROR_HTTP_CODE_OK)
      printf("TIMESTAMP: %lld\n", fbdo.to<uint64_t>());

    // Json形式でデータを書き込む
    FirebaseJson json;

    json.set("Data", "Hello");
    // タイムスタンプを書き込むときは、.svで位置を指定して文字列型の"timestamp"を渡す
    // おそらく、サーバー側で処理するためその時に扱える指定子のようなものを渡す
    json.set("Ts/.sv", "timestamp"); 

    // データとタイムスタンプを書き込む
    // set では、同じPathにデータを書き込むときは上書きされる
    Serial.printf("Set data with timestamp... %s\n", Firebase.RTDB.setJSON(&fbdo, "/test/set/data", &json) ? fbdo.to<FirebaseJson>().raw() : fbdo.errorReason().c_str());

    // push では、指定のPathに子要素として新規に作成されるアドレスのもとへ書き込まれる
    // そのため、IoTで活用したいデータなどに向いている
    Serial.printf("Push data with timestamp... %s\n", Firebase.RTDB.pushJSON(&fbdo, "/test/push/data", &json) ? "ok" : fbdo.errorReason().c_str());

    // pushで書き込んだデータを取得する
    // {"Data":"Hello","Ts":1671713067097} このようなデータが取得できる
    Serial.printf("Get previous pushed data... %s\n", Firebase.RTDB.getJSON(&fbdo, "/test/push/data/" + fbdo.pushName()) ? fbdo.to<FirebaseJson>().raw() : fbdo.errorReason().c_str());
  }
}
```
シリアルモニタ
```
Firebase Client v4.2.7

Set timestamp... ok
TIMESTAMP (Seconds): 1671713066
TIMESTAMP (milliSeconds): 1671713066478
Get timestamp... ok
TIMESTAMP: 1671713066478
Set data with timestamp... {"Data":"Hello","Ts":1671713066893}
Push data with timestamp... ok
Get previous pushed data... {"Data":"Hello","Ts":1671713067097}
```

# さいごに
ここまで読んでくださりありがとうございました。
今回は、制作中のIoTサービスでデータベースを使用するときに使用させていただいたライブラリについて紹介しました。

IoTではサイズの小さいデータを頻繁に送信するため、その用途にあったRealtime Databaseを利用しました。（Realtime Databaseは利用数ではなく通信容量で料金計算されるため）

自分が使用した部分しか記載できていませんが、他にも非同期処理や`push()`でも`pushInt()`など細かなメソッドがありますので、それらを使用する際は[公式のリポジトリ](https://github.com/mobizt/Firebase-ESP-Client)をご覧ください。