🪪

PaSoRIとFlaskを使ってICカードで部活の入退室管理をする

2023/03/18に公開

こんにちは。Takaと申します。普段は大阪の自称進学校でプログラミングを学んでいます。
昔、似たようなシステムを作ったのですが利便性が無さすぎて採用されなかったのでリベンジです。
Pythonを使って本格的にプロジェクトをするのは初めてなので大目に見てください。

PaSoRiって何やねん

PaSoRiとはソニー株式会社が提供する非接触ICカードリーダーです。FeliCa規格のカードなら何でも読み込めます。僕が700クレジットを溶かしたAimeのカードももちろん読み込めます。ちなみに日本橋で300円で売ってたそうです。動いたからヨシ!

環境

  • Python 3.10.8
  • Flask 2.2.3
  • Werkzeug 2.2.3
  • nfcpy 1.0.4
  • Jinja2 3.1.2
  • MacBook Air M1, 2020 16GB

ユーザー登録

def user_register():
    _name = request.form['name']
    _grade = request.form['grade']
    _class = request.form['class']
    _number = request.form['number']
    _id = request.form['id']

    try:
        clf = nfc.ContactlessFrontend('usb')
        tag = clf.connect(rdwr={'on-connect': lambda tag: False})
        _idm = binascii.hexlify(tag.idm).decode('utf-8')
        clf.close()

        con = sqlite3.connect(DATABASE)
        con.execute('INSERT INTO PERSONS VALUES(?, ?, ?, ?, ?, ?, ?)',
                 [_idm, _name, _grade, _class, _number, -1, _id])
        con.commit()
        con.close()
    except AttributeError:
        pass



    return redirect(url_for('index'))

FlaskからFormで諸々のデータを受信してから、

clf = nfc.ContactlessFrontend('usb')
tag = clf.connect(rdwr={'on-connect': lambda tag: False})
_idm = binascii.hexlify(tag.idm).decode('utf-8')
clf.close()

というところでカードを読み込んでいます。_idmとは全てのFeliCaカードの識別番号で、重複することはまずあり得ません。また、

con = sqlite3.connect(DATABASE)
con.execute('INSERT INTO PERSONS VALUES(?, ?, ?, ?, ?, ?, ?)',
	[_idm, _name, _grade, _class, _number, -1, _id])
con.commit()
con.close()

でデータベースに新規の値を挿入しています。FeliCa以外のカードが読み込まれると処理が途中で中断され、値は挿入されません。

入退室

def handle():
    try:
        clf = nfc.ContactlessFrontend('usb')
        tag = clf.connect(rdwr={'on-connect': lambda tag: False})
        idm = binascii.hexlify(tag.idm).decode('utf-8')
        clf.close()

        con = sqlite3.connect(DATABASE)
        con.execute('UPDATE PERSONS SET STATUS = STATUS * -1 WHERE IDM = "' + idm + '"')
        con.commit()
        con.close()
    except AttributeError:
        pass

    return redirect(url_for('index'))

さっきとほぼ同じなので各コードの説明は省きますが、IDMを読み取って対応するデータのSTATUSを-1倍しています。実際に表示するhtmlではこの値が-1なら退室中、1なら入室中と言うふうに処理を下しています。

ホーム画面

{% extends "bootstrap/base.html" %} 
{% import "bootstrap/wtf.html" as wtf %} 
{%block title %}入退室管理画面{% endblock %} {% block navbar %}
<table class="table table-bordered">
    <thead>
        <tr>
            <th>ID</th>
            <th>学籍番号</th>
            <th>学年</th>
            <th></th>
            <th>番号</th>
            <th>名前</th>
            <th>ステータス</th>
        </tr>
    </thead>
    <tbody>
        {% for p in persons %}
        <tr>
            <th>{{p.idm}}</td>
            <th>{{p.id}}</th>
            <th>{{p.grade}}</th>
            <th>{{p.class}}</th>
            <th>{{p.number}}</th>
            <td>{{p.name}}</td>
            {% if p.status == -1 %}
            <th class="danger">退室中</th>
            {% else %}
            <th class="success">入室中</th>
            {% endif %}
        </tr>
        {% endfor %}
    </tbody>
</table>
<button onclick="location.href='/name'" class="btn btn-success btn-default btn-block">新規登録</button>
<button onclick="location.href='/handle'" class="btn btn-info btn-default btn-block">読み込む</button>
{% endblock %}

Jinja2を使うと、Pythonから受け取ったデータを {% 構文 %}でReactのように書くことができます。
新規登録を押すと先ほどのuser_register()が実行され、
読み込むを押すと先ほどのhandle()が実行されます。
正確に言うとちょっと違うんですが...細かいことは気にしない主義です。

感想&課題

今まで紙での管理だったのでカードで管理できるのはとても気持ちいいです。しかも大体の人は定期券で学校に来てるので、新たにカードを発行するコストも省けます。歩き?入室するな


さて、今後の課題としては

  • 入室時刻・退室時刻が不明
  • 常時PC起動は怒られる
    の2点です。前者は技術上の問題で、僕が忘れてただけです。後者は顧問と話し合って決めます...

さいごに

ここまで読んでいただきありがとうございました!
良い音ゲーライフを

Discussion