できあがるもの
ホントはFirebase側も動作している感じのやつを見せられるといいのですが😅
はじめに
前回はFeliCa RC-S620Sを使ってSuicaやPasmoなどといった電子カードを読み取れるようにしました。
更に、読み取ったmIDをFirebaseのRDB上に登録するところまで作成しました。
今回はFirebase Realtime Database(RDB)のルールを設定し、QueryFilter
を使って検索機能を設けていきましょう。
検索機能を使えば、ある電子カードに対して認証済みか否かを判定することができます。
M5StackとFirebaseを連携してスマートロックみたいなものを作る!①
M5StackとFirebaseを連携してスマートロックみたいなものを作る!②
M5StackとFirebaseを連携してスマートロックみたいなものを作る!③
M5StackとFirebaseを連携してスマートロックみたいなものを作る!④
M5StackとFirebaseを連携してスマートロックみたいなものを作る!⑤
RDBにおけるルール
Firebase RDBではセキュリティルールを設定することによって、データベースへの読み取り/書き込みを制限することができます。
またルールでは、あるインデックスに対してクエリをサポートするように指定することもできます。
今回はこのクエリのサポートを使うことによって、検索機能を使えるようにします。
参考:Firebase Realtime Databaseルールについて
現在のRDBの状態
現在は"User"というテーブルがあって、各ICカードのレコードが格納されており、レコードには"id"と"time"というフィールドがあります。
参考:データベースの構造と各名称
以下はデータベースの例です。
前回の続きからであれば、似たような形でICカードが登録されているかと思います。
今回設定するルール
今回はFeliCaのリーダーで読み取ったmIDに対して、それに一致するICカードのレコードを取得したいです。
まずは、ルールを書き換えましょう。
RDBを開いて、「ルール」から設定することができます。
今回は"id"で検索できるようにしたいので、以下の様にデータベースインデックスの定義をします。
{
"rules": {
".read": true,
".write": true,
"User": {
".indexOn":"id"
}
}
}
本来は.read, .writeは認証されたユーザーのみしかアクセスできないようにしたいですが、今回は個人的な利用のみに限るということで設定は見送ります。
Firebase_ESP32で認証機能あるかどうかはまた調べたいところですね🤔
さて、ルールを設定したら上記の表示が出てくると思うので公開を押しましょう。
これでルールの設定は完了です。
検索機能の実装
必要なライブラリ
- ArduinoJson
今回はレコード全体をjsonとして取得するためにArduinoJsonを使います。
レコード内の特定のフィールドのみ取得する方法もあるので、レコード全体の取得は絶対に必要というわけではありません。
コード全体
#include <Arduino.h>
#include <FirebaseESP32.h>
#include <M5Stack.h>
#include "RCS620S.h"
#include <time.h>
#include <ArduinoJson.h>
// WiFi, Firebaseの設定情報
// ID and Auth key for Firebase
#define FIREBASE_HOST "Your Firebase hostname"
#define FIREBASE_AUTH "Your Firebase authkey"
#define WIFI_SSID "Your WiFi SSID"
#define WIFI_PASSWORD "Your WiFi Password"
// Instantiate related with Firebase
// Firebaseのインスタンスを生成
FirebaseData firebaseData;
// FirebaseJsonのインスタンスを生成
FirebaseJson json;
// QueryFilterのインスタンスを生成
QueryFilter query;
// FeliCaの設定情報
// For FeliCa
#define COMMAND_TIMEOUT 400
#define POLLING_INTERVAL 500
RCS620S rcs620s;
// フェーズの設定
// Phase valuable
bool _authenticatePhase = false;
void setup()
{
M5.begin();
Serial.begin(115200);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int _cursorX = 0;
M5.Lcd.setTextFont(4);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setCursor(0, 0);
// WiFiに接続
// Connect to WiFi
M5.Lcd.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
M5.Lcd.setCursor(0 + 5 * _cursorX, 30);
M5.Lcd.print(".");
delay(300);
_cursorX++;
if (_cursorX > 320)
{
_cursorX = 0;
}
}
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.print("Connected with IP:");
M5.Lcd.print(WiFi.localIP());
delay(1000);
// Firebase関連
// Related with Firebase
Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
Firebase.reconnectWiFi(true);
// FeliCaへの接続確認
// Check FeliCa Reader is connected or not
int ret;
ret = rcs620s.initDevice();
while (!ret)
{
M5.Lcd.setTextColor(RED);
ret = rcs620s.initDevice();
Serial.println("Cannot find the RC-S620S");
M5.Lcd.setCursor(0, 60);
M5.Lcd.print("Cannot find the RC-S620S");
delay(1000);
}
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setCursor(0, 90);
M5.Lcd.print("Felica reader is detected");
delay(1000);
M5.Lcd.fillScreen(BLACK);
// どのフィールドから探すかを指定
// Specify field name which you want to search
query.orderBy("id");
}
void loop()
{
int ret;
rcs620s.timeout = COMMAND_TIMEOUT;
M5.update();
M5.Lcd.setTextColor(WHITE);
// ボタンで機能を変える
// Push the button to select the function
if (M5.BtnB.wasPressed())
{
_authenticatePhase = true;
M5.Lcd.fillScreen(BLACK);
}
// 通信待ち
// Polling
ret = rcs620s.polling();
// 待機フェーズ
// Waiting for pressing any key
if (!_authenticatePhase)
{
M5.Lcd.setCursor(0, 0);
M5.Lcd.print("Press any key");
}
// 認証フェーズ
// Authentication phase
else
{
M5.Lcd.setCursor(0, 0);
M5.Lcd.print("Waiting for card...");
String felicaID = "";
if (ret)
{
for (int i = 0; i < 8; i++)
{
if (rcs620s.idm[i] / 0x10 == 0)
felicaID += "0";
felicaID += String(rcs620s.idm[i], HEX);
}
}
// 検索キーワードの指定
// 今回はリーダーに置かれたICカードのmIDと一致するidを検索する
// Specify searching keyword
// In this time, I want to search "id" which is matched mID of IC Card
query.equalTo(felicaID);
if (!felicaID.isEmpty())
{
// "User"というテーブルから検索
// Search from "User" table
if (Firebase.getJSON(firebaseData, "User", query))
{
String key, value = "";
String time = "";
int type = 0;
size_t len = firebaseData.jsonObject().iteratorBegin();
// ArduinoJson用の変数
// Valuable for ArduinoJson
const size_t capacity = 500;
DynamicJsonDocument recordInfo(capacity);
// 今回の場合、
// i=0で検索と一致する全てのフィールド(json)、
// i=1で1番目のフィールド(今回だとid)、
// i=2で2番目のフィールド(今回だとtime)が返って来る
// In this time, i = 0: json which is matched the mID, i = 1: 1st field value of matched mID("id"), i = 2: 2nd field value of matched mID("time")
for (size_t i = 0; i < len; i++)
{
firebaseData.jsonObject().iteratorGet(i, type, key, value);
// iが0のときはmIDと一致するjsonが返ってくる
// When i = 0, json which is matched the mID is returned
if (i == 0)
{
// jsonとして取得したい場合は、ArduinoJsonライブラリを使う
// If you want to get data as json, you should use ArudinoJson library
DeserializationError error = deserializeJson(recordInfo, value);
if (error)
{
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.print(error.c_str());
delay(5000);
return;
}
}
// キーを指定して取得することもできる
// You can get the value by using key name
if (key == "time")
{
time = value;
}
}
// 一致するmIDをjsonファイルから取り出す
// Extract mID from json
const char* mID = recordInfo["id"];
if (len != 0)
{
json.iteratorEnd();
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.print("ID:");
M5.Lcd.println(mID);
M5.Lcd.print("time:");
M5.Lcd.println(time);
delay(3000);
}
else
{
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.print("This card is not registered");
delay(3000);
}
}
else
{
// Firebaseから取得に失敗した場合はエラーを返す
// Return error if you cannot get RDB from Firebase
Serial.println(firebaseData.errorReason());
}
// 登録が終了したら待機フェーズに戻る
_authenticatePhase = false;
M5.Lcd.fillScreen(BLACK);
}
}
rcs620s.rfOff();
delay(POLLING_INTERVAL);
}
クエリのインスタンスを生成と検索キーの設定
...
// Instantiate related with Firebase
// Firebaseのインスタンスを生成
FirebaseData firebaseData;
// FirebaseJsonのインスタンスを生成
FirebaseJson json;
// QueryFilterのインスタンスを生成
QueryFilter query;
...
今回はクエリを使った検索を行うのでまずはQueryFilter
のインスタンスを生成します。
void setup()
{
...
query.orderBy("id");
...
}
"id"の中からFeliCaリーダーで読み取ったmIDと一致する項目を検索するようにします。
検索機能
...
if (M5.BtnB.wasPressed())
{
_authenticatePhase = true;
M5.Lcd.fillScreen(BLACK);
}
...
今回はM5Stackの真中のボタン(ボタンB)が押されたら検索を実施します。
String felicaID = "";
if (ret)
{
for (int i = 0; i < 8; i++)
{
if (rcs620s.idm[i] / 0x10 == 0)
felicaID += "0";
felicaID += String(rcs620s.idm[i], HEX);
}
}
// 検索キーワードの指定
// 今回はリーダーに置かれたICカードのmIDと一致するidを検索する
// Specify searching keyword
// In this time, I want to search "id" which is matched mID of IC Card
query.equalTo(felicaID);
前回同様、felicaID
に読み取ったmIDを挿入します。
そして、query.equalTo()
に検索キーワードを指定します。今回はfelicaID
ですね。
...
if (!felicaID.isEmpty())
{
// "User"というテーブルから検索
// Search from "User" table
if (Firebase.getJSON(firebaseData, "User", query))
{
String key, value = "";
String time = "";
int type = 0;
size_t len = firebaseData.jsonObject().iteratorBegin();
// ArduinoJson用の変数
// Valuable for ArduinoJson
const size_t capacity = 500;
DynamicJsonDocument recordInfo(capacity);
// 今回の場合、
// i=0で検索と一致する全てのフィールド(json)、
// i=1で1番目のフィールド(今回だとid)、
// i=2で2番目のフィールド(今回だとtime)が返って来る
// In this time, i = 0: json which is matched the mID, i = 1: 1st record of matched mID("id"), i = 2: 2nd record of matched mID("time")
for (size_t i = 0; i < len; i++)
{
firebaseData.jsonObject().iteratorGet(i, type, key, value);
// iが0のときはmIDと一致するjsonが返ってくる
// When i = 0, json which is matched the mID is returned
if (i == 0)
{
// jsonとして取得したい場合は、ArduinoJsonライブラリを使う
// If you want to get data as json, you should use ArudinoJson library
DeserializationError error = deserializeJson(recordInfo, value);
if (error)
{
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.print(error.c_str());
delay(5000);
return;
}
}
// キーを指定して取得することもできる
// You can get the value by using key name
if (key == "time")
{
time = value;
}
}
// 一致するmIDをjsonファイルから取り出す
// Extract mID from json
const char* mID = recordInfo["id"];
...
検索にはJSONを取得するFirebase.getJSON()
を使います。ここでquery
をFirebase.getJSON()
を渡すことによって、事前に指定したフィールドkey(今回だと"id")の中からキーワード(felicaID
)と一致するレコードを検索します。
そして、firebaseData.jsonObject().iteratorGet()
を使うことによってtype
,key
, value
へ順々に値を代入していきます。
type
は取得したデータのタイプ(Object or Array)、key
はフィールド名、value
はフィールド内の値になります。
今回使うのは主にkey
とvalue
ですね。
データの順番はコメントに書いてある通り、最初はJSON、次に各フィールドの値になります。
フィールドが多い場合は、JSONとして一括で取得しちゃって必要な値だけkeyを指定して取得してもいいかもですね。
今回は、JSONとして取得する例とkeyで取得する例を記述しています。そのため、ArduinoJson用の変数を設定しています。
if (len != 0)
{
json.iteratorEnd();
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.print("ID:");
M5.Lcd.println(mID);
M5.Lcd.print("time:");
M5.Lcd.println(time);
delay(3000);
}
else
{
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.print("This card is not registered");
delay(3000);
}
もし、一致するレコードが取得できなければ取得したレコードの長さであるlen
は0になるので、その場合はカードが登録されていないという判定にします。
さてこれで登録機能と検索機能が揃ったので、次回は2つを組み合わせていきます!