Raspberry Pi で toio コアキューブをコントロールする（１回目）

(※) タイトル変更, リンク追加(2019/06/30)

先日購入した toio のコアキューブはBLEで命令のやり取りをしているとのこと。おもしろそうだと思い、いろいろ調べていたところ、グッドタイミングで、[toio の公式ページ](https://toio.io)に、toio の[コアキューブの技術仕様](https://toio.github.io/toio-spec/)が公開された！！
技術仕様にBLEの通信仕様は公開されているが、スクリプトとしては、JavaScriptが公開されている。JavaScript は得意ではないので、さっそく、Pythonで動かしてみた。

<b>(※) 2020年1月 コアキューブのfirmware がアップデートされました。アップデートしたコアキューブは、このページに記載されている GATT Handle の値が異なります。具体的な値は、[こちらの冒頭](https://qiita.com/FiveOne/items/176f5ad73d8e383b3505)に記載しましたので、そちらを参照ください。</b>

# 使用環境
* HW:
 * raspberry pi   (zero W / 3 にて確認済み)
 * toio コアキューブ
* SW:
 * raspbian-stretch
 * python3
 * bluepy
 
# raspberry pi で、bluepy を利用可能にする
raspberry pi(stretch)は、bluepyが標準ではインストールされていないので、以下のようにしてインストール必要がある。（python3 では、バージョンの整合性があっていないのか、以下のコマンドの3,4行目のようにファイル作成を行わないと、gattlib のインストールでエラーになってしまう。）

````
$ sudo apt install libbluetooth3-dev libglib2.0 libboost-python-dev libboost-thread-dev
$ sudo apt install python3-pip
$ cd /usr/lib/arm-linux-gnueabihf/
$ sudo ln libboost_python-py35.so libboost_python-py34.so
$ sudo pip3 install gattlib
$ sudo pip3 install bluepy
$ sudo systemctl daemon-reload
$ sudo service bluetooth restart
````

# toio コアキューブの address を調べる
bluepy では、BLEデバイスに対して、address を直接指定する必要がある。そのため、以下のようなpythonスクリプトを使って、address を調べておく。

````find_toio.py
import bluepy

scaner = bluepy.btle.Scanner(0)
devices = scaner.scan(3)       # scan時間は3秒

for device in devices:
  for (adType, desc, value) in device.getScanData():
    if "toio Core Cube" in value:
      print('toio Core Cube  Address=%s ,  RSSI=%s' % (device.addr, device.rssi))
````

scanの実行時には、管理者権限が必要なので要注意。
（以下の例では、2つのコアキューブが見つかった）

````
$ sudo python3 find_toio.py
toio Core Cube  Address=**:**:**:**:**:** ,  RSSI=-38      # 実際には、コアキューブのアドレスが返る
toio Core Cube  Address=**:**:**:**:**:** ,  RSSI=-68
````

# コアキューブをコントロールする

技術仕様書を見ると、コアキューブにはいろいろな機能がある。とりあえず、簡単なところで、MOTOR/LIGHT/SOUND を操作してみた。

````sample_coreCube1.py
import bluepy
import binascii
import time
import sys

class CoreCube(bluepy.btle.Peripheral):
  HANDLE_TOIO_MTR = 0x11
  HANDLE_TOIO_LED = 0x14
  HANDLE_TOIO_SND = 0x17

  def __init__(self):
    bluepy.btle.Peripheral.__init__(self)

  #  ---------------- Motor Control
  def motor(self, speeds, duration):
    data = "01" if duration == 0 else "02"
    data = data + "01" + ("01" if speeds[0] >= 0 else "02") + ("{:02x}".format(abs(speeds[0])))
    data = data + "02" + ("01" if speeds[1] >= 0 else "02") + ("{:02x}".format(abs(speeds[1])))
    if duration != 0:
      data = data + ("{:02x}".format(duration))
    self.writeCharacteristic(self.HANDLE_TOIO_MTR, binascii.a2b_hex(data))

  #  ---------------- Light Control
  def lightOn(self, color, duration):
    data = "03{:02x}0101{:02x}{:02x}{:02x}".format(duration, color[0], color[1], color[2])
    self.writeCharacteristic(self.HANDLE_TOIO_LED, binascii.a2b_hex(data))

  def lightSequence(self, times, operations):
    data = "04{:02x}".format(times)
    data = data + "{:02x}".format(len(operations))
    for ope in operations:
      data = data + "{:02x}0101{:02x}{:02x}{:02x}".format(ope[0], ope[1][0], ope[1][1], ope[1][2])
    self.writeCharacteristic(self.HANDLE_TOIO_LED, binascii.a2b_hex(data))

  def lightOff(self):
    data = "01"
    self.writeCharacteristic(self.HANDLE_TOIO_LED, binascii.a2b_hex(data))

  #  ---------------- Sound Control
  def soundId(self, id):
    data = "02{:02x}FF".format(id)
    self.writeCharacteristic(self.HANDLE_TOIO_SND, binascii.a2b_hex(data))

  def soundSequence(self, times, operations):
    data = "03{:02x}".format(times)
    data = data + "{:02x}".format(len(operations))
    for ope in operations:
      data = data + "{:02x}{:02x}FF".format(ope[0], ope[1])
    self.writeCharacteristic(self.HANDLE_TOIO_SND, binascii.a2b_hex(data))

  def soundStop(self):
    data = "01"
    self.writeCharacteristic(self.HANDLE_TOIO_SND, binascii.a2b_hex(data))

if __name__ == "__main__":

  if len(sys.argv) == 1:
    print('Usage: sample_coreCube1.py BLE_DEVICE_ADDRESS')
    sys.exit()
  
  TOIO_ADDR  = sys.argv[1]

  try:
    toio = CoreCube()
    toio.connect(TOIO_ADDR, bluepy.btle.ADDR_TYPE_RANDOM)
  except:
    print("device connect error")
    sys.exit()
  time.sleep(1)

  # Light は、(300ms, Red), (300ms, Green) を3回繰り返す
  toio.lightSequence( 3, ( (30,(255,0,0)), (30,(0,255,0)) ) )
  # Sound は、(300ms,ド), (300ms,レ), (300ms,ミ) を2回繰り返す
  toio.soundSequence( 2, ( (30,60), (30,62), (30,64) ) )
  time.sleep(2)

  # Light は Redを点灯
  # Sound は、id = 0 を再生
  toio.lightOn((255,0,0), 0)
  toio.soundId(0)
  time.sleep(1)

  # Light は Greenを点灯
  # Sound は、id = 1 を再生
  toio.lightOn((0,255,0), 0)
  toio.soundId(1)
  time.sleep(1)

  # Light は Blueを点灯
  # Sound は、id = 2 を再生
  toio.lightOn((0,0,255), 0)
  toio.soundId(2)
  time.sleep(1)

  toio.lightOff()

  # 左右=(50,50)で進む
  # 左右=(-50,-50)で進む（後退する）
  # 左右=(50, -50)で進む（その場で回転する）
  toio.motor((50, 50), 0)
  time.sleep(1)
  toio.motor((-50, -50), 0)
  time.sleep(1)
  toio.motor((50, -50), 0)
  time.sleep(1)
  toio.motor((0, 0), 0)
  time.sleep(1)

  toio.disconnect()
````

### 補足
* CoreCube クラスでは、全く引数のエラーチェックをしていないので、本気で作るときはちゃんとチェックしよう！
* bluepyの設定なのか、1度に送れるデータのサイズに制限があり、lightSequenceでは2個, soundSequeceでは、5個のoperation しか指定できない・・・。setMTU()を使ってみたが、うまくいかない。（★要調査）
* toio コアキューブのHANDLE の求め方については、[こちら](https://qiita.com/FiveOne/items/ea04b74271da0f382ed6#gatt-handle-%E3%82%92%E8%AA%BF%E3%81%B9%E3%82%8B)に記載されている内容と同じことをすればよい。

# 今後

この他に、ID情報(絶対座標)とか、SENSOR とか、いろいろ楽しそうの機能がついている。せっかくなので、このあたりもまとめた CoreCube クラスを作ってみようと思う。（Notify とか、どうしよう・・・）

おもしろいことに使えたらいいなぁ－。

## 続き
* [Raspberry Pi で toio コアキューブをコントロールする（２回目）～ Notifyの処理+おまけ](https://qiita.com/FiveOne/items/9d4a93abb9c5d23138ce)

# 参考サイト
* [toio 公式](https://toio.io)
* [toio コアキューブ　技術仕様](https://toio.github.io/toio-spec/)
* [bluepy](http://ianharvey.github.io/bluepy-doc/index.html)
* [Raspberry Pi で bluepy を使ってmicro:bitをコントロール](https://qiita.com/FiveOne/items/ea04b74271da0f382ed6)