toio.jsをブラウザで動かしてみた


toioとは?

toio(トイオ)はソニーから発売された手のひらサイズのロボットです。

シンプルな見た目ながら工夫次第で色んな遊び方ができるのが特徴で、我が家では4才になる息子がゲズンロイドにどハマりしています。

toio.js

さてそんなtoioをプログラミングできるNode.js向けのライブラリ toio.js が公開されました。

サンプルを触ってみると動作も安定しておりAPIもお手軽で良い感じです。

何を作ろうか思案しているときに、ふとWeb Bluetoothのことを思い出したので、ブラウザ上でtoio.jsを動かしてみることにしました。

ブラウザからtoioを操作するご様子

まずは実際の動作の様子から。こちらの動画をご覧ください。
toioをブラウザから操作する
Node.jsは使っておらず、ブラウザだけでtoioを制御しています。

toioを持っていればChromeから以下のURLにアクセスすることで同じアプリを試すことができます。

Web Bluetoothを利用するためChromeでしか動作しない1のがやや残念ですが、URLを送りつけるだけで手軽に自作のtoioアプリをドヤれるのはなかなか画期的ではないでしょうか。

toio.jsをブラウザで使う方法

このようなアプリを作るために、toio.jsをブラウザから利用するためのポイントを解説していきます。

1. webpackの設定でnobleのWeb Bluetoothバインディングを有効にする

ブラウザでJSといえばwebpackですね。
toio.jsは依存モジュールが少ないこともあって比較的素直にwebpackが通るのですが、1点だけ特別な設定をしてあげる必要があります。それがこちら。

webpack.config.js
module.exports = {
  ...
  resolve: {
    alias: {
      // noble-macをnobleにバイパス
      'noble-mac': 'noble'
    }
  },
  plugins: [
    // ついでにwsも不要なので無効化
    new webpack.IgnorePlugin(/^ws$/)
  ],
  ...
};

toio.jsがBLEスタックとして利用している noble は元々Web Bluetoothバインディングを持っています2が、noble-mac というラッパーがこのバインディングを無効化してしまっています。

ブラウザではnoble-macモジュールは不要なのでwebpackでオリジナルのnobleにバイパスするように設定します。

またnobleがWeb Bluetoothのフォールバックのためwsモジュールを巻き込もうとしますが、Chromeで使うと割り切れば不要なのでそちらも無効化しておきます。

2. ユーザアクションからcubeを初期化する

Web Bluetooth APIはユーザーアクション(クリックやキーボード操作など)をトリガーに実行する必要があります。

toio.jsの場合は以下のようにcubeの初期化部分をclickリスナーなどからたたくようにしてあげればOKです。

main.js
import { NearestScanner } from '@toio/scanner';

document.getElementById('connect').addEventListener('click', async () => {
  const cube = await new NearestScanner().start();
  await cube.connect();
}

初期化部分さえパスしてしまえば、その後はユーザ操作がなくともAPIを叩けるようになります。

3. Web Bluetoothの一部APIを排他制御する

ここまでの手順で PC Chrome 上ではtoio.jsが動作するようになるのですが、Android Chromeでは初期化時( cube.connect() の実行時)にエラーが出てしまいます。

AndroidのBLEには readValueとstartNotificationを複数同時に実行できない という制約があり、どうやらこれに引っかかってしまうようです。

この制約はWeb Bluetooth APIに以下のようなモンキーパッチをあてることで回避することが出来ます。自前のアプリにそのままコピペするなどしてお試しください。

main.js
if (/android/i.test(navigator.userAgent)) {
  const mutexify = (promiseFn, mutex = Promise.resolve()) => {
    return function(...args) {
      const job = () => promiseFn.apply(this, args);
      return (mutex = mutex.then(job, job));
    };
  };

  // readValue と startNotification を排他制御
  const proto = BluetoothRemoteGATTCharacteristic.prototype;
  const mutex = Promise.resolve();
  proto.startNotifications = mutexify(proto.startNotifications, mutex);
  proto.readValue = mutexify(proto.readValue, mutex);
}

さいごに

以上の3点で、PC/AndroidのChromeからtoio.jsを利用することが出来るようになると思います。サンプルのソースも置いておきますので不明な点があれば参考にしてみてください。

この記事がなにかtoioアプリ開発のきっかけになれば嬉しいです。

それではみなさま良いtoio.jsライフをお過ごしください!


  1. このあたりを見るとOperaでも動作するのかもしれないが未確認 

  2. 何故かnobleのサイト見ててもほとんど言及がなくソース見て初めて気が付くレベル