ラズベリーパイPico WでBluetoothを使ってみるでラズベリーパイPico Wでブルートゥースの子機(ペリフェラル)を構築し、

AndroidアプリのSerial Bluetooth Terminalでブルートゥースの動作を確認してみました。
今回はラズベリーパイ等のパソコンでPythonで似たような動作をするコードを作成してみます。
※2024年1月31日時点でクロームブックのクロスティーニではブルートゥースの機能は利用できません
コードの話題に入る前にブルートゥースでのデータ転送の主軸となるGATT(Generic attribute profile)について触れておきます。
親機と子機がブルートゥースで接続された時、お互いの機器はGATTプロファイルに基づくプロファイルで情報のやり取りを行います。
GATTの構造は

のようにProfie、Service(サービス)とCharacteristic(キャラクタリスティック)で構成されている。
ラズベリーパイPico Wを子機とした時を例とすると、サービスがラズベリーパイPico Wに該当し、キャラクタリスティックがラズベリーパイPico Wに組み込んだ機能と捉えると以後のコードの理解が進みます。
キャラクタリスティックにはATTと呼ばれる属性値が含まれていて、
・ハンドル(handle)
・型(type)
・値(value)
・権限(permission)
があります。
ここらへんの話は一旦用語の把握までにしておいて、実際のコードに触れてから改めて調べる事をおすすめします。
Pythonでコードを書く際にbluepyというライブラリを用いる事にします。
GitHub - IanHarvey/bluepy: Python interface to Bluetooth LE on Linux
最初に端末(ターミナル)を開き、下記のコマンドでbluepyを使用できるようにします。
$ sudo apt install python-pip libglib2.0-dev $ sudo pip install bluepy
今回はプロジェクト名をbleにして話を進めます。
下記のコマンドでブルートゥースの親機(セントラル)用のコードを作成する為のディレクトリを作成します。
$ cd ~ $ mkdir workspace $ cd workspace $ mkdir ble $ cd ble
最初に子機に割り当てられたMACアドレスを調べるコードを作成します。
~/workspace/ble/scan.py
from bluepy import btle
scanner = btle.Scanner(0)
devices = scanner.scan(3.0)
for dev in devices:
print("[MAC-Adress]:", dev.addr)
print("[Address-Type]:", dev.addrType)
上記のコードを実行してみると、
$ python3 ~/workspace/ble/scan.py
[MAC-Adress]: XX:XX:XX:XX:XX:XX [Address-Type]: public
のように出力されます。
どちらの値も重要ですので、記録しておきます。
続いて、子機のUUIDやハンドル(handle)について調べます。
~/workspace/ble/handle.py
from bluepy import btle
import time
PERIPHERAL_MAC_ADDRESS = "XX:XX:XX:XX:XX:XX"
p = btle.Peripheral()
# scan.pyで調べたMACアドレスとアドレスタイプを指定します
p.connect(PERIPHERAL_MAC_ADDRESS, btle.ADDR_TYPE_PUBLIC)
print("connected")
chars = p.getCharacteristics();
for char in chars:
print("UUID : %s" % char.uuid )
print("Handle %04x: %s" % (char.getHandle(), char.propertiesToString()))
p.disconnect()
print("disconnected")
上記のコードを実行してみると、
$ python3 ~/workspace/ble/handle.py
UUID : 00002a00-0000-1000-8000-00805f9b34fb Handle 0003: READ UUID : 00002a05-0000-1000-8000-00805f9b34fb Handle 0006: READ UUID : 6e400003-b5a3-f393-e0a9-e50e24dcca9e Handle 0009: READ NOTIFY UUID : 6e400002-b5a3-f393-e0a9-e50e24dcca9e Handle 000c: WRITE NO RESPONSE WRITE
のような値が返ってきます。
今回は子機から送信された値を取得する予定ですので、ATTの権限でREADが含まれている方のUUIDやハンドラ(0003)を記録しておきます。
※以後で作成するコードではUUID等の値は使用しません
得られた値を基にして、Serial Bluetooth Terminalのように動作する為のコードを作成してみます。
~/workspace/ble/main.py
from bluepy import btle
PERIPHERAL_MAC_ADDRESS = "XX:XX:XX:XX:XX:XX"
class MyDelegate(btle.DefaultDelegate):
def __init__(self):
btle.DefaultDelegate.__init__(self)
def handleNotification(self, cHandle, data):
# 子機から得られたデータを端末に出力する
print(data)
p = btle.Peripheral()
p.connect(PERIPHERAL_MAC_ADDRESS, btle.ADDR_TYPE_PUBLIC)
print("connected")
p.withDelegate(MyDelegate())
# 通信が切れたりSIGINTの割り込みが発生しない限り接続を維持する
try:
TIMEOUT = 3.0
while True:
if p.waitForNotifications(TIMEOUT):
continue
except:
# SIGINTを許可
p.disconnect()
print("disconnected")
上記のコードを実行してみると、
$ python3 ~/workspace/ble/main.py
端末にSerial Bluetooth Terminalで出力されたような値が出力され続けます。
出力を止めたい場合はctrl + cで接続を切ることが出来ます。