各メソッドの説明
ここで紹介している coreCube
クラスは、SIE製 toio のコアキューブを Bluetooth のコマンドを使って操作するものです。エラー処理などは、省いているのでご了承ください。基本的には、Pythonと、bluepy という Pythonモジュールが動作すれば動くと思います。
- Raspberry Pi 3 (rasbian)
- Raspberry Pi 4 (ubuntu)
- Intel Linux (Debian buster)
詳細についてはこちらを参照してください。
★必要なパッケージのインストール
$ 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
toio CoreCube のfirmware は不定期にアップデートされ、機能追加と安定性向上が図られています。 https://toio.io/update/
いろいろな機能がありすぎて、このクラスはすべてに対応しているわけではありません。なるべく有用な機能に対応していこうと思います。
また、CoreCube のバージョンは常に最新にしてください。
2021/06/06 現在、BLEプロトコルバージョン 2.3.0 に対応しています
本家の情報は、以下になります。
このクラスで提供する機能は、大きく4つのグループに分けられます
- 接続関連 コアキューブと接続する
- READ系 センサーなどの情報を読み取る
- WRITE系 モーター、ライト、サウンドに対して命令を送る
- NOTIFY系 センサーなどからの通知を受ける
コアキューブから情報を受け取る手段として、READ系とNOTIFY系があります。
READ系は、順序立てて命令を実行させるやり方に適しています。
NOTIFY系は、コールバック的に、コアキューブからの通知をもとに動作させるようなやり方に適しています。
接続して、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で返ってくるまで、ループし続けるという基本形です
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(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 で行うこと)
電源の入っているコアキューブを探して、見つかったコアキューブのアドレスを配列で返す。 コアキューブが存在しなければ、空の配列をかえす。
配列は、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])
コアキューブが置かれているマットの
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 については、後述。
項目 | 型 | 詳細 |
---|---|---|
x | int | id()メソッドで読み込んだ X座標 |
y | int | id()メソッドで読み込んだ Y座標 |
dir | int | id()メソッドで読み込んだ 角度 |
stdid | int | id()メソッドで読み込んだ Standard ID |
コアキューブのモーションセンサーの傾き情報
項目 | 詳細 |
---|---|
書式 | sensor() |
引数 | なし |
戻り値 | なし |
id = toio.sensor()
if toio.horizon == 0:
print("傾いています!!")
衝突検知の値なども取り出せるが、readメソッドでは、ほぼ「衝突していない」の値しか返ってこない(現在の状態をとってくるので、衝突した瞬間の値を読むのは至難の業・・・)。
衝突検知については、Notifyのところで後述する。
項目 | 型 | 詳細 |
---|---|---|
horizon | int | 水平検出値 00: 水平ではないとき 01: 水平な時 |
posture | int | 姿勢検出値 キューブの姿勢によって 1~6 の値をとる(ex. 1なら天面が上) 詳細 |
左右のモーターの速度を指定して、キューブを動かす
項目 | 詳細 |
---|---|
書式 | 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(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()
を参照のこと。
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を指定しても、指定した時間分待たずに返ってくるため、必要であれば、上記の例のように、呼び出した側で待ち時間を入れる必要がある。
コアキューブの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()
コアキューブの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の変更ができるハズだが、うまくいかない)
コアキューブから効果音を再生する。
項目 | 詳細 |
---|---|
書式 | soundID(id) |
引数 | id: 効果音のID idと音の対応は、 toio 技術仕様 サウンド を参照 |
戻り値 | なし |
toio.soundID(1) # selected再生音
time.sleep(0.5)
soundID()は、サウンドの終了を待たずに処理が戻る。
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(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.soundMono(0, 60) # ドを無限に鳴らす
time.sleep(2.0)
toio.soundStop()
コアキューブのバッテリー残量を返す
項目 | 詳細 |
---|---|
書式 | battery() |
引数 | なし |
戻り値 | バッテリー残量 10%刻みで、0% ~ 100%が返る |
print(toio.battery())
コアキューブのプロトコルバージョンを返す
項目 | 詳細 |
---|---|
書式 | bleVersion() |
引数 | なし |
戻り値 | BLEのプロトコルバージョン "2.3.0" というようなテキストが返る |
print(toio.bleVersion())
コアキューブが衝突したときや、コアキューブのボタンを押した時の通知のことを 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つ。
Delegateクラスを定義
し、デフォルトのnotifyメソッドを、自分なりのメソッドにオーバーライドする- デフォルトのDelegateクラス(toioDefaultDelegateクラス)を継承したクラスを作成する
- Notifyごとにコールバックされる notiry_xxxxx() というメソッドが用意されているので、自分なりのメソッドにオーバーライドする。上記の例では、button が押されたときにNotiry を受け取っている。
- 上記で定義した自分なりのクラスを、delegateクラスとして指定する
- 「おまじない」だと考える。
toio.withDelegate(MyDelegate(toio))
- 「おまじない」だと考える。
- コアキューブに、
Notify を返すように要求
する- 以下のように、必要なハンドルに対して、Notify を要求する
toio.setNotify(toio.HANDLE_TOIO_BTN, True)
- 以下のように、必要なハンドルに対して、Notify を要求する
オーバーライドできる 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は、通常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が返る
つまり、イベントドリブン的な感じになるということ。