Switchbot温度計とラズパイで快適IoTライフ
Switchbot温度計をフル活用するためラズパイで測定値を取得することを試みます。意外と情報が少なく苦戦したため記録しておきます。
Switchbot温湿度計
ボタンプッシュロボットでおなじみのWonderlabs IncによるIoT温湿度計です。電源も電池駆動なので使い勝手が良く、スマートハウス利用には無限の可能性を感じます。
同社が発売中のHubを利用すればAlexaやその他の家電と連携させることも可能です。私はHubが必要と知らずに1年前に買ってショックを受けました…。単体でもBluetoothでスマホアプリと連携可能ですが、アプリの出来があんまり良くないです。私は
うまくいかなかったコード
検索すると一番にヒットするこちらの記事の方法は、私の環境では上手く行きませんでした・・・。少しコードをいじったりしてみたのですが、小手先の修正では動かず・・・。原因が分かったら報告します。
qiita.com
実行結果
$ sudo python3 switchbot_meter.py ^CTraceback (most recent call last): File "switchbot_meter.py", line 48, in <module> scanner.scan( 0 ) File "/usr/local/lib/python3.5/dist-packages/bluepy/btle.py", line 853, in scan self.process(timeout) File "/usr/local/lib/python3.5/dist-packages/bluepy/btle.py", line 821, in process resp = self._waitResp(['scan', 'stat'], remain) File "/usr/local/lib/python3.5/dist-packages/bluepy/btle.py", line 347, in _waitResp rv = self._helper.stdout.readline() KeyboardInterrupt
実行すると固まってしまいますので、キーボードインタラプトしてます。スキャンのタイムアウトが無効になっているため、いつまでもスキャンを続けてるみたいです。デバイスはラズパイからは見えているのに、なんでスキャンが終わらないんだろう。
結局ギブアップして、別の方法を探しました。
とりあえず動いたコード
諦め掛けていたのですが、スイッチボット開発元のWonderLabsが公開しているpython-hostというライブラリの中にそれっぽいのを見つけました。
GitHub - OpenWonderLabs/python-host: The python code running on Raspberry Pi or other Linux based boards to control SwitchBot.
インストール方法はリンク先のgithubに詳しく書いてあります。このライブラリは、ボタンタイプのスイッチボットでも使えるやつなので、私はすでにインストールしてました。
インストールが済んだら早速ディレクトリに移動し、 switchbot_meter.pyを実行してみます。MACアドレスを入れる所がないのですが、とりあえず実行。
~$ cd python-host ~/python-host $ sudo python switchbot_meter.py
結果
Usage: "sudo python switchbot.py [mac_addr cmd]" or "sudo python switchbot.py" Start scanning... 22 16b Service Data 000d5410d6089a40 16 scan timeout (0, [u'XX:XX:XX:XX:XX:XX', "Humiture:26.8'C 64%"]) Input the device number to control:
おお、温度と湿度が取得できました。私のスイッチボットの表示温度、MACアドレスの値が表示されてます!ちなみにpython3のコード(switchbot_meter_py3.py)を実行する際には、pip3でpexpectのインストールが必要でした(No module named 'pexpect’ というエラーが出る)。
「Input the device number to control:」の表示でデバイスナンバーの入力を求められましたので、0を入れると、処理が先に進みました。
Input the device number to control:0 Preparing to connect. Connection successful. Traceback (most recent call last): File "switchbot_meter.py", line 232, in <module> main() File "switchbot_meter.py", line 222, in main trigger_device(bluetooth_adr) File "switchbot_meter.py", line 167, in trigger_device tempFra = int(data[3], 16) / 10.0 IndexError: string index out of range
コネクション成功、その後よくわからないエラーで止まってしまいました。デバイスナンバーに関してはおそらく複数台のスイッチボットを一括で管理することを想定しているのではないでしょうか。今回は1台ですので、0以上の数字はレンジアウトとなります。
とりあえず温度と湿度をラズパイで取得する、という目的はこれで達せられそうです。返り値がシンプルになるようにコードを書き換えます。
とりあえず動いたコードを修正
やっつけではありますがシンプルに温度と湿度とアドレスだけ返してくれるように修正しました。ライブラリが揃っていればpython2でも3でも動くと思います。
#!/usr/bin/env python3 import pexpect import sys from bluepy.btle import Scanner, DefaultDelegate import binascii class ScanDelegate(DefaultDelegate): def __init__(self): DefaultDelegate.__init__(self) class DevScanner(DefaultDelegate): def __init__( self ): DefaultDelegate.__init__(self) #print("Scanner inited") def dongle_start(self): self.con = pexpect.spawn('hciconfig hci0 up') time.sleep(1) def dongle_restart(self): print("restart bluetooth dongle") self.con = pexpect.spawn('hciconfig hci0 down') time.sleep(3) self.con = pexpect.spawn('hciconfig hci0 up') time.sleep(3) def scan_loop(self): # service_uuid = '1bc5d5a50200b89fe6114d22000da2cb' service_uuid = 'cba20d00-224d-11e6-9fb8-0002a5d5c51b' menufacturer_id = '5900f46d2c8a5f31' dev_list =[] bot_list =[] enc_list =[] link_list =[] meter_list=[] self.con = pexpect.spawn('hciconfig') pnum = self.con.expect(["hci0",pexpect.EOF,pexpect.TIMEOUT]) if pnum==0: self.con = pexpect.spawn('hcitool lescan') #self.con.expect('LE Scan ...', timeout=5) scanner = Scanner().withDelegate(DevScanner()) devices = scanner.scan(10.0) print("Start scanning...") else: raise Error("no bluetooth error") for dev in devices: mac = 0 for (adtype, desc, value) in dev.getScanData(): # print(adtype,desc,value) if desc == '16b Service Data': model = binascii.a2b_hex(value[4:6]) mode = binascii.a2b_hex(value[6:8]) if len(value) == 16: # print(adtype,desc,value,len(value)) # celsius tempFra = int(value[11:12].encode('utf-8'), 16) / 10.0 tempInt = int(value[12:14].encode('utf-8'), 16) if tempInt < 128: tempInt *= -1 tempFra *= -1 else: tempInt -= 128 meterTemp = tempInt + tempFra meterHumi = int(value[14:16].encode('utf-8'), 16) % 128 # print("meter:", meterTemp, meterHumi) else: meterTemp = 0 meterHumi = 0 elif desc == 'Local name': if value == "WoHand": mac = dev.addr model = 'H' mode = 0 elif value == "WoMeter": mac = dev.addr model = 'T' mode = 0 elif desc == 'Complete 128b Services' and value == service_uuid : mac = dev.addr if mac != 0 : #print(binascii.b2a_hex(model),binascii.b2a_hex(mode)) dev_list.append([mac, model.decode('utf-8'), mode, meterTemp, meterHumi]) # print(dev_list) for (mac, dev_type,mode,meterTemp, meterHumi) in dev_list: if dev_type == 'L': link_list.append(mac) if dev_type == 'H' or ord(dev_type) == ord('L') + 128: #print(int(binascii.b2a_hex(mode),16)) if int(binascii.b2a_hex(mode),16) > 127 : bot_list.append([mac,"Turn On"]) bot_list.append([mac,"Turn Off"]) bot_list.append([mac,"Up"]) bot_list.append([mac,"Down"]) else : bot_list.append([mac,"Press"]) elif dev_type == 'T': meter_list.append([mac, meterTemp, meterHumi]) # meter_list = {"a":"1", "b":meterHumi} if ord(dev_type) == ord('L') + 128: enc_list.append([mac,"Press"]) # print(bot_list) #print("Scan timeout.") return bot_list + meter_list pass def register_cb( self, fn ): self.cb=fn return def close(self): #self.con.sendcontrol('c') self.con.close(force=True) def main(): #Check bluetooth dongle #print('Usage: "sudo python switchbot.py [mac_addr cmd]" or "sudo python switchbot.py"') connect = pexpect.spawn('hciconfig') pnum = connect.expect(["hci0",pexpect.EOF,pexpect.TIMEOUT]) if pnum!=0: print('No bluetooth hardware, exit now') sys.exit() connect = pexpect.spawn('hciconfig hci0 up') if len(sys.argv) == 3 or len(sys.argv) == 4: dev = sys.argv[1] act = sys.argv[2] if len(sys.argv) < 4 else ('Turn ' + sys.argv[3] ) #trigger_device([dev,act]) elif len(sys.argv) == 1: #Start scanning... scan = DevScanner() dev_list = scan.scan_loop() print(dev_list) dev = sys.argv[1] if len(sys.argv) > 1 else None dev_number = None if not dev_list: print("No SwitchBot nearby, exit") sys.exit() else : print('wrong cmd.') print('Usage: "sudo python switchbot_meter_py3.py [mac_addr cmd]" or "sudo python switchbot_meter_py3.py"') #connect = pexpect.spawn('hciconfig') return(dev_list) #sys.exit() if __name__ == "__main__": main()
実行結果
~/python-host $ sudo python3 SBHumiture.py Start scanning... [['XX:XX:XX:XX:XX:XX', 27.0, 65]]
色々余分なコードも残ってそうですが、とりあえず所望の値が帰ってきました。スキャンに失敗した際は"No SwitchBot nearby, exit"と表示されます。
毎回スキャンするのではなくアドレス指定でコマンドした方がシンプルだし省エネのような気がしますが、そこまでは出来ませんでした・・・。もし出来たら載せます!