Skip to content

fiveone51/toioCoreCube

Repository files navigation

Linux にて、bluepy を使って、toio コアキューブを動かすクラス


目次

coreCubeクラスの概要

各メソッドの説明

Notifyの活用



coreCubeクラスの概要

ここで紹介している coreCube クラスは、SIE製 toio のコアキューブを Bluetooth のコマンドを使って操作するものです。エラー処理などは、省いているのでご了承ください。基本的には、Pythonと、bluepy という Pythonモジュールが動作すれば動くと思います。

動作確認したLinux

  • Raspberry Pi 3 (rasbian)
  • Raspberry Pi 4 (ubuntu)
  • Intel Linux (Debian buster)

bluepy のインストール

詳細についてはこちらを参照してください。

★必要なパッケージのインストール
$ sudo apt install libbluetooth3-dev libglib2.0 libboost-python-dev libboost-thread-dev

★Raspberry Pi (rasbian Bustrer以前) では、バージョン不整合があるようなので以下の操作をする
$ cd /usr/lib/arm-linux-gnueabihf/
$ sudo ln libboost_python-py35.so libboost_python-py34.so

★bluepy のインストール
$ sudo pip3 install gattlib
$ sudo pip3 install bluepy

★bluetooth サービスがインストールされていなければインストールする
$ sudo apt install bluetooth
$ sudo systemctl enable bluetooth
$ sudo restart

CoreCube バージョンについて

toio CoreCube のfirmware は不定期にアップデートされ、機能追加と安定性向上が図られています。 https://toio.io/update/

いろいろな機能がありすぎて、このクラスはすべてに対応しているわけではありません。なるべく有用な機能に対応していこうと思います。

また、CoreCube のバージョンは常に最新にしてください。
2021/06/06 現在、BLEプロトコルバージョン 2.3.0 に対応しています


CoreCube の詳細について

本家の情報は、以下になります。


大雑把な機能の説明

このクラスで提供する機能は、大きく4つのグループに分けられます

コアキューブから情報を受け取る手段として、READ系とNOTIFY系があります。
READ系は、順序立てて命令を実行させるやり方に適しています。
NOTIFY系は、コールバック的に、コアキューブからの通知をもとに動作させるようなやり方に適しています。


簡単な使い方(READ/WRITE系)

接続して、READ系で情報読み込んで、WRITE系で命令を送って、切断するという基本形です

import time
from coreCube import CoreCube

toio = CoreCube()

# --- 接続
toio_addr = "xx:xx:xx:xx:xx:xx"
toio.connect(toio_addr)
time.sleep(1)

# --- READ系
id = toio.id()
if id == 1:
  print("Position: x=%d, y=%d, dir=%d" % (toio.x, toio.y, toio.dir))
print("Battery: %d" % toio.battery())
print("BLE Version : " + toio.bleVersion())

# --- WRITE系
toio.motorTarget(100, 100, speed_max=0x30)
toio.soundId(3)

# --- 切断
time.sleep(5)
toio.disconnect()

簡単な使い方(NOTIFY系)

ボタンがNOTIFYで返ってくるまで、ループし続けるという基本形です

from coreCube import CoreCube
from coreCube import toioDefaultDelegate

class MyDelegate(toioDefaultDelegate):  # toioDefaultDelegateを継承したクラス定義
    def notify_button(self, id, stat):  # button の notifyメソッドをオーバーライド
      self.corecube.soundId(2)

if __name__ == "__main__":
  toio = CoreCube()
  toio.connect("xx:xx:xx:xx:xx:xx")

  # --- 上で定義したクラスを、Delegate用クラスとして設定
  toio.withDelegate(MyDelegate(toio))

  # --- ボタンに対して、Notifyを要求
  toio.setNotify(toio.HANDLE_TOIO_BTN, True)

  # ここでは、Notifyを10秒待つことを、無限ループさせている。
  # Notifyが来たら(ボタンが押されたら)、MyDelegate.notify_button()が実行され、終了する。
  while True:
    if toio.waitForNotifications(10.0):
      # Notify処理が実行された後の処理
      break
    elif
      # Notify処理がなく、10.0秒経った後の処理
      pass

  # --- 切断
  toio.disconnect()

