この記事は
ビジネスエンジニアリング株式会社(B-EN-G)アドベントカレンダー2024の記事です。
はじめに
当記事は、二部構成からなる記事の後編です。
前編に目的や機材構成・システム全体図などを記載しています。
前編:BeaconやRFIDを使って自分で自分を監視する ①Beacon編
RFID連携
1. RFIDリーダー(兼ライター)の組付け
ラズパイにブレッドボード経由でRFIDリーダーライターを組み付けます。
RFIDのセットは、RFIDリーダーライタモジュール、カード型RFIDタグ、キーホルダー型RFIDタグ、接続端子2種で構成されていました。
基盤に接続端子をはんだ付けしてブレッドボードに挿して、キットのマニュアルの「Lesson28 RFID RC522」通りに配線していきます。
はんだ付けするとき、隣のピンとの間隔が結構狭くて、接触しないかとヒヤヒヤしましたがなんとかなりました。
まぁ失敗しても数百円で買えるっていうのは心強かった。
見ての通り基盤むき出しなので、勉強用って感じですね。
2. ラスパイの設定
こちらもマニュアルに沿って設定しました。
SPIなどのインターフェースの有効化、各種モジュールのインストールです。
3. サンプルコードでの動作確認とカードへの社員番号の書き込み
サンプルコードはこちら。
今回はPythonのコードを使用しました。
コードの説明とRC522というデバイスの仕様がマニュアルに書いてあるので、読みつつ動かしつつ理解を深めていきました。
scan、read、dump、write、halt、既定のコマンドを使ってデータを読み書きします。
今回はSector16を使うことにしました。17も入れたものの使ってません。
4. RFID連携プログラムの開発
サンプルでは、RFIDタグを置いてscanコマンドでしたが、タグを置いたら自動で読み込んでアクションを起こす形にしたいなと思い、
サンプルをベースに常にscanし続けるような実装にしました。
import RPi.GPIO as GPIO
import MFRC522
import sys
import os
import requests
import json
import time
# Create an object of the class MFRC522
mfrc = MFRC522.MFRC522()
SECTOR_MAP = {
1: [4, 5, 6],
2: [8, 9, 10],
3: [12, 13, 14],
4: [16, 17, 18],
5: [20, 21, 22],
6: [24, 25, 26],
7: [28, 29, 30],
8: [32, 33, 34],
9: [36, 37, 38],
10: [40, 41, 42]
}
def read_addr_for_order(mfrc, card_id, sector):
read_blocks = SECTOR_MAP[sector]
text_data = {}
# This is the default key for authentication
key = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
for block_addr in read_blocks:
# Authenticate
status = mfrc.MFRC522_Auth(mfrc.PICC_AUTHENT1A, block_addr, key, card_id)
# Check if authenticated
if status == mfrc.MI_OK:
counter = 0
while True:
text = mfrc.MFRC522_Readstr(block_addr)
if text is None:
print(str(block_addr) + " is None.")
counter += 1
if counter < 100:
continue
return 0
break
text = text[0:40]
text = text.replace("\x00", "")
text_data[str(block_addr)] = text
print(text_data)
else:
print("Authentication error")
return 0
return text_data
def send_data_at_put_card(data):
print(json.dumps(data))
url = "http://192.168.1.9:8123/rfid-human/device/device001"
headers = {'Content-Type': 'application/json'}
response = requests.post(url, data=json.dumps(data), headers=headers)
if response.status_code == 200:
print("Data successfully sent to server.")
else:
print("Failed to send data to server. Status code:", response.status_code)
def send_data_at_remove_card():
url = "http://192.168.1.9:8123/rfid-human/device/device001"
headers = {}
response = requests.delete(url, headers=headers)
if response.status_code == 200:
print("RC-Data successfully sent to server.")
else:
print("Failed to send data to server. Status code:", response.status_code)
def loop():
global mfrc
isSendDone = False
isSetOnCard = False
set_retry_count = 0
while True:
# Scan for cards
(status,TagType) = mfrc.MFRC522_Request(mfrc.PICC_REQIDL)
# If a card is found
if status == mfrc.MI_OK:
isSetOnCard = True
set_retry_count = 0
if isSendDone:
print("done")
mfrc = MFRC522.MFRC522()
time.sleep(1)
continue
print ("Card detected")
else:
print("no card")
time.sleep(1)
# カードを外してないけど、読み取れないことがある(まぁこの辺は値段相応か?無線モノだしなぁ)
if isSetOnCard and set_retry_count < 2:
set_retry_count += 1
mfrc = MFRC522.MFRC522()
continue
set_retry_count = 0
isSendDone = False
if isSetOnCard:
send_data_at_remove_card()
isSetOnCard = False
mfrc = MFRC522.MFRC522()
continue
# Get the UID of the card
(status,uid) = mfrc.MFRC522_Anticoll()
# If we have the UID, continue
if status == mfrc.MI_OK:
print ("Card UID: "+ str(map(hex,uid)))
# Select the scanned tag
if mfrc.MFRC522_SelectTag(uid) == 0:
print ("MFRC522_SelectTag Failed!")
result = {}
is_read_ok = True
for sector in SECTOR_MAP:
count = 0
while True:
text_data = read_addr_for_order(mfrc, uid, sector)
# カードの読み込みでエラーが出ることがあるので、リトライ入れとく
if text_data == 0:
is_read_ok = False
break
if not is_read_ok:
break
result.update(text_data)
if not is_read_ok:
# カード読み直す。最初からやり直し
mfrc = MFRC522.MFRC522()
continue
print("read end")
send_data_at_put_card(result)
isSendDone = True
mfrc = MFRC522.MFRC522()
def destroy():
GPIO.cleanup()
if __name__ == "__main__":
try:
loop()
except KeyboardInterrupt: # Ctrl+C captured, exit
destroy()
5. いざ動作確認→トラブル
まぁそんなもんですよね。
1.GPIO動かない問題
まず、ラズパイ5では、GPIO.setupが動きませんでした。
上記コードを実行すると
RuntimeError: Cannot determine SOC peripheral base address
というエラーが出ました。
ググってみると、ラズパイ5ではGPIOは使えなくなったという情報がありました。
回避策もこちらのページにありました。
回避策は、rpi-lgpioライブラリをインストールするようです。
pip install --break-system-packages rpi-lgpio
「--break-system-packages」
初めて見ました。なんか恐ろしそうな響きのオプションなので、一旦調べることにします。
こちらも情報がありました。
PEP668にて提案された仕様が関係しているそうです。
PEP668については、こちらも記事の引用から。
内容としては、 OSが用意するパッケージのコマンド(dpkg/apt/dnf等)の管理外で、
python用のモジュール管理コマンド(例:pip3等)を不用意に使うとOS側のpythonの環境に悪影響を及ぼしてしまうのを防ぎたいというものです。
OS側に影響の出るような変更は一旦エラーにするから、入れたい場合にはvenvで個別の仮想環境立てるか、
「--break-system-packages」使って影響を承知の上で入れるかしろってことみたいですね。
Node.jsで言うところのnpm installのインストール先をグローバルとローカルで分けて扱うのと同じイメージですかね。(そういう意味なら、当たり前な気がしてきた)
オプションの意味が分かったところでエラーの話に戻りまして、
GPIOモジュールを使うためには、rpi-lgpioライブラリのインストールが必要で、
こいつはOS側に変更が入るライブラリだから「--break-system-packages」が無いとインストール出来ない、ってことなんですね。
今回はPEP668について解説記事を書いてくださったスクエニITエンジニア様の対策その1を採用させていただき、
「納期に余裕がないから一番早い方法をタノム!」と叫びながら
「--break-system-packages」で強行突破しました。
2.品質不安定問題
タグを置いてるのに「タグがない」って判定になったり、
セクターの読み取りの途中でエラーが出たり、
とにかく安定性に欠ける挙動をしました。
このタグとリーダーの個体の問題なのか、製品としての品質レベルがそうなのか、
そもそもRFIDってそういうものなのか、私には分かりませんでしたが、
手元にあるのはこのタグとリーダーなのでそういうものだと受け止めることにしました。
ですので、至る所でリトライをするように組んでます。
一応これで途中で落ちることは無くなりました。
(リトライ入れたけどやっぱいらなくて変な残骸になってる箇所ありますね。。。)
6. 動作風景
動画を撮ったので、そこから画像を切り貼りします。
置きます。カードから読み取ってデータをWebアプリ側に送信しています。(HTTPで送信)
Webアプリが受信データから社員番号を取得し、
社員番号から名前と画像とその日の出退勤時刻を表示します。(WebSocketでブラウザに配信)
出勤ボタンを押すと出勤時刻が入ります。
このディスプレイはタッチパネルなので、カードを置いたら画面をポチッとできます。
退勤ボタンなら退勤時刻が入ります。
現在時刻表示の実装が適当過ぎて未来の時刻が入ってますね。
データの見え方
Beaconの章に書いたガントチャートでも出退勤時刻は出ますが、こんな感じでも見えます。
今日はどこで働いてて、いつからいつまで働いてて、どのくらい席に居たのか、ですね。
(この画像は出退勤も適当だし、Beaconの電源切り忘れてたせいで意味不明な勤務時間になってるし、ボロボロです。
稼働時間1,331分て、1日は1,440分しか無いのに。。。)
まとめ
おもちゃで遊ぶのもIoT技術を学ぶのも記事を書くのも楽しかったです。
BeaconもRFIDのどちらにも特徴が有って、その特徴を掴んだ上でシステム設計をしないと
上手くいかない、そんな実感をすることが出来ました。
逆に上手に扱えれば、今まで集められなかった現場のデータが収集出来て、
現場の可視化・改善に繋がり、SEとして有意義な仕事が出来るようになると思います。
今後も楽しみながら学んでいきたいなと思います。
最後までお読みいただきありがとうございました。
なんか最初に自分の勤務を分析するとか改善するとか書いたんですが、
この記事を書いた時点では何か出来るほどまともなデータは集まってません。
よって、これから集めます!
俺たちの戦いはこれからだ!