Raspberry Pi PICO W で Bluetooth (BLE HID) の使い方

プロジェクトの概要

100円ショップでこんな製品良く売られていますよね。

Bluetoothでスマホに接続して、カメラのシャッターをリモートで押せる製品です。

Raspberry Pi Pico W はBluetoothが搭載されていますので、このシャッターボタンで操作できたら素敵ですよね。

今回はそのやり方についてご案内致します。

PICO WでのBluetooth利用については、日本、海外で検索してもほとんど資料が出てきませんので、この動画は貴重な資料になると思います。

今回は、

  1. Bluetoothの規格についての解説
  2. Bluetooth製品の仕様の調べ方
  3. プログラムの解説
  4. 作品のデモ

の順でご説明いたします。

Bluetoothの規格についての解説

まず、Bluetoothの規格は、専門用語の宝庫です。新しい言葉が沢山でてきますので、最初に、わかりやすくまとめてご説明したいと思います。

Bluetoothの規格

Bluetoothには大きく2種類の規格があります。

Bluetooth Classic と BLE (Bluetooth Low Energy) です。 Bluetooth Classicは大量のデータ転送が主な目的で、通話や音楽再生などの用途で使われています。

BLEはClassicよりも低消費電力で、IoTデバイスや、ウェアラブルデバイス、キーボードなどの駆動に適しています。今回のシャッターボタンもBLEになります。

ですので、今回はBLEについて詳しくご説明いたします。

BLEのプロトコル

Bluetoothの通信規約、プロトコルは、機器間の接続のやり方を取り決めるGAP (Generic Access Profile) とデータ構造と実際の通信の仕方を取り決めるGATT (Generic Attribute Profile) で成り立ちます。

GAP(Generic Access Profile)

まずGAP、接続についてのプロトコルから説明します。

通信の親子関係

WEBシステムにクライアントとサーバがあるように、Bluetoothにも親子関係があります。
それが、CentralとPeripheralです。
Centralがサーバに相当する親機になります。
今回はPICO WをCentralとして使います。
Peripheralは子機に相当し、センサーやスマートウォッチがこれに相当します。
シャッターボタンもPeripheralになります。

接続の手順

Centralは、周りにPeripheralがいないか?とScanします。
そしてPeripheralは、私を見つけて!とAdvertiseという電波を発信します。
CentralがAdvertiseを受信すると、受信リストが得られますので、そのなかから接続したい機器を指定して、Connectします。するとBluetooth通信ができる状態になります。
Bluetooth機器を使う必要がなくなった時は、Disconnectして接続を終了します。

GATT (Generic Attribute Profile)

GAPにより機器同士で接続ができたら、いよいよデータ通信の準備を経て、通信をしていきます。それらをとりまとめたプロトコルがGATTです。

Bluetooth通信のためのデータ構造は、机の引き出しの中にフォルダが複数入っていて、その各々の中に必要な書類が入っているようなイメージで捉えるとわかりやすいです。

Service

まず、机の引き出しに相当する概念がServiceです。どんな種類のデータをやり取りするのかをまとめた入れ物です。

Characteristic

引き出しの中のフォルダに相当する概念がCharacteristicです。例えば、体温計、心拍計など、複数の機能を持ったスマートウォッチのような機器の場合、機能毎に複数のCharacteristicを持っているイメージです。

Value

Characteristicの持っている値がValueになります。体温計の場合は体温。キーボードの場合はどのキーが押されているか。といった情報が入ってきます。

Property

CharacteristicのValueが読み書きできるのかを示す属性です。

Propertyには、以下の3種類があります。

Read:Valueを読み取ることができる。

Write:Valueにデータを書き込むことができる。

Notify:CharacteristicのValueが変化したら、そのタイミングでCentralに向けてValueを通知する。

UUID

GATTでは、Service、Characteristicの2階層構造でValueを格納していますが、それらにつけられたIDがUUIDになります。

これらは名前ではなく、UUIDという番号で識別されますので注意してください。

Bluetooth製品の仕様の調べ方

Bluetooth製品の仕様の調べ方は、スマホのアプリで便利なものがあります。LightBuleというアプリで、これを使うと、以下の情報を調べる事ができます。

  1. ServiceとCharacteristicのUUID
  2. Characteristicの種類
  3. CharacteristicのProperty

LightBuleの使い方

