6月 16, 2022

Bluetooth BLEによるSwitchbotのセンサー情報取得

Bluetooth BLEに対応した各種センサーがSwitchbotから出ているので、それらの情報を取得しスマートホームの制御に用いたい。
Switchbotの純正アプリでもトリガー&アクションを設定できるが、Switchbot製品しか設定できないため、自分で実装しあとでNode-redに接続する。



ライブラリのインストール

$sudo apt-get install python3-pip libglib2.0-dev
    $ pip install bluepy

bluepyにsudo権限を設定し、ユーザでも実行できるようにする。詳細はこちら

    $find /usr/local/lib -name bluepy-helper
    $find ~/ -name bluepy-helper
    $cd 上記のコマンドで確認したインストールPATH
    $sudo setcap 'cap_net_raw,cap_net_admin+eip' bluepy-helper

Switchbotの通信プロトコル

Switchbotから送られてくるデータの形式の詳細は、こちらに記載されています。
例えば、開閉センサー(Contact sensor)は、RESP Packet(下記プログラム中の"servicedata")の下記を見れば情報が分かります。
  • Byte:1の7bit目: PIR(受動型赤外線センサ)による動体検知結果(0=動きなし, 1=移動体あり)
  • Byte:2の1~7bit: バッテリー残量[%]
  • Byte:3の1bit目: 明るさセンサーによる照度状態(1=明るい, 0=暗い)
  • Byte:3の2,3bit目: ドアの開閉状態(2=開けっ放し, 1=開いている, 0=閉まっている)

通信&データ解析

開閉センサー:Contact

Pythonのライブラリを使って、BLE通信を行いSwitchbotからの受信データ解析を行います。
下記のプログラムは、こちらを参考に実装しました。
import binascii
import sys
import influxdb
from bluepy.btle import Scanner, DefaultDelegate
import datetime

sensorType = ['meter'
        ,'curtain'
        ,'contact'
        ]
sensorAddr = ['aa:aa:aa:aa:aa:aa'
        ,'bb:bb:bb:bb:bb:bb'
        ,'cc:cc:cc:cc:cc:cc'
        ]
sensorPlace = ['2F'
        ,'1F'
        ,'Bedroom'
        ]
count = [0]

class ScanDelegate( DefaultDelegate ):
    def __init__( self ):
        DefaultDelegate.__init__( self )

    def handleDiscovery( self, dev, isNewDev, isNewData ):
        global count
        #countの初期化がまだであれば、addrのデータ数で配列を初期化
        if ( len(count) != len(sensorAddr) ):
            count = [0] * len(sensorAddr)
        
        #MACアドレスが検索対象だったら
        if dev.addr in sensorAddr:
            #対応するセンサーの配列番号を特定
            sensorNum = sensorAddr.index(dev.addr)
            #Scandataの読み込み
            scanData = dev.getScanData()

            for data in scanData:
                #読み出したいデータが入っている配列だけ抽出して、データを解析
                if ( data[0] == 22 ):
                    #同じaddrが2回来たら中断
                    if ( count[sensorNum] > 0 ):
                        print("----")
                        print("finish")
                        print("  type: "          + sensorType[sensorNum] )
                        print("  place: "         + sensorPlace[sensorNum] )
                        print("  addr: "          + sensorAddr[sensorNum] )
                        print("----")
                        sys.exit()
                    count[sensorNum] += 1

                    #データ読み取り
                    servicedata = binascii.unhexlify( data[2][4:] )

                    #Contact sensor
                    if ( sensorType[sensorNum] == "contact"):
                        #https://www.core-da-core.com/switchbot-opensensor-raspberry-pi/
                        #https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/contactsensor.md
                        pir = ((servicedata[1]) & 0b01000000) >> 6
                        battery = (servicedata[2]) & 0b01111111
                        isIlluminance = (servicedata[3]) & 0b00000001
                        isOpen = ((servicedata[3]) & 0b00000110) >> 1
                        
                        print("----")
                        print("type: "          + sensorType[sensorNum] )
                        print("place: "         + sensorPlace[sensorNum] )
                        print("addr: "          + sensorAddr[sensorNum] )
                        print(" > battery: "        + str(battery))                 #バッテリー残容量 [%]
                        print(" > pir: "            + str(pir))                     #移動体検知 0=動きなし, 1=移動体あり
                        print(" > isIlluminance: "  + str(isIlluminance))           #照度状態 1=明るい, 0=暗い
                        print(" > isOpen: "         + str(isOpen) )                 #開閉状態 2=開けっ放し,  1=開いている, 0=閉まっている
                        
                    else:
                        print("----")
                        print("type: "          + sensorType[sensorNum] )
                        print(" > value" )
                        print(data[2])
        else:
            #他ののBLE端末
            print("----")
            print("addr: "  + dev.addr )

scanner = Scanner().withDelegate( ScanDelegate() )
scanner.scan( 0 )
                                                                    

温湿度計:Meter

                    #Meter
elif ( sensorType[sensorNum] == "meter"):
    #https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
    battery = (servicedata[2]) & 0b01111111
    temperature = ( (servicedata[4]) & 0b01111111 ) + ( (servicedata[3]) & 0b00001111 ) / float(10)
    isTemperatureAboveFreezing = (servicedata[4]) & 0b10000000
    if not isTemperatureAboveFreezing:
        temperature = -temperature
    humidity = (servicedata[5]) & 0b01111111

    print("----")
    print("type: "          + sensorType[sensorNum] )
    print("place: "         + sensorPlace[sensorNum] )
    print("addr: "          + sensorAddr[sensorNum] )
    print(" > battery: "        + str( battery ) )               #バッテリー残容量 [%]
    print(" > temperature: "    + str( temperature ) )           #温度 [℃]
    print(" > humidity: "       + str( humidity ) )              #湿度 [%]
                                                                    

カーテン:Curtain

                    #Curtain
elif ( sensorType[sensorNum] == "curtain"):
    #https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/curtain.md   
    battery = (servicedata[2]) & 0b01111111
    isMotion = ((servicedata[3]) & 0b10000000) >> 7
    position = (servicedata[3]) & 0b01111111
    lightLevel = ((servicedata[4]) & 0b11110000) >> 4
    
    print("----")
    print("type: "          + sensorType[sensorNum] )
    print("place: "         + sensorPlace[sensorNum] )
    print("addr: "          + sensorAddr[sensorNum] )
    print(" > battery: "    + str(battery))             
    print(" > isMotion: "   + str(isMotion))            #カーテンの移動状態 0=stationary, 1=movement
    print(" > position: "   + str(position))            #開閉状態 [%]
    print(" > lightLevel: " + str(lightLevel))          #LightLevel (1-10)
                                                                    

0 comments:

コメントを投稿