4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Global WalkersAdvent Calendar 2020

Day 19

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

Last updated at Posted at 2020-12-19

できあがるもの

register_felica_low_quality.gif

はじめに

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

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

必要なもの

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

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

また、Arduino用のライブラリが公開されており、こちらを使って開発を進めていきます。
Arduino向けRC-S620/S制御ライブラリ
ただ、ライブラリそのままでは以下のサイトにも記述してある通り、エラーを吐いてしまいます。
参考:Arduino Megaで電子工作!FeliCaリーダーで入退室記録システムを作ろう
なので、ライブラリの以下の部分を変更しましょう。


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
変換基盤は左からVDD(電源)、RX、TX、GND、無し、GNDというようになっているので、画像のようにジャンパーケーブル等で接続しましょう。
ここで気をつけたいのが、「変換基盤のRX」→「M5StackのTX」、「変換基盤のTX」→「M5StackのRX」というように接続する点です。

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

M5StackのディスプレイにICカードのIDを表示してみる

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への接続を外さないと書き込みができません。

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

最初にカードリーダーがタイムアウトする時間COMMAND_TIMEOUTと送信要求があるかどうかを調べる時間POLLING_INTERVALを設定します。
また、RCS620Sのインスタンスを生成します。

接続を確認
  // 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上に検出されたことが表示されます。

入力待ち
int ret, i;
String felicaID = "";
rcs620s.timeout = COMMAND_TIMEOUT;
ret = rcs620s.polling();

ここで読み込みのタイムアウトする時間を設定します。
また、電子カードが置かれたかどうかの入力街をするために、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);
  }

入力があった場合は16進数のIDmがfelicaIDに書き込まれます。
また、書き込まれた場合のみM5Stackのディスプレイ上に表示するようにします。

FeliCaの情報をFirebaseのRDBへ登録していく

ここからは、WiFiが必要になります。

コード全体
#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リーダーの併用上の注意

登録フェーズと待機フェーズを作っておく
// ボタン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のように登録するためのフェーズを設定して、それを用いた条件分岐をしておかないといけません。なぜなら、リーダーの上にカードを置き忘れたままにしておくと永遠とカードの情報を登録し続けてしまうという点です。
つまり、永遠とRDBへ書き込み続けることになるので無課金ゾーンを超えてしまう可能性があります。
クラウド上のデータベースを使う場合はしっかり動作確認を行い、無駄な通信・書き込み・読み込みが発生しないようにしましょう。

FirebaseJsonの活用

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")に登録することができ、ユーザーの登録時などに活用することができます。

時間の取得

時間の取得
#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の活用のなかでtimegetTimeAsString()を使ってますが、今回は登録した時間が取得できるようにしています。

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

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?