M5Stack UnitV2 とFirebaseとenebularで、一定時間現れないとLINEにお知らせを作ってみた。

#はじめに

「M5Stack UnitV2で顔認識して、一定時間その人が現れなかったらお知らせ」を作ります。「一定時間その人が現れなかったら」の部分はenebularを使って、一定時間が任意に遠隔で変更できるようにします。

#M5Stack UnitV2の準備
ラズベリーパイとM5Stack UnitV2をUSBケーブルでつなぐ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/b46c43d3-11ab-54af-d595-94dfa055d706.png)
ラズベリーパイのブラウザChromiumを立ち上げて、http://10.254.239.1/
にアクセス。  


なお、今回使用する人物写真は、photoACのAI人物素材（ベータ版）で、AIが生み出した実在しないモデルです。
https://www.photo-ac.com/main/genface
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/a764626c-a23d-34f4-e960-7aa564538180.png)
この実在しない6人のモデルの顔を、Face Recognition modeで、学習させた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/c3d9ec49-37fd-e0a4-ffbf-234975f4c9b0.png)


学習させる前 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/850a8aa1-21f3-d6a2-e670-d212c6708a6f.png)
学習させた後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/dd6e40dd-3183-3a14-4c51-d2e7fb1b037b.png)


#Firebaseの準備

田中正吾さんの記事「M5StackとFirebaseを使ってenebularとつないでみよう」
https://blog.enebular.com/firebase/m5stack-firebase-enebular-collaboration/
を参考に　Firebaseを設定する。
Google Developerログインを済ませて、Firebaseにアクセス。
コンソールに移動する。
プロジェクトを追加を押してプロジェクト作成。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/f4110f3e-3605-cc9f-5287-9fb37982e295.png)
プロジェクトに名前をつける。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/d3436ba2-db92-143a-bf3b-50baee61f3d0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/92383663-dd8e-a7dd-0c9a-e54b2b05f906.png)

ウェブアプリに Firebase を追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/95f0feb3-c515-1803-4bf6-aec865c9efb3.png)
アプリニックネームを入力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/99538e1c-4760-b582-4d51-428878e2e82a.png)

scriptタグを使用するを選択
コンソールに進む
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/2fba7c70-a2b4-6ee1-9e3a-4eb5976b6787.png)

Realtime Databaseを作成する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/72c7d638-3409-3a0f-d226-60dae7dbe024.png)

ロケーションの設定で米国を選択する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/dc90f3e3-1a50-bb5c-e91d-6d79fcdbb676.png)

テストモードで開始するを選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/97c315ff-9e73-1655-d79c-49384dea2bc2.png)

この画面の赤四角部分　https://xxxxxxxxxxxxxxxxxxxxxx-default-rtdb.firebaseio.com/
をコピペで保管しておく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/eaaf7ed2-58c6-9753-d121-8d8d532ffdeb.png)


#M5StickCの準備
M5StickCに以下のスケッチを書き込み、M5Stack UnitV2とM5StickCをGroveケーブルでつなぐ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/f2f48513-f7e3-f420-541f-69ceb20ca9fc.png)

書き込む前の準備として、必要なライブラリをインストールする。
1. Firebaseライブラリをインストール。
https://github.com/ArtronShop/IOXhop_FirebaseESP32
からZIPをダウンロードして、ArduinoIDEにインストールする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/09a79d1e-2910-e1b2-046f-f1b2289a8b3f.png)
2.ArduinoJsonはバージョン6.18.5が最新である(2021.12.16現在）が、バージョン6では動かないので、バージョン5.13.5をライブラリマネージャーから、インストールする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/da20c005-92fa-8e23-7b92-e910fb5fffae.png)

3.プログラムをM5StickCに書き込む。
プログラムは以下の3つの記事を参考にした。
Takahiro MITSUOKAさんの記事「M5UnitV2の検出結果をAmbientに送信するメモ」
https://zenn.dev/tmitsuoka0423/articles/15fcd13dc5334365e7ab
田中正吾さんの記事「M5StackとFirebaseを使ってenebularとつないでみよう」
https://blog.enebular.com/firebase/m5stack-firebase-enebular-collaboration/
スラニューさんの記事「M5StickCからFirebase Realtime Databaseに値を書き込んじゃうよ。」
https://slanew.com/news/819

