Raspberry-pi+Slackで入退室管理

2022-04-15(金)

コロナ禍で研究室がスッカラカンな状態でしたが,今年度からはかなり人の出入りも増えてきたので入退室記録のシステムを作りました。 使ったのはSonyのICカードリーダー RC-S380。Windows, macOS以外だと選択肢がこれ以外ほぼ無い状態のようです。

  1. 特定のiSerialNumberのUSB機器を探す
  2. 機器のUSB busとaddressを取得して接続
  3. 数秒おきにNFCリーダーの入力待機
  4. NFCリーダーで学生証のIDを読む
  5. 音を鳴らす
  6. 予め用意したdictに従ってID->人名変換をする
  7. Slackにメッセージを投げる
  8. 3に戻る

と,やってることは至ってシンプル。メッセージについては各自について入室状況を保持してトグル処理を考えたものの,大抵タッチし忘れたときに破綻するので毎日リセットとか色々面倒なので,入室用と退室用でICカードリーダーを別にする最もシンプルな方法に決定。

一番面倒だったのはpynfcとRC-S380の接続で,USBのバス番号は接続状況で変わる可能性があるのと,入室用と退室用を確実に決め打ちできるシリアルナンバー(iSerialNumber)をpyUsbでスキャン。ちなみにidVendor 0x054cはSony。

import usb
devs = usb.core.find(find_all = True, idVendor=0x054c)
for dev in devs:
    print(dev)

でずらずらと表示される一覧のiSerialNumberがそれ。ただしstringなので注意。 find(serial_number='xxxxx')となる。

もう一つの課題としては,どう自動実行するかで,Raspberry piの起動時にサービスとして走らせる方法がよく紹介されているものの,それも面倒なので

crontab -e

で @rebootを指定して入室待機と退室待機の2つのスクリプトを走らせる手抜きで対応。

import nfc
import binascii
import usb
import requests
import datetime
import time
import subprocess
'''
https://api.slack.com/
からCreate an appでbotを作る
https://note.com/npaka/n/n4bcb38a1ea74
らへん参照。多少UIは変わってるが問題ない。
そっちのテストは
https://api.slack.com/methods/chat.postMessage/test
からやる
CHANNELで指定するチャンネルは予め作るBotを指定してインストールしておく必要あり(チャンネルの設定→インテグレーション)
'''
TOKEN = 'ここに取得したtokenが入る'
CHANNEL = '入退室記録'
url = "https://slack.com/api/chat.postMessage"
headers = {"Authorization": "Bearer "+TOKEN}

# 入室USB ID '1412926' #うちのはこれだったが当然個体で変わる
# 退室USB ID '1413147'
usbid = '1412926' #これで入室用か退室用かを決める(もうちょいエレガントな方法もあるが手抜き)
if usbid == '1412926':
    msg_valid = ' が入室しました :tada: '
    msg_warn = ' が入室しました。 :warning: '
    mp3file = 'Enter.mp3'
else: 
    msg_valid = ' が退室しました :wave:'
    msg_warn = ' が退室しました。 :warning: '
    mp3file = 'Leave.mp3'
members = { #IDと名前の対応テーブル
    '0000000000000000': 'aaaaa',
    '1111111111111111': 'Momma'
}

def process(tag):
    idm = binascii.hexlify(tag.idm).decode('utf-8')
    dt_now = datetime.datetime.now().strftime('%Y%m%d-%H:%M:%S')
    try:
        who = members[idm]
        data  = {
           'channel': CHANNEL,
           'text': dt_now + ' ' + who + msg_valid,
        }
    except:
        data  = {
           'channel': CHANNEL,
           'text': dt_now + f' 不明ID {idm} '+ msg_warn,
        }
        print(f'Unknown ID {idm}')
    r = requests.post(url, headers=headers, data=data)
    # pythonからスマートに音が出ないのでsubprocessに逃げた
    subprocess.Popen(['mpg123', mp3file])

'''
シリアルナンバー決め打ちでUSB デバイスを探す
'''
def find_reader(serial_str):
    dev = usb.core.find(serial_number = serial_str)
    return dev

def main():
    dev = find_reader(usbid)
    usb_address = f'usb:{dev.bus:#03}:{dev.address:#03}'
    while True:
        time.sleep(3)
        with nfc.ContactlessFrontend(usb_address) as cf:
            tag = cf.connect(rdwr={'on-connect': process})

if __name__ == '__main__':
    main()

で,完成。↓のような感じで運用(スピーカーの裏にRaspberry piがある)

None

Slackにはこんな感じで送られてくる。

None

Category: Memo Tagged: Python Slack Raspberry-pi