SANDFISH FACTORY

技術ブログです。python・vuejsを愛でる日々について綴ります

toioをpythonで動かす:BLE操作

はじめに

前回は「toioをpythonで動かす:バイナリデータ操作」を行いました。
今回から本丸のBLE操作を行って、toioを制御したいと思います。

BLE入門

BLEはBluetooth Low Energyの略で、Bluetoothを用いて通信を行う規格のことをです。
toioではキューブとの通信は Bluetooth® 標準規格 Ver. 4.2で行っているとのことです。
通信概要 · toio™コア キューブ 技術仕様

BLEにはセントラル(Central)とペリフェラル(Peripheral)の2種類の役割があります。
通信のホストとなるデバイスをセントラル、何かしらの情報や機能を提供するデバイスペリフェラルと呼びます。
今回の実装は、toioのキューブがペリフェラルで、pythonで実装しようとしているのがセントラルの部分です。

f:id:sandfish03:20200509150817p:plain

BLEは規格なので、やり取りするための手順やデータ構造は定まっていて、GATT(Generic attribute profile:汎用アトリビュートプロファイル)と呼ばれています。
このGATTはサービス(Service)とキャラクタリスティック(Characteristic)から構成されています。

toioのキューブが提供するサービスには8つのキャラクタリスティックが存在します。
サービス、キャラクタリスティックはUUIDで識別して操作することとなります。

  • ID Information / 読み取りセンサー
  • Sensor Information / モーションセンサー
  • Button Information / ボタン
  • Battery Information / バッテリー
  • Motor Control / モーター
  • Light Control / ランプ
  • Sound Control / サウンド
  • Configuration / 設定

BLE操作ライブラリの導入

pythonでBLEを操作するライブラリは存在しています。少し調べた中では以下の2つが検索して見つかりました。

リンクも張りましたが、参考にしたスライドが最初分かりやすかったので、Adafruitを用いることにしました。
windows環境の方はbleakを用いて試してみてください。番外編として後ほど記事を投稿します。

環境構築

まずは必要なライブラリをpipでインストールします。前の記事で書いた通り、venvを有効にして(activate)おいてください。

Macを利用する場合の前提条件にpyobjcをインストールすることと書いてあるので、まずそちらを実行します。

pip install pyobjc

次にAdafruitをインストールしますが、github上のコードをcloneしてからsetup.pyを実行します。

git clone https://github.com/adafruit/Adafruit_Python_BluefruitLE.git
cd Adafruit_Python_BluefruitLE
sudo python setup.py install

サンプルコードを配置するディレクトリを作成して環境構築は完了です。

cd ..
mkdir bleapp
cd bleapp
touch main.py

toioに接続する下準備を行う

Adafruit_BluefruitLEを利用する上で下準備となる実装を行います。

import Adafruit_BluefruitLE

def main():
    # ここにメインの処理を実装します

if __name__ == '__main__':
  provider = Adafruit_BluefruitLE.get_provider()
  provider.initialize()
  provider.run_mainloop_with(main)

まずは、Adafruit_BluefruitLEからproviderを取得します。
取得したproviderを初期化して、run_mainloop_withを呼び出すところまではこの順番の通りに実施して下さい。
Adafruit_BluefruitLEのライブラリがBLEを操作するために必要な準備を行ってくれています。

  • BLEはOSのネイティブライブラリを呼び出すため、OS毎に異なります。Macはcorebluetooth、linuxはbluez_dbusです。
  • get_providerで取得したproviderはOS毎のライブラリの差分を吸収してBLE操作を行います。

toioをスキャンする

動かすための下準備のコードを実装したら、次はメインの実装を行います。

def main():
  provider.clear_cached_data()
  adapter = provider.get_default_adapter()
  adapter.power_on()
  
  # adapterがスキャンを始める
  adapter.start_scan()

  # adapterがスキャンしている間にペリフェラルを探す
  # (deviceという名前になってます)
  device = provider.find_device()

  # 取得できたら、スキャンを止める
  adapter.stop_scan()

provider経由でadapterを取得します。
Adafruit_BluefruitLEのadapterはペリフェラルをスキャンする機能を提供しています。
これはあくまでAdafruit_BluefruitLEでの役割なので、BLE操作に必ずadapterという名前のモジュールがあるわけではないです。

