11
14

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 3 years have passed since last update.

Raspberry Pi で toio コアキューブをコントロールする(1回目)

Last updated at Posted at 2019-06-15

(※) タイトル変更, リンク追加(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 を調べておく。

find_toio.py
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 を操作してみた。

sample_coreCube1.py
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 とか、どうしよう・・・)

おもしろいことに使えたらいいなぁ-。

続き

参考サイト

11
14
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
11
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?