1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Raspberry Pi で toio コアキューブをコントロールする(2回目)~ Notifyの処理+おまけ

Last updated at Posted at 2019-06-22

(※) タイトル変更, リンク追加(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秒になると、処理終了
sample_notify.py
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()

考え方としては、bluepyPeripheralクラス(CoreCubeクラスの親クラス)に、withDelegateメソッドで、Notifyが返ってきたときの実行されるクラスを定義してあげればよい。

.py
  # --- 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になるようなので、深く考えず設定している。

.py
  # --- 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かを見分けて処理分岐を行えばよい。

.py
    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台を同時に接続して、輪唱させてみた。
「同時制御」なんて仰々しいように書いているが、単に、インスタンスを二つ作るだけ。

sample_flog.py
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台のコアキューブで輪唱させてみた。:grin:
せっかくだから、2台ではなく、4台ぐらいでやってみたいけど、コアキューブがない・・・。

今後

技術仕様で公開されているコマンドでは、単に車輪の速さを指定することができるだけ。しかし、toio ビジュアルプログラミングで提供されている SCRATCH のコマンドには、「(X座標, Y座標) = (xx,yy)に動かす」とか、「xx度に向ける」というものがある。ソースコードが公開されているので、ちょっと中を覗いて、CoreCubeクラスに追加できるか考えてみたい。

続き

Raspberry Pi で toio コアキューブをコントロールする(3回目)~ ID(位置情報)の処理

参考サイト

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?