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

# toioとは？
[toio(トイオ)](https://toio.io/)はソニーから発売された手のひらサイズのロボットです。
![toio](https://toio.io/images/body_cover.jpg)
シンプルな見た目ながら工夫次第で色んな遊び方ができるのが特徴で、我が家では4才になる息子が[ゲズンロイド](https://toio.io/titles/gesundroid.html)にどハマりしています。

# toio.js

さてそんなtoioをプログラミングできるNode.js向けのライブラリ [toio.js](https://github.com/toio/toio.js) が公開されました。

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

何を作ろうか思案しているときに、ふと[Web Bluetooth](https://webbluetoothcg.github.io/web-bluetooth/)のことを思い出したので、ブラウザ上でtoio.jsを動かしてみることにしました。

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

まずは実際の動作の様子から。こちらの動画をご覧ください。
[![toioをブラウザから操作する](http://img.youtube.com/vi/MqSiwYrK-bw/0.jpg)](http://www.youtube.com/watch?v=MqSiwYrK-bw)
Node.jsは使っておらず、ブラウザだけでtoioを制御しています。

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

https://snoguchi.github.io/toio-browser-example/dist/

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

[^2]: [このあたり](https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#Browser_compatibility)を見るとOperaでも動作するのかもしれないが未確認

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

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

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

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

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

toio.jsがBLEスタックとして利用している [noble](https://github.com/noble/noble)  は元々Web Bluetoothバインディングを持っています[^3]が、noble-mac というラッパーがこのバインディングを無効化してしまっています。

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

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

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

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

Web Bluetooth APIは[ユーザーアクション（クリックやキーボード操作など）をトリガーに実行](https://qiita.com/umi_kappa/items/da60e3c8cec19624df91#web-bluetooth%E3%81%8C%E5%8B%95%E3%81%8F%E6%9D%A1%E4%BB%B6)する必要があります。 

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

```javascript: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を複数同時に実行できない](https://qiita.com/umi_kappa/items/da60e3c8cec19624df91#readvalue%E3%81%A8startnotification%E3%82%92%E8%A4%87%E6%95%B0%E5%AE%9F%E8%A1%8C%E3%81%97%E3%81%9F%E3%81%84%E3%81%A8%E3%81%8D%E3%81%AB%E5%90%8C%E6%99%82%E3%81%AB%E5%AE%9F%E8%A1%8C%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84) という制約があり、どうやらこれに引っかかってしまうようです。

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

```javascript: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を利用することが出来るようになると思います。サンプルのソースも置いておきますので不明な点があれば参考にしてみてください。

https://github.com/snoguchi/toio-browser-example

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

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