Pythonライブラリ bleakでWindows10/macOS/Linux上でtoioコア キューブを動かしてみる

#身近にあるパソコンでtoioコア キューブを動かしてみたい

　toioコア キューブはBLE通信で制御できる「とてもかわいい」二輪ロボットです。
https://toio.github.io/toio-spec/

　先日、[スマホを使ってBLE通信を直につかってtoioコアキューブを動かしてみる話](https://qiita.com/kenichih/items/ef5fde749c9a20bfa2bc)を書いてみましたが今回はパソコンを使ってtoioコアキューブを動かしてみます。

　オフィシャルにはjavascriptおよびビジュアルプログラミングの環境が用意されています。

- ビジュアルプログラミング環境(scratch)
    - https://toio.io/programming/visual-programming.html
- JavaScriptライブラリ(node.js)
    - https://github.com/toio/toio.js

　が、この記事ではpythonを使います。
　[ビジュアルプログラミング環境](https://toio.io/programming/visual-programming.html)は~~ (2019年12月10日時点で)macOS用しかリリースされていませんし、~~ Windows10 64bit あるいは macOS 10.13以降が必要です。 [JavaScriptライブラリ](https://github.com/toio/toio.js)もnode.js、nobleのインストールが必要です。この記事の趣旨としては、身近に使えるパソコンやRaspberry Piなどをつかってサクッとtoioコアキューブを動かしたい、というものなので、インストールが比較的簡単なpythonを使う方法を紹介してみます。

　なお、bleakのサンプルコードがasyncioを使っているので、対応するPythonのバージョンはPython3.5以降になります。

## bleakって？
　Windows10/Linux/macOSで動くBLE通信Python用ライブラリです。

- https://pypi.org/project/bleak/
- https://github.com/hbldh/bleak

　BLE通信用pythonライブラリはいろいろなものがありますが、Windows10、Linux(raspbian、ubuntu)、macOSの3種のOSで(ほぼ)同じように動かせるものとしてbleakを選んでみました。

### その他のBLE通信pythonライブラリ

 BLE直でというのはちょっとなーという場合は偉大な先人たちが作成されたtoio コアキューブ用ライブラリを使うのが良いでしょう。動作するOSなどはそれぞれの動作要件を確認してください。

- [Raspberry Pi で toio コアキューブをコントロールする（２回目）～ Notifyの処理+おまけ](https://qiita.com/FiveOne/items/9d4a93abb9c5d23138ce)
- [Toioで遊んでみる (1)](https://qiita.com/tomoto335/items/2672cbba8f13a9dcfb43)
- [toio を Mac + Python で制御できるライブラリつくった](https://qiita.com/mizumasa/items/2a350e4188a5904fa829)

### bleakのしくみ
　bleakは各OSのBLE通信APIをwrapする形で実装されています。

- Windows10だと共通言語ランタイム(CLR)を使ってdllを呼び出しその中でUWP APIを呼び出して呼び出しています。
- Linuxだと[bluez](http://www.bluez.org/) スタックを呼び出しています。
- macOSだとpyobjcを使ってObjective-Cの[Core Bluetooth](https://developer.apple.com/documentation/corebluetooth) APIを呼び出しています

　それぞれのOSのBLE通信部を隠蔽する形でpythonライブラリ化されているので、どのOS上でも(ほぼ)同じpythonスクリプトでBLE通信をすることができるようになっています。

## まー、なにはともあれやってみましょう

### Windows10とmacOSにpythonをインストール

　Linuxでは(だいたいは)最初からpythonが入っていますが、Windows10には入っていないので、インストールする必要があります。また、macOSはpython2.7は入っていますが、python3は入っていないのでこちらもインストールする必要があります。以下の記事が参考になります。

- [【Windows 10】コマンドプロンプトからPython環境を一発でインストールする](https://www.atmarkit.co.jp/ait/articles/1906/17/news023.html)
- [Python3インストール（Mac編）](https://qiita.com/ms-rock/items/72b8f1abc661c539bb09)

　以下の説明ではプロンプトが「$」になっていますが、Windows10、macOS、Linuxそれぞれの環境でのプロンプトになっているものとします。動作自体はどの環境でも同じです。

### bleakをインストール

- コマンドプロンプト(Windows10ならPowerShellかコマンドプロンプト(cmd.exe))、LinuxならLXterminal等、macOSならターミナルを起動)を使える状態にします。
- pipコマンドでbleakをインストールします。

```Bash
$ pip install bleak
```

しばらく待つとbleakライブラリのインストールが終わり、利用可能になります。

### toioコアキューブのBLEデバイスアドレスを調べる

　これは[bleakのサンプルコードdiscover.py](https://github.com/hbldh/bleak/blob/master/examples/discover.py
)そのまんまです。

```python:discover.py
import asyncio

from bleak import discover

async def run():
    devices = await discover()
    for d in devices:
        print(d)

loop = asyncio.get_event_loop()
loop.run_until_complete(run())
```
これを動かすと以下のように出力されます。

```Bash
$ python3 discover.py
D0:8B:7F:12:34:56: toio Core Cube
B0:52:22:33:44:5A: Unknown
6E:E4:DA:12:34:55: bluetooth deviceY
63:87:F5:55:22:33: bluetooth deviceX
```
　toio Core Cubeとあるところの6バイト16進数がデバイスアドレスになります。この値をメモしておきます。この値を後のスクリプトの中でセットします。上の実行例だと「D0:8B:7F:12:34:56」。
　macOSの場合、6バイト16進数ではなく、243E23AE-4A99-406C-B317-18F1BD7B4CBEのような長いUUID文字列になります。

### toioコアキューブのバッテリー残量を読み取り、LEDを点灯し、モーターを回す

　toioコアキューブのバッテリー残量を読み取るには、oioコアキューブに接続したあと、バッテリー(Battery Information)のキャラクタリスティック(10B20108-5B3B-4571-9508-CF3EFCD7BBAE)を読み取ります。
　LEDを点灯するには、ランプ(Light Control)のキャラクタリスティック(10B20103-5B3B-4571-9508-CF3EFCD7BBAE)に書き込みます。
　モーターを動かすにはモーター(Motor Control)のキャラクタリスティク(10B20102-5B3B-4571-9508-CF3EFCD7BBAE)に値を書き込みます。

　toioコアキューブ技術仕様書の通信仕様の「ランプ」「モーター」の例を参考に値を書き込んでみます。

- toioコアキューブ技術仕様書の通信仕様
    - [バッテリー](https://toio.github.io/toio-spec/docs/ble_battery)
    - [ランプ](https://toio.github.io/toio-spec/docs/ble_light)
    - [モーター](https://toio.github.io/toio-spec/docs/ble_motor)

```python:read_write_sample.py
import asyncio
import platform

from bleak import BleakClient

# バッテリーとLEDとモーターのキャラクタリスティック　
BATTERY_CHARACTERISTIC_UUID = ("10b20108-5b3b-4571-9508-cf3efcd7bbae")
LAMP_CHARACTERISTIC_UUID = ("10b20103-5b3b-4571-9508-cf3efcd7bbae")
MOTOR_CHARACTERISTIC_UUID = ("10b20102-5b3b-4571-9508-cf3efcd7bbae")  
# bleakではキャラクタリスティックのUUIDは小文字で書かないとダメみたい

async def run(address, loop):
    async with BleakClient(address, loop=loop) as client:
        x = await client.is_connected()
        #logger.info("Connected: {0}".format(x))
        print("Connected: {0}".format(x))
        # バッテリー残量読み取り
        battery = await client.read_gatt_char(BATTERY_CHARACTERISTIC_UUID)
        print("battery: {0}".format(int(battery[0])))
        # LEDを160ミリ秒、赤に点灯
        write_value = bytearray(b'\x03\x10\x01\x01\xff\x00\x00')
        await client.write_gatt_char(LAMP_CHARACTERISTIC_UUID, write_value)
        # モーター　左を前に100の速度、右を後ろに20の速度
        write_value = bytearray(b'\x01\x01\x01\x64\x02\x02\x14')
        await client.write_gatt_char(MOTOR_CHARACTERISTIC_UUID, write_value)
        # 5秒後に終了
        await asyncio.sleep(5.0, loop=loop)

if __name__ == "__main__":
    address = (
        # discovery.pyでみつけたtoio Core Cubeのデバイスアドレスをここにセットする
        "D0:8B:7F:12:34:56"  # Windows か Linux のときは16進6バイトのデバイスアドレスを指定
        if platform.system() != "Darwin"
        else "243E23AE-4A99-406C-B317-18F1BD7B4CBE"  # macOSのときはmacOSのつけるUUID
    )
    loop = asyncio.get_event_loop()
    loop.run_until_complete(run(address, loop))
```

```bash
$ python read_write_sample.py
Connected: True
battery: 100
```
動かすとバッテリー残量を読み取って表示した後、LEDが赤く一瞬点灯します。
<img alt="P_20191209_221300.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431649/15592fd3-07a8-0fae-264a-a564c4612ba3.jpeg" width="50%" />
その後、toioコアキューブのやや右側を中心に時計回りに回転します。
<img alt="P_20191209_221458.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431649/54286699-4678-659d-dee4-81bd822d7020.jpeg" width="50%" />
５秒後に終了します。

### toioコアキューブのボタン状態や読み取りセンサーの読み取るIDを通知させる

 toioコアキューブのボタン状態や読み取りセンサーの読み取るIDを通知(notify)させるにはボタン(Button Information)のキャラクタリスティック(10B20107-5B3B-4571-9508-CF3EFCD7BBAE)と、読み取りセンサー(ID Information)ののキャラクタリスティック(10B20101-5B3B-4571-9508-CF3EFCD7BBAE)の通知をうけるようにします。

　これは[bleakのサンプルコードenable_notifications.py](https://github.com/hbldh/bleak/blob/master/examples/enable_notifications.py)を参考に、以下のようなスクリプトを書きます。

```python:enable_button_and_id_info_notification.py
import asyncio
import platform

from bleak import BleakClient

# ボタンと読み取りセンサーのキャラクタリスティック　
BUTTON_CHARACTERISTIC_UUID = ("10b20107-5b3b-4571-9508-cf3efcd7bbae") 
ID_READER_CHARACTERISTIC_UUID = ("10b20101-5b3b-4571-9508-cf3efcd7bbae") 
# bleakではキャラクタリスティックのUUIDは小文字で書かないとダメみたい

# ボタン状態の通知ハンドラ
def button_notification_handler(sender, data):
    print("BUTTON {0}: {1}".format(sender, data))

#読み取りセンサーの通知ハンドラ
def id_reader_notification_handler(sender, data):
    print("toio ID {0}: {1}".format(sender, data))

async def run(address, loop):
    async with BleakClient(address, loop=loop) as client:
        x = await client.is_connected()
        print("Connected: {0}".format(x))
        # ボタン状態の通知スタート
        await client.start_notify(BUTTON_CHARACTERISTIC_UUID, button_notification_handler)
        # 読み取りセンサーの通知スタート
        await client.start_notify(ID_READER_CHARACTERISTIC_UUID, id_reader_notification_handler)
        # 10秒後に終了
        await asyncio.sleep(10.0, loop=loop)
        # ボタン状態、読み取りセンサーの通知終了
        await client.stop_notify(BUTTON_CHARACTERISTIC_UUID)
        await client.stop_notify(ID_READER_CHARACTERISTIC_UUID)

if __name__ == "__main__":
    address = (
        # discovery.pyでみつけたtoio Core Cubeのデバイスアドレスをここにセットする
        "D0:8B:7F:12:34:56"  # Windows か Linux のときは16進6バイトのデバイスアドレスを指定
        if platform.system() != "Darwin"
        else "243E23AE-4A99-406C-B317-18F1BD7B4CBE"  # macOSのときはmacOSのつけるUUID
    )
    loop = asyncio.get_event_loop()
    loop.run_until_complete(run(address, loop))
```

　動かしたらtoioコアキューブを持ち上げてボタンを押してみたり、トイオコレクションのマットやステッカーの上にのせてみます。

```Bash
$ python enable_button_and_id_info_notification.py
Connected: True
toio ID 10b20101-5b3b-4571-9508-cf3efcd7bbae: bytearray(b'\x01\xa4\x01g\x01|\x00\xaf\x01e\x01|\x00')
toio ID 10b20101-5b3b-4571-9508-cf3efcd7bbae: bytearray(b'\x01\xa4\x01h\x01|\x00\xaf\x01f\x01|\x00')
toio ID 10b20101-5b3b-4571-9508-cf3efcd7bbae: bytearray(b'\x01\xa4\x01g\x01|\x00\xaf\x01e\x01|\x00')
```
逐一変わる値が通知されてくるのがわかります。

## 実は...

　実際に動作を確認できたのはWindows10(1909)とLinux(raspbian busterで、RasPi3Bと4Bで動作確認)だけです。
　すみません。自分の環境ではmacOSでbleakを動かすことはできませんでした。discovery.pyは動くのですが、それ以外がさっぱり動かなかったです。(環境はMacBookPro 13inch(early2015) macOS 10.14.6(mojave))
　このmacOS環境では、Linux/macOS対応をうたう[AdafruitのBLEライブラリ](https://github.com/adafruit/Adafruit_Python_BluefruitLE/tree/master/Adafruit_BluefruitLE)だと動くのですが、逆にこちらはraspbian busterでは動きませんでした。(bluezのバージョン違いが原因)　bluezのbackendを修正すれば動くような気がしますが、原因がつかめてないのでなんとも。


## さいごに

　小学校や中学校のパソコン部？にもありそうな身近にあるパソコンやRaspberry Piでpythonを使ってtoioコアキューブを動かして見る方法を紹介してみました。ノートPCのようにbluetoothインタフェースが内蔵されていないPCでも、USBドングル型のBluetoothインタフェースをつければ動かすことができると思います。
　node.jsとnoble、scratch-linkの導入がちょっとハードルが高いという方、またWindows10は使えるが、インストールできるアプリはストアアプリに制限されているような環境でもストアアプリのpython3はインストールできてbleakが使えるので、ぜひ試してみてください。