SwitchBotの開閉センサーの値をRaspberry Piで取得!

2022年9月24日

今回は少し上級者向けの記事になります。

こんな人におすすめの記事です。

  • SwitchBotの開閉センサー値をRaspberry Piで取得する方法を知りたい
  • SwitchBotの開閉センサーを取得して、SwitchBot以外の機器を操作したい

別にわざわざ、わけのわからないことをしなくても、SwitchBotの開閉センサーをSwitchbotアプリで連携すれば良いじゃん!って思う人が大多数でしょう。

もちろん、SwichBotアプリで連携すれば、「ドアが開いたら、照明を消す」などの操作は可能です。

ただ、この問題点は、SwitchBotの開閉センサーを検知して、操作できるデバイスはSwichBotデバイスだけであることです。SwichBotのボットやHub、カーテンなどに限られます。

例えば、個別にGoogle Homeに連携しているルンバなどのスマート家電は、SwitchBotの開閉センサーを検知して動作させるということができません。

これを実現する方法として有名なのがIFTTTというサービスを利用することですが、IFTTTも有料化してしまい、使い勝手が悪くなってしまいました。

何とかできないかと思い立って、考え着いた方法が自前で作ってしまおうということです。

自前で作るといっても、開閉センサーのセンサー値を読み取れなければ意味がありません。

今回は、開閉センサーのセンサー値を読み取るところに焦点を当てて説明します

私の紹介ですが、私はスマートホームに興味がでて1ヶ月で基本環境を構築しました。今では家中スマートホームデバイスが溢れており、妻には呆れられています(笑)遠隔操作、声で操作、GPSで操作、人が通ったら…などあらゆるシーンで活用しています!

SwitchBotの開閉センサー値をRaspberry Piで取得する方法

センサー値を取得するにはこの記事に書かれている手順に沿って作成するだけです。

この記事でできることは、以下になります。

  • 開閉センサーのセンサー値を取得
  • センサー値を利用してアクションをする方法

具体的な活用例としては、例えば以下があります。

  • ドアを開けて、照度が明るい場合は、照明を消す
  • ボタンを押したら、ルンバで掃除を開始する
  • ドアが開けっ放しになっていたら、音を鳴らす

今回の環境構築に必要なもの

この記事で紹介している環境を構築するためには以下が必要です。

前提

センサー値を取得するために事前に確認や設定が必要となります。これらの確認・設定をするためには以下を事前に済ませておく必要があります。

  • スマートフォンにSwitchBotアプリがインストールされている
  • SwitchBotのアカウントがある
  • SwitchBotアプリと開閉センサーの登録設定が済んでいる

要は、SwitchBotの開閉センサーを本来の使い道の通りに使える準備ができているということです。

また、Raspberry Piは初期設定が完了していることを前提とします。初期設定についてはGoogleで検索すればいくらでも情報を入手できます。

LXTerminalや他のターミナル(Windowsのコマンドプロンプトでも良い)についても、数度程度は使ったことがあり、なんとなく使い方を知っている前提で書いています。

センサー値を取得する手順

手順は以下の通りになっています。

  1. 事前準備(必要なもののインストール)
  2. MACアドレスの確認
  3. センサー値を取得のPythonスクリプトを作成
  4. 実行してみる
  5. センサー値に応じた処理を記述

⓪事前準備

Raspberry Piに事前に設定しておくことがあります。以下の2つをRaspberry Piにインストールします。

  • libglib2.0-dev
  • bluepy

libglib2.0-devのインストール

Raspberry Piで「LXTerminal」を起動し、以下を入力します。

sudo apt install libglib2.0-dev

bluepyのインストール

同じく「LXTerminal」で以下を入力します。

pip3 install bluepy

①MACアドレスの確認

まずは開閉センサーのMACアドレスを確認します。MACアドレスは機器を識別するために必要です。

スマートフォンのSwichBotアプリを使用します。

下記の手順で該当の開閉センサーのMACアドレスを調べておきます。

②センサー値取得のPythonスクリプトを作成

センサー値を取得するためのスクリプトを作成します。

