はじめに
はじめまして。私は現在大学生であり、プログラミングをしたりゲームを制作したりするサークルに所属しています。
部室はPCが置かれていて冷暖房もあるためかなり快適な環境になっているのですが少し問題がありました。
さて何でしょう
...
...
...
鍵が使いにくすぎる!!!!!!!!
説明しますと、鍵収納BOXというダイヤル式で開けることのできる箱の中に部屋に入るための鍵が保管されているのですが、この鍵収納BOXがなかなか開け辛いのです。。。
これではユーザーエクスペリエンスが低すぎ!ってことなので自分でUX高めなシステムを構築しました。
制作したもの
PaSoRiに大学の学生証をかざすとサーボモーターがサムターンを回すことで開錠できるようになっています。
また、扉が閉まると施錠されるオートロックとなっており、出るときには青いボタンで解錠することが可能になっています。
使用部品
- RaspberryPi zero W (WHでも可)
- PaSoRi RC-S380
- サーボモータ MG-92B
- A/D Converter MCP3002-I/P
- 圧力センサFSR400 SHORT
- スイッチ
- LED
使用ライブラリ
製作にあたって
私は回路もRaspberryPiもPythonも触るのが初めてだったため、インターネットで先人たちの多大なる努力の結果実現されたライブラリやその使用方法を書き記したブログなどを大変利用させていただきました。おかげで完成させることができ大変助かりました。ありがとうございました。
製作
RaspberryPiにはGPIOというピンが生えており、センサーの読み取りやサーボモーターの制御に使用することができます。
初RaspberryPiのためGPIO操作の方法を調べまくった結果pigpioといのが良さそうということでpigpioを採用することに決定しました。
割り込みと高精度PWMをどちらも行えるのが制作していてとても役に立ちました。
参考サイト:
pigpioはとても便利なのですが、pythonでライブラリを読み込むだけでは実行できず、raspberryPiの起動ごとにpidpiodというデーモンを起動させないと動作しません。流石に面倒なので自動化します。下記の参考サイトに載っているものを使わせていただき、service化することで自動起動を実現しました。
参考サイト:
ここまでできると、あとはひたすらPythonでプログラムを組んでいくだけです。
サーボモータ
まずはサーボモーターを動かしてみましょう。
サーボモーターはPWMという信号で制御することができます。このサーボモーターだと、20msのうちに電圧がHighになっていた割合で回転角度を決定することができます。この(Hiの時間/20ms)*100をデューティー比と呼び、例えば10msだけHighになっていた場合はデューティー比50%となります。
データシートが公開されてなく正確な値を割り出すことはできませんが、ほかのサーボモータ(SG90)の場合だと2.5%~12%の間で操作します。
また、サーボモータのPWMはデータシートを見ると5VがHighとなっていますが、raspberryPiでは3.3Vしか出力できません。しかし、3.3Vでも操作可能でしたので昇圧せずこのまま使用することにしました。
from time import sleep
class Servo:
# 変えてね
servo_pin = 18
def __init__(self, pi):
# pigpioのインスタンスを渡してね
self.pi = pi
# 任意の角度に回転0-180
def servo_rotate(self, degree):
motor_pulse = ((degree * 100 / 9) + 500)
self.pi.set_servo_pulsewidth(Servo.servo_pin, motor_pulse)
sleep(0.5)
self.pi.set_servo_pulsewidth(Servo.servo_pin, 0)
# 鍵を開ける方向に回転
def open_lock(self):
# duty:11.25%
self.pi.set_servo_pulsewidth(Servo.servo_pin, 2250)
sleep(0.2)
# サーボ開放
self.pi.set_servo_pulsewidth(Servo.servo_pin, 0)
# 鍵を締める方向に回転
def close_lock(self):
# duty:6.5%
self.pi.set_servo_pulsewidth(Servo.servo_pin, 1300)
sleep(0.2)
# サーボ開放
self.pi.set_servo_pulsewidth(Servo.servo_pin, 0)
set_servo_pulsewidthの2つ目の引数の値を変えていろいろ試してみてください。
PaSoRi
次はPaSoRiを触ってみましょう。raspberryPiにUSBで接続し、nfcpyを使用して操作します。
nycpyをインストールするときは公式のInstallationを参考にしましょう。libusb入れる必要があるのを忘れがち。
https://nfcpy.readthedocs.io/en/latest/topics/get-started.html#installation
次に公式のexampleであるtagtool.pyを動かしてみましょう
https://github.com/nfcpy/nfcpy
ここまでできるとnfcpyを利用する準備はばっちり!
使用したいカードを解析していきましょう!
なんですが、ここだけ大学の先輩がコードを書いてくれたため方法があまりわかりません
大学の先輩が書いてくれたコードでは学籍番号とカード固有のIDmをハッシュ化して取り出しています。
扉の開閉検知
はじめはリードスイッチ(磁気に感知するセンサ)を使用していたのですが、むき出して使用していたことによる破損に加えてマグネットの位置がいつのまにかずれている事件が発生したので圧力センサを使用することに途中で変更しました。扉の軸がある方に圧力センサを設置し、ある閾値を超えると扉がしまったと検知します。
また、今回使用した圧力センサはアナログ出力なためraspberryPiで使用するにはA/D変換が必要でした。なのでA/DコンバーターのMCP3002-I/Pを使用しました。
参考サイト:
- https://qiita.com/f_nishio/items/4b9723c4e622a51aaeb5
- http://akizukidenshi.com/download/ds/microchip/mcp3002.pdf
class Door:
MINIMUM_VALUE = 250
CLOSE = 0
OPEN = 1
def __init__(self, pi):
self.pi = pi
self.h = pi.spi_open(0, 200000, 0) # 0ch 200kHz(5V) mode0
def door_status(self):
(count, rx_data) = self.pi.spi_xfer(self.h, [0x68, 0x00])
value = ((rx_data[0] & 3) << 8) + rx_data[1]
if value >= Door.MINIMUM_VALUE:
return Door.CLOSE
else:
return Door.OPEN
データベース
同時アクセスが不要なためPythonに標準で付属するライブラリSQLiteを採用
- User Database
Student_id | Name | IDm |
---|
こんな感じで保持してあります。一応IDmはSHA256によりHash化してあります。
- Logs database
num | Student_id | Name | enter_time | leave_time |
---|
ログはこんな感じで保持してあります。numはauto incrementになっており、自動で数字が入るようになっております。
timeを分けた理由は聞かないでください
スイッチ
nfcpyではカードを読み取るまで無限ループが発生しボタンの検知を行うことができません。そんなときにもpigpioが大活躍してくれます!
pigpioには入力割り込みという機能があり、電圧レベルの変化を検知してプログラムを実行する機能が備わっているのです。
import time
import door
import pigpio
class Switch:
switch_pin = 22
def __init__(self, pi, servo):
self.pi = pi
self.door = Door(pi)
self.time_old = time.time()
self.servo = servo
# switchのピンを入力に設定
pi.set_mode(Switch.switch_pin, pigpio.INPUT)
# プルダウン抵抗を有効化
pi.set_pull_up_down(Switch.switch_pin, pigpio.PUD_DOWN)
# 割り込みの関数を設定 RISING_EDGE→LOWからHIGHになると実行
self._cb = pi.callback(Switch.switch_pin, pigpio.RISING_EDGE, self._cbf)
def _cbf(self, gpio, level, tick):
# チャタリング防止
if time.time() - self.time_old < 1:
return
else:
# 鍵開ける
self.servo.open_lock()
time.sleep(5)
# 扉しまってなかったら鍵閉めるまで待つ
while self.door.door_status() == self.door.OPEN:
time.sleep(0.5)
self.servo.close_lock()
# チャタリング防止用の時間更新
self.time_old = time.time()
Slackへ送信
部内での連絡にSlackを使用しているので入退室情報をSlackに送信するように設定しました。
SlackにIncoming webhookを利用できるように設定し、requestsライブラリを使って送信しようと思ったのですがRaspberryPi Zeroには重すぎたのかrequestsライブラリを全然読み込まない事件が発生したので標準のurllib2を使用するようにしました。
import urllib2
import json
url = 'https://hooks.slack.com/services/XXXXXXXX/XXXXXXX/XXXXXXXXXXXXXXXXXXXX'
def send_slack(Name):
body = "%sが入室しました" % Name
data = json.dumps({"text": body})
req = urllib2.Request(url, data, {'Content-Type': 'application/json'})
try:
f = urllib2.urlopen(req)
except urllib2.HTTPError:
print "slack_error"
f.close()
自動起動
ここまででほとんど機能的なものは完成したのですが、再起動すると自動ではpythonプログラムは実行されないためmainのプログラムをサービス化して自動起動するようにしましょう。
cd ~/smartlock
python __main__.py
↑このファイルを/home/pi/において
[Unit]
Description = smart lock
[Service]
ExecStart=/bin/sh /home/pi/smartlock.sh
ExecStop=/bin/kill -INT ${MAINPID}
Restart=always
Type=simple
[Install]
WantedBy=multi-user.target
こんな感じでサービスを定義して、
# 自動起動有効化
$ sudo systemctl enable smartlock.service
# 起動
$ sudo systemctl start smartlock.service
# 状態確認
$ sudo systemctl status smartlock.service
できた
登録画面
さて、ここまででスマートロックの機能の99%を制作できたはずなのですが、肝心のユーザー登録画面がありませんね。いちいちssh接続してユーザー登録用のpythonを実行するのもかなり面倒なのでPCからクリック1回で登録・削除・確認ができるシステムを作っていきましょう。
PCはWindowsがほとんどだと思うのでPowershellを使っていきます。
事前準備としてRaspberryPiとPCとの間で公開鍵を交換します。linuxにはssh-copy-idという便利コマンドがあるのですが、Windowsには当然ながらありません。しかし同等機能を実現するスクリプトを公開してくださってる方がいたのでありがたく利用させていただきました。
上記の手順に従ってもらえば鍵交換が簡単に終わります。
更に事前準備としてユーザーを登録するpythonコードとシェルスクリプトを用意しましょう
# -*- coding: utf-8 -*-
import hashlib
import time
from logging import DEBUG, Formatter, StreamHandler, getLogger
# from reader import Reader
import database
import slack
logger = getLogger(__name__)
handler = StreamHandler()
handler.setFormatter(Formatter("[%(levelname)s] %(message)s"))
logger.setLevel(DEBUG)
logger.addHandler(handler)
logger.propagate = False
def main():
name = raw_input("追加するユーザー名を入力してください(英数字のみ):")
# ここでカード読み込み
database.make_info("ここに番号", name, "ここにIDm")
print ("追加完了しました。")
slack.add_user("ここに番号", name)
time.sleep(1)
if __name__ == "__main__":
main()
cd ~/smartlock
# 一旦止めないと読み取りができない
sudo systemctl stop smartlock.service
# 追加用pythonコード実行
python add_user.py
# 再開
sudo systemctl start smartlock.service
やっと事前準備が完了しましたね。
次にPowershellで実行するスクリプトを用意していきましょう
Write-Output "入退室管理システム管理スクリプト"
$input = Read-Host "何を実行しますか?(1:ユーザー追加 2:名前変更 3:ユーザー削除 その他:終了)"
switch($input){
"1"{
Write-Output "しばらくお待ちください。"
ssh pi@192.168.xxx.xxx -t 'stty erase ^H
sh add_user.sh'
}
"2"{
Write-Output "しばらくお待ちください。"
ssh pi@192.168.xxx.xxx -t 'stty erase ^H
sh rename_user.sh'
}
"3"{
Write-Output "しばらくお待ちください。"
ssh pi@192.168.xxx.xxx -t 'stty erase ^H
sh delete_user.sh'
}
default{
Write-Output "終了します。"
}
}
Read-Host "終了しました。Enterを押してください。"
xxxのところは環境に応じて変更お願いします。
リネームと削除は同じようにpythonとシェルスクリプトを用意してください。
上記のファイルを好きなところに保存し右クリック→送る→デスクトップにショートカットを作成
の手順でショートカットを作成します。それだけだとpowershellが自動で立ち上がらないので、
デスクトップのショートカットを右クリック→プロパティ→リンク先の前に
powershell -ExecutionPolicy RemoteSigned -File
を追加する。
例) powershell -ExecutionPolicy RemoteSigned -File C:\Users\XXX\manager.ps1
はいこれでダブルクリックで実行できるようになりました
仕上げ
はんだ付けしましょう! 回路はこんな感じで接続しました
少し余計な部品がついていますが、こんな感じで接続しました!
ハンダきたねえとかの苦情は受け付けません。ハンダ付け2回目なんで許してください。
ピンヘッダがなくて頑張って線でつないだのですが、皆さんはピンヘッダ使いましょう。 地獄を見たければ真似してください。
ケースづくり
何を思ったのか3Dプリンタを購入してしまったので3Dプリンタで制作していきます。
Fusion360を使用し3回ぐらい試行錯誤したらいい感じのが出来上がりました
穴が一個多いのはサーボモータの電源用に準備していたのですが、不要とわかったので使いません。
あと、蓋のデータが行方不明になっちゃいました
サーボマウンタ作り
こちらも3Dプリンタで制作していきます。
Fusion360を使ってこんな感じのものを作ってみました
説明が難しいのでこの3枚で察してください
扉には強力両面テープで接着してあります。
ちなみに、このマウンタだけで印刷に3時間近くかかりました。
超ベンリ
##Github
何もかもが初心者の書いたコードでも良いなら、まとめておいておきます。
カード読み込み部分だけ自分が書いたわけではないので省略させていただきました。
https://github.com/konikoni428/smartlock
最後に
自分で制作したものが日常で活躍できてるってたのしー。
部員にも喜んでいただけたようで、大変よい経験となりました。
今後もどんどん制作していこうと思うので、そのときはまたシェアしたいと思います。
読んでいただきありがとうございました。