Pythonで楽しくtoio非同期制御！(その1)

<div align="center">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635110/017f6468-5158-c912-95f7-c4072d5747c6.jpeg">
</div>


# 初めに
初めまして、大学院生のヨッシーです。初めてのQiita投稿なので不慣れながらも頑張ります！
遅ばせながら、最近になってtoio買いました。ちょこちょこ動く姿が可愛いんですよねぇ。

ところで、最近toioの[技術仕様書](https://toio.github.io/toio-spec/)も公開されたということで、試しに私のMacBookからBLE通信で操作を試したのですが、、、、

**操作が難しい。**

Characteristics？リトルエンディアン？などなど、バイト配列でtoioにデータを送り込むみたいですが、もっと直感的にtoioを操作したい！

ということで、Pythonで直感的なtoio操作のAPIを作成したのでこちらで紹介していきます。


# 前準備
こちらの[レポジトリ](https://github.com/Yoshi-0921/toio_API)を基に話を展開していきます。
クローン後、該当ディレクトリにて`$ pip install -e .`を実行してください（場合によってはsudoで）。

###### 動作環境
- macOS Big Sur (M1 chip)
- Python 3.8.5
- bleak 0.11.0

# まずは接続してみる
### `discover_toios`
まずはご自身のPCがtoioを認識するかのテストをします。ここでは、`discover_toios`というmethodを使います。試しに、toioが見つからない場合、一つ見つかった場合、二つ見つかった場合を示しています。

```tmp.py
import asyncio
from toio_API.utils.general import discover_toios

if __name__ == '__main__':
    toio_addresses = asyncio.run(discover_toios())
```
出力結果：

```py
$ python tmp.py
[INFO] [2021-04-27 10:20:58,068] [general.py:49] 
 No toio discovered :(

$ python tmp.py
[INFO] [2021-04-27 10:21:09,650] [general.py:47] 
 1 toio discovered: ['33139358-E12D-45F8-9B83-A23EBC3CDD58']

$ python tmp.py
[INFO] [2021-04-27 10:21:25,622] [general.py:47] 
 2 toio discovered: ['B62DBD30-1D16-4796-A60F-E76903A3BEF0', '33139358-E12D-45F8-9B83-A23EBC3CDD58']
```

BLE通信が可能なtoioのBLE addressをリストで返します（これ意外と便利）。

### `create_toios`
次に見つけたtoioに名前をつけてプログラムで扱いやすくするためにregisterしていきます。
ここでは`create_toios`というmethodを使います。

```tmp.py
import asyncio
from toio_API.utils.general import create_toios, discover_toios

if __name__ == '__main__':
    toio_addresses = asyncio.run(discover_toios())
    toios = create_toios(toio_addresses=toio_addresses)
```
出力結果：

```py
$ python tmp.py
[INFO] [2021-04-27 10:27:12,504] [general.py:47] 
 2 toio discovered: ['B62DBD30-1D16-4796-A60F-E76903A3BEF0', '33139358-E12D-45F8-9B83-A23EBC3CDD58']
[INFO] [2021-04-27 10:27:12,506] [general.py:30] 
 2 Successfully registered the toio: ['toio_0', 'toio_1']
```
この時、toioに名前をつけることができます！デフォルトでは`toio_#`ですが、以下のように任意の名前をつけてあげることもできます。

```tmp.py
import asyncio
from toio_API.utils.general import create_toios, discover_toios

if __name__ == '__main__':
    toio_addresses = asyncio.run(discover_toios())
    toios = create_toios(toio_addresses=toio_addresses, toio_names=['Yoshi', 'Moto'])
```
出力結果：

```py
$ python tmp.py
[INFO] [2021-04-27 10:27:43,762] [general.py:47] 
 2 toio discovered: ['B62DBD30-1D16-4796-A60F-E76903A3BEF0', '33139358-E12D-45F8-9B83-A23EBC3CDD58']
[INFO] [2021-04-27 10:27:43,764] [general.py:30] 
 2 Successfully registered the toio: ['Yoshi', 'Moto']
```
やっぱり名前つけてあげたほうが愛着湧きますよねぇ笑

### `connect` ・ `disconnect`
ここからは非同期処理を実際に扱っていきます。
toioをBLE通信で接続するmethodである`connect`、及び接続解除するmethodの`disconnect`は`async def `で実装されているため、複数体のtoioを扱う場合は以下のように新しい関数`connect_disconnect()`を作成します。
この関数は複数toioを一秒間だけ接続し、その後接続解除しています。

```tmp.py
import asyncio
from toio_API.utils.general import connect, create_toios, disconnect, discover_toios

async def connect_disconnect(toios):
    await asyncio.gather(*[connect(toio) for toio in toios])
    await asyncio.sleep(1)
    await asyncio.gather(*[disconnect(toio) for toio in toios])

if __name__ == '__main__':
    toio_addresses = asyncio.run(discover_toios())
    toios = create_toios(toio_addresses=toio_addresses, toio_names=['Yoshi', 'Moto'])
    asyncio.run(connect_disconnect(toios))
```
出力結果：

```py
$ python tmp.py
[INFO] [2021-04-27 11:19:50,834] [general.py:47] 
 2 toio discovered: ['33139358-E12D-45F8-9B83-A23EBC3CDD58', 'B62DBD30-1D16-4796-A60F-E76903A3BEF0']
[INFO] [2021-04-27 11:19:50,836] [general.py:30] 
 2 Successfully registered the toio: ['Yoshi', 'Moto']
[INFO] [2021-04-27 11:19:52,551] [general.py:62] 
 [Yoshi] Successfully connected the toio!
[INFO] [2021-04-27 11:19:53,069] [general.py:62] 
 [Moto] Successfully connected the toio!
[INFO] [2021-04-27 11:19:54,108] [general.py:79] 
 [Yoshi] Successfully disonnected the toio!
[INFO] [2021-04-27 11:19:54,171] [general.py:79] 
 [Moto] Successfully disonnected the toio!
```
無事に接続、接続解除ができていることが確認できますね。

# 動かしてみる
最後にtoioに一秒間の回転運動をしてもらいます。
これまでの手順通りtoioを接続した後に、toioのモーターを`toio.motor`を使って制御するコマンドを入力します。ここでは最もシンプルな`toio.motor.control`というモーター駆動コマンドを実行します。デフォルトで回転運動します。

```tmp.py
import asyncio
from toio_API.utils.general import connect, create_toios, disconnect, discover_toios

async def kaiten(toios):
    await asyncio.gather(*[connect(toio) for toio in toios])
    await asyncio.gather(*[toio.motor.control() for toio in toios])
    await asyncio.sleep(1)
    await asyncio.gather(*[disconnect(toio) for toio in toios])

if __name__ == '__main__':
    toio_addresses = asyncio.run(discover_toios())
    toios = create_toios(toio_addresses=toio_addresses, toio_names=['Yoshi', 'Moto'])
    asyncio.run(kaiten(toios))
```

簡単ですね、一行加えるだけで回転運動を非同期でさせることができました。
[デモ動画](https://youtu.be/rINq-bm9uKI)も用意したので是非確認してみてください！

# 最後に
今回は自作APIでtoioをpythonで簡単に非同期制御することを目標に進めてきました。
無事できてよかったです笑
モーター以外にも他にも様々な機能のAPIも合わせて実装したのでこれから時間があればじゃんじゃん紹介していけたらと思います！

# 続き
- その他の`toio.motor`のコマンド紹介
- その他諸々

# 参考サイト
- [toio公式](https://toio.io/?utm_source=google&utm_medium=cpc&utm_campaign=S-shimei)
- [toio技術仕様書](https://toio.github.io/toio-spec/docs/about)
- [toio を Mac + Python で制御できるライブラリつくった](https://qiita.com/mizumasa/items/2a350e4188a5904fa829)
- [Pythonライブラリ bleakでWindows10/macOS/Linux上でtoioコア キューブを動かしてみる](https://qiita.com/kenichih/items/8baa27b3aecc94dd8193)
- [toioをRaspberry Piで制御する](https://note.com/pixeln/n/nbd86fdbec6ae)