Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
6
Help us understand the problem. What is going on with this article?
@kikuyan8540

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

More than 1 year has passed since last update.

はじめに

社員証をピッとタッチすると会議とかイベントの出欠取れないかなと職場で話題になっていたので、試しに作ってみました。
とりあえずカード識別用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でスマートロックをつくった

6
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
6
Help us understand the problem. What is going on with this article?