接続したいBluetooth機器を見つけてCONNECTを押します。今回の製品の機器名は BT Shutter です。これは製品の箱やマニュアルに記載があると思います。

Advertised service UUIDs の下の数値の先頭4桁。この場合、1812がこのデバイスのServiceのUUIDになります。この1812は16進数です。

つづいて、Device Addres欄を見ます。この下のコロン区切りの16進数の数値が、このデバイスの番号で、Connect時に使用します。

下にスクロールすると、HUMAN INTERFACE DEVICE の文字があります。通称HIDと呼ばれるもので、キーボードやマウスなどで一般的に使われているServiceです。

HIDサービスの中に、Reportと言う名前のものが2つ登録されています。これらが、ボタンの状態を表すCharacteristicです。これらを開いて中を見ます。

すると、CharacteristicのUUIDが2a4dだとわかります。

そしてその下に、ReadableとSupports notificationsの所にチェックが入っています。これにより、Propertyにread と Notify があるのがわかります。

ここまでわかればプログラムを作ることができます。

プログラムの解説

import asyncio
import aioble
import bluetooth
import machine
import binascii

LED = machine.Pin("LED", machine.Pin.OUT)
SWITCH = machine.Pin(0, machine.Pin.OUT)

# ダイソーのBluetoothのボタン情報
# 皆様のボタンデバイスに合わせて変更してください
# このプログラムをThonny上で実行すると、ターミナル画面に、デバイス情報が出てきます。
# その中からデバイス名が「BT Shutter」的なものをさがして、デバイス番号を探してください。
_ENV_DEVICE_BUTTON = "xx:xx:xx:xx:xx:xx"

# Human Interface Device service 
_ENV_BUTTON_SERVICE_UUID = bluetooth.UUID(0x1812)
# Report :read,notify
_ENV_BUTTON_CHARACTERICTIC_UUID = bluetooth.UUID(0x2A4D)

MycroPythonにはaiobleというBluetoothを制御するための便利なライブラリがありますので、それを使います。

aiobleとbluetoothライブラリをインポートして下さい。

先程調べた、デバイス番号と、ServiceのUUID、CharacteristicのUUIDを定数として保存しておきます。

メイン処理の流れは、

  1. Bluetoothデバイスのスキャン
  2. DeviceConnectionオブジェクトを取得する
  3. ClientServiceを取得する
  4. characteristicを(複数)取得する
  5. ボタン通知を登録する
  6. ボタン通知を受け取る

となります。

具体的なプログラムはこうなります。

async def main():
    while True:
        LED.on()
        SWITCH.off()
        try:
            # Bluetoothデバイスのスキャン
            await scan_for_button()
            # DeviceConnectionオブジェクトを取得する
            deviceConnection = await connect()
            print("Connected to device:",deviceConnection)

            # ClientServiceを取得する
            button_clientService = await deviceConnection.service(_ENV_BUTTON_SERVICE_UUID)
            print("get the Button client service:",button_clientService)

            # characteristicを(複数)取得する
            button_characteristics = await get_characteristics(button_clientService)
            print("get the Button characteristics:",button_characteristics)

            # ボタン通知を登録する
            await subscribe_button(button_characteristics)
            LED.off()

            # ボタン通知を受け取る
            await button_notified(button_characteristics)
        except Exception as e:
            print(e)
        finally:
            disconnect(deviceConnection)

asyncio.run(main())

各関数の中身を見ていきましょう。

# Bluetoothデバイスのスキャン(デバイス番号の調査)
async def scan():
    devices = []
    async with aioble.scan(duration_ms=1000) as scanner:
        async for result in scanner:
            devices.append(result)
    return devices

# ボタンデバイスがあるか見つかるまでループさせる関数
async def scan_for_button():
    while True:
        device_address = []
        try:
            devices = await scan()
        except asyncio.TimeoutError:
            print('Timeout')
        for d in devices:
            addr = binascii.hexlify(d.device.addr, ':').decode('utf-8')
            device_address.append(addr)
            print(f"Device: {d.name()} address: {addr}")
        if _ENV_DEVICE_BUTTON in device_address:
            break

Scan関数の中で、AdvertiseされているPeripheralの電波をscanしています。そしてキャッチした個々のPeripheralの情報をdevidesリストに格納してリターンします。

