できあがるもの
はじめに
この記事ではFeliCa RC-S620Sを使って、SuicaやPasmoなどといった電子カードを読み取れるようにしていきます。
また、Firebaseとも組み合わせてFeliCaのmIDをデータベースへ登録していきましょう。
M5StackとFirebaseを連携してスマートロックみたいなものを作る!①
M5StackとFirebaseを連携してスマートロックみたいなものを作る!②
M5StackとFirebaseを連携してスマートロックみたいなものを作る!③
M5StackとFirebaseを連携してスマートロックみたいなものを作る!④
M5StackとFirebaseを連携してスマートロックみたいなものを作る!⑤
必要なもの
- FeliCa リーダー・ライター RC-S620S
- FeliCa RC-S620S/RC-S730 ピッチ変換基板のセット(フラットケーブル付き)
- 10枚セット FeliCa [フェリカ] カード Lite-S (無地)
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との接続
変換基盤は左からVDD(電源)、RX、TX、GND、無し、GNDというようになっているので、画像のようにジャンパーケーブル等で接続しましょう。
ここで気をつけたいのが、「変換基盤のRX」→「M5StackのTX」、「変換基盤のTX」→「M5StackのRX」というように接続する点です。
変換基盤とカードリーダーをフラットケーブルで接続するのですがリーダー側への接続がちょっと難しい、というか入ったかどうかわかりにくいですが頑張って差し込んで下さい!
フラットケーブルに少しでっぱりがあるので、爪が長いと押し込みやすいと思います笑
M5StackのディスプレイにICカードの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の活用
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
の活用のなかでtime
にgetTimeAsString()
を使ってますが、今回は登録した時間が取得できるようにしています。
次回はqueryを用いた検索機能を実装してカードが登録されているかどうかを判定するようにしていきます!