Notifyについては、後述しますが、マットのXY座標や、姿勢角検出値など、連続的に読み出際には、こちらのやり方で行います。



接続関連

connect メソッド

コアキューブのアドレスを指定して、接続するメソッド

詳細

項目 詳細
書式 connect(deviceAddr)
引数 deviceAddr: 接続したいコアキューブのアドレス
戻り値 なし

実行例

  toio_addr = "xx:xx:xx:xx:xx:xx"
  toio = coreCube()
  try:
    toio.connect(toio_addr)
  except:
    print("接続エラー")
    sys.exit()

備考

コアキューブのアドレスを知るには、find_toio.py を実行する。近くにある電源の入ったコアキューブの一覧が表示される。(find_toio.py の実行は、root で行うこと)


cubeSearch メソッド

電源の入っているコアキューブを探して、見つかったコアキューブのアドレスを配列で返す。 コアキューブが存在しなければ、空の配列をかえす。

配列は、RSSIの強さでソートされる(つまり、近くにあるコアキューブから順)。

★ クラスメソッドなので、インスタンスを作成しなくても実行できる。

★ このメソッドは、rootで実行する必要がある

詳細

項目 詳細
書式 cubeSearch()
引数 なし
戻り値 コアキューブのアドレスが、配列で戻る

実行例

  toio_addrs = CoreCube.cubeSearch()

  if len(toio_addrs) == 1:
    toio1 = coreCube()
    toio1.connect(toio_addr[0])
    print("Connect to " + toio_addrs[0])
  elif len(toio_addrs) == 2:
    toio1 = coreCube()
    toio1.connect(toio_addr[0])
    toio2 = coreCube()
    toio2.connect(toio_addr[1])


センサー関連(READ系)

id メソッド

toio 技術仕様 読み取りセンサー

コアキューブが置かれているマットのPosition ID( X,Y座標, 角度)や、カード・ステッカーのStandard ID( カードを識別する番号、角度)を取り出す。

BLEのREADで読み込む(Notifyではない)。このメソッドを呼び出すことで、CoreCubeクラスの x, y, dir, stdid オブジェクトにPosition ID/Standard ID が設定される。

詳細

項目 詳細
書式 id()
引数 なし
戻り値 読込んだIDの種類

1: Position ID(x, y, dir が設定される)

2: Standard ID(stdid, dir が設定される)

3: その他

実行例

toio = CoreCube()
toio.connect("xx:xx:xx:xx:xx:xx")  # 実際のアドレスを指定

id = toio.id()
if id == 1:
  print("id=%d: x=%d, y=%d, dir=%d" % (id, toio.x, toio.y, toio.dir))
elif id == 2:
  print("id=%d: stdid=%d, dir=%d" % (id, toio.stdid, toio.dir))
else:
  print("id=%d:" % id)

補足

読み取りセンサー情報は、このモジュールを使って、直接READする方法と、Notifyで受け取る方法がある。Notify については、後述。


id関連のオブジェクト

項目 詳細
x int id()メソッドで読み込んだ X座標
y int id()メソッドで読み込んだ Y座標
dir int id()メソッドで読み込んだ 角度
stdid int id()メソッドで読み込んだ Standard ID

sensor メソッド

toio 技術仕様 モーションセンサー

コアキューブのモーションセンサーの傾き情報

詳細

項目 詳細
書式 sensor()
引数 なし
戻り値 なし

実行例

id = toio.sensor()
if toio.horizon == 0:
  print("傾いています!!")

補足

衝突検知の値なども取り出せるが、readメソッドでは、ほぼ「衝突していない」の値しか返ってこない(現在の状態をとってくるので、衝突した瞬間の値を読むのは至難の業・・・)。
衝突検知については、Notifyのところで後述する。