```cpp
//これはArduinoJsonはVer5を使用する
#include <IOXhop_FirebaseESP32.h>
#include <M5StickC.h>
#include <ArduinoJson.h>
#include <WiFi.h>
#include <Ambient.h>

WiFiClient client;
const char *ssid = "xxxxxxx";
const char *password = "xxxxxxxxxxxxx";

Ambient ambient;
unsigned int channelId = xxxxx; // CHANNEL ID
const char *writeKey = "xxxxxxxxxxxxx";

#define FIREBASE_DATABASE_URL "xxxxxxxxxxxxxxxxxxxx-default-rtdb.firebaseio.com"

int lastSendTime = 0;
//DynamicJsonDocument doc(1024);
DynamicJsonBuffer jb;
float x;


void setup(){
  
  M5.begin();
  Serial.begin(115200);
  Serial2.begin(115200, SERIAL_8N1, 33, 32);
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(BLACK);  

  // connect to wifi.
  WiFi.begin(ssid, password);
  Serial.print("connecting");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();

  // WiFi Connected
  Serial.println("\nWiFi Connected.");
  Serial.println(WiFi.localIP());
  M5.Lcd.println("WiFi Connected.");
  M5.Lcd.println("");

  Firebase.begin(FIREBASE_DATABASE_URL);
  Firebase.stream("/M5Stack/message", [](FirebaseStream stream) {
    String eventType = stream.getEvent();
    eventType.toLowerCase();

    Serial.print("event: ");
    Serial.println(eventType);
    if (eventType == "put") {
      String path = stream.getPath();
      String data = stream.getDataString();
      Serial.print("data: ");
      Serial.println(stream.getDataString());
    }
  });

  Firebase.setString("/M5Stack/message", "start");
  ambient.begin(channelId, writeKey, &client);

}

void loop()
{
  if (Serial2.available())
  {
    String receiveString = Serial2.readStringUntil('\n');

    // JSONをデシリアライズ
    //deserializeJson(doc, receiveString);
    JsonObject& obj = jb.parseObject(receiveString);
   if (!obj.success()) {
   Serial.println("parseObject() failed");
   return;
   }

    // こんな感じで中身を取り出せる
    String name = obj["face"][0]["name"];
    Serial.println(name);


   if(name == "sato"){
    x=1;
    M5.Lcd.fillRect(0,20,160,60,BLACK);
    M5.Lcd.setCursor(20,20);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.print("sato");
    // Firebaseへ送信
    Firebase.setInt("/M5Stick/personID", 1);
    }
    else if (name == "suzuki"){
    x=2;
    M5.Lcd.fillRect(0,20,160,60,BLACK);
    M5.Lcd.setCursor(20,20);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.print("suzuki");
    // Firebaseへ送信
    Firebase.setInt("/M5Stick/personID", 2);
    }
    else if (name == "takahashi"){
    x=3;
    M5.Lcd.fillRect(0,20,160,60,BLACK);
    M5.Lcd.setCursor(20,20);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.print("takahashi");
    // Firebaseへ送信
    Firebase.setInt("/M5Stick/personID", 3);
    }
    else if (name == "tanaka"){
    x=4;
    M5.Lcd.fillRect(0,20,160,60,BLACK);
    M5.Lcd.setCursor(20,20);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.print("tanaka");
    // Firebaseへ送信
    Firebase.setInt("/M5Stick/personID", 4);
    }
    else if (name == "ito"){
    x=5;
    M5.Lcd.fillRect(0,20,160,60,BLACK);
    M5.Lcd.setCursor(20,20);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.print("ito");
    // Firebaseへ送信
    Firebase.setInt("/M5Stick/personID", 5);
    }
    else if (name == "watanabe"){
    x=6;
    M5.Lcd.fillRect(0,20,160,60,BLACK);
    M5.Lcd.setCursor(20,20);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.print("watanabe");
    // Firebaseへ送信
    Firebase.setInt("//M5Stick/personID", 6);
    }
    else if (name == "unidentified"){
    x=7;
    M5.Lcd.fillRect(0,20,160,60,BLACK);
    M5.Lcd.setCursor(20,20);
    M5.Lcd.setTextSize(1);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.print("unidentified");
    // Firebaseへ送信
    Firebase.setInt("/M5Stick/personID", 7);
    }
    delay(2000);
    M5.Lcd.fillRect(0,20,160,60,BLACK);
    M5.Lcd.setCursor(20,20);
    M5.Lcd.setTextSize(1);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.print("no person");
    // Firebaseへ送信
    Firebase.setInt("/M5Stick/personID", 8); //when no person, send 8
    delay(1000);
    Serial.println(x);

    // 一定間隔でAmbientに送信
    if (millis() - lastSendTime > 60000)
    {
      ambient.begin(channelId, writeKey, &client);
      ambient.set(1, x);
      //ambient.send();
      lastSendTime = millis();
    }
  }
}

```
#人物認識結果を確認

