(※) タイトル変更, リンク追加(2019/06/30)
前回 Raspberry Pi で toio コアキューブをコントロールする(1回目)の続き。
今回は、toio コアキューブから Notify
を受け取って処理してみる。
toio 通信概要から、
- ID Information / 読み取りセンサー
- Sensor Information / モーションセンサー
- Button Information / ボタン
の3つのNotify
を受け取ってみる。(最後に2台のキューブ同時制御のおまけあり)
前準備
必要な環境は、前回のまま。
CoreCube クラス
コアキューブとのもろもろの通信部分を、簡単にクラス化した。エラー処理とか、全然していないので、参考程度ということで。ここに置くと大きいので、githubに置いたので、ダウンロードして、サンプルコードと同じフォルダに入れておくこと。
サンプルコード
以下のような動作をするソースコード
- コアキューブのボタンを押すと sound id = 2 が再生される。
- コアキューブをたたくと、sound id = 6 が再生される。
- マット、カードによって、X,Y座標、角度や、カード番号などが表示される。
- 何も Notifyが返ってこない時間が 10秒になると、処理終了
from coreCube import CoreCube
import time
import bluepy
import sys
import struct
class MyDelegate(bluepy.btle.DefaultDelegate):
def __init__(self, params, ptoio): # コンストラクタで対応するtoioを指定する
bluepy.btle.DefaultDelegate.__init__(self)
self.ctoio = ptoio
# notify callback: cHandle で何のNotifyかを見分けて処理分岐
def handleNotification(self, cHandle, data):
# ------------- ボタン
if cHandle == CoreCube.HANDLE_TOIO_BTN:
id, stat = struct.unpack('BB', data[0:2])
if stat == 0x80:
self.ctoio.soundId(2)
# ------------- モーションセンサー
if cHandle == CoreCube.HANDLE_TOIO_SEN:
id, horizon, collision = struct.unpack('BBB', data[0:3])
print("SENSOR: HORIZON={:02x}, COLLISION={:02x}".format(horizon, collision))
if collision:
self.ctoio.soundId(6)
# ------------- IDセンサー
if cHandle == CoreCube.HANDLE_TOIO_ID:
id = struct.unpack('b', data[0:1])[0]
if id == 0x01:
x, y, dir = struct.unpack('hhh', data[1:7])
print("X,Y,dir = (%d,%d), %d" % (x,y,dir))
elif id == 0x02:
stdid = struct.unpack('i', data[1:5])[0]
dir = struct.unpack('h', data[5:7])[0]
print("ID = %d, dir = %d" % (stdid,dir))
if __name__ == "__main__":
# --- コアキューブへの接続(自動接続を行うには、rootで実行する必要あり)
toio = CoreCube()
if len(sys.argv) == 1:
toio_addr = toio.cubeFinder()
print("Connect to " + toio_addr)
else:
toio_addr = sys.argv[1]
toio.connect(toio_addr, bluepy.btle.ADDR_TYPE_RANDOM)
time.sleep(1)
# --- Notifyを受け取るクラスの設定
toio.withDelegate(MyDelegate(bluepy.btle.DefaultDelegate, toio))
# --- Notifyを要求
toio.writeCharacteristic(CoreCube.HANDLE_TOIO_ID + 1, b'\x01\x00', True)
toio.writeCharacteristic(CoreCube.HANDLE_TOIO_SEN + 1, b'\x01\x00', True)
toio.writeCharacteristic(CoreCube.HANDLE_TOIO_BTN + 1, b'\x01\x00', True)
# --- Notify待ち関数を実行させる。 10秒Notifyがなければ終了
while True:
if toio.waitForNotifications(10.0):
pass
else:
break
toio.disconnect()
考え方としては、bluepy
の Peripheralクラス
(CoreCubeクラスの親クラス)に、withDelegateメソッド
で、Notify
が返ってきたときの実行されるクラスを定義してあげればよい。
# --- Notifyを受け取るクラスの設定
toio.withDelegate(MyDelegate(bluepy.btle.DefaultDelegate, toio))
次に、コアキューブにNotifyを要求する必要がある。以下のように、Notifyが必要な Handleに +1 した値に対して、01(b'\x01\x00') を書き込むと Nofity を要求することができる(Notify を無効にする場合は、00 を書き込む)。
BLEの用語的には、CCCDにNotify通知の許可フラグを設定する、ということらしい。ほとんどの特性において、Handle に +1 したものが、CCCDになるようなので、深く考えず設定している。
# --- Notifyを要求
toio.writeCharacteristic(CoreCube.HANDLE_TOIO_ID + 1, b'\x01\x00', True)
toio.writeCharacteristic(CoreCube.HANDLE_TOIO_SEN + 1, b'\x01\x00', True)
toio.writeCharacteristic(CoreCube.HANDLE_TOIO_BTN + 1, b'\x01\x00', True)
Notifyを処理するクラス(ここでは、MyDelegateクラス)には、2つのメソッドが定義されていて、一つはコンストラクタ。もう一つは Notify が返ってきたときに実行されるメソッド(handleNotification)。
Notify が返ってきたときに、このメソッドに、Notify 通知元のHandle(cHandle)と、実際のデータ(data)が渡される。このHandleで何のNotifyかを見分けて処理分岐を行えばよい。
def handleNotification(self, cHandle, data):
# ------------- ボタン
if cHandle == CoreCube.HANDLE_TOIO_BTN:
#(ボタンが押されたときの処理)
# ------------- モーションセンサー
if cHandle == CoreCube.HANDLE_TOIO_SEN:
#(モーションサンサーの情報が変化したとき)
# ------------- IDセンサー
if cHandle == CoreCube.HANDLE_TOIO_ID:
#(読み取りセンサーの読み取る情報が変化したとき)
(せっかく、CoreCobeクラスとして、BLE通信処理を隠したのに、MyDelegate クラスにBLE通信処理が出てきてしまっているのが、気持ち悪い・・・。改善の余地あり。)
サンプルの実行
前回、いちいちコアキューブのアドレスを指定するのが面倒だったので、scanして、自動で接続するようなメソッド(cubeFinder)を追加した。ただし、scan は root で実行する必要があるので、要注意。上記のサンプルでは、2種類の実行方法があるので、お好きな方で。
$ python3 sample.py **:**:**:**:**:** # コアキューブのアドレスを指定
または
$ sudo python3 sample.py # 一番近くのコアキューブに自動接続
実際に実行し、コアキューブをマットやカードの上に置くと、びっくりするぐらい(1秒間に何十個)XY座標や角度の情報が返ってくるのが分かる。技術仕様には、センサーの情報が変化するとNotifyが返るとなっているが、そんなに変わるものなのか?と思いよく見ると、微妙に角度や座標が変化しているようだ。それだけ、細かい情報を読んでいるということか。
$ sudo python3 sample_notify.py
Connect to **:**:**:**:**:**
SENSOR: HORIZON=00, COLLISION=00
SENSOR: HORIZON=01, COLLISION=00
SENSOR: HORIZON=00, COLLISION=01
SENSOR: HORIZON=00, COLLISION=01
SENSOR: HORIZON=01, COLLISION=00
SENSOR: HORIZON=01, COLLISION=00
SENSOR: HORIZON=01, COLLISION=01
X,Y,dir = (323,340), 270
X,Y,dir = (323,341), 270
SENSOR: HORIZON=01, COLLISION=01
X,Y,dir = (323,15957), 256
X,Y,dir = (323,341), 268
X,Y,dir = (323,341), 270
X,Y,dir = (323,342), 271
X,Y,dir = (323,341), 270
X,Y,dir = (323,342), 271
X,Y,dir = (323,341), 270
X,Y,dir = (323,341), 270
X,Y,dir = (323,342), 271
X,Y,dir = (323,341), 270
X,Y,dir = (323,342), 271
X,Y,dir = (323,342), 271
X,Y,dir = (323,341), 270
X,Y,dir = (323,342), 271
X,Y,dir = (323,342), 271
X,Y,dir = (323,341), 270
X,Y,dir = (323,342), 271
X,Y,dir = (323,341), 270
X,Y,dir = (323,342), 271
X,Y,dir = (323,342), 271
X,Y,dir = (323,341), 270
X,Y,dir = (323,342), 271
X,Y,dir = (323,341), 270
X,Y,dir = (323,342), 271
:
おまけ(2つのキューブの同時制御)
せっかく、今回 CoreCube クラスを定義したし、toio に2つのキューブが入っているので、2台を同時に接続して、輪唱させてみた。
「同時制御」なんて仰々しいように書いているが、単に、インスタンスを二つ作るだけ。
from coreCube import CoreCube
import time
import bluepy
toio1_addr = '**:**:**:**:**:**' # 実際には、コアキューブのアドレス
toio2_addr = '**:**:**:**:**:**'
toio1 = CoreCube()
toio1.connect(toio1_addr, bluepy.btle.ADDR_TYPE_RANDOM)
toio2 = CoreCube()
toio2.connect(toio2_addr, bluepy.btle.ADDR_TYPE_RANDOM)
MELODY_LENGTH = 0.5
MELODY_FLOG = [60, 62, 64, 65, 64, 62, 60, 128, 64, 65, 67, 69, 67, 65, 64, 128, 60, 128, 60, 128,
60, 128, 60, 128, 60, 62, 64, 65, 64, 62, 60, 128, 128, 128, 128, 128, 128, 128, 128, 128]
time.sleep(1)
for i in range(len(MELODY_FLOG)):
toio1.soundMono(0xFF, MELODY_FLOG[i])
if i >=8:
toio2.soundMono(0xFF, MELODY_FLOG[i-8])
time.sleep(MELODY_LENGTH)
toio1.soundStop()
toio1.disconnect()
toio2.soundStop()
toio2.disconnect()
「カエルの歌」を2台のコアキューブで輪唱させてみた。
せっかくだから、2台ではなく、4台ぐらいでやってみたいけど、コアキューブがない・・・。
今後
技術仕様で公開されているコマンドでは、単に車輪の速さを指定することができるだけ。しかし、toio ビジュアルプログラミングで提供されている SCRATCH のコマンドには、「(X座標, Y座標) = (xx,yy)に動かす」とか、「xx度に向ける」というものがある。ソースコードが公開されているので、ちょっと中を覗いて、CoreCubeクラスに追加できるか考えてみたい。
続き
Raspberry Pi で toio コアキューブをコントロールする(3回目)~ ID(位置情報)の処理