Debian12(bookworm)対応!Swictchbot温湿度計の遊び方

debian12の仮想化python環境でswitchbotのデータを読み取る方法を紹介します。 PC/デジモノ

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防水型温湿度計

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センサー

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

  # 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の温湿度計のデータを読めるようになります。

ご参考に。

コメント