[Dd]enzow(ill)? with DB and Python

DBとか資格とかPythonとかの話をつらつらと

Raspberry Pi でトイレの使用状況をSlackに通知してみた(IOTT:Internet Of The Toilet)

Raspberry Pi と照度センサを使って、トイレの使用状況をSlackからわかるようにしてみました。 ※全部会社で買ってもらった(*´ω`*)

f:id:denzow:20171216225031p:plain

動機

職場は2フロアあり、下のフロアがオープンスペースになっていおりトイレもここにあります。私は上のフロアで仕事をしていますので、トイレにいくには下のフロアに降りるのですが、トイレが1個しかないので、降りてみたら使用中で仕方なく上に戻るという非生産的な行動が多発していました。

そのため、上のフロアにいてもひと目でトイレの使用状況がわかるようにしたかったのです。

実現方法

会社のトイレは家庭用のトイレ個室のような感じですので、こちたの記事のように扉の開閉センサーでの判定は難しい(使い終わって扉を閉められると使用中になりうる)ので、個室の電気がついていれば使用中とみなすことにして実装しました。

ざっくりいうとこんな感じです。

  • トイレに照度センサーを置いて、明るさを監視する
    • 照度センサー等はもろもろラズパイで作る
  • 明るさが変化した時点でSlackのBotアカウントのステータスアイコンを変更する
    • 使用中ならactive
    • 空いている場合はaway

照度センサーについて

会社にあまっていたラズパイ(raspberry pi 3 model B)があったので貰い受け、照度センサ:BH1750をバックオフィスの方にねだることで機材を揃えました。

なお、購入した照度センサはピンがはんだ付けされていないので別途ハンダゴテなども必要です。(これも買ってもらった)

ラズパイとBH1750の接続や値の取得については、以下の記事の輝度センサ部分を参考とさせていただきました。

http://jimaoka.hatenablog.jp/entry/raspi-sensor

一通りセットアップを終えたら以下のPython3のコードで輝度を取得できることを確認しました。

# smbus モジュールの入手
$ pip3 install smbus
import smbus

def get_lux():
    bus = smbus.SMBus(1)
    addr = 0x23
    lux = bus.read_i2c_block_data(addr, 0x10)
    return (lux[0]*256+lux[1])/1.2

if __name__ == '__main__':
    print(get_lux())

私の環境では暗いときは100未満で、蛍光灯レベルでは200以上の値が出ていました。

Skackへの通知

単にトイレ使用開始時にSlackにメッセージを通知してもいいのですが、それはあまりに監視されている感じもするので、SlackBotのステータスで使用中かどうかを表現するようにしました。

f:id:denzow:20171216225031p:plain
使用中
f:id:denzow:20171216225100p:plain
空き状態

とりあえず、ラズパイ上でSlackBotを動かすことにしました。SlackBotはslackbotを使うのが楽そうなので、こちらを使います。

$ pip3 install slackbot

ファイルはこんな感じで構成します。

.
├── bot.py                 # 実際に実行するスクリプト
├── plugins                # 話しかけられた際の応答処理を書いておくディレクトリ
│   ├── __init__.py
│   └── toilet.py          # 会話の応答処理を定義するスクリプト
└── slackbot_settings.py   # Botの設定ファイル

Slack Tokenの取得

Botを作成するので、事前にSlack側で作業をしておきます。

https://my.slack.com/services/new/bot

f:id:denzow:20171216225132p:plain f:id:denzow:20171216225146p:plain

あとは設定を保存し、表示されていたAPI トークンを確認しておきます。取得したトークンはslackbot_settings.pyに設定しておきます。

:
# botアカウントのトークンを指定
API_TOKEN = '確認しておいたトークン'
:
:

Botによる会話

このslackbotは話しかけられた際の応答の定義をplugins配下のスクリプトで定義します。例えば以下をplugins/toilet.pyとして配置します。

# coding: utf-8
import smbus
from slackbot.bot import respond_to     # @botname: で反応するデコーダ
from slackbot.bot import default_reply  # 該当する応答がない場合に反応するデコーダ


def get_lux():
    bus = smbus.SMBus(1)
    addr = 0x23
    lux = bus.read_i2c_block_data(addr,0x10)
    return (lux[0]*256+lux[1])/1.2


# といれ と話しかけらたときの応答
@respond_to('といれ')
def check_toilet(message):
    message.reply('明るさ{}'.format(get_lux()))
    if get_lux() > 100:
        message.reply('たぶん、中に誰もいなくないですよ')
    else:
        message.reply('たぶん、中に誰もいませんよ')


# いずれのルールにも該当しない場合の応答
@default_reply
def default_func(message):
    message.reply('といれ って話しかけてね')

このBotに対してといれと話しかけると、現在の明るさと空室状況を応答します。

f:id:denzow:20171216225205p:plain

Botの在籍状況(ステータス)の設定

これで、トイレにいく前にBotに話しかけておけば空室状況がわかるようになりましたが、毎回聞くのは面倒なのでステータスアイコンで判別したいわけです。しかし、今回利用しているslackbotにはBotアカウントの在籍・離席といったステータスを設定する機能がないようです。そのため、以下のusers.setPresenceというSlackのWEBAPIを直接利用します。

https://api.slack.com/methods/users.setPresence

PythonでHTTPリクエストをするのであれば、requestsが楽なので導入します。

$ pip3 install requests

あとはこんな感じでステータスを変更できました。

API_URL = 'https://slack.com/api/users.setPresence?token={token}&presence={status}'
API_TOKEN = 'YOUR_BOT_TOKEN'

# ステータスON
requests.get(API_URL.format(token=API_TOKEN, status='auto')).content
# ステータスOFF
requests.get(API_URL.format(token=API_TOKEN, status='away')).content

また、空室状況にあわせてステータスを設定するには定期的に明るさを取得する必要がありますが、そのような定期実行の機能もなさそうでした。

しかたないので、エントリポイントであるbot.pyでBotを起動する際に独自にスレッドを生やして定期実行させることにしました。最終的にbot.pyは以下のようになりました。

#!/usr/bin/env python3
# coding: utf-8
import time
import datetime
import threading

import requests
from slackbot.bot import Bot
from slackbot.settings import API_TOKEN

# toilet.pyから照度取得関数をもってきておく
from plugins.toilet import get_lux

API_URL = 'https://slack.com/api/users.setPresence?token={token}&presence={status}'
LIGHT_THRESHOLD = 100


def set_away():
    """
    Botのステータスを離席にする
    :return:
    """
    res = requests.get(API_URL.format(token=API_TOKEN, status='away'))
    print(res.content)


def set_active():
    """
    Botのステータスを在籍にする
    :return:
    """
    res = requests.get(API_URL.format(token=API_TOKEN, status='auto'))
    print(res.content)


def check_lux():
    """
    Threadで定期的に明るさをチェックしてしきい値を下回ったらステータスをOFFにする
    """

    is_using = False
    # 初期化
    set_away()
    while True:
        lux = get_lux()
        print(datetime.datetime.now(), lux)
        # ステータスが変化したら通知する
        # 未使用 で明るくなった -> 利用開始
        if lux > LIGHT_THRESHOLD and not is_using:
            is_using = True
            print('ON')
            set_active()

        # 使用中から暗くなった -> 利用終了
        elif lux <= 100 and is_using:
            is_using = False
            print('OFF')
            set_away()

        time.sleep(3)
        

def main():
    bot = Bot()
    # 照度監視用のスレッドを起動する
    th_me = threading.Thread(target=check_lux, name="th_check_lux")
    th_me.setDaemon(True)
    th_me.start()
    try:
        # botを起動する
        bot.run()
    except Exception as e:
        print(e)


if __name__ == "__main__":
    print('start slackbot')
    main()

これで以下のようにBotを起動すれば監視が開始されます。

$ python3 bot.py
start slackbot
b'{"ok":true}'
2017-12-16 21:58:54.596923 184
ON ★ 明るかったので使用中になった
2017-12-16 21:58:57.598332 126
2017-12-16 21:59:00.602300 180
2017-12-16 21:59:03.607373 3
OFF ★暗くなったので空き状態になった
b'{"ok":true}'
2017-12-16 21:59:06.891987 157
:

自動起動の設定

ラズパイを起動した時点で、トイレの監視を自動的に有効化してほしいのでsystemdに登録しておきます。まずは、起動用のシェルスクリプトを書いておきます。

#!/bin/sh
# service 登録用のスクリプト

python3 /home/pi/apps/toi/bot.py

そして、systemdに登録するための設定をtoiletmonitor.serviceとして作成します。

[Unit]
Description = monitor toilet

[Service]
ExecStart = /path/to/shell/script ※要書き換え
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

作成したtoiletmonitor.service/etc/systemd/system/の配下に配置します。そして、以下のように起動できるか確認しておきます。

# 起動
$ sudo systemctl start toiletmonitor
# 停止
$ sudo systemctl stop toiletmonitor

問題なく動作ができたら、以下で自動起動を有効化します。

$ sudo systemctl enable toiletmonitor

これで、トイレにラズパイを置いて電源をいれるだけで監視が開始されるようになります。

まとめ

実際にこんな感じで会社のトイレに配置しています。

f:id:denzow:20171216224913p:plain

今回のコード等は以下のリポジトリで公開していますのでよろしければご覧ください。

https://github.com/denzow/iott/blob/master/toiletmonitor.service.sample

はじめて真面目にラズパイで遊んでみましたが、ハマりどころもすくなくなかなか楽しかったです。照度センサも3個入りでまだまだ余っていますので他の活用方法も考えてみたいと思います。

なお、本記事を作成中に確認したところ金曜日まで生きていたBotが息をしていないので社外からトイレの利用状況を把握できないという重大なインシデントが発生しているので、週明けに復旧作業をする見込みです・・・