M5StackとFirebaseを連携してスマートロックみたいなものを作る！②

# できあがるもの
![register_felica_low_quality.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271414/5ab5b65b-3528-94c0-cb68-a285728f0465.gif)

# はじめに
この記事ではFeliCa RC-S620Sを使って、SuicaやPasmoなどといった電子カードを読み取れるようにしていきます。
また、Firebaseとも組み合わせてFeliCaのmIDをデータベースへ登録していきましょう。

[M5StackとFirebaseを連携してスマートロックみたいなものを作る！①](https://qiita.com/bigface00/items/87346c31d12346f85716)
[M5StackとFirebaseを連携してスマートロックみたいなものを作る！②](https://qiita.com/bigface00/items/29511d3a4425da5a499c)
[M5StackとFirebaseを連携してスマートロックみたいなものを作る！③](https://qiita.com/bigface00/items/e92f3a12ed0162c17d84)
[M5StackとFirebaseを連携してスマートロックみたいなものを作る！④](https://qiita.com/bigface00/items/d59ba7ccffce99ce259a)
[M5StackとFirebaseを連携してスマートロックみたいなものを作る！⑤](https://qiita.com/bigface00/items/590834705146e65f4826)


# 必要なもの
- [FeliCa リーダー・ライター RC-S620S](https://www.switch-science.com/catalog/353/)
- [FeliCa RC-S620S/RC-S730 ピッチ変換基板のセット(フラットケーブル付き)](https://www.switch-science.com/catalog/1029/)
- [10枚セット FeliCa [フェリカ] カード Lite-S (無地)](https://amzn.to/2WpVlee)

RC-S620SはSuicaやPasmoなどといった電子カードを読み取れることができるリーダー・ライターで、スイッチサイエンスさんから購入することができます。
電子カードは、もしSuicaやパスモ、おサイフケータイを持っていればそれが使えます。

FeliCa自体は非接触型ICカードのための通信技術として、ソニーが開発したものです。
日本で使われている電子カード・おサイフケータイはFeliCaが基本となっています。
そのため、Amazonとかで海外メーカーのスマホを買うとNFCなどの非接触通信が搭載されていたりしますが、FeliCaが搭載されておらずおサイフケータイが使えなかったりします（昔、Zenfone4を買った際に経験しました…）。

また、Arduino用のライブラリが公開されており、こちらを使って開発を進めていきます。
[Arduino向けRC-S620/S制御ライブラリ](http://blog.felicalauncher.com/sdk_for_air/?page_id=2699)
<b>ただ、ライブラリそのままでは以下のサイトにも記述してある通り、エラーを吐いてしまいます。</b>
参考：[Arduino Megaで電子工作！FeliCaリーダーで入退室記録システムを作ろう](https://deviceplus.jp/hobby/entry_f02/)
なので、ライブラリの以下の部分を変更しましょう。

```c++

10行目	#include “Wprogram.h”         => #include “Arduino.h”
312行目	Serial.write(data, len);      => Serial1.write(data, len);
327行目	if (Serial.available() > 0) { => if (Serial1.available() > 0) {
328行目	data[nread] = Serial.read();  => data[nread] = Serial1.read();
338行目	Serial.flush();	              => Serial1.flush();
```

# M5Stackとの接続
![felica1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271414/a0321d92-bdbe-3ed6-13dd-9b2e69463cdf.png)
変換基盤は左からVDD（電源）、RX、TX、GND、無し、GNDというようになっているので、画像のようにジャンパーケーブル等で接続しましょう。
ここで気をつけたいのが、「変換基盤のRX」→「M5StackのTX」、「変換基盤のTX」→「M5StackのRX」というように接続する点です。

変換基盤とカードリーダーをフラットケーブルで接続するのですがリーダー側への接続がちょっと難しい、というか入ったかどうかわかりにくいですが頑張って差し込んで下さい！
フラットケーブルに少しでっぱりがあるので、爪が長いと押し込みやすいと思います笑

# M5StackのディスプレイにICカードのIDを表示してみる
```c++:IDを表示
#include <Arduino.h>
#include <M5Stack.h>
#include "RCS620S.h"

// FeliCaの設定情報
#define COMMAND_TIMEOUT 400
#define POLLING_INTERVAL 500
RCS620S rcs620s;

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

  // FeliCaへの接続確認
  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);
}

void loop()
{
  M5.update();

  int ret, i;
  String felicaID = "";
  rcs620s.timeout = COMMAND_TIMEOUT;
  ret = rcs620s.polling();
  if (ret)
  {
    for (i = 0; i < 8; i++)
    {
      if (rcs620s.idm[i] / 0x10 == 0)
        felicaID += "0";
      felicaID += String(rcs620s.idm[i], HEX);
    }
  }
  if (!felicaID.isEmpty())
  {
    M5.Lcd.setCursor(0, 15);
    M5.Lcd.print(felicaID);
    delay(3000);
    M5.Lcd.fillScreen(BLACK);
  }
  rcs620s.rfOff();
  delay(POLLING_INTERVAL);
}
```
前回のファイルに追加で書いてもらってもいいですし、新しく作ってもらっても構いません！
また、M5Stackへの書き込みの際はRXもしくはTXへの接続を外さないと書き込みができません。

```c++:最初に設定
// FeliCaの設定情報
#define COMMAND_TIMEOUT 400
#define POLLING_INTERVAL 500
RCS620S rcs620s;
```
最初にカードリーダーがタイムアウトする時間```COMMAND_TIMEOUT```と送信要求があるかどうかを調べる時間```POLLING_INTERVAL```を設定します。
また、RCS620Sのインスタンスを生成します。

```c++:接続を確認
  // FeliCaへの接続確認
  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);
}
```
M5Stackへの書き込み時にTXかRXのどちらかを外しちゃうので、書き込み後にちゃんと接続し直しているか確認します。
接続が確認できれば、初期化し、M5Stack上に検出されたことが表示されます。

```c++:入力待ち
int ret, i;
String felicaID = "";
rcs620s.timeout = COMMAND_TIMEOUT;
ret = rcs620s.polling();
```
ここで読み込みのタイムアウトする時間を設定します。
また、電子カードが置かれたかどうかの入力街をするために、```rcs620s.polling()```を行います。

```c++:入力があった場合
if (ret)
  {
    for (i = 0; i < 8; i++)
    {
      if (rcs620s.idm[i] / 0x10 == 0)
        felicaID += "0";
      felicaID += String(rcs620s.idm[i], HEX);
    }
  }
if (!felicaID.isEmpty())
  {
    M5.Lcd.setCursor(0, 15);
    M5.Lcd.print(felicaID);
    delay(3000);
    M5.Lcd.fillScreen(BLACK);
  }
```
入力があった場合は16進数のIDmが```felicaID```に書き込まれます。
また、書き込まれた場合のみM5Stackのディスプレイ上に表示するようにします。

# FeliCaの情報をFirebaseのRDBへ登録していく
<b>ここからは、WiFiが必要になります。</b>

```c++:コード全体
#include <Arduino.h>
#include <FirebaseESP32.h>
#include <M5Stack.h>
#include "RCS620S.h"
#include <time.h>

// WiFi, Firebaseの設定情報
#define FIREBASE_HOST "Your firebase host name"
#define FIREBASE_AUTH "Your firebase auth key"
#define WIFI_SSID "Your WiFi SSID name"
#define WIFI_PASSWORD "Your WiFi Password"

FirebaseData firebaseData;
FirebaseJson json;

// FeliCaの設定情報
#define COMMAND_TIMEOUT 400
#define POLLING_INTERVAL 500
RCS620S rcs620s;

// 時間の取得
const char *ntpServer = "ntp.jst.mfeed.ad.jp";
const long gmtOffset_sec = 9 * 3600;
const int daylightOffset_sec = 0;

// フェーズの設定
bool _registerPhase = false;

// 時間をStringの形で取得
String getTimeAsString()
{
  struct tm timeinfo;
  String _timeNow = "";
  if (!getLocalTime(&timeinfo))
  {
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.print("Failed to obtain time");
    return _timeNow;
  }
  _timeNow = String(1900 + timeinfo.tm_year) + "," + String(1 + timeinfo.tm_mon) + "," + String(timeinfo.tm_mday) + "," + String(timeinfo.tm_hour) + ":" + String(timeinfo.tm_min) + ":" + String(timeinfo.tm_sec);
  return _timeNow;
}

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に接続
  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関連
  Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
  Firebase.reconnectWiFi(true);

  // FeliCaへの接続確認
  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);

  //時間の初期化と取得
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}

void loop()
{
  int ret;
  rcs620s.timeout = COMMAND_TIMEOUT;
  M5.update();
  M5.Lcd.setTextColor(WHITE);
  
  // ボタンAを押すことで登録フェーズに入る
  if (M5.BtnA.wasPressed())
  {
    _registerPhase = true;
    M5.Lcd.fillScreen(BLACK);
  }
  
  // 通信待ち
  ret = rcs620s.polling();
  // 待機フェーズ
  if (!_registerPhase)
  {
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.print("Press any key");
  }
  // 登録フェーズ
  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);
      }
    }
    // FeliCaが置かれた場合は、Firebaseへ登録するためのjsonを作成する
    if (!felicaID.isEmpty())
    {
      // "id"というフィールドにFeliCaのmIDを登録する
      json.set("id", felicaID);
      json.set("time", getTimeAsString());
      // "User"というテーブルにユーザーの情報を登録していく
      if (Firebase.pushJSON(firebaseData, "/User", json))
      {
        M5.Lcd.fillScreen(BLACK);
        M5.Lcd.setTextColor(YELLOW);
        M5.Lcd.setCursor(0, 30);
        M5.Lcd.print(felicaID);
        M5.Lcd.setCursor(0, 60);
        M5.Lcd.print("Your card is registered now!!");
        delay(3000);
      }
      // Firebaseと通信できなければエラーを返す
      else
      {
        M5.Lcd.setTextColor(RED);
        M5.Lcd.setCursor(0, 30);
        M5.Lcd.print("FAILED");
        M5.Lcd.setCursor(0, 60);
        M5.Lcd.print("REASON: " + firebaseData.errorReason());
        delay(10000);
      }
      // 登録が終了したら待機フェーズに戻る
      _registerPhase = false;
      M5.Lcd.fillScreen(BLACK);
    }
  }
  rcs620s.rfOff();
  delay(POLLING_INTERVAL);
}
```
## FirebaseとFeliCaリーダーの併用上の注意
```c++:登録フェーズと待機フェーズを作っておく
// ボタンAを押すことで登録フェーズに入る
  if (M5.BtnA.wasPressed())
  {
    _registerPhase = true;
    M5.Lcd.fillScreen(BLACK);
  }
...
  if (!_registerPhase)
  {
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.print("Press any key");
  }
  // 登録フェーズ
  else
  {
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.print("Waiting for card...");
...
    // 登録が終了したら待機フェーズに戻る
    _registerPhase = false;
    M5.Lcd.fillScreen(BLACK);
  }
```
ここで気を付けたいのが```_registerPhase```のように登録するためのフェーズを設定して、それを用いた条件分岐をしておかないといけません。なぜなら、リーダーの上にカードを置き忘れたままにしておくと<b>永遠とカードの情報を登録し続けてしまう</b>という点です。
つまり、永遠とRDBへ書き込み続けることになるので<b>無課金ゾーンを超えてしまう可能性</b>があります。
クラウド上のデータベースを使う場合はしっかり動作確認を行い、無駄な通信・書き込み・読み込みが発生しないようにしましょう。

## FirebaseJsonの活用
```c++:jsonを活用
FirebaseJson json;
...
 // "id"というフィールドにFeliCaのmIDを登録する
      json.set("id", felicaID);
      json.set("time", getTimeAsString());
      // "User"というテーブルにユーザーの情報を登録していく
      if (Firebase.pushJSON(firebaseData, "/User", json))
      {
...
```
FirebaseESP32では```FirebaseJson```のインスタンスを生成すれば、Firebase RDBでjsonを扱うことができます。
上記のコードのように、FeliCaのmID、登録した時間を一括で指定したテーブル（今回の場合"/User"）に登録することができ、ユーザーの登録時などに活用することができます。

## 時間の取得
```c++:時間の取得
#include <time.h>
...
// 時間の取得
const char *ntpServer = "ntp.jst.mfeed.ad.jp";
const long gmtOffset_sec = 9 * 3600;
const int daylightOffset_sec = 0;
...
String getTimeAsString()
{
  struct tm timeinfo;
  String _timeNow = "";
  if (!getLocalTime(&timeinfo))
  {
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.print("Failed to obtain time");
    return _timeNow;
  }
  _timeNow = String(1900 + timeinfo.tm_year) + "," + String(1 + timeinfo.tm_mon) + "," + String(timeinfo.tm_mday) + "," + String(timeinfo.tm_hour) + ":" + String(timeinfo.tm_min) + ":" + String(timeinfo.tm_sec);
  return _timeNow;
}
...
void setup()
{
...
//時間の初期化と取得
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}

```
もう既に```FirebaseJson```の活用のなかで```time```に```getTimeAsString()```を使ってますが、今回は登録した時間が取得できるようにしています。

次回はqueryを用いた検索機能を実装してカードが登録されているかどうかを判定するようにしていきます！