凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

キューブ型ロボットの toio を制御できる toio.js を触ってみた

はじめに

toioソニーから発売されているキューブ型のロボットです。配布されている様々なキットと合わせて遊ぶことで、子供でも実際にものを触りながらゲームを遊んだり動く工作をしたりといったことが簡単に楽しく遊べるおもちゃです。

発売日に私も買って手に入れて遊んでみました。以下はバリューパックに含まれていたトイオ・コレクションのゲームの一つで、土俵から相手を追い出したら勝ちになクラフトファイターというゲームです。コアキューブ(toio のキューブ型ロボット部分)の天面の突起はレゴもはめられるので、重心を考えたりしながら色々な形で戦うとなかなか面白いです。

キットの種類の中にはこれ以外にもジーボの秘密*1というものがあり、「いっぽすすむ」「ひだりをむく」から「くりかえし」や「じょうけん」まで色々あるカードを並べてプログラムを作成し、それを読み込んで問題を解いていくといったゲーム x プログラム学習なものもあります。

キット以外にも、Mac 限定ですがビジュアルプログラミング(Scratch)で toio を制御できる仕組みがこれまで提供されていました。

toio.io

そしてこれに加えて先日、遂に 6/13 に待望の toio コアキューブの技術仕様が公開され、BLE 経由で toio をコントロールできるようになりました。公式からは Node.js によるライブラリである toio.js が提供されています。

toio.io

本記事では、この toio.js について色々と見ていきたいと思います。

注文

各オンラインショップで購入可能なようです。私は amazon で買いました:

環境

本記事は以下の環境のもとで実験しています。

  • macOS High Sierra 10.13.6
  • Node.js v12.4.0
  • toio.js 8c41cb9

インストール

github.com

toio.js というリポジトリで配布されています。パッケージマネージャは npm ではなく yarn を使っているので、入れてない場合はインストールしておきましょう(Mac なら brew install yarn で入ります)。

yarnpkg.com

あとはドキュメントの How to play sample application を参考に導入すれば準備完了です。

サンプルを実行してみる

サンプルを見ていきましょう。各サンプルは yarn から起動できます。例えば id-reader サンプルを起動するときは以下のようにします。

yarn build example:id-reader

package.jsonnode *** なコマンドが定義されているので、直接実行しても動きます。

id-reader

ID リーダーのサンプルは、下部の読み取りセンサーを通じて各種カードに印刷されたパターンを読み込んで得られた情報をコンソール出力するものです。

コアキューブの検索および情報の出力は次のように行っています。

const { NearestScanner } = require('@toio/scanner')

async function main() {
  // 最も近いところにあるキューブを探す
  const cube = await new NearestScanner().start()

  // キューブに接続
  cube.connect()

  // toid の ID 情報に関するリスナを購読する
  cube
    .on('id:position-id', data => console.log('[POS ID]', data))
    .on('id:standard-id', data => console.log('[STD ID]', data))
    .on('id:position-id-missed', () => console.log('[POS ID MISSED]'))
    .on('id:standard-id-missed', () => console.log('[STD ID MISSED]'))
}

main()

NearestScannerstart() して await すると、最も近い位置にあるコアキューブをコントロールできる Cube が返ってくる形です。ON になったデバイスが近くにない場合はここで待受し続けます。Cubeconnect() するとデバイスから「ピロピロリー」と音がして接続状態になります。あとは on() で各種イベントを待ち受けるリスナを用意している形になり、ID のついたカードなどにコアキューブの読み取りセンサーを当てると、その ID と角度が返ってきます。

また、動画には含まれていませんが、プレイマット上に置くと次のように位置も返ってきます。

[POS ID] { x: 148, y: 85, angle: 36, sensorX: 146, sensorY: 75 }
[POS ID] { x: 147, y: 84, angle: 39, sensorX: 146, sensorY: 74 }
[POS ID] { x: 148, y: 84, angle: 36, sensorX: 146, sensorY: 74 }
...

これらの読み取りセンサの仕様については技術仕様のページにまとまっています。

toio.github.io

上記以外のイベントもあり、例えばバッテリや衝突、傾きといったものが取得できます。詳しくは以下のドキュメントをご参照ください。

keyboard-control

キーボードコントロールのサンプルは上下左右キーを押してラジコンのようにコアキューブをコントロールできるものです。

コードを見てみましょう。

const keypress = require('keypress')
const { NearestScanner } = require('@toio/scanner')

const DURATION = 700 // ms
const SPEED = {
  forward: [70, 70],
  backward: [-70, -70],
  left: [30, 70],
  right: [70, 30],
}

async function main() {
  // 最も近いキューブに接続
  const cube = await new NearestScanner().start()
  await cube.connect()

  // キーボード入力に応じて cube.move() を叩く
  keypress(process.stdin)
  process.stdin.on('keypress', (ch, key) => {
    ...
    switch (key.name) {
      case 'up':
        cube.move(...SPEED.forward, DURATION)
        break
      case 'down':
        cube.move(...SPEED.backward, DURATION)
        break
      case 'left':
        cube.move(...SPEED.left, DURATION)
        break
      case 'right':
        cube.move(...SPEED.right, DURATION)
        break
    }
  })

  process.stdin.setRawMode(true)
  process.stdin.resume()
}

