SwitchBot MeterTH の温度と湿度をPythonで読み取る(Raspberry Pi 4)


#データフォーマット
データフォーマットみて実装できる人はここを見よう
[公式のWiki](https://github.com/OpenWonderLabs/python-host/wiki/Meter-BLE-open-API)に詳しく書かれています。
https://github.com/OpenWonderLabs/python-host/wiki/Meter-BLE-open-API

|  Index | Byte[0]  | Byte[1]  | Byte[2]  |Byte[3]   |Byte[4]   |Byte[5]   |
|---|---|---|---|---|---|---|
| Description|  **Enc Type Dev Type**<br>Bit[7]予約済み<br>Bit[6:0]デバイスタイプ | **Status**<br>Bit[7:4]予約済み<br>Bit[3:0]デバイスグループ  |**Update UTC Flag Battery** <br>Bit[7]予約済み<br>Bit[6:0]バッテリー残量  |  **alarm flag the fraction part of temperatrue value (WoSensorTH)**<br>Bit[7:6]温度アラートの状態<br>Bit[5:4]湿度アラートの状態<br>Bit[3:0]温度の小数点以下 |  **temperature mode Positive/Negative temperature flag the integer part of temperature value (WoSensorTH)** <br>Bit[7]マイナスかプラスか<br>Bit[6:0]温度の整数値| **humidity value (WoSensorTH)**<br>Bit[7]温度スケール<br>Bit[6:0]湿度の整数値  |

##Byte[0]
###Bit[6:0]のデバイスタイプ
|  製品名 | Hex(16進数表記)  | Ascii  |備考 |
|---|---|---|---|
|  SwitchBot Bot (WoHand) | 0x48  | H  |   |
|  WoButton |  0x42 | B  |   |
| SwitchBot Hub (WoLink)  |  0x4C | L  | addMode   |
| SwitchBot Hub (WoLink)  | 0x6C  |  l | Normal   |
| SwitchBot Hub Plus (WoLink Plus)  | 0x50  |  P |addMode   |
| SwitchBot Hub Plus (WoLink Plus)  | 0x70  |  p | Normal  |
| SwitchBot Fan (WoFan)  | 0x46  | F  | addMode  |
| SwitchBot Fan (WoFan)  |  0x66 |  f | Normal  |
|  SwitchBot MeterTH (WoSensorTH) | 0x74  | t  | addMode  |
| SwitchBot MeterTH (WoSensorTH)  | 0x54  |  T | Normal  |
| SwitchBot Mini (HubMini)  |  0x4D |  M | addMode  |
| SwitchBot Mini (HubMini)  |  0x6D | m  |  Normal |

##Byte[1]
###Bit[3:0]のデバイスグループ
#####グループ分け

|  Bit | Group  |
|---|---|
| Bit[3] |  Group D |
| Bit[2] |  Group C |
| Bit[1] |  Group B |
| Bit[0] |  Group A |

#####値

| 値  | 状態  |
|---|---|
| 0 | デバイスがこのグループに含まれていない  |
| 1  |  デバイスがこのグループに含まれています |

##Byte[2]
 Bit[6:0]はバッテリー残量を0~100の値で表します、単位は%です。

##Byte[3]
#####温度アラートの状態

|値|状態|
|---|---|
| 0 | アラートなし |
| 1 | 低温度アラート(温度が下限以下) |
| 2 | 高温度アラート (温度が上限以上)|
| 3 | 温度アラート(温度は下限以上、上限以下) |

#####湿度アラートの状態

|値|状態|
|---|---|
| 0 | アラートなし |
| 1 | 低湿度アラート(湿度が下限以下) |
| 2 | 高湿度アラート (湿度が上限以上)|
| 3 | 湿度アラート(湿度は下限以上、上限以下) |

~~公式Wikiには ``higher than``とか ``lower than``とか書いてあったから``より高い``と``未満``だと思って湿度で検証したら``以上``と``以下``じゃね～かよ。~~
低湿度アラートを(0~39%)高湿度アラートを(39~99%)にしたら3が出力されたよ!!

##Byte[4]
###Bit[7]
| 値 | 説明 |
|---|---|
| 0 | マイナスの温度 |
| 1 | プラスの温度 |

##Byte[5]
###Bit[7]
| 値 | 説明 |
|---|---|
| 0 | 摂氏スケール |
| 1 | 華氏スケール|

スケールによってByte[3]の温度が変わるわけではなく、華氏スケールでの温度が提供される。
摂氏に変換する場合は

```math
摂氏スケール温度=(華氏スケール温度*1.8)+32
```

となる。

##全体像
![screenshot.139.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402287/9597e76e-afb8-cb37-b910-fc7e3ab3d3ba.png)


#実装例
##環境
- Raspberry Pi 4 Model B 4GB
- Raspbian  10
- Python 3.7.3


##コード
``` python:Meter.py

import toml
import binascii
from bluepy.btle import Scanner, DefaultDelegate

config = toml.load('config.toml')
macaddr = config['Device']['MACAddress']

mask_7Bit = 0b01111111


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

    def handleDiscovery(self, dev, isNewDev, isNewData):
        if dev.addr != macaddr: return
        for (adtype, desc, value) in dev.getScanData():
            if adtype !=22 :continue
            data = binascii.unhexlify(value[4:])
            if(data[0] != 0x54):break
            battery = data[2] & mask_7Bit
            decimal_temperature =  data[3] & 0b00001111
            temperature = float(data[4] & mask_7Bit) + decimal_temperature/10
            subzero = (data[4] & 0b10000000) != 0b10000000
            humidity = data[5] &mask_7Bit
            if subzero :
                temperature = -temperature
            print(battery,end='% \n')
            print(temperature,end='C \n')
            print(humidity,end = '% \n')

scanner = Scanner().withDelegate(ScanDelegate())
scanner.scan(0)
```
``` toml:config.toml
[Device]
MACAddress="ff:ff:ff:ff:ff:ff"
```



まずはSwitchBotMeterのMACアドレスでフィルタリングをかけるので、SwitchBotのスマートフォンアプリからMACアドレスを取得してきます。
例としてff:ff:ff:ff:ff:ffを書いていますので環境に合わせて書き換えてください。
Macアドレスはコード内にそのまま書いても問題ないです。
個人のポリシーでconfig.tomlに書いています。
adtypeが22の場合に温度や湿度の情報がパケットに含まれているのでadtype22でフィルタリングします。
valueの最初の4桁は使わないのでスライスで5桁目(インデックスでいうと4)以降を参照します。
その値を、```binascii.unhexlify()```でバイト列に変換します。
その後は上記で紹介した表に合わせてバイト列の値に対してビット演算でマスクを掛けています。
例えばバッテリー残量はByte[2]の0から6ビットまでの値で取得できるので

```python
battery = data[2] & 0b01111111
```
で取得できます。



##実行

pip3でbinasciiやbluepyをインストールしてからroot権限で実行します。

```
sudo Python3 Meter.py
```

##参考
https://qiita.com/warpzone/items/11ec9bef21f5b965bce3