SORACOM Advent Calendar 2020 の 16 日目の記事です。昨日は @noexpect さんの SORACOMのコストダッシュボードを作ってみた、でした。グラフ化されると、ふとした拍子に課金額が増えてもすぐに気付けるので良いですね!
はじめに
皆さんマイナンバーカードはもう入手されたでしょうか?私は当初から申し込まないといけないなと思いつつ重い腰が上がらなかったのですが、住民票がコンビニで取れるとかマイナポイントがお得とかをきっかけにようやく入手しました。このマイナンバーカードを使うためには PaSoRi が求められることが多いので購入したのですが、これを活用してみようというのが本日の内容となります。
今回利用したハードウェアは以下のとおりです。
- Raspberry Pi 3 Model B+ (Raspberry Pi OS Full 32-bit)
- 非接触ICカードリーダー/ライター PaSoRi RC-S380
- Suica カード
- Huawei MS2372h-607 + SORACOM IoT SIM
困りごと
私は6歳の子供がいるのですが、なかなか勉強に集中してくれず困っていました。1科目30分というように時間を決めてドリルを解かせるのですが、大体15分くらいで「もう終わり―」と言ってしまいます。色々考えた結果、勉強の開始と終了を記録する打刻システムを作ったら、もう少し時間を意識してくれるのではないかという結論に至りました。そこで PaSoRi の登場です。
勉強を開始する時にカードをかざすと開始を記録して絵が表示され、終了時にカードをかざすと終了を記録して別の絵を表示する動作をイメージしました。ただし、マイナンバーカードは無くすと非常に困るので、打刻には Suica カードを使いました。
ライブラリの調査
PaSoRi にカードをかざすとそのカードの ID を読み取ってくれるようなライブラリを探したところ、Pythonのライブラリを見つけました。このライブラリにはサンプルコードが付いており、それを使うと比較的容易に ID を取ることができました。そこでこのライブラリを使って SORACOM にデータを送信するコードを書こうと思ったのですが、そこで1点問題が。私はあまり Python のコードが書けないので、そのコードを公開するというのはできれば避けたいと思いました。そこでコードを書かずになんとか実現できないかと考え Node-RED のライブラリを探したところ、ありがたいことに上記の Python ライブラリを使うノードが見つかったので、こちらで実現することにしました。
準備
先ほどのライブラリの説明でnode-nfcpy-id
を利用すると書かれているので、node-nfcpy-idのページを参考に、以下のコマンドを実行します。(node-nfcpy-id
自体のインストールは Node-RED の node-red-contrib-nfcpy-id
ノードのインストール時に自動的に行われるので不要です。)PaSoRi は初めから Raspberry Pi に挿したままの状態で作業して大丈夫です。
sudo apt-get install python-usb python-pip -y
必要なライブラリのインストールと、PaSoRi RC-S380 用の設定をします(こちらを見た感じ、今出回っているのはこの型番だけのようです)。
sudo apt-get install python-usb python-pip -y
sudo pip install -U nfcpy-id-reader
cat << EOF | sudo tee /etc/udev/rules.d/nfcdev.rules
SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="06c3", GROUP="plugdev"
EOF
インストールが完了したら、Raspberry Pi を再起動します。
sudo reboot
他の準備として、SORACOM への接続は USB ドングルの Huawei MS2372h-607 を利用して SORACOM Air へ接続できるようにしておきました。
Node-REDフローの作成
さて準備はできたので、先ほどのライブラリを利用した Node-RED のフローを作成します。Raspberry Pi OS Full であれば初めから Node-RED がインストールされていますが、他のエディションの場合は Node-RED がインストールされていないので、こちらを参考にインストールします。
今回、Node-RED を利用して実現したいことは、「カードがタッチされた時に、奇数回(初回含む)は開始を記録し、偶数回は終了を記録する」というものです。それを実現するために作成したフローは、以下のようになりました。
それぞれのノードについて説明していきます。
-
nfcpy-id
:PaSoRi に Suica をかざすと、カードの ID を含んだメッセージを送信します。設定はデフォルトで OK です。 -
Increment count
:フローコンテキストというオンメモリの変数を利用し、count
という変数のカウンタを1ずつ増やします。初回は変数が存在しないので、その場合は1を設定します。ここで利用している JSONata は非常に便利で、これを利用することで function ノードを使ったコーディングを減らすことができるのでオススメです!
-
Start or finish
:変数count
が偶数かどうかで、開始か終了かを判断します。こちらも JSONata を利用しました。
-
Start studying!
/Finish studying!
:SORACOM Lagoon で表示するファイルのパスを指定します。パスの詳細については、後述します。
-
To SORACOM Harvest
:先ほどmsg.payload
に設定したデータを、HTTP リクエストで SORACOM Harvest に送信します。
SORACOMの設定
Node-REDの準備は完了したので、次にSORACOMの設定をします。SORACOM Harvest Data を利用して受け取ったデータを蓄積し、SORACOM Lagoon を利用してデータや絵をダッシュボードに表示したいと思います。
グループの設定
Raspberry Pi で利用している SIM グループの設定をします。データの保存には SORACOM Harvest Data を使うので有効にします。さらに絵も表示したいので、SORACOM Harvest Files も有効にします。この時、HARVEST DATA 連携
も有効にし、連携対象のファイルパス
にはファイルを格納するパスを指定します。今回は、/kaz/.*
(/kaz
というパス以下を対象)と指定しました。
ファイルのアップロード
次にファイルをアップロードするため、メニューから SORACOM Harvest Files
に移動します。アップロード
ボタンをクリックするとダイアログが表示されるので、ファイルをドラッグ&ドロップします。するとアップロード先のフルパス
が表示されるので、先ほど設定した連携対象のファイルパス (/kaz)
を含め、開始時の絵のパスを/kaz/start.png
、終了時の絵のパスを/kaz/finish.png
としてアップロードしました。
こんな感じになってれば OK!
SORACOM Lagoonの設定
最後に SORACOM Lagoon のダッシュボードの設定をします。今回は、先ほどアップロードした絵を表示するために SORACOM Dynamic Image Panelを設定するの Harvest Files モードを利用します。具体的には、SORACOM Dynamic Image Panel
を追加し、メトリック
タブで Raspberry Pi が利用している SIM を選択したあと、Table
を選択します。
そして、設定
タブでは以下のように設定します。モードは、先ほど触れた通り Harvest Files
を選択します。名前は、image
を指定することで、Node-RED から送信した{"image":"/v1/files/private/kaz/finish.png"}
のimage
の値を利用します。この image
の値には、/v1/files/private
までは固定で、その後ろに SORACOM Harvest Files のパス(今回のケースだと /kaz/start.png
)を繋げたパスを指定することで、Harvest Files にアップロードした画像を表示できます。先ほどの Node-RED の Start studying!
/ Finish studying!
ノードでは、実はこの値を指定していました。
時間の記録には Table
パネルを利用します。こちらはパネルを追加して、Time
でソートするだけでOKです。
以上で設定は全て完了しました!
実際の動作
ハードウェアの見た目はこちらです。右上はディスプレイの HDMI ケーブルと電源ケーブル、左は USB ドングルと PaSoRi の USB ケーブルで、まずまずシンプルかと思います。Raspberry Pi を操作したい時は、SORACOM Napter を使うので、マウスとキーボードは不要です!
そして動作はこちら!
まとめ
以上、非常に簡易的な学習記録システムでしたがいかがでしたでしょうか?(ほぼ) コーディング無しにシステムを作れた点にぜひ注目して頂ければと思います。そして肝心のシステムの方は、今のところうちの子供は面白がって使ってくれていますが、こちらは果たしていつまで続くことやら、、いずれにしても「マイナンバーカードを使うために PaSoRi 買った」といった方達に、今回の内容が何かの参考になれば幸いです。
今回作成した Node-RED フローのデータは以下です。クリップボードから簡単にフローを読み込むことができるので、ぜひお試しください。
[{"id":"b0542dfa.f1b5f","type":"http request","z":"d1deb604.e682c8","name":"To SORACOM Harvest","method":"POST","ret":"txt","paytoqs":"ignore","url":"http://harvest.soracom.io","tls":"","persist":false,"proxy":"","authType":"","x":660,"y":280,"wires":[[]]},{"id":"6f7fd0d1.d306","type":"change","z":"d1deb604.e682c8","name":"Increment count","rules":[{"t":"set","p":"count","pt":"flow","to":"$flowContext(\"count\") ? $flowContext(\"count\") + 1 : 1","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":180,"wires":[["4db6465d.7e9678"]]},{"id":"4db6465d.7e9678","type":"switch","z":"d1deb604.e682c8","name":"Start or finish","property":"count","propertyType":"flow","rules":[{"t":"jsonata_exp","v":"$boolean($flowContext(\"count\") % 2)","vt":"jsonata"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":250,"y":280,"wires":[["1c06728d.85329d"],["cf09ce95.4128b"]]},{"id":"1c06728d.85329d","type":"change","z":"d1deb604.e682c8","name":"Start studying!","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"image\": \"/v1/files/private/kaz/start.png\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":440,"y":260,"wires":[["b0542dfa.f1b5f"]]},{"id":"cf09ce95.4128b","type":"change","z":"d1deb604.e682c8","name":"Finish studying!","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"study\":false,\"image\":\"/v1/files/private/kaz/finish.png\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":440,"y":300,"wires":[["b0542dfa.f1b5f"]]},{"id":"66cf3421.06536c","type":"nfcpy-id","z":"d1deb604.e682c8","waitTime":"1","name":"","x":360,"y":180,"wires":[["6f7fd0d1.d306"]]}]
今回のような SORACOM と Node-RED を組み合わせたシステムにご興味を持たれた方は、GPSマルチユニット SORACOM EditionとNode-REDを組み合わせた子どもの見守りシステムもぜひご覧ください!