9
6

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 5 years have passed since last update.

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

Last updated at Posted at 2019-04-24

はじめに

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

用意した物

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

実装の方針

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

環境設定

必要なライブラリのインストール

$ 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ディレクトリを置いてください。

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

IDmのハッシュ化

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

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

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でスマートロックをつくった

9
6
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
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?