背景
これまでRaspberry Pi 4でSwitchbot Hub miniの温度と湿度を取得してZabbixに投げていたけど、RP4の設定場所を変えたのでbluetoothが届かなくなった。
使っていないPico Wがあったので、代わりにやらせてみた。
Bluetooth
読むフレーム
RP4はbluepy、Pico Wはubluetoothを使いました。
RP4の方はインターネットに多くあるコードを使ってできました。そのコードの意味は深く理解していなかったせいで、Pico Wの最初コードではtypeの判定をしていなかった。そのためアドバタイズフレームを読んでいて、温度・湿度が含まれていませんでした。
Bluetooth LE自体を調査して、SCAN_RSPフレームを読まないといけないことを理解しました。
addr_type, addr, adv_type, rssi, adv_data = data
addr_str = ':'.join(['{:02x}'.format(b) for b in addr])
if addr_str == self.target_addr and adv_type == 4: # SCAN_RSPフレーム
self._parseData(adv_data)
サービスデータの取得
Switchbotのドキュメントを読んで0x16のブロックを見つける必要がありました。bluepyではそこがイテレータになっているみたいです。
i = 0
while i < len(adv_data):
length = adv_data[i]
type = adv_data[i + 1]
if type == 0x16: # サービスデータ
uuid = bytes(reversed(adv_data[i + 2:i + 4])).hex()
service_data = adv_data[i + 4:i + length + 1]
return uuid, service_data
i += length + 1
return None, None
Zabbixへの送信
RP4では cron -> python -> data.json -> fluentd -> zabbix の流れで動かしていたので、zabbixのことは全部fluentdにお任せしていましたが、Pico Wでは自前で書く必要がありました。
早速インターネットにあふれているコードを持ってきて動かしましたが送信できず、zabbixのドキュメントを読んだらヘッダーが必要とのことで、ヘッダーを付加したら成功しました。
data = {
"request": "sender data",
"data": [
{
'host': self.zabbix_host,
'key': key,
'value': value,
}
for key, value in data.items()
]
}
data = str(json.dumps(data)).encode('utf-8')
packet = b"ZBXD\1" + struct.pack("<II", len(data), 0) + data
ソース