# スマートロッカーPOC
作成者:AY
###### tags: `work` `programmming` `POC` `raspberry Pi` `python`
### いきさつ
スマートロッカーを用いた備品の貸し出しを行いたい
との要望があり、POCを作成する
### 業務概要
期間:2019年9月13日~19日(3営業日)
人数:1人
### ご要望
* 工数をかけずに物品の貸し出しを実現したい
* 取り扱い品
* テプラ・**プロジェクター**
* 切手もできればベスト
* 利用者ログを取得
* 予約者のみがロッカー開錠できる仕組み
* 先ずは日比谷・豊洲等で検証利用/良ければ全国展開
---
## 完成物
### 外見

左部分にラズベリーパイが入っている(開かない)

### ロック機構
電流を流すことで上下に棒が移動する
ソレノイドラッチを用いて実装した


### 認証開始状態
認証が可能になるとLEDライトが青に光る

### 認証成功
認証成功するとLEDライトが緑に光る

### 認証失敗
認証失敗するとLEDライトが赤に光る
(例1)ロッカーIDが異なる場合

(例2)利用可能時間より早い場合

(例3)利用可能時間を過ぎている場合

### 返却時
返却時には備品に張り付けられているQRコードを読み取らせる

返却時画面

---
## POC仕様
プロジェクタ1台を管理するスマートロッカーを作成する。
ロッカーの開け閉めはQRコードを用いて行う。
### 想定される利用の流れ(貸出時)
1. **利用者**が**総務備品貸し出しサイト**から備品貸出を依頼
* 貸出依頼時に**ロッカー利用希望**、**利用時間の指定**を行う
2. **総務備品貸し出しサイト**から**QRコード**が発行され、
自動メールで**利用者**に送信される
3. **利用者**は指定した時間にスマートロッカーの
**QRコード読み取り部**に**発行されたQRコード**かざすことで
ロックを外し、備品を借りる
※今回はQRコードが発行された状態とする
### 想定される利用の流れ(返却時)
1. **利用者**は返却時にスマートロッカーの**QRコード読み取り部**に
**備品に貼られているQRコード**をかざすことでロックを外し、
備品を返却する
---
### ハード仕様
* ロッカー本体
* サイズ:220[mm]×300[mm]×90[mm]
* 段ボールで作成
* ロック機構
* ソレノイド
* raspberry3
* マイコン
* 読み取ったQRコードの照会を行う
* USBカメラ
* QRコードの読み取りに用いる
* フルカラーLEDライト
* 認証状態を表す
* 電源
* rasbperry Pi3、回路用に5V
* ソレノイド動作用に24V
### つなぎ方
raspberry Piの17PinをHIにすると、
ソレノイドへの電源供給がされ鍵が開く

---
### ソフト仕様
* 環境:rasbian
* 言語:Python2.7
* パッケージ:OpenCV, gpiozero, zbar
### ファイル構成
-- QRCode.py
QRコードを読み込み登録されたものならばPINをHIにする
### QRコード
発行されるQRコードには以下の情報が付与されている
1. 利用可能日
2. 利用可能時間帯
3. 利用者名(name)
4. ロッカーIDと扉番号(locker)
5. パスワード(key)
2019年9月15日の14時~17時にロッカーID00の00番扉を
開けるQRコードは以下の情報を持つ
```例:2019091514001700?name=TaroTanaka?locker=00_00?key=secret```

