Raspberry PiとPaSoRiでFelicaのNFCタグを読んでみる

# はじめに
社員証をピッとタッチすると会議とかイベントの出欠取れないかなと職場で話題になっていたので、試しに作ってみました。
とりあえずカード識別用ID(IDｍ)を取得してネットワーク越しにタッチした時間と識別用IDを取得できることを目標にしています。

# 用意した物
* RaspberryPi 3 Model B
* PaSoRi(SONY RC-S380)
* 社員証とかSuicaとか
* 読み取り確認用のLEDや電子ブザーとか手持ちのこまごまとしたパーツ

# 実装の方針
* 個人的な好みと勉強を兼ねてPythonで作る
    * nfcpyがPython3未対応らしいので今回は2系列で
* NFCタグの読み込みはnfcpyを使用
* データはIFTTTのWebhocks経由でGoogle Sheetsに記録
* セキュリティとか
    * 他のシステムやスマートキーで使っている可能性があるので、万が一流出したことを考えてIDｍそのものをデータとして保持しない
    * 氏名とか社員番号とかの情報は紛失の可能性がある端末側に持たせない。必要ならバックエンド側で管理する

# 環境設定
## 必要なライブラリのインストール

```sh:
$ pip install nfcpy
```
追記：標準のurllib2を使うようにした

## IFTTTの設定
調べれば色々やり方の解説があるのでとりあえず項目だけ
1. Maker Webhocksを有効にする
2. 新しいアプレットを作成する
3. This:Webhocksを選択、イベント名を"touch_nfc"のように設定
4. That:GoogleSheetsを選択、アクション"Add row to spreadsheet"を選択。シートの名前など細かい設定はとりあえず既定値で
5. WebhocksのDocumentation画面でURLとkeyを確認。この画面で"Test It"を押してGoogle Sheetが新規作成されればOK

# プログラム本体
適当なディレクトリに置いて、直下にlogディレクトリを置いてください。

```python:nfc_demp.py
from urllib2 import Request, urlopen, URLError
import nfc          # pip install nfcpy
import binascii
import hashlib
import RPi.GPIO as GPIO
import time
import json
from logging import getLogger, StreamHandler, Formatter, DEBUG
from logging.handlers import TimedRotatingFileHandler, SMTPHandler


GPIO.setmode(GPIO.BCM)
LEDPin = 26
BuzzerPin = 13
GPIO.setup(LEDPin, GPIO.OUT)
GPIO.setup(BuzzerPin, GPIO.OUT)


class MyNFC:
    def read(self):
        last_tag = None
        last_time = time.time()
        while True:
            try:
                clf = nfc.ContactlessFrontend(self.device)
            except IOError as e:
                self._error()
                self.logger.error(e)
                time.sleep(5)
                clf.close()
                continue
            tag = clf.connect(rdwr={'on-connect' :lambda tag: False})
            clf.close()
            if (tag.idm == last_tag) and (time.time() - last_time < 5):
                continue
            nfc_idm = binascii.hexlify(tag.idm)
            nfc_idm_hash = hashlib.sha256(nfc_idm).hexdigest()
            #send data to ifttt
            url = self.service
            data = {
                'value1':time.time(), 'value2':nfc_idm_hash, 'value3':self.id,
                }
            headers = {'Content-Type':'application/json',}
            req = Request(url, json.dumps(data).encode(), headers)
            try:
                urlopen(req)
            except URLError as e:
                self._error()
                if hasattr(e, 'reason'):
                    self.logger.error(e.reason)
                elif hasattr(e, 'code'):
                    self.logger.error(e.code)
                continue
            self._notice()
            last_tag = tag.idm
            last_time = time.time()
            self.logger.info((self.id, nfc_idm_hash))
            print(tag)


    def _notice(self):
        GPIO.output(LEDPin, True)
        for i in range(0,2):
            GPIO.output(BuzzerPin, True)
            time.sleep(0.1)
            GPIO.output(BuzzerPin, False)
            time.sleep(0.1)
        GPIO.output(LEDPin, False)


    def _error(self):
        GPIO.output(LEDPin, True)
        GPIO.output(BuzzerPin, True)
        time.sleep(1)
        GPIO.output(BuzzerPin, False)
        GPIO.output(LEDPin, False)


class Logger:
    def __init__(self, handler = StreamHandler()):
        self.logger=getLogger(__name__)
        formatter=Formatter(fmt='%(asctime)s %(name)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        handler.setLevel(DEBUG)
        self.logger.setLevel(DEBUG)
        self.logger.addHandler(handler)


    def error(self, message):
        self.message = message
        self.logger.error(self.message)


    def debug(self, message):
        self.message = message
        self.logger.debug(self.message)


    def info(self, message):
        self.message = message
        self.logger.info(self.message)


if(__name__=='__main__'):
    mynfc = MyNFC()
    mynfc.logger = Logger(TimedRotatingFileHandler('log/nfc_demo.log', when='D'))
    mynfc.id = 'raspi3'
    mynfc.service = 'https://maker.ifttt.com/trigger/touch_nfc/with/key/{Your key here.}'
    mynfc.device = 'usb'
    mynfc.read()
```

# タッチ成功のフィードバック
デバッグ用にコンソール画面にタグ情報を表示していますが、実運用では画面なしで使うことになると思いますので、正しくタッチして通信できたことが判るようにLEDやビープ音でフィードバックします。
GPIOの26ピン→LED→GNDで繋ぐとLEDが光ります。
GPIOの13ピン→自励式電子ブザー→GNDでブザーがピピっと鳴ります。

エラーがあったときは1秒間LEDが光りブザーが鳴ります。

![raspi3_LED_Buzzer_ブレッドボード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404407/ccb041cb-fd64-fc00-9e01-1ab57dcff0df.png)


# IDｍのハッシュ化
ソルトが無いので気休めですがIDｍそのものではなくハッシュを保存しています。実際に出席管理やタイムカード的に使うときは、氏名や社員番号といった情報とIDｍハッシュのルックアップテーブルをバックエンド側で持つことになります。

# sudoなしでRC-S380を認識できるようにする

```sh:
sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c3\", GROUP=\"plugdev\" >> /etc/udev/rules.d/nfcdev.rules'
```
# プログラムの実行

カレントディレクトリに移動して、`python nfc_demo.py`で実行します。

# 参考記事
IFTTTの設定とか色々参考になりました
[Raspberry Piでスマートロックをつくった](https://qiita.com/undo0530/items/669b6ee6b2277ea64b33)