学生証を鍵にしてみた?!


はじめに

 はじめまして.今回の記事は合同ブログ3号目です.なんの合同ブログかといいますと,セキュリティキャンプのグループワークで”控え目関西人”として合同ブログを書くことになり,その3人目という感じです.今までの記事はこちらです.
https://modestkansaipeople.hatenablog.com/entry/tsudoinoba

概要

 大学3回生くらいになるとゼミ室に配属されると思いますが,そのゼミ室の鍵を物理キーにするのは信頼性は高いが,とても不便ですよね...情報系の学生なら自分で改善すればいいじゃん!ということで,信頼性を確保しつつ,利便性を上げます.利便性向上として学生が常に携帯しているだろう”学生証”を鍵にすることにしました.学生証の解析は先人がやってくくださっているので,ハードとソフトのシステムの構築だけです.

環境

ソフト実装

 鍵だけなら鍵を回す処理だけつくればいいのですが,教授の何気ない

  「誰がゼミ室にいるかもそれでわかるといいよね♪入退室管理システムみたいな」

これにより要件が少し複雑になりました...錠を回すときだけ学生証をタッチするのではなく入退出の度にタッチされることになります.

要件

  • 学生証から認証情報を取得
  • 鍵の開け閉め
  • 入室と退出の区別
  • 鍵の開け閉めの条件

以上の4つが最低要件として定義しました.今までの記事やリポジトリを見たらわかる通り今までなにも作ったことがないので恐らく欠陥だらけ...

学生証から学生情報を抜き出す

 学生証はNFCのためNFCリーダからアクセスします.
pythonではNFC用のライブラリの"nfcpy"を使用します.(公式:https://github.com/nfcpy/nfcpy.git)
PaSoRiを使うのに自分は権限がないって怒られた場合は下記のコマンドを実行してください.私はもうaliasしました...
2-2:1.0はusblibコマンドで出てきた自分のPaSoRiのやつにしてください.

sudo sh -c "echo -n '2-2:1.0' > /sys/bus/usb/drivers/port100/unbind"

cloneするとexampleにtagtool.pyがあるので,それを実行すると吸えるデータはすべて出力されます.もし,権限など実行時エラーがあれば解決方法まで出力されるため,一度これを実行することをおすすめします.単純にNFCの中の一部データが簡単に見れるので構造の把握のために実行しときましょう.

 そうすると学生番号が保存されている領域とサイズが分かったのでそこを読み出すコードを書きます.

scan.py(一部抜粋)
def read_idm_No(self, tag):
    # read IDm        
    self.idm = binascii.hexlify(tag.idm).upper().decode('utf-8')
    # defined service code & block
    sc = nfc.tag.tt3.ServiceCode(0x120B >> 6, 0x120B & 0x3f)
    bc = nfc.tag.tt3.BlockCode(0, service=0)
    # read blockdata
    block_data = tag.read_without_encryption([sc], [bc])
    self.No = block_data[0:8].decode("utf-8")
    return True

学生番号が保存されている領域が書き込みが可能な領域のため鍵情報として学生番号とIDmを取得します.

錠を回す処理

 サーボモータをPWM制御で操作します.出力制御は空回りしたり,強すぎたりしないように実験して調整したので,ここは使われる錠の種類によって変わると思います.

magic.py
import RPi.GPIO as GPIO
import time
servo_GPIO = 18 

def openSESAMI():
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(servo_GPIO, GPIO.OUT)
    servo = GPIO.PWM(servo_GPIO, 50)
    servo.start(0)
    servo.ChangeDutyCycle(3.3)
    time.sleep(0.5)
    servo.stop()
    GPIO.cleanup()

def lockSESAMI():
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(servo_GPIO, GPIO.OUT)
    servo = GPIO.PWM(servo_GPIO, 50)
    servo.start(0)
    servo.ChangeDutyCycle(12.0)
    time.sleep(0.5)
    servo.stop()
    GPIO.cleanup()

入退出管理処理と錠の開け閉めの条件

 入退出管理として,NFCのタッチログを利用します.ログはusername/IDm/学生番号/H:M:Sを日付ごとに追行する形式にし,入退出はその日のログの各行のusernameを取得して,奇数回目か偶数回目かで判定する.

scan.py(一部抜粋)
def check_room_state(self, user):
    path = 'logs/' + str(self.year) + '/' + str(self.mon) + '/' + str(self.day) + '/access.log' #file path
    with open(path) as f:
        logs = f.readlines() #get logs
        f.close()

    # nobody's room
    if not logs:
        return 2

    cnt = 0
    for i in logs:
        log_user = i.split(sep='/') #get username form logs
        if user == log_user[0]:
            cnt += 1
        if cnt % 2 == 1: #when access time is even, enter the room
            return 0
        else: #when access time is odd, leave the room
            return 1
def main():
    sl = SmartLock()
    key_state = 2
    room_state = 0
    while True:
        print ("touch card:")
        sl.scan()#scan student card
        user = sl.authentication()#authentication
        sl.get_time()
        sl.Log(user)#take log
        if user == 0:#no regist user
            print("unknown")
            pass
        else:
            flag = sl.check_room_state(user)
            print(user, key_state, flag, room_state)
            if key_state == 2 or key_state == 0 and flag == 0 and room_state == 0:
                print("open")
                magic.openSESAMI()
                key_state = 1
                room_state += 1
            elif key_state == 1 and flag == 1 and room_state == 1:
                print("lock")
                magic.lockSESAMI()
                key_state = 0
                room_state = 0
            elif key_state == 1 and flag == 0:
                print("entry")
                room_state += 1
            elif key_state == 1 and flag == 1 and room_state > 1:
                print("leave")
                room_state -= 1
            else:
                print("ERROR!")

鍵の状態と部屋にいる人数を変数として保持しておき,鍵が閉まっているかつ部屋の人数が0人で入室の時鍵を開けて,鍵が開いているかつ部屋の人数が1人で退出の時鍵を閉めます.それ以外は入退出による部屋の人数の変更のみ行います.

ハード実装

 これでシステムは完成したのであとは実際に錠を回す物理実装だけです.

サーボモータ

 うちの研究室では以前にも似たようなものが使われていたのですが,引継ぎの時に壊れて鍵が物理キーでも開けられない事件が発生したらしく,要件として,物理キーでも最悪開けられることが望ましいことが上げられました.そのためソフトにも関係しますが,通電していなければ軸が固定されない性質を生かして,鍵を開け閉めするときだけ通電するようにして,物理キーでもエスケープできるようにしました.

SG-90はそのままでは錠を回せないので引っかかりを作りました.サイコロは100均でいい感じのサイズだったので...3Dプリンタで作ってもよかったのですが,CADとか使ったことないので面倒でした...

SG-90は5VピンとGNDピンと制御するために好きなGPIO制御ピンに挿してください.

ハード全体図

まとめ

 あとはモータとかラズパイとかリーダーをドアにどう固定するかで完成です.学生証から学生番号とIDmを読み出すのはたくさん記事が上がってるのですぐに実装できました.そして認証のうまい実装方法が分からなかったのでDBを建てて問い合わせのレスポンスで実装しました.こちら改善方法募集です...鍵の開け閉めの条件式がごちゃごちゃしているのでこちらも改善したいです.でもまぁ,はじめてなにか作った割にはうまくできたんじゃないかと思ってます.

そして最後にまた教授の一言により...

「お!いいねぇ じゃあ使えてないThingWorxあるんだけどそれにデータあげていい感じに見れるやつも作ってくれない?」

次回 ThingWorxにデータを上げて表示するまで作ってみた(ThingWorxなんて聞いたことないんですが...)