はじめに
社員証をピッとタッチすると会議とかイベントの出欠取れないかなと職場で話題になっていたので、試しに作ってみました。
とりあえずカード識別用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の設定
調べれば色々やり方の解説があるのでとりあえず項目だけ
- Maker Webhocksを有効にする
- 新しいアプレットを作成する
- This:Webhocksを選択、イベント名を"touch_nfc"のように設定
- That:GoogleSheetsを選択、アクション"Add row to spreadsheet"を選択。シートの名前など細かい設定はとりあえず既定値で
- WebhocksのDocumentation画面でURLとkeyを確認。この画面で"Test It"を押してGoogle Sheetが新規作成されればOK
プログラム本体
適当なディレクトリに置いて、直下にlogディレクトリを置いてください。
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が光りブザーが鳴ります。
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でスマートロックをつくった