scan_for_button関数の中で、scanしたデバイス情報と、BT Shutter のデバイス番号を突き合わせて、デバイス情報にBT Shutterが含まれていたらループを抜けて関数を終了するようにしています。

Bluetoothデバイスと接続
async def connect():
    # Connect with known address
    device = aioble.Device(aioble.ADDR_PUBLIC, _ENV_DEVICE_BUTTON)
    try:
        connection = await device.connect(timeout_ms=2000)
    except asyncio.TimeoutError:
        print('Timeout')
    return connection

#Bluetoothデバイスと切断
async def disconnect(deviceConnection):
    await deviceConnection.disconnect(timeout_ms=2000)

connect関数により、PicoWつまりCentralと、ボタンつまりPeripheralとを接続しています。

プログラム終了時には、デバイスを切断しなくてはいけませんので、disconnect関数も用意しておきます。

# characteristicを(複数)取得する
async def get_characteristics(button_clientService):
    button_characteristics = []
    async for characteristic in button_clientService.characteristics(_ENV_BUTTON_CHARACTERICTIC_UUID, timeout_ms=2000):
        if characteristic.uuid == _ENV_BUTTON_CHARACTERICTIC_UUID:
            button_characteristics.append(characteristic)
    return button_characteristics

# ボタン通知を登録する
async def subscribe_button(button_characteristics):
    for c in button_characteristics:
        await c.subscribe(notify=True)

get_characteristics関数で、Characteristicを取得しています。Characteristicは今回は、同一UUIDで2つ存在していましたので、複数取得するようにします。

subscribe_button関数で、先程取得したCharacteristicに対し、notifyのサブスクライブつまり購読登録をします。これで、ボタンが押されたときと、離された時のタイミングで通知が来るようになります。注意点は、同一UUIDで複数のCharacteristicがありますので、それら全てに対し、Notify登録をする必要があるということです。

# ボタン通知を受け取る
async def button_notified(button_characteristics):
    while True:
        # ボタン通知を受け取る
        data = await button_characteristics[0].notified()
        print(data)
        if data == b'\x00\x00':
            # ボタンを離した
            LED.off()
            SWITCH.off()
        else:
            # ボタンを押した
            LED.on()
            SWITCH.on

button_notified関数の中で永久ループさせて、ボタンからのNotifyを待ち受けます。そして、状態変化があった時には、それに見合った処理を記述します。ボタンが離された時には、00というデータがNotifyされますので、それを受け、ここではLEDを消灯させています。ボタンが押された時には、00以外のデータがNotifyされますので、それを受け、ここではLEDを点灯させています。

以上がプログラムの解説になります。

シャッターボタンを使った応用作品のデモ動画

これを応用すると、シャッターボタンで、色々なものを制御できるようになります。

今回は以前の動画で作った、アヒル扇風機の楽器をブルートゥースボタンで動かすようにしてみました。

https://youtu.be/snACTrOwFQk?si=YP2O72wCC6WjoI54

まとめ

Wifiとスマホを連携させてスマホをリモコンとして使うのも良いのですが、Wifiの無い所ではそれはできません。ですが、このBluetoothを使えば、Wifiの無い所でもリモートでモノを制御できるようになります。最近では安価なBluetooth製品が増えてきました。これにより、PicoWの応用活用の可能性もかなり広がるのではないでしょうか。

References

PICO W 公式ページ

https://www.raspberrypi.com/documentation/microcontrollers/raspberry-pi-pico.html

最新MicroPythonダウンロードページ

https://www.raspberrypi.com/documentation/microcontrollers/micropython.html#drag-and-drop-micropython

マイクロパイソン ドキュメント Bluetooth

https://docs.micropython.org/en/latest/library/bluetooth.html

bluetoothドキュメント(日本語公式)

https://micropython-docs-ja.readthedocs.io/ja/latest/library/bluetooth.html

aioble ライブラリ

https://github.com/micropython/micropython-lib/tree/master/micropython/bluetooth/aioble

マイクロパイソン Bluetooth GitHubリポジトリ

https://github.com/micropython/micropython/tree/master/examples/bluetooth

Bluetooth UUIDのドキュメント

https://www.bluetooth.com/specifications/assigned-numbers/

Human Interface Device Service 1.0 documents

 https://www.bluetooth.com/specifications/specs/human-interface-device-service-1-0/