名前はユーザ設定、パスワードはシステムから発行される。
備品に張り付けられているQRコードはユーザ名・パスワード
ともにシステムから設定され、ロッカーの扉を常時解錠可能。
### プログラムの流れ
以下にプログラムの流れを示す。
```
1. 初期化
1-1. LED初期化
1-2. OpenCV初期化・エラー処理
1-3. QRコード初期化
1-4. 自分のlocker_IDと最大ドアの数を読み込む
2. 起動画面表示
3. QRコードを読み取るループ
3-1. グレースケール/バイナリ化
3-2. PILに変換
3-3. データがあれば内容表示(チェック関数呼出)
3-4. 読み取った画像を1/4サイズにしてウィンドウに表示
3-5. 中止キー読み取り
4. チェック関数
4-1. データが文字列か確認
4-1-1. エラー処理、QRコード読み取りへ
4-2. 利用者名の確認と表示
4-2-1. エラー処理、QRコード読み取りへ
4-3. ロッカーIDと扉番号の確認と表示
4-3-1. エラー処理
4-4. 備品に張り付けられたQRコード確認
4-4-1. ロック解除関数呼び出し
4-5. 利用時間の確認
4-5-1. エラー処理、QRコード読み取りへ
4-5-2. ロック解除関数呼び出し
5. ロック解除関数
5-1. ロック解除画面表示
5-2. 30秒間ロックを解除する
5-3. プログラム終了
```
### 実プログラム
QRCode.py
```C++
#!/usr/bin/python
# -*- coding: utf-8 -*-
import cv2
import zbar
import PIL.Image
from gpiozero import LED
from time import sleep
import datetime
import sys
# 初期化_LED
Rled = LED(10)
Bled = LED(9)
Gled = LED(11)
door0 = LED(17)
door1 = LED(21)
this_locker_ID = "00"
this_locker_door = "2"
#initialize CV2
cap = cv2.VideoCapture(0)
cap.set(3, 800) # Width
cap.set(4, 800) # Heigh
cap.set(5, 15) # FPS
if cap.isOpened() is False:
raise("IO Error")
# 初期化_initialize QR_Code
scanner = zbar.ImageScanner()
scanner.parse_config('enable')
def led_on(color):
if(color == "R"):
#pass
Rled.on()
elif(color == "B"):
#pass
Bled.on()
elif(color == "G"):
#pass
Gled.on()
def led_off(color):
if(color == "R"):
#pass
Rled.off()
elif(color == "B"):
#pass
Bled.off()
elif(color == "G"):
#pass
Gled.off()
def open_locker(str_door):
sleep(1)
led_off("R")
led_off("B")
led_off("G")
sleep(1)
led_on("G")
sleep(1)
led_off("G")
sleep(1)
led_on("G")
print(" +-+-+--------------------------------------+-+-+")
print(" " + str_door + "番の扉を解錠します。")
print(" 備品を取り出したら扉を閉めてください。")
print(" 30sec後に施錠されます")
print(" +-+-+--------------------------------------+-+-+")
if( int(str_door) == 0 ):
door0.on()
pass
elif( int(str_door) == 1):
door0.on()
pass
sleep(30)
led_off("G")
led_off("R")
sleep(1)
led_on("B")
sleep(1)
door0.off()
door1.off()
sys.exit()
def check_open(str_data):
# 開けて良いデータかチェックする
# 返り値はtrue/false
if( type(str_data) is not str ):
print("E: invalid data: not string")
str_data_array = str_data.split("?")
if(len(str_data_array) is not 4):
print("E: invalid data: size not 4")
# 利用者名の確認
tmp = str_data_array[1].split("=")
user_name = tmp[1]
print("OK: 利用者は" + str(user_name)+ "さんです")
# ロッカー番号の確認
tmp = str_data_array[2].split("=")
locker_num = tmp[1].split("_")
if( this_locker_ID != str(locker_num[0])):
led_off("G")
led_on("R")
print("E: ロッカーが異なるようです。")
print(" このロッカーのID:"+ this_locker_ID + ", 指定ロッカーのID:" + str(locker_num[0]))
sleep(3)
led_off("R")
sleep(1)
led_on("B")
return 1
if( 0 <= int(locker_num[1]) and int(locker_num[1]) <= this_locker_door):
print("OK: ロッカーの扉番号は"+ str(locker_num[1]) + "番です")
else:
led_off("G")
led_on("R")
print("E: 指定されたロッカーの扉番号が正しくないようです")
sleep(3)
led_off("R")
sleep(1)
led_on("B")
return 1
# マスターキー(いつでも開けることができる)かどうか確認
tmp = str_data_array[3].split("=")
password = tmp[1]
if( user_name == "Admin" and password == "cscscs"):
print(" +-+-+--------------------------------------+-+-+")
print(" 返却ありがとうございます ")
print(" +-+-+--------------------------------------+-+-+")
open_locker(str(locker_num[1]))
# 利用時間の確認
date_from = datetime.datetime.strptime(str_data_array[0][0:12], "%Y%m%d%H%M")
date_due = datetime.datetime.strptime(str_data_array[0][0:8] + str_data_array[0][12:16], "%Y%m%d%H%M")
today = datetime.datetime.today()
# for debug
today = datetime.datetime(2019, 9, 15, 16, 0)
if( date_from <= today and today <= date_due ):
led_off("G")
led_on("R")
print("OK: 利用可能時間の範囲内です")
open_locker(str(locker_num[1]))
elif( date_from >= today ):
led_off("G")
led_on("R")
print("E: invalid date time: 利用可能時間より早いです")
sleep(3)
led_off("R")
sleep(1)
led_on("B")
return 1
elif( today >= date_due ):
led_off("G")
led_on("R")
print("E: invalid date time: 利用可能時間を過ぎています")
sleep(3)
led_off("R")
sleep(1)
led_on("B")
return 1
def read_QR():
while True:
#input image
ret, img = cap.read()
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#grayscale
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#Binarization
tresh = 100
max_pixel = 255
ret, img = cv2.threshold(img, tresh, max_pixel, cv2.THRESH_BINARY)
#picture change PIL
pil_img = PIL.Image.fromarray(img)
width, height = pil_img.size
raw = pil_img.tobytes()
image = zbar.Image(width, height, 'Y800', raw)
#result
scanner.scan(image)
for symbol in image:
print("---------------------------------------------------------------")
print(symbol.data)
print("---------------------------------------------------------------")
led_off("B")
led_on("G")
check_open(symbol.data)
# 1/4サイズに縮小
img = cv2.resize(img, (int(img.shape[1]/4), int(img.shape[0]/4)))
# 加工なし画像を表示する
cv2.imshow('Raw Frame', img)
#finish flag
k = cv2.waitKey(1)
if k == 27: #ESC
break
if __name__ == '__main__':
led_on("B")
print("----------------------")
print(" * CR Code Reader * ")
print("----------------------")
QR_data = read_QR()
print(QR_data)
cap.release()
cv2.destroyAllWindows()
print("OK: 正常終了")
```
### 全体テスト
行ったテストは以下の5項目である。
実施日:2019年9月19日11時
実施者:横地
項目 :5
結果 :全5項目でクリア
1. プログラム起動時に以下が実行される
1-1. 起動画面の表示
1-2. LEDが青に光る
1-3. カメラ画像が表示される
2. 指定された時間帯内のQRコードをかざす
2-1. 認証成功画面の表示
2-2. LEDが緑に2回点滅
2-3. ドアのロックが外れる
2-4. 30秒後にロックがかかり、プログラム終了
3. 指定された時間外のQRコードをかざす
3-1(1). 認証失敗画面の表示(利用時間が早いです)
3-1(2). 認証失敗画面の表示(利用時間を過ぎています)
3-3. LEDが赤に3秒間光る
3-4. LEDが青に光り、QRコード読み取りモードに戻る
4. 備品に張り付けられたQRコードをかざす
4-1. 返却画面・認証成功画面の表示
4-2. LEDが緑に2回点滅
4-3. ドアのロックが外れる
4-4. 30秒後にロックががかりプログラム終了
5. カメラをつながずにプログラムを実行する
5-1. エラー表示してプログラム終了
#### テストに使用したQRコード
QRコード作成サイトにて作成。
1. 利用時間内

2. 利用時間を過ぎている

3. 利用時間より早い

4. ロッカーIDが異なる

5. ロッカーの扉1番

6. 備品に張り付けられるQRコード

---
### 今後の開発案
* システム
* ネットワークに接続しパスワード照会を行う
* ワンタイムパスワードの発行
* 利用者と貸出者を別にする
* QRコードの暗号化
* ロッカー拡張
* UI/UXの作成
* ハード
* 電流不足の解決(ソレノイド用)
* ラズパイにヒートシンクをつける
* ロッカー拡張
* アクリルなどの耐久性に優れた素材での作成
* 画面を小さくし、カメラとともにロッカーに組み込む