PaSoRi を使った学習記録システムを SORACOM と Node-RED で作ってみた

[SORACOM Advent Calendar 2020](https://qiita.com/advent-calendar/2020/soracom) の 16 日目の記事です。昨日は [@noexpect](https://qiita.com/noexpect) さんの [SORACOMのコストダッシュボードを作ってみた](https://qiita.com/noexpect/items/c1f083e9e2f5f47f9426)、でした。グラフ化されると、ふとした拍子に課金額が増えてもすぐに気付けるので良いですね！

## はじめに
皆さんマイナンバーカードはもう入手されたでしょうか？私は当初から申し込まないといけないなと思いつつ重い腰が上がらなかったのですが、住民票がコンビニで取れるとかマイナポイントがお得とかをきっかけにようやく入手しました。このマイナンバーカードを使うためには PaSoRi が求められることが多いので購入したのですが、これを活用してみようというのが本日の内容となります。

今回利用したハードウェアは以下のとおりです。

* Raspberry Pi 3 Model B+ (Raspberry Pi OS Full 32-bit)
* 非接触ICカードリーダー/ライター PaSoRi RC-S380
* Suica カード
* Huawei MS2372h-607 + SORACOM IoT SIM

## 困りごと
私は６歳の子供がいるのですが、なかなか勉強に集中してくれず困っていました。１科目３０分というように時間を決めてドリルを解かせるのですが、大体１５分くらいで「もう終わり―」と言ってしまいます。色々考えた結果、勉強の開始と終了を記録する打刻システムを作ったら、もう少し時間を意識してくれるのではないかという結論に至りました。そこで PaSoRi の登場です。
勉強を開始する時にカードをかざすと開始を記録して絵が表示され、終了時にカードをかざすと終了を記録して別の絵を表示する動作をイメージしました。ただし、マイナンバーカードは無くすと非常に困るので、打刻には Suica カードを使いました。

## ライブラリの調査
PaSoRi にカードをかざすとそのカードの ID を読み取ってくれるようなライブラリを探したところ、[Pythonのライブラリ](https://nfcpy.readthedocs.io/en/latest/topics/get-started.html)を見つけました。このライブラリにはサンプルコードが付いており、それを使うと比較的容易に ID を取ることができました。そこでこのライブラリを使って SORACOM にデータを送信するコードを書こうと思ったのですが、そこで１点問題が。私はあまり Python のコードが書けないので、そのコードを公開するというのはできれば避けたいと思いました。そこでコードを書かずになんとか実現できないかと考え [Node-RED のライブラリ](https://flows.nodered.org/)を探したところ、ありがたいことに[上記の Python ライブラリを使うノード](https://flows.nodered.org/node/node-red-contrib-nfcpy-id)が見つかったので、こちらで実現することにしました。

## 準備
先ほどのライブラリの説明で`node-nfcpy-id`を利用すると書かれているので、[node-nfcpy-id](https://www.npmjs.com/package/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 用の設定をします（[こちら](https://www.sony.co.jp/Products/felica/consumer/products/list.html)を見た感じ、今出回っているのはこの型番だけのようです）。

```
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](https://soracom.jp/products/module/ms2372h_607/) を利用して SORACOM Air へ接続できるようにしておきました。

## Node-REDフローの作成
さて準備はできたので、先ほどのライブラリを利用した Node-RED のフローを作成します。Raspberry Pi OS Full であれば初めから Node-RED がインストールされていますが、他のエディションの場合は Node-RED がインストールされていないので、[こちら](https://nodered.jp/docs/getting-started/raspberrypi)を参考にインストールします。

今回、Node-RED を利用して実現したいことは、「カードがタッチされた時に、奇数回（初回含む）は開始を記録し、偶数回は終了を記録する」というものです。それを実現するために作成したフローは、以下のようになりました。

![スクリーンショット 2020-12-12 18.49.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/83244f61-ca2c-9f48-3eeb-4ccfb0be2307.png)

それぞれのノードについて説明していきます。

- `nfcpy-id`：PaSoRi に Suica をかざすと、カードの ID を含んだメッセージを送信します。設定はデフォルトで OK です。

- `Increment count`：[フローコンテキスト](https://nodered.jp/docs/user-guide/context)というオンメモリの変数を利用し、`count`という変数のカウンタを１ずつ増やします。初回は変数が存在しないので、その場合は１を設定します。ここで利用している [JSONata](https://jsonata.org/) は非常に便利で、これを利用することで function ノードを使ったコーディングを減らすことができるのでオススメです！

![スクリーンショット 2020-12-12 18.40.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/061d37c1-5290-29fa-1bc9-965d4c55608c.png)

- `Start or finish`：変数`count`が偶数かどうかで、開始か終了かを判断します。こちらも JSONata を利用しました。

![スクリーンショット 2020-12-12 18.41.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/4f260f86-2487-5df1-02de-6f0672c6801f.png)

- `Start studying!` / `Finish studying!`：SORACOM Lagoon で表示するファイルのパスを指定します。パスの詳細については、[後述](#soracom-lagoonの設定)します。

![スクリーンショット 2020-12-12 18.48.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/3b0e77b0-1d24-32c5-bb1b-48d28dbb613a.png)

- `To SORACOM Harvest`：先ほど`msg.payload`に設定したデータを、HTTP リクエストで SORACOM Harvest に送信します。

![スクリーンショット 2020-12-12 18.41.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/9765f174-b4ac-48f8-ef10-356d6903414b.png)

## SORACOMの設定
Node-REDの準備は完了したので、次にSORACOMの設定をします。SORACOM Harvest Data を利用して受け取ったデータを蓄積し、SORACOM Lagoon を利用してデータや絵をダッシュボードに表示したいと思います。

### グループの設定
Raspberry Pi で利用している SIM グループの設定をします。データの保存には SORACOM Harvest Data を使うので有効にします。さらに絵も表示したいので、SORACOM Harvest Files も有効にします。この時、`HARVEST DATA 連携`も有効にし、`連携対象のファイルパス`にはファイルを格納するパスを指定します。今回は、`/kaz/.*`（`/kaz`というパス以下を対象）と指定しました。

![group.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/1b8c3cbe-c438-fcf4-b312-6cb8838f373c.png)

### ファイルのアップロード
次にファイルをアップロードするため、メニューから `SORACOM Harvest Files`に移動します。`アップロード`ボタンをクリックするとダイアログが表示されるので、ファイルをドラッグ＆ドロップします。すると`アップロード先のフルパス`が表示されるので、先ほど設定した`連携対象のファイルパス (/kaz)`を含め、開始時の絵のパスを`/kaz/start.png`、終了時の絵のパスを`/kaz/finish.png`としてアップロードしました。

![upload.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/15f51d2e-1dd8-6812-c762-806fbc19ca46.png)

こんな感じになってれば OK！

![スクリーンショット 2020-12-12 20.26.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/050628a2-e273-728b-ddbb-11ce60085fd4.png)

### SORACOM Lagoonの設定
最後に SORACOM Lagoon のダッシュボードの設定をします。今回は、先ほどアップロードした絵を表示するために [SORACOM Dynamic Image Panelを設定する](https://dev.soracom.io/jp/start/lagoon-panel/#soracomdynamicimage_panel)の Harvest Files モードを利用します。具体的には、`SORACOM Dynamic Image Panel`を追加し、`メトリック`タブで Raspberry Pi が利用している SIM を選択したあと、`Table`を選択します。

![metric.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/021997eb-7803-1372-992a-78bc9b54f25e.png)

そして、`設定`タブでは以下のように設定します。モードは、先ほど触れた通り `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!` ノードでは、実はこの値を指定していました。

![image_panel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/cdd4e043-82d9-8c0f-2324-2a57dc9d57ac.png)

時間の記録には `Table` パネルを利用します。こちらはパネルを追加して、`Time` でソートするだけでOKです。

![sort.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/009c98b1-f364-dbee-15eb-065534ec9c1b.png)

以上で設定は全て完了しました！

## 実際の動作
ハードウェアの見た目はこちらです。右上はディスプレイの HDMI ケーブルと電源ケーブル、左は USB ドングルと PaSoRi の USB ケーブルで、まずまずシンプルかと思います。Raspberry Pi を操作したい時は、[SORACOM Napter](https://soracom.jp/services/napter/) を使うので、マウスとキーボードは不要です！

![hardware.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/09845845-10bb-2048-70a8-0dc5d2c1b06e.png)

そして動作はこちら！

![800-4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377165/2c1d449e-4a05-1874-2f7f-c494fdc379dd.gif)

## まとめ
以上、非常に簡易的な学習記録システムでしたがいかがでしたでしょうか？(ほぼ) コーディング無しにシステムを作れた点にぜひ注目して頂ければと思います。そして肝心のシステムの方は、今のところうちの子供は面白がって使ってくれていますが、こちらは果たしていつまで続くことやら、、いずれにしても「マイナンバーカードを使うために PaSoRi 買った」といった方達に、今回の内容が何かの参考になれば幸いです。

今回作成した Node-RED フローのデータは以下です。[クリップボードから簡単にフローを読み込む](https://nodered.jp/docs/user-guide/editor/workspace/import-export)ことができるので、ぜひお試しください。

```
[{"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を組み合わせた子どもの見守りシステム](https://speakerdeck.com/soracom/node-red-with-gps-multiunit-node-red-con-tokyo-2020)もぜひご覧ください！