(※) タイトル変更, リンク追加(2019/06/30)
先日購入した toio のコアキューブはBLEで命令のやり取りをしているとのこと。おもしろそうだと思い、いろいろ調べていたところ、グッドタイミングで、toio の公式ページに、toio のコアキューブの技術仕様が公開された!!
技術仕様にBLEの通信仕様は公開されているが、スクリプトとしては、JavaScriptが公開されている。JavaScript は得意ではないので、さっそく、Pythonで動かしてみた。
(※) 2020年1月 コアキューブのfirmware がアップデートされました。アップデートしたコアキューブは、このページに記載されている GATT Handle の値が異なります。具体的な値は、こちらの冒頭に記載しましたので、そちらを参照ください。
使用環境
- 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 を調べておく。
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 を操作してみた。
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 の求め方については、こちらに記載されている内容と同じことをすればよい。
今後
この他に、ID情報(絶対座標)とか、SENSOR とか、いろいろ楽しそうの機能がついている。せっかくなので、このあたりもまとめた CoreCube クラスを作ってみようと思う。(Notify とか、どうしよう・・・)
おもしろいことに使えたらいいなぁ-。