sensor関連のオブジェクト

項目 詳細
horizon int 水平検出値

00: 水平ではないとき

01: 水平な時
詳細

posture int 姿勢検出値 キューブの姿勢によって 1~6 の値をとる(ex. 1なら天面が上)
詳細


モーター関連(WRITE系)

mortor メソッド

toio 技術仕様 モーター

左右のモーターの速度を指定して、キューブを動かす

詳細

項目 詳細
書式 mortor(speed, duration)
引数 speed: 左右のスピードを指定した配列

[left, right] のように -115 <= 0 <= 115 で指定

ただし、+値は前進、-値は後退。

また、遅すぎると(-8~8 )、0に丸められる。
これらの値は、コアキューブのバージョンによって異なる可能性があるため、詳細ページ にて確認すること

duration: モーターを回す時間を指定

0 時間指定なし(無期限)

1~255 指定された数×0.01秒

戻り値 なし

実行例

speed = [50, 50]
toio.mortor(speed, 100)     # 1秒前進
time.sleep(1)
toio.mortor([50, -50], 100)    # 1秒回転
time.sleep(1)
toio.mortor([s*-1 for s in speed], 100)   # 1秒後退
time.sleep(1)

補足

mortor()で、durationを指定しても、指定した時間分待たずに返ってくる。必要であれば、上記の例のように、呼び出した側で待ち時間を入れる必要がある。


motorTarget メソッド

toio 技術仕様 目標指定付きモーター制御

マットの上に置かれたコアキューブを指定された座標まで動かす。

詳細

項目 詳細
書式 motorTarget(x, y, dir, timeout, mtype, speed_max, speed_type))
引数 X: 目的のX座標
Y: 目的のY座標
dir: 目的地でのコアキューブの角度。省略可能。省略時は角度の修正は行わない。 詳細
timeout: タイムアウト時間(秒)。省略可能。省略時は10秒
mtype: 移動タイプ。省略可能。省略時は0(回転しながら移動)。 詳細
speed_max: モーターの最大速度指示値。省略可能。省略時は 0x50
speed_type: モーターの速度変化タイプ。 省略可能。省略時は 0(速度変化なし) 詳細
戻り値 なし

実行例

toio.motorTarget( 200, 200, speed_max=0x30)
time.sleep(10)

補足

motorTarget()も、呼び出したらすぐに返ってくるので注意。

処理がうまくいったかどうかについては、Notifyで確認することができる。詳細は、Notify関連の notify_motor_response() を参照のこと。



ランプ関連(WRITE系)

lightOn メソッド

toio 技術仕様 ランプ

RGBを指定して、コアキューブのLEDを点灯させる

詳細

項目 詳細
書式 lightOn(color, duration)
引数 color: LEDの色を指定した配列

[RED, GREEN, BLUE] のように、それぞれ、0 <= 255 で指定

duration: モーターを回す時間を指定

0 時間指定なし(無期限)

1~255 指定された数×0.01秒

戻り値 なし

実行例

color = [255, 255, 255]
toio.lightOn(color, 0)
time.sleep(1)
for i in range(256):
  toio.lightOn([i, i, i], 0) 
  time.sleep(0.1)
toio.lightOff()  

補足

lightOn()で、durationを指定しても、指定した時間分待たずに返ってくるため、必要であれば、上記の例のように、呼び出した側で待ち時間を入れる必要がある。


lightOff メソッド

toio 技術仕様 ランプ

コアキューブのLEDを消灯させる

詳細

項目 詳細
書式 lightOff()
引数 なし
戻り値 なし

実行例

color = [255, 255, 255]
time.sleep(1)
for i in range(256):
  toio.lightOn([i, i, i], 0) 
  time.sleep(0.01)
toio.lightOff()  

lightSequence メソッド

toio 技術仕様 ランプ

コアキューブのLEDをパターンを指定して点灯させる

詳細

項目 詳細
書式 lightSequence(times, operations)
引数 times:

0: 無限に繰り返す

