Entersys作成記
はじめに
こんにちは.まずはこの記事を開いてくれてありがとうございます.
これは私が所属するMMAというサークル(総合格闘技じゃないよ)の部室の入退室管理システムEntersys
を作成した際の記録です.
初心者が初心者なりに作成して記事を書くので,拙い部分も多いとは思いますが,最後まで読んでいただければ幸いです.
Entersys とは
Entersys
は,MMA の部室の入退室管理を行うシステムです.
この名前の由来は,Enter
とSystem
を組み合わせたものです.MMA には...sys
というシステムが数多く存在しています.その慣習に合わせて,Entersys
という名前にしました.(入ることしか考えてないネーミングですね.いったい誰が考えたんでしょう.)
実際に動作すると次の画像のようになります.
作成の経緯
数年前に新型コロナ(COVID-19)が流行し,部室に誰が出入りしたのかを把握する必要が出てきました.これまでは Slack 上に毎回て入力をしていました.しかし,これは面倒です.そこで,入退室管理をなるべく簡潔にしようと思い,自動化することにしました.そしてできたのが Entersys
です.
Entersys の概要
先述のようにEntersys
は,部室の入退室管理を行うシステムです.学生証についているバーコード(学籍番号)を読み取ることで,入退室の記録を簡潔にしました.読み取った学籍番号から MMA 内での id を取得します.その後は,入退室処理を行い,その結果と在室者を Slack に通知します.
作成詳細
ここからは作成についての詳細を書いていきます.
細かい関数の使い方やオプションなどについてはたくさんネットに転がっているので省略します.
動作環境
- Raspberry Pi 4
- Python3
ディレクトリ構成
この記事内では,Entersys
のディレクトリ構成を以下のようにします.また,この記事内で書かれるパスはEntersys
のディレクトリからの相対パスとします.
ディレクトリ構成
Entersys
├── src
│ ├── main.py
│ ├── update_member.py
│ ├── get_id.py
│ └── slackicon.py
└── data
├── user_data.json
├── member.csv
└── inside.txt
id の取得
まずは id の取得です.学生証のバーコードから学籍番号を読み取ります.id は./src/get_id.py
で学籍番号から取得します.対応表は./data/member.csv
にあります.この対応表から学籍番号を検索し,id を取得します.下のコードのCSVFILE
は./data/member.csv
のパスです.また,update_member.update_member()
は./src/update_member.py
にある関数です.これは入部届と紐付けされているスプレッドシートから学籍番号と id を取得し,./data/member.csv
に書き込んで更新する関数です.
get_id.py
def get_id(number):
id = ""
with open(CSVFILE) as f:
reader = csv.reader(f)
row_list = [row for row in reader]
lastrow = len(row_list)
with open(CSVFILE) as f:
for i in range(0, lastrow):
if number == row_list[i][1]:
id = row_list[i][0]
return id
update_member.update_member()
with open(CSVFILE) as f:
reader = csv.reader(f)
row_list = [row for row in reader]
lastrow = len(row_list)
with open(CSVFILE) as f:
for k in range(0, lastrow):
if number == row_list[k][1]:
id = row_list[k][0]
return id
このファイル内で行っていることは次の通りです.
-
./data/member.csv
を読み込み,それぞれを配列に格納 - 学籍番号を検索し,id を取得
- id が見つからなかった場合,
update_member()
関数を実行し,./data/member.csv
を更新 - 再び 1.~2.を行う
- これでもない場合は
Error: Number Not Found
と表示して,再び学籍番号の入力を待つ(この例外処理は./src/main.py
で行っています.)
入退室処理
在室者リストを./../data/inside.txt
で準備しておき,学生証を読み取ると./../data/inside.txt
に在室者がいるかどうかを確認します.この時,入室者リストを配列に格納してから確認しています.入室者リストにない場合は入室,ある場合は退室として処理を行います.また,Slack 通知のためにここでmesssage
を決めています.
ENTLANTS
とは./../data/inside.txt
のことです.
入退室処理プログラム
message = "enter"
with open(ENTLANTS, "r", encoding="utf-8") as f:
inside = f.readlines()
inside = [line.rstrip("\n") for line in inside]
for someone in inside:
if someone == id:
inside.remove(id)
message = "exit"
break
if message == "enter":
inside.append(id)
このファイル内で行っていることは次の通りです.
-
./../data/inside.txt
を読み込み,それぞれを配列に格納 -
./../data/inside.txt
に在室者がいるかどうかを確認 - 入退室の処理
Slack 通知
Incoming Webhook
次に Slack への通知部分です.Slack への通知はrequests
のPost
を使って行っています.Slack にはIncoming Webhook
という機能があり,これを使うことで簡単に通知を行うことができます.Incoming Webhook
は Slack に送信するメッセージをプログラムで設定することができます.今回はblocks
という形式で送信しています.blocks
はjson
形式で設定できます.
Slack アイコンの取得
初めの方にあるslackicon.get_url(id)
にて,先ほど取得したid
からSlack
のアイコンを取得しています.これは MMA の先輩が Slack から取得してきてくれているものなので省略させていただきます.次のfor
文では取得した画像をblocks
に追加しています.blocks
は8
人ごとに改行しています.これは Slack 上での横並びの最大が 8 人までだっためこのようにしています.(自動で改行してくれなかったのが残念.)
Slack 通知の送信の装飾
要素 | どうしたか |
---|---|
チャンネル | 送信したいチャンネルを選択 |
ユーザー名 | 先ほど取得した id |
アイコン | 先ほど取得したユーザーアイコン |
メッセージ | 先ほど決めたメッセージと在室者リスト |
Slack 通知プログラム
user_icon = slackicon.get_url(id)
slack_user_icon = slackicon.get_url(id)
blocks = [
{
"type": "context",
"elements": [
{"type": "mrkdwn", "text": f":{message}: 在室"},
],
}
]
for i in range(len(inside)):
user_icon = slackicon.get_url(inside[i])
if i % 8 == 0 and i != 0:
blocks.append({"type": "context", "elements": []})
if user_icon:
blocks[i // 8]["elements"].append(
{
"type": "image",
"image_url": user_icon,
"alt_text": f"@{inside[i]}",
}
)
else:
blocks[i // 8]["elements"].append(
{"type": "mrkdwn", "text": f"@{inside[i]}"}
)
with open(ENTLANTS, "w") as f:
for one in inside:
f.write("%s\n" % one)
print("%s : %s" % (id, message))
requests.post(
WEB_HOOK_URL,
data=json.dumps(
{
"channel": CHANNEL,
"username": id,
"icon_url": slack_user_icon,
"blocks": json.dumps(blocks),
}
),
)
その他の処理
入室者のリセット
退室処理を忘れた時のために,入室者リストをリセットしています.これは./src/main.py
で行っています.schedule
モジュールを使って,毎日 4 時に./data/inside.txt
をリセットしています.
入室者リストのリセット
```python:./src/main.py
def inside_update():
with open(ENTLANTS, "w") as f:
f.write("")
メンバーの更新
id が見つからなかった時のみでなく,毎朝 4 時に./data/member.csv
を更新しています.これは./src/update_member.py
で行っています../data/member.csv
は入部届と紐付けされているスプレッドシートから取得しています.
メンバーの更新のプログラム
def update_member():
try:
with open(csvfile) as f:
reader = csv.reader(f)
row_list = [row for row in reader]
lastrow_in = len(list(filter(None, worksheet.col_values(2))))
if worksheet.acell("C" + str(lastrow_in)).value != row_list[0][0]:
for k in range(2, lastrow_in + 1):
if row_list[0][0] == worksheet.acell("C" + str(k)).value:
worksheet_last_id = worksheet.acell("C" + str(lastrow_in)).value
while row_list[0][0] != worksheet_last_id:
row_list.insert(
0,
[
worksheet.acell("C" + str(k + 1)).value,
worksheet.acell("E" + str(k + 1)).value,
],
)
k += 1
break
with open(csvfile, "w") as f:
writer = csv.writer(f)
writer.writerows(row_list)
except:
pass
else:
return 0
ここでは,./data/member.csv
を読み込み,それぞれを配列に格納しています.次に,最新の情報が異なっていれば入部届と紐付けされているスプレッドシートから学籍番号と id を取得し,./data/member.csv
に書き込んでいます.
Discussion