Bluetooth APIを用いたWindowsアプリでのBLEアドバタイズパケットの受信方法【第5回】
こんにちは。ムセンコネクト三浦です。
前回の記事で「センサ搭載BLEビーコン」が完成しました。最終回となる今回は「WindowsパソコンでBLEスキャンを行い、ビーコンが発信したセンサ値(アドバタイズパケット)を取得する方法」について解説します。
WindowsでBLEスキャンを実行するには?
Windowsで簡単にBLEスキャンを行うには、Pythonとbleakを使うのがおすすめです。
bleakのインストールコマンド
pip install bleak
サンプルコード
async def start_scan():
ble_scanner = BleakScanner(on_advertisement_received)
await ble_scanner.start()
while True:
await asyncio.sleep(1.0)
asyncio.run(start_scan())
asyncio(awaitのようなPythonの特殊な文法)がここだけ必要になります。アドバタイズの受信だけに限ればあまり重要な箇所ではないので、一旦ここでは割愛します。
以下のコードに注目してください。
ble_scanner = BleakScanner(detection_callback=on_advertisement_received)
BleakScanner
生成時に引数detection_callback
として関数を与えてあげると、スキャン開始後にアドバタイズを受信したタイミングでその関数が呼び出されるようになります。
このコールバック関数では、BLEDevice, AdvertisementDataの2つを受け取ることができるので、そこでアドバタイズデータの解析が行えます。
def on_advertisement_received(device: BLEDevice, advertisement_data: AdvertisementData):
...
アドバタイズデータを解析してみる
通信仕様書を見ながらbytesをスライス・整数変換して、データを解析していきます。
AdvertisementData
からService Dataのバイト列を取得します。
OPEN_SENSOR_SERVICE_UUID = normalize_uuid_str("FCBE")
...
service_data = advertisement_data.service_data.get(OPEN_SENSOR_SERVICE_UUID)
advertisement_data.service_dataはdict(辞書オブジェクト) です。 キーが Service UUID文字列、値がServiceDataバイト列です。
ただし、このキーは128bit UUID文字列なので、16bit表現から変換する必要があります。bleakが提供するnormalize_uuid_str()を使えば、短縮された16bit UUID文字列から128bit UUID文字列が得られます。
今回のデータの確認
今回はデータ種別が照度のデータ構造のものを例に解析してみます。 照度データのアドバタイズフォーマットは以下のようになっています。
Offset | Size | Field | Description | 備考 |
---|---|---|---|---|
0 | 1 | 0x0B | Length | |
1 | 1 | 0x16 | Service Device data type value | |
2 | 2 | 0xFCBE | Open Sensor Service | オープンセンササービスでは固定値0xFCBEを利用します |
4 | 1 | 0x01 | Data Schema Version | 現状は固定値0x01を利用します |
5 | 4 | 0xXXXXXXXX | 個体識別番号 | 4バイト固定 |
9 | 1 | 0x13 | データ種別=照度(2byte) | |
10 | 2 | 0xXXXX | 照度データ | 2バイト unsigned 単位:0.1lx |
Pythonでバイト列を整数に変換する
解析にはint.from_bytesを使い、フィールドに対応するバイト列を抜き出し、int型に変換していきます。
この関数を使ってデータ種別、照度データなどを取り出します。
data_schema_version = int.from_bytes(
service_data[0:1],
byteorder="big",
signed=False,
)
device_identifier = int.from_bytes(
service_data[1:5],
byteorder="big",
signed=False,
)
data_type = int.from_bytes(
service_data[5:6],
byteorder="big",
signed=False,
)
illuminance = int.from_bytes(
service_data[6:8],
byteorder="big",
signed=False,
)
illuminance_lx = illuminance * 0.1 # NOTE: 単位=0.1lxのため変換
Data Schema Versionの解析部分を例に説明します。
data_schema_version = int.from_bytes(
service_data[0:1],
byteorder="big",
signed=False,
)
int.from_bytes
には変換したいバイト列、バイトオーダー、符号の有無を表す bool値の3つを渡します。
アドバタイズフォーマットを見るとData Schema Versionのsizeは1、service dataは Data Schema Versionから始まっているので、service_data[0:1]
として対応する部分のバイト列をスライスしています。
バイトオーダーや符号については通信仕様書を確認して、都度適切なものを指定してください。
このようにしてパースしたデータ種別やデータ値をprintで標準出力させれば、Windowsパソコン上でセンサ値の確認ができるようになります。
別のBLEデバイスからのアドバタイズと区別する
on_advertisement_received
は、別のBLEデバイスからのアドバタイズ受信時でも呼ばれるため、コールバック内で受信対象のデバイスからのアドバタイズデータかどうか判断する必要があります。
Open Sensor ServiceのServiceDataが得られなければ排除
AdvertisementData
からService Dataのバイト列を取得するときにUUIDを指定してservice dataを取得していましたが、UUIDが一致しない場合はNoneが返るので、対象のデバイスではないとみなします。
if service_data is None:
return
今回のデモ用ServiceDataの長さ (8) とマッチしてなければ排除
今回のデバイスが発するservice dataバイト列のlengthは (8) なので、一致するかどうか判断します。
if len(service_data) != 8:
return
ただし、lengthチェックだけでは照度のデータ構造であると確定することはできないので、バイト列の解析を行ってData Schema Versionとデータ種別を取り出し、それぞれの値を確認します。
現行のデータスキームバージョン (1) とマッチしてなければ排除
Data Schema Version情報はバージョンの確認に使用します。バージョンによってフォーマットが変更されている可能性があるので、対象デバイスと同じバージョンか確認する必要があります。
if data_schema_version != 0x01:
return
データ種別が「照度」 (0x13) とマッチしてなければ排除
データ種別が照度かどうかも確認します。
if data_type != 0x13:
return
完成したデモ動画
今回はPythonで簡単にBLEスキャンし、解析する方法を紹介しました。
照度データのアドバタイズフォーマットの解析を行うソースコードは下記リンクからダウンロード可能です。
これで「センサデータを発信するBLEビーコン」と「センサデータを受信するWindowsアプリ」が完成しました。
完成したデモはこちらです。
まとめ
ムセンコネクトでは、誰でも自由に、手軽にBLEビーコン通信を試せるビーコンフォーマット「オープンセンササービス」を通じて、BLEビーコンの普及に取り組んでまいります。
本企画、BLEビーコン、または「オープンセンササービス」についてのご意見、ご感想、ご不明な点などございましたら、ぜひムセンコネクトまでご連絡ください。みなさまからの忌憚のないご意見をお待ちしております。