1~255: 繰り返し回数

operations: LED色の点灯パターンを配列で指定

[ [duration, [R, G, B]], [duration, [R, G, B]], ... ] のように指定する。

戻り値 なし

実行例

op_W = [20, [255, 255, 255]]
op_R = [20, [255, 0, 0]]
toio.lightSequence(5, [op_W, op_R])
time.sleep(2)
toio.lightOff()  

補足

lightSequence()で、durationを指定しても、指定した時間分待たずに返ってくるため、必要であれば、上記の例のように、呼び出した側で待ち時間を入れる必要がある。

BLEでは、1度に送れるデータ量に制限がある。bluepyでは、デフォルト値が小さいため、2 operation しか送ることができない。(MTUの変更ができるハズだが、うまくいかない)



サウンド関連(WRITE系)

soundID メソッド

toio 技術仕様 サウンド

コアキューブから効果音を再生する。

詳細

項目 詳細
書式 soundID(id)
引数 id: 効果音のID

idと音の対応は、 toio 技術仕様 サウンド を参照

戻り値 なし

実行例

toio.soundID(1)   # selected再生音
time.sleep(0.5)

補足

soundID()は、サウンドの終了を待たずに処理が戻る。


soundMono メソッド

toio 技術仕様 サウンド

note(音階)を指定して、コアキューブのサウンドを再生させる

詳細

項目 詳細
書式 soundMono(duration, note)
引数 duration: サウンドを鳴らす時間を指定

0 時間指定なし(無期限)

1~255 指定された数×0.01秒

note: note id

note idは、toio 技術仕様 サウンド を参照

戻り値 なし

実行例

toio.soundMono(50, 60)   # ド
time.sleep(0.5)
toio.soundMono(50, 62)   # レ
time.sleep(0.5)
toio.soundMono(50, 64)   # ミ
time.sleep(0.5)

補足

soundMono()で、durationを指定しても、指定した時間分待たずに返ってくるため、必要であれば、上記の例のように、呼び出した側で待ち時間を入れる必要がある。


soundSequence メソッド

toio 技術仕様 サウンド

コアキューブのサウンドをパターンを指定して再生させる

詳細

項目 詳細
書式 soundSequence(times, operations)
引数 times:

0: 無限に繰り返す

1~255: 繰り返し回数

operations: サウンドの再生パターンを配列で指定

[ [duration, note], [duration, note], ... ] のように指定する。durationは0.01ms単位

戻り値 なし

実行例

op_do = [20, 60]
op_re = [20, 62]
op_me = [20, 64]
toio.soundSequence(5, [op_do, op_re, op_mi])
time.sleep(3)
toio.soundStop()  

補足

soundSequence()も、再生の終了を待たずに返ってくるため、必要であれば、上記の例のように、呼び出した側で待ち時間を入れる必要がある。

BLEでは、1度に送れるデータ量に制限がある。bluepyでは、デフォルト値が小さいため、4 operation しか送ることができない。(MTUの変更ができるハズだが、うまくいかない)


soundStop メソッド

toio 技術仕様 サウンド

コアキューブの効果音を停止する。

詳細

項目 詳細
書式 soundStop()
引数 なし
戻り値 なし

実行例

toio.soundMono(0, 60)   # ドを無限に鳴らす
time.sleep(2.0)
toio.soundStop()

その他(READ系)

battery メソッド

toio 技術仕様 バッテリー

コアキューブのバッテリー残量を返す

詳細

項目 詳細
書式 battery()
引数 なし
戻り値 バッテリー残量

10%刻みで、0% ~ 100%が返る

実行例

print(toio.battery())

bleVersion メソッド

toio 技術仕様 設定

コアキューブのプロトコルバージョンを返す

詳細

項目 詳細
書式 bleVersion()
引数 なし
戻り値 BLEのプロトコルバージョン

"2.3.0" というようなテキストが返る

実行例

print(toio.bleVersion())


Notifyの活用

Notifyとは?