M5Stack UnitV2とM5StickCをGroveケーブルで接続して、M5StackUnitV2のカメラで人物写真を写す。
その認識結果を以下の3か所で確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/16421ca0-209e-f438-8d89-0d767a8010f3.png)


1. シリアルモニタで人物認識結果を確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/40aa53f2-55d9-d0a6-d29b-5eab42cbac6c.png)

2. M5StickCのディスプレイで人物認識結果を確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/8f67b936-63af-61b5-49e8-d916c91125e7.png)

3. FirebaseのRealtime Databaseで人物認識結果を確認する。例えばsuzukiは、2と表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/e0739ed9-3125-ea70-64a6-d5ba9dfb8455.png)


#enebularを準備する。
Firebaseのデータをenebularで取得します。

「Firebaseにデータ連携できるノード」https://blog.enebular.com/nodes/firebase-nodes/
を読んで、node-red-contrib-firebase-data　を使います。

以下にアクセス（プロジェクト名を入れる）。
https://console.firebase.google.com/u/0/project/`[your-project]`/settings/serviceaccounts/adminsdk
左上のプロジェクトの概要の右にある歯車ボタンを押して、プロジェクトの設定→サービスアカウントタブでもこの画面に行けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/f485be1c-806c-10ca-0e08-fce7e9d95405.png)


新しい秘密鍵の生成ボタンを押す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/acf1f684-2313-ca85-4eac-f287b7a63cc6.png)
!![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/84c4dc94-8aa8-896f-1127-f987dff0e3c7.png)
生成してダウンロードした秘密キーの中で、 "private_key", "client_email"　の部分を次のAdd Firebaseノード内で入力します。

enebularのパレットの管理から　node-red-contrib-firebase-data　という名前のノードを追加する。
node-red-contrib-line-messaging-api ノードも追加する。
一定時間現れないとお知らせ部分は、triggerノードとswitchノードを組み合わせる。

全体フローはこれ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/adb382e8-e336-62b9-90e2-d0a24db771cb.png)

フローの初めはinjectノードで2秒ごとにAdd Firebaseノードを刺激
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/15411d0c-acdf-1535-9ca3-70a4347badbc.png)

Add Firebaseノード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/bf7a1dcb-5d46-908b-0164-3a410890db7b.png)

Add Firebaseノードの中のFirebaseCertificateノード
上述のFirebaseで生成してダウンロードした秘密キーの中で、 "private_key", "client_email"　の部分をコピペする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/c051c0fd-0f65-5c8e-3d8a-3252a04159c6.png)


Switchノード（人物で分岐）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/2d1c944d-ad3c-0959-4e3d-5004dc2ec5e4.png)

triggerノード
ここで一定時間を指定する。この図では、48時間と設定している。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/69fdd7fa-6207-f926-7584-169e2cb8913a.png)

switchノード。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/72d4e5d6-1be8-a388-9896-539e9e5aa21a.png)

Notifyノード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/14569100-1e5f-3439-5e7f-2fea59a7c483.png)

これで、satoさんが初回現れてから48時間現れないと、satoさんが現れないというメッセージがpayloadとLINE Notifyノードに流れる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/c76c51de-f6f8-ea6a-89ed-bf85e433b03f.png)


#動作確認
satoさんとsuzukiさんが一定時間現れないと、LINE Notifyにお知らせがきた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/22570f4f-6288-d17f-e464-4633ad333951.png)



#まとめ
全体像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/fe9dd25a-291c-f657-cb1d-1a6ce4e6e59f.png)

これで、一定時間現れなかったらLINEにお知らせ、さらに、enebularを使って一定時間を任意に遠隔で変更できて、ガッツポーズ。