知的好奇心 for IoT

IoT関連の知的好奇心を探求するブログです

シェアハウスのお風呂をみんなで使いやすくするシステムを作った

ぼくが住んでいるシェアハウスにはシャワールームが8個あったのですが、最近のリニューアルで2個を潰して代わりにお風呂になったんです。

で、お風呂利用者が多いと思われる女性の専用フロアが4階にあって、わざわざ1階に降りて来なくてもお風呂が空いているかどうかわかるようにならないかと相談があって作ったのが「Ofuro Board β」です。

「Board」っとなっているのは、お風呂に入っている時間は長いので空いているかどうかだけではなく、いつまで入るかや入りたい予定をホワイトボードに書いて共有できるようにしたからです。

スマホのブラウザで見た画面はこんな感じになります。

f:id:IntellectualCuriosity:20190219215618p:plain

Ofuro Board β

 システム構成図はこんな感じ。

f:id:IntellectualCuriosity:20190219220344p:plain

Ofuro Board System Diagram

TWELITE

TweliteはIEEE802.15.4準拠した32bit無線マイコンモジュールで、ZigBeeライクなものです。秋葉原秋月電子の店頭でたまたまBLUE PALを見かけて、100均でも売っている一般的なコイン型リチウム電池で4年も動作する!という謳い文句にひかれて購入してしまいました。

最初は秋月電子¥30で売っているホールICをBLUE PALに繋いで使おうと思っていたのですが、無線タグアプリをスイッチとして使おうとした場合、ONかOFFの状態が出力されるのではなく、データが届くたびに単に出力がトグルする仕様になっていたため使用を断念しました。

しようがないので、開閉センサーパルも買ってBLUE PALにデフォルトで書き込まれているパルアプリで動作させることにしました。

でも、結局、BLUE PALと開閉センサーパルの組み合わせは、写真のように見た目も良くスッキリしていて取り付けも楽なので、これが正解なんだと納得しています。

f:id:IntellectualCuriosity:20190221202754j:plain

BLUE PAL + OPEN-CLOSE SENSE PAL

Raspberry Pi側に付けたMONOSTICKや、BLUE PALのプログラム書き換えと設定変更用のTWELITE Rも購入したので、TWELITEを使うために結構出費してしまいました。

TWELITE関連購入機器秋月電子調べ)

 

ラズパイ周り

まずはハードウェアから。

使っているものはこんなものです。

写真だとこんな感じです。

f:id:IntellectualCuriosity:20190221230531p:plain

Around Raspberry Pi

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 PiWindows 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」を書き込みます。

f:id:IntellectualCuriosity:20190222202323p:plain

TWELITE プログラマ

プログラム

開閉センサーパルからのデータを受け取るプログラムと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をファイル名として開閉情報と時刻を保存します。定期送信の場合は、前回の状態と同じ場合は時刻を更新しないようにしています。

オフィシャルな シリアルデータの受信形式はお世辞にも見やすくはないので、わかり易い図を作ってみました。プログラムで使っているのは明るい色の部分だけです。

f:id:IntellectualCuriosity:20190224221859p:plain

開閉センサーパルのデータ構造

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つのことを行っています。

  • カメラで写真を撮ってJPEGファイルを作る。
  • 開閉センサーパルが作ったファイルを読み取って、空きと閉じのJPEGファイルを切り替える。

最後の行がrun(host='0.0.0.0', port=80)になっているのは、dataplicityWormhomeが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も使っています。

 

おわり