コアキューブが衝突したときや、コアキューブのボタンを押した時の通知のことを Notify という。コアキューブでは、ほとんどの命令が Notify に対応している。
Notifyが発生したときに実行される関数(callback関数的なもの)を定義することで、Notifyが発生した時の処理を記述することができる。

実行のイメージ

from coreCube import CoreCube
from coreCube import toioDefaultDelegate

class MyDelegate(toioDefaultDelegate):  # toioDefaultDelegateを継承したクラス定義
    def notify_button(self, id, stat):  # button の notifyメソッドをオーバーライド
      self.corecube.soundId(2)

if __name__ == "__main__":
  toio = CoreCube()
  toio.connect("xx:xx:xx:xx:xx:xx")

  # --- 上で定義したクラスを、Delegate用クラスとして設定
  toio.withDelegate(MyDelegate(toio))

  # --- ボタンに対して、Notifyを要求
  toio.setNotify(toio.HANDLE_TOIO_BTN, True)

  # ここでは、Notifyを10秒待つことを、無限ループさせている。
  # Notifyが来たら(ボタンが押されたら)、MyDelegate.notify_button()が実行され、終了する。
  while True:
    if toio.waitForNotifications(10.0):
      # Notify処理が実行された後の処理
      break
    elif
      # Notify処理がなく、10.0秒経った後の処理
      pass

  # --- 切断
  toio.disconnect()

Notifyを受け取るためにやることは3つ。

  1. Delegateクラスを定義し、デフォルトのnotifyメソッドを、自分なりのメソッドにオーバーライドする
    • デフォルトのDelegateクラス(toioDefaultDelegateクラス)を継承したクラスを作成する
    • Notifyごとにコールバックされる notiry_xxxxx() というメソッドが用意されているので、自分なりのメソッドにオーバーライドする。上記の例では、button が押されたときにNotiry を受け取っている。
  2. 上記で定義した自分なりのクラスを、delegateクラスとして指定する
    • 「おまじない」だと考える。  
      toio.withDelegate(MyDelegate(toio))
  3. コアキューブに、Notify を返すように要求する
    • 以下のように、必要なハンドルに対して、Notify を要求する
      toio.setNotify(toio.HANDLE_TOIO_BTN, True)

Delegateクラスの作成方法

オーバーライドできる notify_xxxxx() メソッドとしては、以下のものが用意されている。
これらのメソッドの中で、Notifyを返してきたコアキューブを特定するために、
corecube
というプロパティが用意されているので、こちらを参照することで、対象コアキューブに命令を送りなおすことなどができる
  使用例)  self.corecube.soundId(2)  # Notifyを送ってきたコアキューブで音を出す


notify_positionID(self, x, y, dir):

項目 詳細
x int X座標
y int Y座標
dir int 角度

PositionIDに変化があった場合に呼ばれる


notify_standardID(self, stdid, dir):

項目 詳細
stdid int StandardID
dir int 角度

StandardIDを読み込んだときに呼ばれる


notify_motion(self, id, horizon, tap, dbltap, posture, shake):
詳細情報

項目 詳細
id int 0x01 固定
horizon int 水平検出
tap int 衝突検出
dbltap int ダブルタップ検出
posture int 姿勢検出
shake int シェイク検出

モーションに変更があったときに呼ばれる


notify_sensor_angle(self, id, mode, roll, pitch, yaw):
詳細情報

項目 詳細
id int 0x03 固定
mode int 種類(0x01 オイラー角のみ利用可能)
roll int Roll X軸
pitch int Pitch Y軸
yaw int Yaw Z軸

姿勢角に変更があったときに呼ばれる 事前に sensor_angle() メソッドで姿勢角検出をONにしなければならない


notify_magnetic(self, id, status, power, x, y, z):
詳細情報

