ESP-WROOM-02でもFirebaseのイベントをリッスンしたかった

# はじめに 
この記事では、GoogleHomeの音声入力により、ESP-WROOM-02に接続したサーボモータで物理スイッチを押下する装置を作成する際に悩んだりハマったりした部分を書いていこうと思います。
# 当初の構成
1. GoogleHome(またはgoogleAssistant)
2. IFTTTでFirebaseを更新(ON)する
3. RaspberryPiで変更をリッスン
4. ONの場合はESP-WROOM-02へサーボモータを動作するようPOSTする
5. Firebaseを更新(OFF)する

という流れで作成していたが、ESP-WROOM-02でFirebaseの読み書きが出来ればRaspberryPiが不要だなと思い始めた
# Arduino用Firebaseライブラリ
https://github.com/firebase/firebase-arduino
# ハマリポイント
## node.jsのコード

```hoge.js
var ref = db.ref( "/foo" );

ref.on( "child_changed", function( changedSnapshot ) {
  var key = changedSnapshot.key;
  console.log( key );
var message = changedSnapshot.val();
  sendMessage( message );
  console.log( message );
  //
  if (( message = 'OK' ) && ( key = 'bar' )) {
       postMessage( message )
       var updates = {};
       updates[key] = 'OFF';
      ref.update(updates);
  }

});
```

## 公式ドキュメント
[void stream(const String &path)](http://firebase-arduino.readthedocs.io/en/latest/#_CPPv2N15FirebaseArduino6streamERK6String)
>Starts streaming any changes made to the node located at path, including any of its children.

>You should check success() after calling. This changes the state of this object. Once this is called you may start monitoring available() and calling readEvent() to get new events.

>>Parameters
>>>path: The path inside of your db to the node you wish to monitor.

[bool available()](http://firebase-arduino.readthedocs.io/en/latest/#_CPPv2N15FirebaseArduino9availableEv)
>Checks if there are new events available.
>
>This is only meaningful once stream() has been called.
>
>>Return
>>>If a new event is ready.


[FirebaseObject readEvent()](http://firebase-arduino.readthedocs.io/en/latest/#_CPPv2N15FirebaseArduino9readEventEv)
>Reads the next event in a stream.
This is only meaningful once stream() has been called.

>>Return
>>>FirebaseObject will have [“type”] that describes the event type, [“path”] that describes the effected path and [“data”] that was updated.

## 公式ドキュメントやサンプルスケッチを参考にしたArduino のスケッチ

```cpp:hoge.ino

void setup() {
// 略

  Firebase.begin("hoge.firebaseio.com");
  Firebase.stream(PATH);
}

void loop() {

  if (Firebase.failed()) {
    Serial.println("streaming error");
    Serial.println(Firebase.error());
  }

  if (Firebase.available()) {
    FirebaseObject event = Firebase.readEvent();
    if (Firebase.success()) {
      String eventType = event.getString("type");
      eventType.toLowerCase();

      if (eventType == "put") {
        String data = event.getString("data");

        if (data == "ON") {

          //  サーボ操作
          servo.attach(13);
          servo.write(0);
          servo.write(45);
          delay(500);
          servo.write(0);
          servo.detach();

          Firebase.setString(PATH, "OFF");
          // handle error
          if (Firebase.failed()) {
            Serial.print("/Foo/bar failed:");
            Serial.println(Firebase.error());
            return;
          }

        }
      }
    }
  }
}
```

## 想定と実行結果
### 想定(ノードの追加と削除は考慮しないとして) 
1. loopの外でFirebase.streamでストリーム開始
2. loop内でFirebase.available()によるイベント監視
3. 状態が変更されたらFirebase.readEvent()でイベントの種類とデータを取得
4. イベントがputでデータがONなら、サーボを動かし、データをOFFに戻す
5. loopの先頭に戻りイベント監視を続ける

という想定だったが
### 実行結果
1. Firebaseの値を変更していないのに、初回のFirebase.readEvent()で、イベントがputと取得
2. 概ね30秒おきに、Firebase.available()が真になるが、readEvent()でイベントは取得出来ない
3. Firebaseの値をONに変更すると、即時にavailable()が真となり、readEvent()でイベントタイプがput、データがONと正しく取得出来る
4. Firebaseの値をOFFに変更後ストリームは閉じてしまうので、もう一度ONにしても取得出来ない

## 改良したArduino のスケッチ

```cpp:hoge.ino

void setup() {
// 略

  Firebase.begin("hoge.firebaseio.com");
  Firebase.stream(PATH);
}

void loop() {

  if (Firebase.failed()) {
    Serial.println("streaming error");
    Serial.println(Firebase.error());
  }

  if (Firebase.available()) {
    FirebaseObject event = Firebase.readEvent();
    if (Firebase.success()) {
      String eventType = event.getString("type");
      eventType.toLowerCase();

      if (eventType == "put") {
        String data = event.getString("data");

        if (data == "ON") {

          //  サーボ操作
          servo.attach(13);
          servo.write(0);
          servo.write(45);
          delay(500);
          servo.write(0);
          servo.detach();

          Firebase.setString(PATH, "OFF");
          // handle error
          if (Firebase.failed()) {
            Serial.print("/Foo/bar failed:");
            Serial.println(Firebase.error());
            return;
          }
        Firebase.stream(PATH);  // 追加

        }
      }
    }
  }
}
```
とりあえずは問題なく動作したが

[Firebase.cpp](https://github.com/firebase/firebase-arduino/blob/master/src/Firebase.cpp)
[Firebase.h](https://github.com/firebase/firebase-arduino/blob/master/src/Firebase.cpp)
をいくら読んでも、イベントタイプは何かしろセットされるようにしか読めない(最低限UNKNOWNはセットされるはず)

# 終わりに
1. Firebaseの利用状況では、loopの中でdelayで間を開けながら、Firebase.getほげを実行するよりは無駄な読み込みは少なそう
2. iOS/Android版のC++用ライブラリにはValueListener クラスが存在する

>OnValueChanged コールバックを使用して、特定のパスにあるコンテンツに対する変更をサブスクライブできます。このコールバックはリスナーがアタッチされたときに 1 回トリガーされます。さらに、データ（子も含む）が変更されると、そのたびに再びトリガーされます。コールバックには、その場所にあるすべてのデータ（子のデータも含む）を含んでいるスナップショットが渡されます。データが存在しない場合、返されるスナップショットは null です。

 **Google的にはArduino(ESP8266)はあくまでセンサーを繋いで、計測結果をFirebaseにpushする運用を想定でFirebaseをトリガとしてArduinoを操作ということは重要視していないのでは**