Switchbotの温湿度計のデータをLinuxマシンで読み取る方法について紹介してきました。
ただ、現状最新環境のDebian12では上記で紹介した方法は使えなくなっています。そこで、今回は大幅に内容を書き直して、Debian12で使う方法について紹介します。
Debian12(Bookworm)で何が変わったか?
Debian12、RaspberryPiOSでいうとBookworm世代からpythonの実行には仮想環境を用いることが推奨(強制)されるようになりました。端的に言えば
$sudo pip install hogehoge
が使えなくなってしまいました。これまでの記事で紹介してきた方法ではBLEのスキャン、データの読み取りの部分ではbluepyというライブラリを使用しており、パーミッションの関係でsuにbluepyをインストールしないとBluetoothのスキャンなどで問題が起こってしまいます。
また、bluepy自体も4年ほどすでに更新がありません。そこで今回はbleakというライブラリを使用して全体を書き直しました。
準備
検証に使用した環境は、RaspberryPi5(RaspberryPiOS Bookworm64bit lite)です。
ライブラリのインストール
まずは必要なライブラリなどのインストールを行います。
$sudo apt update $sudo apt upgrade $sudo apt -y install libglib2.0-dev libbluetooth-dev $sudo apt -y install python3-dev $sudo apt -y install python3-pip
仮想化環境の準備
仮想化環境を準備します。ここではホームディレクトリ以下に作成していますが、各自お好きな場所に。
$python3 -m venv ~/bleak-env $source ~/bleak-env/bin/activate $pip install bleak
pythonプログラム
Switchbotの製品によってデータ構造が微妙に違いますので、それぞれに合わせた以下3つのプログラム例を載せます。引数に指定したMACアドレスの温湿度計のデータ(バッテリー、温度、湿度、CO2濃度)を返します。
Swichbot温湿度計
(追記)インデントが間違っていたので修正しました。
import asyncio
import sys
from bleak import BleakScanner
import binascii
# 引数からMACアドレス取得(小文字に統一)
macaddr = sys.argv[1].lower()
# グローバル変数(データ保持用)
battery = None
humid = None
temp = None
async def run():
# スキャン終了を通知するFuture(イベントループに紐づけ)
scan_complete = asyncio.get_event_loop().create_future()
def parse_advertisement_data(advertisement_data):
global battery, humid, temp
# Service Data (AD Type 0x16)
service_data = advertisement_data.service_data
if '0000fd3d-0000-1000-8000-00805f9b34fb' in service_data:
servicedata = service_data['0000fd3d-0000-1000-8000-00805f9b34fb']
battery = servicedata[2] & 0b01111111
temp=(servicedata[3] & 0b00001111) / 10 + (servicedata[4] & 0b01111111)
isOverZero=servicedata[4] & 0b10000000
if not isOverZero:
temp=-temp
humid=servicedata[5] & 0b01111111
# バッテリーが取得できたら出力して終了
if battery is not None and not scan_complete.done():
print(f"{battery},{temp},{humid}")
scan_complete.set_result(True)
def detection_callback(device, advertisement_data):
if device.address.lower() == macaddr:
manufacturer_data = advertisement_data.manufacturer_data
parse_advertisement_data(advertisement_data)
# スキャナ開始
scanner = BleakScanner(detection_callback)
await scanner.start()
try:
# 最大20秒間スキャン(途中で完了すれば即終了)
await asyncio.wait_for(scan_complete, timeout=20.0)
except asyncio.TimeoutError:
pass
await scanner.stop()
# 実行
asyncio.run(run())
Swichbot防水型温湿度計
【熱中症対策】SwitchBot 防水温湿度計 Alexa 温度計 湿度計 - スイッチボット ワイヤレス 室内 屋外用 温...
import asyncio
import sys
from bleak import BleakScanner
import binascii
# 引数からMACアドレス取得(小文字に統一)
macaddr = sys.argv[1].lower()
# グローバル変数(データ保持用)
battery = None
humid = None
temp = None
async def run():
# スキャン終了を通知するFuture(イベントループに紐づけ)
scan_complete = asyncio.get_event_loop().create_future()
def parse_advertisement_data(advertisement_data):
global battery, humid, temp
# Service Data (AD Type 0x16)
service_data = advertisement_data.service_data
if '0000fd3d-0000-1000-8000-00805f9b34fb' in service_data:
servicedata = service_data['0000fd3d-0000-1000-8000-00805f9b34fb']
battery = servicedata[2] & 0b01111111
# Manufacturer Data (AD Type 0xFF)
manufacturer_data = advertisement_data.manufacturer_data
raw_data = next(iter(manufacturer_data.values()))
s_payload = raw_data[6:]
humid = s_payload[4] & 0b01111111
temp = (s_payload[2] & 0b00001111) / 10 + (s_payload[3] & 0b01111111)
isOverZero=(s_payload[3]&0b10000000)
if not isOverZero:
temp=-temp
# バッテリーが取得できたら出力して終了
if battery is not None and not scan_complete.done():
print(f"{battery},{temp},{humid}")
scan_complete.set_result(True)
def detection_callback(device, advertisement_data):
if device.address.lower() == macaddr:
manufacturer_data = advertisement_data.manufacturer_data
parse_advertisement_data(advertisement_data)
# スキャナ開始
scanner = BleakScanner(detection_callback)
await scanner.start()
try:
# 最大20秒間スキャン(途中で完了すれば即終了)
await asyncio.wait_for(scan_complete, timeout=20.0)
except asyncio.TimeoutError:
pass
await scanner.stop()
# 実行
asyncio.run(run())
Switchbot CO2センサー
SwitchBot CO2センサー 二酸化炭素濃度計 温湿度計 アラーム - スイッチボット デジタル 大画面 高精度 CO2...
import asyncio
import sys
from bleak import BleakScanner
import binascii
# 引数からMACアドレス取得(小文字に統一)
macaddr = sys.argv[1].lower()
# グローバル変数(データ保持用)
battery = None
humid = None
temp = None
co2 = None
async def run():
# スキャン終了を通知するFuture(イベントループに紐づけ)
scan_complete = asyncio.get_event_loop().create_future()
def parse_advertisement_data(advertisement_data):
global battery, humid, temp, co2
# Service Data (AD Type 0x16)
service_data = advertisement_data.service_data
if '0000fd3d-0000-1000-8000-00805f9b34fb' in service_data:
servicedata = service_data['0000fd3d-0000-1000-8000-00805f9b34fb']
battery = servicedata[2] & 0b01111111
# Manufacturer Data (AD Type 0xFF)
manufacturer_data = advertisement_data.manufacturer_data
raw_data = next(iter(manufacturer_data.values()))
s_payload = raw_data[6:]
co2=s_payload[7]*256 + s_payload[8]
humid = s_payload[4] & 0b01111111
temp = (s_payload[2] & 0b00001111) / 10 + (s_payload[3] & 0b01111111)
isOverZero=(s_payload[3]&0b10000000)
if not isOverZero:
temp=-temp
# バッテリーが取得できたら出力して終了
if battery is not None and not scan_complete.done():
print(f"{battery},{temp},{humid},{co2}")
scan_complete.set_result(True)
def detection_callback(device, advertisement_data):
if device.address.lower() == macaddr:
manufacturer_data = advertisement_data.manufacturer_data
parse_advertisement_data(advertisement_data)
# スキャナ開始
scanner = BleakScanner(detection_callback)
await scanner.start()
try:
# 最大20秒間スキャン(途中で完了すれば即終了)
await asyncio.wait_for(scan_complete, timeout=20.0)
except asyncio.TimeoutError:
pass
await scanner.stop()
# 実行
asyncio.run(run())
Bashスクリプト例
仮想化環境で動かしますので、そのままでは動きません。どの仮想化環境を使うかを明示して実行するbashスクリプトの例を以下に載せます。
#!/bin/bash SCRIPT_DIR=`dirname $0` cd $SCRIPT_DIR #引数(MACアドレス) macaddr=$1 # 仮想環境のパス VENV_PATH="./bleak-env" # 実行したい Python スクリプト SCRIPT_PATH="./sbm_e.py" # 仮想環境の Python を使って実行 "$VENV_PATH/bin/python" "$SCRIPT_PATH" $macaddr
まとめ
今回紹介した方法で、最新のOS(Debian12系)でもswitchbotの温湿度計のデータを読めるようになります。
ご参考に。






コメント