項目 詳細
id int 0x02 固定
status int 磁石の状態
power int 磁力の検出(強度)
x int 磁力の検出((磁力の方向 X軸)
y int 磁力の検出((磁力の方向 Y軸)
z int 磁力の検出((磁力の方向 Z軸)

磁気センサーの情報が変化すると呼ばれる 事前に magnetic() メソッドで磁気センサー検出をONにしなければならない


notify_button(self, id, status):

項目 詳細
id int 0x02 固定
status int ボタンの状態
押されたら0x80, 話したら0x00 が返る

ボタンが押されたら呼ばれる


notify_motor_response(self, response):
詳細情報

項目 詳細
self int 正常終了のとき0x00
その他は上記、詳細情報を参照

motorTarget() メソッドにてコアキューブを動かした際、その動作が終了したときに返る


Notify要求について

Notifyは、通常OFFになっているため、HANDLEを指定して、ONにしなければならない。 BLEの通信帯域はそれほど広くないため、必要なものだけをONにすべきである。
また、HANDLEは、connect時に取得されるため、connectの後に指定する。
現時点で指定できるのは以下の4種類

  • HANDLE_TOIO_ID  → Position ID, Standard ID系
  • HANDLE_TOIO_SEN  → 各種センサー系
  • HANDLE_TOIO_BTN  → ボタン
  • HANDLE_TOIO_MTR  → 目標指定モーター応答

全部を指定した例

  toio = CoreCube()
  toio.connect("xx:xx:xx:xx:xx:xx")

  toio.setNotify(toio.HANDLE_TOIO_ID, True)
  toio.setNotify(toio.HANDLE_TOIO_SEN, True)
  toio.setNotify(toio.HANDLE_TOIO_BTN, True)
  toio.setNotify(toio.HANDLE_TOIO_MTR, True)

また、デフォルトでOFFになっているセンサーがあるので、ONにするためのメソッドが用意されている


磁気センサーをONにする

magnetic(mode, interval)

項目 詳細
mode int 0x00 無効化
0x01 磁石の状態検出の有効化
0x02 磁力の検出の有効化
interval int Notifyを返す間隔(10ms単位)

「状態検出」と「磁力の検出」は同時に設定できない。詳細は、こちらを参照のこと


姿勢角検出をONにする

sensor_angle(mode, interval)

項目 詳細
mode int 通知内容の種類(0x01 オイラー角のみ)
interval int Notifyを返す間隔(10ms単位) 0x00 を指定すると無効化する

本来、オイラー角の他、クォータニオンでの通知が可能だが、複雑になるので、オイラー角のみ有効にしている


プログラムの構造について

Notifyを受け取り、処理するためには、waitForNotifications() というメソッドを呼ぶことで、Notify待ち状態のループにする。(← ココ大事) 具体的には、下記サンプルを参照

from coreCube import CoreCube
from coreCube import toioDefaultDelegate

class MyDelegate(toioDefaultDelegate):  # toioDefaultDelegateを継承したクラス定義
    def notify_button(self, id, stat):  # button の notifyメソッドをオーバーライド
      self.corecube.soundId(2)

if __name__ == "__main__":
  toio = CoreCube()
  toio.connect("xx:xx:xx:xx:xx:xx")

  # --- 上で定義したクラスを、Delegate用クラスとして設定
  toio.withDelegate(MyDelegate(toio))

  # --- ボタンに対して、Notifyを要求
  toio.setNotify(toio.HANDLE_TOIO_BTN, True)

  # ここでは、Notifyを10秒待つことを、無限ループさせている。
  # Notifyが来たら(ボタンが押されたら)、MyDelegate.notify_button()が実行され、終了する。
  while True:
    if toio.waitForNotifications(10.0):
      # Notify処理が実行された後の処理
      break
    elif
      # Notify処理がなく、10.0秒経った後の処理
      pass

  # --- 切断
  toio.disconnect()

プログラムの処理としては、waitForNotifications()を呼び、指定時間だけNotify待ちになり、その間にNotifyが来たらdelegateクラスのコールバックメソッドが実行され、waitForNotifications()は Trueを返す。もし、指定時間にNotifyがなければ、Falseが返る
つまり、イベントドリブン的な感じになるということ。