obnizを使ってスマホの傾きでtoioを操作

この記事は [obniz Advent Calendar 2020](https://qiita.com/advent-calendar/2020/obniz) 13日目の記事です。

# やったこと

obnizを使って、スマホの傾きでtoioをラジコンのように操作できるようにしてみました。
研究室にtoioがたくさん転がっていたので、toioで遊んでみたかったのが動機です。

<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://twitter.com/TOS?ref_src=twsrc%5Etfw">@tos</a> <a href="https://t.co/2KD5uxLgA6">pic.twitter.com/2KD5uxLgA6</a></p>&mdash; さくあ@思いつきぶん投げ(?) (@sakua_create) <a href="https://twitter.com/sakua_create/status/1337767376926822401?ref_src=twsrc%5Etfw">December 12, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# toioについて

>toioはロボットを使った新しいあそびのプラットフォームです。ひとりでもみんなでも、専用タイトルをかえることでゲームや工作、絵本、運転などいろいろあそびを通して楽しむことができます。いよいよ本格化するプログラミング教育に向けて、気軽に楽しく自宅学習にもお使いいただけます。
[(HPより抜粋)](https://toio.io/)

まだ触ったばかりで理解は浅いのですが、いくつかのセンサや小さいタイヤのついた、白いキューブを使ったプラットフォームのようです。
予めコンテンツが用意されているのでそのままでも遊べますが、ブロックプログラミングやコーディングによって自分だけの拡張も楽しめます。

# 材料
* obniz
* toio
* スマートフォン
* モバイルバッテリー

# プログラム

全体の流れは、
1. toioを探してBLEで接続する
2. 接続できたら常にスマホの回転姿勢を取得する
3. 回転姿勢によって左右のタイヤの回転方向・スピードを調整
となっています。

### 1. toioを探してBLEで接続

obnizのパーツライブラリの [toio コア キューブ](https://obniz.com/ja/sdk/parts/toio_corecube/README.md) のページを参考に、toio コア キューブとの接続をしています。
`if(Toio_CoreCube.isDevice(peripheral)){...}`のところで受信したBLEのアドバタイズがtoioなのかを判定してインスタンスを作り、`.connectWait()`で接続しています。

```javascript

await obniz.ble.initWait();
const Toio_CoreCube = Obniz.getPartsClass("toio_CoreCube");

obniz.ble.scan.onfind = async (peripheral) => {
  //toioの探索
  if(Toio_CoreCube.isDevice(peripheral)){
    console.log("find");
    const toio = new Toio_CoreCube(peripheral);

    toio.ondisconnect = (reason) => {
      console.log(reason);
    }
    //toioとの接続
    await toio.connectWait();
    console.log("connected");
    ...
  }
  ...
}
```

### 2. スマートフォンの回転姿勢を取得

toioとの接続が完了したら、以下の部分でスマートフォンの回転姿勢を取得しています。
今回は`beta`と`gamma`の値しか使っていませんが、以下の3つの値を取得できます。

* `alpha`: z軸を基準にしたデバイスの角度(画面を水平にしたときの水平回転)
* `beta`: x軸を基準にしたデバイスの角度(画面を水平にしたときの前後の傾き)
* `gamma`: y軸を基準にしたデバイスの角度(画面を水平にしたときの左右の傾き)


```javascript

window.addEventListener("deviceorientation", async (e) => {
  //X軸を中心にしたデバイスの角度(画面を水平にしたときの前後の傾き) -180~180
  let beta = e.beta;
  //y軸を中心にしたデバイスの角度(画面を水平にしたときの左右の傾き) -90~90
  let gamma = e.gamma;
  ...
  }
});
```

### 3. 左右のタイヤの回転方向・スピードを調整

前節で`beta`で前後の傾き、`gamma`で左右の傾きを取得しました。これらの値に応じて左右のタイヤの回転方向やスピードを調整して、toioの動く方向を制御します。

まずは、`beta`で前後の傾きを取得し、奥へ傾けたときは前進、手前に傾けたときは後退するように条件分けしました。また、水平からある閾値`HORIZONTAL_THRESHOLD`(今回は±10度)までは水平とみなし、toioを静止させるようにしました。

```javascript
if(beta > HORIZONTAL_THRESHOLD){
  //後ろ(手前)に進むように調整
　...
}else if(beta < -HORIZONTAL_THRESHOLD){
  //前(奥)に進むように調整
  ...
}else{
  //静止
  ...
}
```

次に、`gamma`の左右の傾きの値も使って、それぞれの条件の中で左右のタイヤのスピードを計算しています。
計算結果が正の値だとタイヤが前方回転、負の値だと後方回転します。左右どちらかの回転スピードを緩めると、緩めた方に曲がって進みます。

回転姿勢を使ったタイヤの回転の計算方法がわからなかったので、自分でこんな感じの式を立ててみました。
`(最大スピード) * (縦の傾き具合0.0〜1.0) ± (横の傾き角度/20)`
縦の傾き具合によって、最大スピードの何割で進むのかを決めています。
また、横の傾きを調整した値を足し引きすることによって、曲がる方のタイヤのスピードを緩めています。

```javascript
if(beta > HORIZONTAL_THRESHOLD){
  //後ろ(手前)に進むとき
  speedLeft = MAX_SPEED * beta / MAX_TILT_BETA - gamma / 20;
  speedRight= MAX_SPEED * beta / MAX_TILT_BETA + gamma / 20;
}else if(beta < -HORIZONTAL_THRESHOLD){
  //前(奥)に進むとき
  speedLeft = MAX_SPEED * beta / MAX_TILT_BETA + gamma / 20;
  speedRight= MAX_SPEED * beta / MAX_TILT_BETA - gamma / 20;
}else{
  //静止
  speedLeft = 0;
  speedRight = 0;
}
```

最後に、100msおきに、計算した左右のスピードに合わせてtoioを動かしています。

```javascript
obniz.repeat(async () => {
  await toio.moveAroundWait(speedLeft,speedRight);
}, 100);
```

### 完成したコード

```javascript
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    />
    <!--- <link rel="stylesheet" href="/css/starter-sample.css" /> -->
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script
      src="https://unpkg.com/obniz@3.10.1/obniz.js"
      crossorigin="anonymous"
    ></script>
  </head>
  <body>
    <div id="obniz-debug"></div>

    <div id="ui-buttons" class="text-center">
      <h3 class="text-center">スマホの傾きでtoioを操作</h3>
      <button class="btn btn-primary" id="stop">ストップ(補助用)</button>
    </div>

    <script>
      let obniz = new Obniz("OBNIZ_ID_HERE");
      const HORIZONTAL_THRESHOLD = 10;
      const MAX_SPEED = 255;
      const MAX_TILT_BETA = 180;

      let speedLeft = 0;
      let speedRight = 0;

      obniz.onconnect = async() =>  {
        await obniz.ble.initWait();
        const Toio_CoreCube = Obniz.getPartsClass("toio_CoreCube");

        obniz.ble.scan.onfind = async (peripheral) => {
          //toioの探索
          if(Toio_CoreCube.isDevice(peripheral)){
            console.log("find");
            const toio = new Toio_CoreCube(peripheral);

            toio.ondisconnect = (reason) => {
              console.log(reason);
            }
            //toioとの接続
            await toio.connectWait();
            console.log("connected");

            window.addEventListener("deviceorientation", async (e) => {
              //X軸を中心にしたデバイスの角度(画面を水平にしたときの縦の傾き) -180~180
              let beta = e.beta;
              //y軸を中心にしたデバイスの角度(画面を水平にしたときの横の傾き) -90~90
              let gamma = e.gamma;

              if(beta > HORIZONTAL_THRESHOLD){
                //後ろ(手前)に進むとき
                speedLeft = MAX_SPEED * beta / MAX_TILT_BETA - gamma / 20;
                speedRight= MAX_SPEED * beta / MAX_TILT_BETA + gamma / 20;
              }else if(beta < -HORIZONTAL_THRESHOLD){
                //前(奥)に進むとき
                speedLeft = MAX_SPEED * beta / MAX_TILT_BETA + gamma / 20;
                speedRight= MAX_SPEED * beta / MAX_TILT_BETA - gamma / 20;
              }else{
                //静止
                speedLeft = 0;
                speedRight = 0;
              }
            });

            //緊急ストップ用
            $('#stop').on('click', async () => {
              console.log("stop");
              await toio.moveAroundWait(0,0);
            });

            obniz.repeat(async () => {
              await toio.moveAroundWait(speedLeft,speedRight);
            }, 100);

          }
        };
        await obniz.ble.scan.startWait();
      };


    </script>
  </body>
</html>
```

# さいごに

お疲れであろう同期に、このラジコンtoioで栄養ドリンクを届けてみました...
(危ないので皆さんは真似しないでくださいね。)

<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://twitter.com/TOS?ref_src=twsrc%5Etfw">@tos</a> <a href="https://t.co/erfqWARIrb">pic.twitter.com/erfqWARIrb</a></p>&mdash; さくあ@思いつきぶん投げ(?) (@sakua_create) <a href="https://twitter.com/sakua_create/status/1337767482489069568?ref_src=twsrc%5Etfw">December 12, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>