蒸し暑いので温湿度計を買ってIoTに入門してみた

# 蒸し暑い！！

最近とても蒸し暑く、寝苦しい夜が続きますよね...

外の気温は天気予報で知ることができますが、部屋の温度や湿度は計測しないとわからないですよね...

というわけで、Bluetooth 対応の温湿度計を購入し、温度や湿度を取得するプログラムを作ってみました。

# とりあえず温湿度計を買ってみた

Bluetooth 対応の温湿度計を探してみたところ、以下の記事を見つけました。

https://qiita.com/c60evaporator/items/675452d5de9587eed25c

こちらの記事によると、Inkbird の IBS-TH1 がよさそうだったので探してみましたが、在庫切れだったので代わりに IBS-TH2 を購入しました。

https://inkbird.com/products/ibs-th2-temp?variant=43582705631408

↓ 実物の写真です ↓

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325392/dc62f007-275c-78d9-7de8-ed5192f031db.jpeg" width="400px"></img>

公式アプリで動作確認してみました。問題なく動作していそうです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325392/8afc04ac-53ab-5269-f6ff-f8982140e8fe.png" width="400px"></img>

# PCと温湿度計を接続してみた

家のデスクトップPC (Windows 11) + Bluetoothアダプタ (‎TP-Link UB400)で取得できないか試してみました。

https://www.tp-link.com/jp/home-networking/computer-accessory/ub400/

Windows で Bluetooth を扱うことができるライブラリを探したところ、 python の Bleak というライブラリを発見しました。

https://github.com/hbldh/bleak

公式ドキュメントも豊富っぽいので、これを使ってみます。

https://bleak.readthedocs.io/en/latest/index.html

とりあえずインストール。

```bash
$ pip install bleak
```

まずは、[README](https://github.com/hbldh/bleak/blob/082c5fbf124695f94aa33c79bfbd60e4aaae8611/README.rst?plain=1#L61-L71) に記載のデバイスを検出するプログラムをそのまま実行してみます。

```py:discover.py
import asyncio
from bleak import BleakScanner

async def main():
    devices = await BleakScanner.discover()
    for d in devices:
        print(d)

asyncio.run(main())
```

`BleakScanner` で検知できるようです。

https://bleak.readthedocs.io/en/latest/api/scanner.html?highlight=BleakScanner

プログラムを実行すると、IBS-TH2 の MACアドレスが検出できました。（公式アプリからもMACアドレスが確認できるので、それと一致していることが確認できました。）

```
> python discover.py
XX:XX:XX:XX:XX:XX: sps
```

# 温度と湿度を取得するプログラムを書いてみた

ドキュメントを読むと `BleakClient` にアドレスを指定することで、Bluetooth機器と**いい感じ**に通信できるらしいです。

https://bleak.readthedocs.io/en/latest/api/client.html?highlight=BleakClient

「いい感じ」をもう少しちゃんと説明するためには、Bluetooth の **BLE** (Bluetooth Low Energy) と **GATT** (Generic Attribute Profile) という2つの概念の説明が必要なのですが、以下のサイトの説明がわかりやすかったので、それを紹介しておきます。

https://monomonotech.jp/kurage/webbluetooth/ble_guide.html

簡単に説明すると、Bluetooth機器には複数のServiceがあり、それぞれのServiceはCharacteristicというデータを持っていて、それに対して読み書きできる、という感じです。

では、温湿度計のServiceとCharacteristicを確認してみましょう。
判明したMACアドレスを `BleakClient` に指定して、以下のように取得できます。

```py
import asyncio
from bleak import BleakClient

address = "xx:xx:xx:xx:xx:xx"

async def main(address):
    async with BleakClient(address) as client:
        services = client.services

        for key in services.services:
            service = services.services[key]
            print('service({})'.format(service.handle), service.description)
            for characteristic in service.characteristics:
                print('characteristic({})'.format(characteristic.handle), characteristic.description)

asyncio.run(main(address))
```

実行してみると、以下のように4つのServiceが存在し、それぞれ複数のCharacteristicを持つようです。

```:実行結果
service(1) Generic Access Profile
characteristic(2) Device Name
characteristic(4) Appearance
characteristic(6) Peripheral Preferred Connection Parameters

service(8) Generic Attribute Profile
characteristic(9) Service Changed

service(12) Device Information
characteristic(13) System ID
characteristic(15) Model Number String
characteristic(17) Serial Number String
characteristic(19) Firmware Revision String
characteristic(21) Hardware Revision String
characteristic(23) Software Revision String
characteristic(25) Manufacturer Name String
characteristic(27) IEEE 11073-20601 Regulatory Cert. Data List
characteristic(29) PnP ID

service(31) Vendor specific
characteristic(32) cfg data
characteristic(35) Real time data
characteristic(38) cfg data2
characteristic(41) measure
characteristic(44) recoder frame
characteristic(47) history data
characteristic(51) run/stop recoder
characteristic(54) his data type
characteristic(57) reset
```

この中の4つ目のServiceに `Real time data` という Characteristic があり、その中に温度や湿度のデータが入っていそうです。

`Real time data` の識別子は35なので、`client.read_gatt_char(35)` でデータを取得できます。

```python
import asyncio
from bleak import BleakClient

address = "xx:xx:xx:xx:xx:xx"

async def main(address):
    async with BleakClient(address) as client:
        rtd = await client.read_gatt_char(35)
        print(rtd)

asyncio.run(main(address))
```

実行すると、以下のように出力されました。

```
bytearray(b'\xe7\t\x14\x1b\x00Y~')
```

bytearray、つまりバイト列が帰ってきているようです。バイト列から自力でリトルエンディアンやビッグエンディアンなどの判別をして解読する余力はなかったので、IBS-TH2 を扱っている他の記事を探してみました。以下の記事によると、`struct.unpack('<hhBBB', rtd)` で湿度と温度を抽出できるらしいです。

https://qiita.com/revsystem/items/4097d0ff447913e2675a

先程のプログラムを書き換えてみました。

```py:jimejime.py
import asyncio
import struct
from bleak import BleakClient

address ="xx:xx:xx:xx:xx:xx"

async def main(address):
    async with BleakClient(address) as client:
        rtd = await client.read_gatt_char(35)
        print(struct.unpack('<hhBBB', rtd))

asyncio.run(main(address))
```

結果が出ました。

```
$ python jimejime.py
(2503, 7035, 0, 232, 164)
```

温度は 25.05 度、湿度は 70.35% のようです。
冷房を25度に設定していたので、正しい値が取得できていそうです。

湿度が高いので、冷房を除湿に切り替えました。

# 今回はここまで

軽い気持ちで始めた計測でしたが、BLE や GATT など Bluetooth の開発方法について知ることができてよかったです。

今回は現在の数値を計測するところまでしかできませんでしたが、定期的に温度や湿度を計測したり、データを適当な場所に送信して家の外からでも見られるようにしたり、温度や湿度に応じて冷房や除湿を切り替えたりしてみたいです！