main()

先ほどと同じようにキューブに接続したあと、キー入力に応じて Cube.move() を呼び出しています。この API は 3 つの引数を取り、左モーターの速度(-100 ~ 100)、右モーターの速度、および時間(ms)を指定するものです。サンプルでは SPEED で定義しておいた値をスプレッド構文で与えている形です。

速度に関してはソフトウェアと違い、ハードウェアですのでちょっと特性を理解する必要があります。入力スピードと実際のモータの回転速度の特性が以下の技術仕様にまとまっているので目を通しておきましょう:

toio.github.io

キー入力のハンドルは keypress を使っているようです。

www.npmjs.com

chase

チェイスのサンプルは、一方のコアキューブにもう一つのコアキューブが追従するものです。

こちらは長いので分割してみていきましょう。

const { NearScanner } = require('@toio/scanner')

async function main() {
  // 近くにいるキューブを 2 つ探す
  const cubes = await new NearScanner(2).start()

  // 2 つのキューブに接続してトムとジェリーと名付ける
  const jerry = await cubes[0].connect()
  const tom = await cubes[1].connect()

  // ジェリーは紫色に光らせる
  // また位置のイベントが降ってきたらそれを保存しておく
  let jerryX = 0
  let jerryY = 0
  jerry.turnOnLight({ durationMs: 0, red: 255, green: 0, blue: 255 })
  jerry.on('id:position-id', data => {
    jerryX = data.x
    jerryY = data.y
  })

  // トムは黄色に光らせる
  // こちらは位置と自身の角度を保存しておく
  let tomX = 0
  let tomY = 0
  let tomAngle = 0
  tom.turnOnLight({ durationMs: 0, red: 0, green: 255, blue: 255 })
  tom.on('id:position-id', data => {
    tomX = data.x
    tomY = data.y
    tomAngle = data.angle
  })

  // 50 ms 起きにトムを動かしてジェリーに追従させる
  setInterval(() => {
    tom.move(...chase(jerryX, jerryY, tomX, tomY, tomAngle), 100)
  }, 50)
}

main()

まずはメインのアップデートループを作るところです。NearestScanner ではなく、NearScanner を使用すると複数のコアキューブを探すことができます。ここでは 2 を指定して、2 つのキューブを探しています。

また turnOnLight() でコアキューブのランプを制御することができます。以下の技術仕様のページにも書かれているように、ここでは 0 ms を指定しているので、常にその色で点灯し続ける形になります。

toio.github.io

そして 2 つのコアキューブの位置および角度を id:position-id イベントで読み取って更新します。そして setIntervai()50 ms おきに追従処理をかけます。chase() で左右モータの速度の配列が返るようになっているので、次はこの chase() を見てみましょう。

function chase(jerryX, jerryY, tomX, tomY, tomAngle) {
  // 距離を調べる
  const diffX = jerryX - tomX
  const diffY = jerryY - tomY
  const distance = Math.sqrt(diffX * diffX + diffY * diffY)

  // 距離が近くなったら停止
  if (distance < 50) {
    return [0, 0] // stop
  }

  // 相対角度を調べる
  // ここで diffX や diffY の座標系はマット座標系であり、トムの角度もマット座標系の角度
  // なので、atan2 でマット座標系の 2 つのキューブの位置関係の角度からトムの角度を引けば、
  // トムが向かうべき相対角度が求められる
  // 角度は -180 ~ 180 度になるようにする
  let relAngle = (Math.atan2(diffY, diffX) * 180) / Math.PI - tomAngle
  relAngle = relAngle % 360
  if (relAngle < -180) {
    relAngle += 360
  } else if (relAngle > 180) {
    relAngle -= 360
  }

  // 相対角度の絶対値が大きければ大きいほど回転が必要なので、これを行うために
  // -1 ~ 1 の ratio を求める(まっすぐの 0 度なら 1、反対の 180 度なら -1)
  const ratio = 1 - Math.abs(relAngle) / 90
  let speed = 100
  if (relAngle > 0) {
    return [speed, speed * ratio]
  } else {
    return [speed * ratio, speed]
  }
}

詳細はコメントに書きましたが、コアキューブの方を向かせて走っていく処理が書かれています。プレイマット座標系で情報が降ってくるおかげで、簡単な角度の計算だけで追従処理が書けるようになっているのが面白いですね。

サンプル以外の機能

その他にもサンプルに含まれていない機能があります。例えばサウンドplaySound() というコアキューブの API で制御することができます。

サウンドの使用に関しては、技術仕様のページにまとまっています。

toio.github.io

このように、API ドキュメントと技術仕様を併せて見ると、一通りの機能が使えるようになると思います。

おわりに

絶対位置が取れるため、動きのみに注力したプログラムでハードウェアを簡単に動かせるので、プログラムの学習にとても役立ちそうです。また、学習だけでなく動きを伴うゲームや作品にも使えると思いますので、次は Unity とつなげてみようと思います。

ライセンス

コード

/**
 * Copyright (c) 2019-present, Sony Interactive Entertainment Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

ドキュメント

Original document by Sony Interactive Entertainment Inc., is licensed under CC BY 4.0.
https://github.com/toio/toio-spec
https://creativecommons.org/licenses/by/4.0/

*1:とても面白いのでおすすめ