adapterでスキャンを開始している間、provider経由でペリフェラル(メソッド名はdeviceですが)を探します。
ペリフェラルが見つかったら、不要なのでスキャンを止めます。

toioを動かす

ペリフェラルを取得できたら、接続して実際に動かす実装を行います。
行数が多いので順番に解説します。

まず、取得したペリフェラル(=device)に接続します。
接続した後、sleepで3秒程待ちます。

device.connect()  
time.sleep(3)

Macの場合、内部ではcorebluetoothを利用して操作しているのですが、接続した後に必要な情報は同期で返却されないので、3秒待っています。 (3秒が妥当な数値ではないので適宜直してみて下さい)

次に、ペリフェラルから実際に動かすために必要なキャラクタリスティックを取得します。

device.discover([service_uuid], [motor_uuid])  
service = device.find_service(service_uuid)  
chara = service.find_characteristic(motor_uuid)  

uuidはtoioの技術仕様に書かれています。

最後に、toioを動かすためにペリフェラルに動かすために必要情報を書き込みます。
書き込む内容はtoioの技術仕様を参考にしました。

toio.github.io

  l_direction = 1
  r_direction = 1
  l_power = 100
  r_power = 100
  duration = 100

  bdl = [
    2, 1, l_direction, l_power, 2, 
    r_direction, r_power, duration
  ]
  size = len(bdl)
  byte_data = pack('B'*size, *bdl)

  chara.write_value(byte_data)
  time.sleep(5)
  device.disconnect()

前回の記事でも書きましたバイナリデータの書き込みも、早くもここで活用します。
書き込んだあと、実際に動作させるため5秒待ちます。
(すぐdisconnectを呼び出すとtoioが動く前に処理が終わってしまいます)

一番最後にdisconnectを呼び出して、ペリフェラルとの接続を閉じます。

実際に動かしてみる

これまでに書いたコードを実際に動作させてみます。
当たり前ですが、MacBluetoothは有効にしておいて下さい。(私は最初、有効するの忘れてて、なんで動かないのか10分くらい戸惑いました)

python main.py

実際に動くので、toioは床などの広い場所においてから実行して下さい。

まとめ

ひとまず、BLE操作をpythonで行うことができました。
技術仕様が公開されているので、この仕様とtoio.jsのコードを読めば、ある程度、理解することは可能でした。

試しに動かすためのコードはこちらに書いておきます。

from uuid import UUID
import time
from struct import pack

import Adafruit_BluefruitLE

def main():
    service_uuid = UUID('10b201005b3b45719508cf3efcd7bbae')
    motor_uuid = UUID('10b201025b3b45719508cf3efcd7bbae')
    provider.clear_cached_data()
    adapter = provider.get_default_adapter()
    adapter.power_on()

    adapter.start_scan()
    device = provider.find_device()
    adapter.stop_scan()

    device.connect()
    time.sleep(3)

    device.discover([service_uuid], [motor_uuid])
    service = device.find_service(service_uuid)
    chara = service.find_characteristic(motor_uuid)

    l_direction = 1
    r_direction = 1

    l_power = 100
    r_power = 100

    duration = 100

    bdl = [2, 1,
    l_direction, l_power, 2,
    r_direction, r_power, duration]
    size = len(bdl)
    byte_data = pack('B'*size, *bdl)

    chara.write_value(byte_data)
    time.sleep(5)

    device.disconnect()

if __name__ == '__main__':
    provider = Adafruit_BluefruitLE.get_provider()
    provider.initialize()
    provider.run_mainloop_with(main)

最初はBLE操作の実装の仕方が分からなかったので、Adafruit_BluefruitLEのコードも読んで何となく構造は分かったのですが、その辺りは余裕があったらブログに書きたいと思います。

次回は、少し寄り道で、mypy、flake8、blackを導入して試した話を書きます。

今回のそもそもの目的はtoio.jsのコードをpythonに置き換えることです。
toio.jsはtypescriptで書かれていて、型安全になっているのでmypyを入れてみました。
ついでではありますが、flake8とblackも入れて試してみたので、その辺りを書ければと思います。