早い話、テキストエディタを開いて、以下をコピーすればOKです。

テキストエディタもなんでも構いません。Windowsなら「メモ帳」、Raspberry Piで作るなら「Thonny」や「Text Editor」でも構いません。

保存先はどこでも構いませんが、この記事ではRaspberry Piのデスクトップに「switchbot」というディレクトリを作成して、そこに保存しています。

そして、ファイル名は「switchbot-sensor.py」としています。

import binascii
from bluepy.btle import Scanner, DefaultDelegate

class ScanDelegate( DefaultDelegate ):
    def __init__( self,macaddr ):
        self.macaddr=macaddr
        self.isButton=0
        self.postButtonCount=0
        self.output=[{"isButton":self.isButton}]
        DefaultDelegate.__init__( self )

    def handleDiscovery( self, dev, isNewDev, isNewData ):
        if dev.addr == self.macaddr:
            for ( adtype, desc, value ) in dev.getScanData():
                if ( adtype == 22 ):
                    servicedata = bytes.fromhex( value[4:] )
                    isIlluminance=servicedata[3] & 0b00000001         #照度取得(明るい/暗い)
                    isOpen=(servicedata[3] & 0b00000010) >> 1         #開閉状態取得
                    isLeaveOpen=(servicedata[3] & 0b00000100) >> 2    #開けっ放し状態取得
                    time=servicedata[7]                               #開けっ放しの時間取得
                    buttonCount=servicedata[8] & 0b00001111           #ボタンが押された回数取得

                    if self.postButtonCount==0:
                        self.postButtonCount=buttonCount

                    #ボタンが押されたかどうか判定
                    if (buttonCount>=self.postButtonCount+1) or (buttonCount==1 and self.postButtonCount==15):
                        self.isButton=1
                    elif buttonCount==self.postButtonCount:
                        self.isButton=0

                    if buttonCount!=self.postButtonCount:
                        self.postButtonCount=buttonCount

                    #出力データの整形
                    self.output={"isIlluminance":isIlluminance,"isOpen":isOpen,"isLeaveOpen":isLeaveOpen,"time":time,"buttonCount":buttonCount,"isButton":self.isButton}

    def startScan(self,scan,time):
        Scanner().withDelegate( self ).scan(time)

if __name__ == '__main__':
    macaddr = 'aa:00:bb:11:cc:22'   #あなたのデバイスのMACアドレスに書き換えてください(アルファベットは小文字) ※左は記入例

    scan=ScanDelegate(macaddr) #BLEスキャンのための初期設定

    while True:
        scan.startScan(scan,3)  #スキャン開始 3秒間隔

        #取得データの出力
        print("isIlluminance:",scan.output["isIlluminance"])    #照度出力 明るい=1 暗い=0
        print("isOpen:",scan.output["isOpen"])                  #開閉状態   開いている=1 閉まっている=0
        print("isLeaveOpen:",scan.output["isLeaveOpen"])        #開けっ放し状態 開けっ放し=1 開けっ放しではない=0
        print("time:",scan.output["time"])                      #開けっ放し時間 単位:秒
        print("buttonCount:",scan.output["buttonCount"])        #ボタンが押された回数 1~15で表示。15の時に押されると1に戻る
        print("isButton:",scan.output["isButton"])              #ボタンが押されたか 押された=1 押されていない=0
        print("-----------")

        #取得データによるアクションを以下に記述

そして、①で確認したMACアドレスを42行目の’ '内に記載します。

macaddr = 'aa:00:bb:11:cc:22'   #あなたのデバイスのMACアドレスに書き換えてください(アルファベットは小文字) ※左は記入例

スキャンの間隔は3秒に設定しています。変更する場合はscan.startScan(scan,3)の"3″の部分の数字を変更してください。例えば、10秒にするには、 scan.startScan(scan,10)です。

③実行してみる

問題なく取得できているか確認するために、Raspberry Piで実行してみましょう。

「LXTerminal」で実行します。

②で作成したPythonスクリプトを保存したディレクトリまで移動します。

デスクトップのswitchbotというディレクトリであれば、以下を入力すると移動できます。

