ぼくが住んでいるシェアハウスにはシャワールームが8個あったのですが、最近のリニューアルで2個を潰して代わりにお風呂になったんです。
で、お風呂利用者が多いと思われる女性の専用フロアが4階にあって、わざわざ1階に降りて来なくてもお風呂が空いているかどうかわかるようにならないかと相談があって作ったのが「Ofuro Board β」です。
「Board」っとなっているのは、お風呂に入っている時間は長いので空いているかどうかだけではなく、いつまで入るかや入りたい予定をホワイトボードに書いて共有できるようにしたからです。
スマホのブラウザで見た画面はこんな感じになります。
システム構成図はこんな感じ。
TWELITE
TweliteはIEEE802.15.4準拠した32bit無線マイコンモジュールで、ZigBeeライクなものです。秋葉原の秋月電子の店頭でたまたまBLUE PALを見かけて、100均でも売っている一般的なコイン型リチウム電池で4年も動作する!という謳い文句にひかれて購入してしまいました。
最初は秋月電子で¥30で売っているホールICをBLUE PALに繋いで使おうと思っていたのですが、無線タグアプリをスイッチとして使おうとした場合、ONかOFFの状態が出力されるのではなく、データが届くたびに単に出力がトグルする仕様になっていたため使用を断念しました。
しようがないので、開閉センサーパルも買ってBLUE PALにデフォルトで書き込まれているパルアプリで動作させることにしました。
でも、結局、BLUE PALと開閉センサーパルの組み合わせは、写真のように見た目も良くスッキリしていて取り付けも楽なので、これが正解なんだと納得しています。
Raspberry Pi側に付けたMONOSTICKや、BLUE PALのプログラム書き換えと設定変更用のTWELITE Rも購入したので、TWELITEを使うために結構出費してしまいました。
TWELITE関連購入機器(秋月電子調べ)
- TWELITE BLUE PAL アンテナ内蔵タイプ ¥1,835
- 開閉センサーパル OPEN-CLOSE SENSE PAL ¥1,380
- USBスティック MONOSTICK ブルー ¥2,980
- USBアダプター TWE-Lite-R(ソケット付完成品)¥2,430
ラズパイ周り
まずはハードウェアから。
使っているものはこんなものです。
- Pi 3 Official 2.5A 黒 電源セット [122-5826-KitF] ¥4,995 KSY
- Raspberry Pi 3 Model B用公式ケース ¥1,280 秋月電子
- Micro SD Card Toshiba EXCERIA 16GB
- BUFFALO BSW200MBK 200万画素WEBカメラ ¥2,360 ヨドバシカメラ
- MoNoStick(モノスティック) ブルー ¥2,760(税抜き)マルツ
写真だとこんな感じです。
BUFFALO BSW200MBK
後ろを向いてしまっていますが、このBUFFALOのWEBカメラ、120°の広角レンズが使われていてすごく広い範囲が映るんです。ホワイトボードを映す際も広角が効いていて奥行きが短くて済むんです。しかも画像の歪が非常に少なくて本当にビックリしました。また、UVC(USB Video Class)に対応しているので、ドライバーをインストール必要もありませんでした。
lsusb
コマンドを実行するとKYE System Corp. (Mouse Systems) Genius WideCam F100
と表示されます。
pi@raspberrypi:~ $ lsusb Bus 001 Device 005: ID 0458:708c KYE Systems Corp. (Mouse Systems) Genius WideCam F100 Bus 001 Device 004: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. SMC9514 Hub Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
ビデオキャプチャデバイスとして認識してるかはls /dev/video*
で/dev/video0
が表示されればOKです。(接続しているカメラが1つの場合)
pi@raspberrypi:~ $ ls /dev/video* /dev/video0
TWELITE MONOSTIC
ラズパイの後ろに付いているがMONOSTICで、お風呂のドアに付けたBLUE PAL (+OPEN-CLOSE SENSE PAL)からのデータを受信しています。Raspberry PiやWindows 10ではこちらもドライバーをインストールする作業をする必要はありませんでした。
lsusb
コマンドを実行するとFuture Technology Devices International, Ltd FT232 USB-Serial (UART) IC
と表示されます。
TTYデバイスとして認識されているかはls /dev/ttyUSB*
で/dev/ttyUSB0
と表示されればOKです。(USB接続しているTTYデバイスが1つの場合)
pi@raspberrypi:~ $ ls /dev/ttyUSB* /dev/ttyUSB0
このMONOSTICには「超簡単!標準アプリ」がプリインストールされているので、子機として使うBLUE PALとアプリを合わせるために、パルアプリの親機用プログラムを書き込む必要があります。
ぼくは、普段調べ事やファイルのダウンロードはWindows PCで行っているので、プログラム書き換え作業もWindowsで行いました。
まず、パルアプリ(App_pal)ダウンロードページの「ソフトウェア」リンクで「App_PAL_bin_1_0_0.zip」をダウンロードします。次に、TWELITEプログラマページの「ダウンロード」リンクで「TWE-Programmer_0_3_5_3.zip」をダウンロードします。
Windows PCにMONOSTICを付けて「TWE-Programmer.exe」を実行し、「App_PAL-Parent-BLUE-MONOSTICK.bin」を書き込みます。
プログラム
開閉センサーパルからのデータを受け取るプログラムとWebサーバープログラムをPythonで書きました。
まずは、プログラムで使用しているモジュールをインストールします。
最初は、カメラ制御に使う「OpenCV」です。ほんとは画像解析や機械学習なんかに使うものですが、贅沢に写真を撮るだけに使ってます。
pi@raspberrypi:~ $ sudo apt-get install python-opencv
次は、MONOSTICからのデータを受け取るためのシリアル通信モジュール「pySerial」です。
pi@raspberrypi:~ $ pip install pyserial
その次は、今回初めて使用したPython用Webフレームワークの「Bottle」です。
pi@raspberrypi:~ $ pip install bottle
最後は、これまた初めて使うPython用テンプレートエンジンの「Jinja2」です。
pi@raspberrypi:~ $ pip install jinja2
普通はこれでOKなんですが、今回はWebサーバーを80ポートで実行する必要があったため、Bottleを管理者権限で実行できるようにしています。
pi@raspberrypi:~/Bottle $ sudo pip install bottle
TWELITE受信プログラム
いよいよお待ちかねのプログラムです。まずはTWELITE受信プログラムから。
#!/usr/bin/env python # -*- coding: utf-8 -*- # # Receiver program from OPEN-CLOSE SENSE PAL # Feburary 21, 2019 # By Hiroyuki ITO # http://intellectualcuriosity.hatenablog.com/ # MIT Licensed. import serial import datetime # Open MONOSTICK ser = serial.Serial('/dev/ttyUSB0', 115200) while True: try: # Recive data line = ser.readline() if line[0:1] == ":" : serialdid = line[15:25] # Serial No. & Logical device ID periodic = line[63:64] # Periodic flag magnetic = line[64:65] # Magnetic data # When periodic communication # Check the previous magnetic state update = True if periodic == "8" : with open(serialdid, "r") as f: if f.read(1) == magnetic : update = False # Write magnitic state and time if update == True : now = datetime.datetime.now() w_data = magnetic + "," + now.strftime('%m/%d %H:%M:%S') with open(serialdid, "w") as f: f.write(w_data) print(w_data) except KeyboardInterrupt: break ser.close()
開閉センサーパルが扉の開け閉めのタイミングで送るデータと、定期的に送るデータ(デフォルトは1分)を受け取って、シリアル番号とデバイスIDをファイル名として開閉情報と時刻を保存します。定期送信の場合は、前回の状態と同じ場合は時刻を更新しないようにしています。
オフィシャルな シリアルデータの受信形式はお世辞にも見やすくはないので、わかり易い図を作ってみました。プログラムで使っているのは明るい色の部分だけです。
Webサーバープログラム
次はWebサーバープログラムです。TWELITE受信プログラムと同じディレクトリで動作させることを前提としています。
#!/usr/bin/env python # -*- coding: utf-8 -*- # # Ofuro Board Server program # Feburary 21, 2019 # By Hiroyuki ITO # http://intellectualcuriosity.hatenablog.com/ # MIT Licensed. import cv2 from bottle import route, run, static_file, auth_basic from bottle import TEMPLATE_PATH, jinja2_template as template # Serial No. and Device ID of OPEN-CLOSE SENSE PAL SERIAL_DID = "8888888801" # for Basic authentication #USERNAME = "RepraceUsername" #PASSWORD = "RepracePassword" TEMPLATE_PATH.append("./template") # Basic authentication def check(username, password): return username == USERNAME and password == PASSWORD # Take picture and save image file def capture(): cap = cv2.VideoCapture(0) ret = cap.set(3, 1280) ret = cap.set(4, 720) ret, frame = cap.read() cv2.imwrite('images/board.jpg', frame) cap.release() # Read magnetic state from file def readpal(): with open(SERIAL_DID, "r") as f: if f.read(1) > "0" : status = "close" else : status = "open" f.seek(2) time = f.read(11) return status, time # Response of root access @route('/') #@auth_basic(check) def index(): capture() status, time = readpal() return template('index.j2', ofuro=status, time=time) # Response of JPEG file @route('/images/<filename:re:.*\.jpg>') def send_image(filename): return static_file(filename, root='/home/pi/Bottle/images', mimetype='image/jpg') # Useing port 80 needs ROOT run(host='0.0.0.0', port=80)
リクエストがあった時に次の2つのことを行っています。
最後の行がrun(host='0.0.0.0', port=80)
になっているのは、dataplicityのWormhomeが80ポートしかリダイレクトしてくれないからです。
ベーシック認証も使えるようにコードは入れていますがコメントになっています。
Jinja2テンプレートファイル
最後はJinja2のテンプレートファイルです。興味本位でJinja2を使ってしまいましたが、表示するものがトップページしかないこともあり、「動作すればいいか」というコードになっています。
index.j2
{% extends "base.j2" %} {% block content %} <div data-role="page"> <div data-role="header" data-theme="b"> <h3>Ichikawa Ofuro</h3> </div> <div role="main" class="ui-content"> <div><img src="/images/{{ofuro}}.jpg" style="width:100%" /></div> <div align="center"><input type="button" value="The door is {{ofuro}} from {{time}}" data-icon="refresh" data-iconpos="right" onclick="window.location.reload();" /></div> <div><img src="/images/board.jpg" style="width:100%" /></div> </div> <div data-role="footer" data-theme="b"> <h3>Created by Hiroyuki ITO</h3> </div>
</div> {% endblock %}
base.j2
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ICHIKAWA OFURO</title> <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.css" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.js"></script> </head> <body> {% block content %} {% endblock %} </body> </html>
コードを見てお分かりのようにjQuery Mobileも使っています。
おわり