ミニドローン Parrot MAMBO を Leap Motion で操縦する（Node.js & Windows 編）

# 目的

以前（2017年4月25日）、[ミニドローン Parrot MAMBO を Node.js で飛ばす（Windows 編）](https://qiita.com/mkoku/items/7458239f5bcca88eed49)という記事を投稿し、その時は PC のキーボードでミニドローンを操縦するサンプルを紹介しました。

ふと思い立ち、Leap Motion （手のジェスチャ）で操縦できるようにしました。

既にある[先人の記事]
(https://qiita.com/jyuko/items/cd39d93146fd392c8be3)と、やっていることは変わりません。違いは、Windows マシン 1台で完結していることくらいです。

以下、[私自身の過去の記事](https://qiita.com/mkoku/items/7458239f5bcca88eed49)と重複する部分も多いですが、この記事だけ見れば済むように、省略せずに全体を書きました。


# 機材・環境

## ハードウェア
* Windows 10 の PC
* [Parrot MAMBO](https://www.parrot.com/us/minidrones/parrot-Mambo)（[Amazon](http://amzn.asia/bkL2VLa)）
* [iBUFFALO Bluetooth アダプタ BSBT4D09BK](http://buffalo.jp/product/peripheral/wireless-adapter/bsbt4d09bk/)（[Amazon](http://amzn.asia/1k1zmmB)）
    * このアダプタ（BSBT4D09BK）は「CSR8510 A10」というチップが使われており、下記 node-rolling-spider が依存している [node-bluetooth-hci-socket](https://github.com/sandeepmistry/node-bluetooth-hci-socket) モジュールに適合しています
* [Leap Motion](https://www.leapmotion.com/)

## ソフトウェア
* [Node.js](https://nodejs.org/ja/) v6.11.4
* [windows-build-tools](https://github.com/felixrieseberg/windows-build-tools) モジュール
* [node-rolling-spider](https://github.com/voodootikigod/node-rolling-spider) モジュール
* [Zadig](http://zadig.akeo.ie/)  v2.3
* [Leap Motion のドライバ・SDK](https://developer.leapmotion.com/get-started)
    * この記事では Orion 3.2.0 ドライバでの例を紹介しますが、[V2 SDK](https://developer.leapmotion.com/sdk/v2) でも以下の方法で問題なく動作することを確認しています
* [leapjs](https://github.com/leapmotion/leapjs) モジュール


## 環境構築

### Node.js のインストール

* 通常どおりインストーラでインストール
    * 私は C:\nodejs にインストールしました

### windows-build-tools のインストール

* 今回の用途に限らず、各種の Node モジュールを Windows ネイティブでコンパイルするための環境一式で、以下を一括でインストール・各種設定できます（[こちらをご参考](https://github.com/felixrieseberg/windows-build-tools)）。
    * Visual C++ Build Tools 2015
    * Python 2.7.13

* コマンドプロンプト（cmd.exe）を __管理者として実行__ して、以下を実行

```
npm install --global windows-build-tools
```

* インストールが終わるまで気長に待ちます

### rolling-spider モジュールのインストール

* Parrot のミニドローンと Bluetooth でやりとりできるモジュールです
* これからアプリケーションを作るフォルダを適当に作成
    * __例　C:\Users\\(ユーザ名)\leapdrone__
* コマンドプロンプトでそのフォルダに移動
* 以下のコマンドを実行

```
npm install rolling-spider
```

* 黄色い文字で「WARN」がいくつか出てくると思いますが、たいてい大丈夫です
* 赤い文字で「ERROR」の場合はネイティブコンパイル等に失敗しているので、windows-build-tools をインストールし直したり、エラーメッセージをもとに試行錯誤してください


### Bluetooth アダプタの設定
* Bluetooth アダプタをパソコンの USB ポートにさします（自動的に Bluetooth デバイスとして認識されますが…）
* 上記 node-rolling-spider が依存している [node-bluetooth-hci-socket](https://github.com/sandeepmistry/node-bluetooth-hci-socket) というモジュールを通じて使う場合は、「WinUSB」というドライバに書き換える必要があります（[こちらに記載](https://github.com/sandeepmistry/node-bluetooth-hci-socket#windows)）。
* Zadig というドライバ書き換えツールを[こちらからダウンロード](http://zadig.akeo.ie/)します
* zadig-2.3.exe をダブルクリックで実行します
* Options メニュー -> List All Devices を選択するとデバイスの一覧が出ます

![zadig.PNG](https://qiita-image-store.s3.amazonaws.com/0/157570/e34787e9-3283-98ec-7fa4-7b6fc21f557a.png)

* デバイスの一覧から Bluetooth アダプタ（今回の場合は「CSR8510 A10」）を選ぶと、左側に現在のドライバが出ます
* これを「WinUSB」に書き換えるため、「Replace Driver」をクリックします

![zadig2.PNG](https://qiita-image-store.s3.amazonaws.com/0/157570/4f599bc6-0743-0e2a-6eb7-e639e19b1295.png)

* 「The driver was installed successfully.」と出れば成功です。

![zadig3.PNG](https://qiita-image-store.s3.amazonaws.com/0/157570/0415c982-1f7c-48b7-3963-f5db56843000.png)

* するとこれまで「デバイスマネージャー」で「Bluetooth」として認識されていたアダプタが、「ユニバーサル シリアル バス デバイス」として認識されるようになります。こうなれば OK です。


### Leap Motion のドライバ・SDK のインストール

* [ここ](https://developer.leapmotion.com/get-started)からダウンロードした「LeapDeveloperKit_3.2.0+45899_win.zip」を解凍
* ドライバをインストーラ（Leap_Motion_Orion_Setup_win_3.2.0.exe）でインストール
* Leap Motion を PC に接続し、「Leap Motion コントロールパネル」の「診断ビジュアライザー」などを使って動作を確認
    * なお、この「Leap Motion コントロールパネル」はタスクバーに常駐させておかなくても Leap Motion でのデータは取得できます



### leapjs モジュールのインストール

* Leap Motion 純正の JavaScript モジュールです
* コマンドプロンプトで、これからアプリケーションを作るフォルダに移動
    * __例　C:\Users\\(ユーザ名)\leapdrone__
* 以下のコマンドを実行

```
npm install leapjs
```

* 黄色い文字で「WARN」がいくつか出てくると思いますが、たいてい大丈夫です
* 赤い文字で「ERROR」の場合はネイティブコンパイル等に失敗しているので、windows-build-tools をインストールし直したり、エラーメッセージをもとに試行錯誤してください





# 実装

## コーディング

以下のコードをテキストエディタで入力し、さきほど rolling-spider と leapjs をインストールしたフォルダ（例　C:\\(ユーザ名)\leapdrone）内に「app.js」として保存します。

```js:app.js
"use strict";

// モジュールの読み込み
const Drone = require("rolling-spider");          // rolling-spider モジュールを使う 
const Leap  = require("leapjs");                  // leapjs モジュールを使う

// 各インスタンスを作る
const drone = new Drone();                        // Drone のインスタンス
const leap  = new Leap.Controller();              // Leap の Controller のインスタンス

// 各種変数
drone.isActive = false;                           // ドローンがアクティブか否か
const STEPS = 2;                                  // 各種操作のステップ数

// ドローンの初期設定
drone.connect( () => {                            // BLE でドローンに接続し、接続できたらコールバック
  drone.setup( () => {                            // ドローンを初期設定してサービスや特徴を取得、その後コールバック
    drone.flatTrim();                             // トリムをリセット
    drone.startPing();                            // 継続的に接続させる
    drone.flatTrim();                             // トリムをリセット
    drone.isActive = true;                        // ドローンをアクティブ状態にする
    console.log(drone.name + "is ready.");        // 準備OKなことをコンソール出力
    console.log("battery: " + drone.status.battery + "%");  // バッテリ残量
  });
});

// LeapMotion の Frame ループ
leap.loop( (frame) => {                           // Frame データが取得される度に呼ばれるコールバック
  if (!drone.isActive) { return; }                // ドローンがアクティブれなければ抜ける
  if (frame.hands.length > 0) {                   // 手が1個以上あれば
    const hand = frame.hands[0];                  // 手の取得
    // 離陸
    if (!drone.status.flying) {                   // ドローンが離陸していなければ
      drone.takeoff();                            // 離陸させる
      rest();                                     // 一時的に入力を受け付けない（以下同様）
    }
    // 上昇下降
    if (hand.sphereRadius > 100) {                // 手のひらで作る球の半径が 100 mm より大きい時
      drone.up( {steps: STEPS} );                 // 上昇させる
      rest();
    } else if (hand.sphereRadius < 50) {          // 半径が 50 mm より小さい時
      drone.down( {steps: STEPS} );               // 下降させる
      rest();
    }
    // 左右旋回
    if (rad2deg(hand.yaw()) > 30) {               // 手の yaw が 30 deg より大きい時
      drone.clockwise( {steps: STEPS} );          // 右（時計回り）に旋回させる
      rest();
    } else if (rad2deg(hand.yaw()) < -30) {       // -30 deg より小さい時
      drone.counterClockwise( {steps: STEPS} );   // 左（反時計回り）に旋回させる
      rest();
    }
    // 左右移動（ロール）
    if (rad2deg(hand.roll()) > 30) {              // 手の roll が 30 deg より大きい時
      drone.left( {steps: STEPS} );               // 左に移動させる
      rest();
    } else if (rad2deg(hand.roll()) < -30) {      // -30 deg より小さい時
      drone.right( {steps: STEPS} );              // 右に移動させる
      rest();
    }
    // 前後移動（ピッチ）
    if (rad2deg(hand.pitch()) < -20) {            // 手の pitch が -20 deg より小さい時
      drone.forward( {steps: STEPS} );            // 前進させる
      rest();
    } else if (rad2deg(hand.pitch()) > 20) {      // 20 deg より大きい時
      drone.backward( {steps: STEPS} );           // 後退させる
      rest();
    }
    // 宙返り（後方）
    if (hand.pinchStrength === 1) {               // 手で pinch （親指と人差指を付ける）していたら
      drone.backFlip( {steps: STEPS} );           // 後方宙返りさせる
      rest();
    }
  } else {                                        // 手が1個も無ければ
    // 着陸
    drone.land();                                 // 着陸させる
  }
});

// 一時的に入力を間受け付けないようにする関数
function rest() {
  drone.isActive = false;
  setTimeout( () => {
    drone.isActive = true;
  }, 50);
}

// ラジアンを角度に変換する関数
function rad2deg(rad) {
  return rad * 180 / Math.PI;
}
```

## 動作確認

* ミニドローンにバッテリーを装着し、電源を ON にします
    * Parrot MAMBO の目が緑色に点滅します（ペアリングモード？）
* コマンドプロンプトで、上記の app.js があるディレクトリ（例　__C:\\(ユーザ名)\drone__）に移動し、以下を実行します

```
node app.js
```

コンソールに以下のように表示され、MAMBO の目が緑色の「点灯」になったら準備OKです（xxxxxxは個体の番号）。

```
Mambo_xxxxxx is ready.
```

### 操縦方法

Leap Motion を机上などに（カメラを上向きに）置いて、そこに手をかざしつつ（手のひらは下向き）、以下のようなジェスチャで操縦します。

|手の動作|MAMBO の動作|メソッド|
|:--:|:--|:--|
|Leap Motion に手をかざす|離陸（飛行中でなければ）|takeOff()|
|Leap Motion から手をどかす|着陸|land()|
|手のひらを大きく開く|上昇|up()|
|手のひらを脱力する|その場ホバリング|-|
|手のひらを軽く握る|下降|down()|
|手首を右に水平に曲げる|右旋回|clockwise()|
|手首を左に水平に曲げる|左旋回|counterClockwise()|
|手のひらを左に傾ける|左に移動|left()|
|手のひらを右に傾ける|右に移動|right()|
|手首を下に曲げる|前進|forward()|
|手首を上に曲げる|後退|backward()|
|ピンチ（親指と人差指を付ける）|後方宙返り|backFlip()|
|キーボードで Ctrl + c|プログラム終了|-|


# まとめ

rolling-spider と leapjs のサンプルを合体させただけですので、「だからどうした」な作例で恐縮ですが、どなたかのお役に立てば幸いです。



# 付録

## Bluetooth アダプタのドライバの戻し方

* デバイスマネージャーで WinUSB ドライバで書き換えていた Bluetooth アダプタを右クリックし、「ドライバーの更新」を選択

![zadig4.PNG](https://qiita-image-store.s3.amazonaws.com/0/157570/a22b1799-0c6e-0b5e-1005-9374d440da46.png)


* 「コンピューターを参照してドライバーソフトウェアを検索」を選択
* 「コンピューター上の利用可能なドライバーの一覧から選択します」を選択
* 「Generic Bluetooth Radio」（当初に認識されていたデバイス名）を選択し、「次へ」でドライバをインストール
* デバイスマネージャーで当該のアダプタが「Bluetooth」として認識されていれば OK です
* 念のため zadig-2.3.exe を実行し、「List All Device」して、当該アダプタを選択して、もとのドライバになっていることを確認します