cd Desktop/switchbot/

そこで、②で作成したPythonスクリプトを実行します。実は、今回のスクリプトで使用しているBLEスキャンを実行するには、sudo権限が必要です。そのため、実行には以下を入力します。

sudo python3 switchbot-sensor.py

これで実行されます。

センサー値がターミナルに表示されれば成功です。(以下が成功例)

出力の数値の意味は上から、

isIlluminance照度 明るい=1 暗い=0
isOpen開閉状態 開いている=1 閉まっている=0
isLeaveOpen開けっ放し状態 開けっ放し=1 開けっ放しではない=0
time開けっ放し時間 単位:秒
buttonCountボタンが押された回数 1~15で表示。15の時に押されると1に戻る
isButtonボタンが押されたか 押された=1 押されていない=0

何もしなければ、永遠にセンサー値が出力されます。終了するときは、Ctrl+cで終了できます。

④センサー値に応じた処理を記述

センサー値の取得であれば、③までで十分ですが、実際はセンサー値を取得して何かアクションを起こすと思います。

そのアクションをスクリプトに記載する必要があります。

そのアクションは②のスクリプトの最終行以降に記載すればOKです。(#取得データによるアクションを以下に記述というコメントの下に記載)

今回の仕組みの解説

ここからは、より深く知りたい人向けに今回の仕組みやスクリプトの解説をしていきます。とりあえず、実現すれば良いやというような場合は、わざわざ読む必要はありません。

基本的な仕組み

SwitchBotの開閉センサーはBluetoothのBLEという技術を使って周囲に情報を発信しています。

今回はこの発信されている情報をRaspberry Piを使ってスキャンして、取得するという方法を取っています。

スキャンして、該当の開閉センサーのMACアドレスから発信されている情報を取得して、現在の開閉センサーの状態を判別しています。

スキャン生データについて

先ほどのPythonスクリプトは取得したデータを加工して、それぞれ「開けた」状態や「閉めた」状態、「開けっ放し」の状態などを識別しています。

取得した生データ(スクリプト内のvalue[4:])はこのようなデータになっています。

value[4:]の値 = 6400640006320d958e

16進数で表されており、1byte=2桁で区切られます。

全てのByteの意味までは解読できていませんが、わかっている範囲でそれぞれのByteで次のような意味を持っています。

配列のインデックスでいう

3番目・・・明るい/暗い/開けた/閉めた/開けっ放し などの状態

7番目・・・開けっ放しの時間

8番目・・・開閉の検出/ボタンを押した回数

3番目はさらに、各bitで以下の状態を格納しています。

1桁目・・・明るい/暗い

2桁目・・・開閉

3桁目・・・開けっ放し検出

8番目は前半bitと後半4bitで開閉の検出とボタンの押下回数を格納しています。

1~4桁目にボタンの押下回数が格納されています。

これらを図にするとこのようになります。

おそらくは、設定値などもどこかに格納されているのでしょうが、そこまでは解析しませんでした。より詳しい方はぜひ教えてください。

APIを使わないのはなぜか

実はSwitchBotにはAPIがあります。しかも、最近、開閉センサーもAPIに追加されました。APIを使用しても、今回と同じようにセンサー値を取得できます。

ただし、APIの欠点は使用上限があることです。SwichBotのAPIは10,000回/日が上限となっています。

今回のような開閉センサーが開いたかどうかを検出するには、常にAPIを叩き続けなければなりません。(多分。うまいやり方あったら教えてください!)

なので、1日中、開閉センサーを監視しようとするのは結構上限に引っかかってきます。

1日10,000回しかAPIを利用できないので、1日を10,000回をで割ると8.64秒です。(1日×24時間×60分×60秒÷10,000回)

8.64秒ごとに1回しかAPIにアクセスできません。つまり、監視間隔は最短で8.64秒であり、8.64秒以内に行われる開閉は検知できないかもしれません。(あくまでも1日中監視する場合。監視する必要のない時間を除けば、もっと間隔は短くできます)

また、これは10,000回すべてを開閉センサーの監視に使った場合です。実際は、開閉センサーを検知した後に、電気を消すなどのアクションがあり、そこでまたAPIを使うことが考えられます。また、開閉センサーだけでなく、人感センサーも同時に監視する場合もあるでしょう。

この場合は、それぞれの操作・監視で10,000回を取り合うことになるので、さらに監視間隔は長くなるでしょう。

そう考えると、1日の上限が10,000回では、物足りないことも起こり得えます。

と、まぁAPIのデメリットっぽいことをつらつらと書きましたが、APIがダメだということではないです。そのデメリットを考慮して、十分であればAPIを活用する方が簡単かもしれません。

ただ、私がこの開閉センサーの検出を自前で作ろうと思った時には、まだAPIが無かったんです。作り始めてからAPIが出てきたので、自前の方が優れているとちょっと言いたくなっただけです。すみません。

【追記】複数のセンサーがある場合のスクリプト

有難いことにこの記事で色々と質問をいただきます。

その中で多いのが、複数のセンサーがある場合にどうやったら良いのか?というものです。

答えは簡単でインスタンスを複数設定してあげればOKです。

しかし、それが10台とかになってくると、結構書くのも大変になってくるので、複数台に対応したスクリプトを載せておきます。

先ほどのスクリプトのif __name__ == '__main__’:の行以降を以下に書き換えてください。

if __name__ == '__main__':
    macaddrs = [
        'xx:xx:xx:xx:xx:xx',    #1台目のMACアドレス
        'oo:oo:oo:oo:oo:oo'     #2台目のMACアドレス 3台目以降はこの行の末尾に","を追加し、3行目に記載
        ]

    scans=[]

    for macaddr in macaddrs:
        scans.append(ScanDelegate(macaddr))

    while True:
        for scan in scans:
            print("-----------------")
            print(scan.macaddr)

            scan.startScan(scan,3)  #スキャン開始 3秒間隔

            #取得データの出力
            print("isIlluminance:",str(scan.output["isIlluminance"]))    #照度出力 明るい=1 暗い=0
            print("isOpen:",scan.output["isOpen"])                  #開閉状態   開いている=1 閉まっている=0
            print("isLeaveOpen:",scan.output["isLeaveOpen"])        #開けっ放し状態 開けっ放し=1 開けっ放しではない=0
            print("time:",scan.output["time"])                      #開けっ放し時間 単位:秒
            print("buttonCount:",scan.output["buttonCount"])        #ボタンが押された回数 1~15で表示。15の時に押されると1に戻る
            print("isButton:",scan.output["isButton"])              #ボタンが押されたか 押された=1 押されていない=0

            #取得データによるアクションを以下に記述
            if scan.macaddr==macaddrs[0]:
                #1台目の処理
                print("1台目の処理")
            elif scan.macaddr==macaddrs[1]: #3台目以降はここからの3行をコピーし、maccaddrsの数字を増やす
                #2台目の処理
                print("2台目の処理")

            print("-----------")

まとめ

いかがだったでしょうか?

SwichBotの開閉センサーのセンサー値を取得する方法がわかったと思います。

もし、わからないこと、詰まったところ等ありましたら、遠慮なくお問い合わせください。できる限りサポートさせていただきます。専門家ではないので、完ぺきな回答はできないかもしれませんが、お金はもちろんいただきませんので、気軽に聞いてみてください。何かヒントになるかもしれません。(→Twitter)

お問い合わせフォーム

スマートホーム化に関する記事を他にも書いています。こちらも参考にしてみてください!

この記事を書いた人
author

ユキヒト

家電大好き+子育て真っ只中のブロガーです。
スマートホームを始めとする家電関連の記事や子育て・知育関連の記事を書いています。
質問や相談はいつでも受け付けています。お問い合わせフォームやTwitterからお待ちしております。(もちろん無料です)
どんなことでも構いません。
これまでにあった例としては、
「SwitchBotハブの設定ができないので教えてもらえませんか?」
「ベビーカメラの安いやつないですか?」
などです。全く記事と関係ない質問もOKです。
できる限り答えていきたいと思いますので、お気軽にどうぞ。
お問い合わせ
Twitter