Google Homeに話しかけてドローンを音声操作してみる

# はじめに
![image.jpg](https://qiita-image-store.s3.amazonaws.com/0/207109/92813578-7f1f-782e-531b-f759358be3e8.jpeg)

Telloという12,800円ほどのコスパやばい小型ドローンをGWに買いました！
ドローンと言うと航空法による規制で中々自由に飛ばせないんじゃないの？というイメージがありましたが、実は航空法の対象となるのは200g以上のドローンなのです。
Telloは80gなので航空法の対象外となり、飛ばす場所の持ち主が禁止していなければ外でも飛ばすことが可能です！

そして[公式SDK](https://dl-cdn.ryzerobotics.com/downloads/tello/0228/Tello+SDK+Readme.pdf)によりプログラムで操作可能です。
※Scratchやスマホアプリでビジュアルプログラミングも可能です。

これはもう音声操作するっきゃないですね。
ということでやってみました。



# Telloプログラミング
まずはTello単体のプログラミングを試してみます。
[こちら](https://dl-cdn.ryzerobotics.com/downloads/tello/0228/Tello+SDK+Readme.pdf)(PDF)がTelloのSDK仕様となりますが、Tello自体が192.168.10.1のWi-Fi親機となっており、PCをTelloのWi-Fiにぶら下げることで接続できます。
あとはSDKにあるコマンドをUDPで投げるだけです。

## Node.jsでプログラミング
[Pythonのサンプル](https://github.com/microlinux/tello)が公開されていますが、今回はNode.jsで制御してみましょう。

ちなみに私の開発環境は以下の通りです。

* OS: Windows10
* Node: 8.11.1
* npm: 5.6.0

※OSはMacでもラズパイ(Linux)でもなんでもいいです

### UDPパッケージのインストール
適当にディレクトリ作成し、UDP操作用に[`dgram`](https://nodejs.org/api/dgram.html)というパッケージをインストールします。

```
mkdir tello
cd tello
npm i dgram
```

### Tello制御プログラム
`dgram`を使ってUDPでコマンドを投げます。
離陸や移動といった各動作を行ってる間waitするミリ秒も一緒に記述します。
`index.js`というファイルを作って以下をコピペってみましょう。

```js:index.js
"use strict"

const dgram = require("dgram")
const sock = dgram.createSocket("udp4")

// Telloの固定情報
const tello = {
    ip: "192.168.10.1",
    port: 8889
}

// Telloにコマンド送信する関数
const send = async (buf, ms = 0) => {
    console.log(buf)
    const command = new Buffer(buf)
    sock.send(command, 0, command.length, tello.port, tello.ip)
    await wait(ms)
}
const wait = ms => new Promise(res => setTimeout(res, ms))

// Telloに操作したいコマンドを記述
const main = async () => {
    await send("command", 100)
    await send("takeoff", 4000)
    await send("land")
    process.exit()
}
main()

```

### プログラム実行
それではプログラムを実行してみます。
プログラム実行時はPCをWi-FiでTelloにつないでください。
念の為`ping 192.168.10.1`を打ってTelloと疎通できてることを確認しておきましょう。
問題なく疎通できたら以下のコマンドを実行します。

```
node index.js
```

Telloが離陸して着陸したら成功です。
SDKに従って、`await send("left 50", 2000)`（移動）や`await send("cw 90", 2000)`（旋回）や`await send("flip l", 2000)`（フリップ）やら色々試してみましょう。

こんな感じでプログラムから操作できます。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">子供の昼寝中に <a href="https://twitter.com/hashtag/Tello?src=hash&amp;ref_src=twsrc%5Etfw">#Tello</a> のプログラム作成。<br>Node.jsでUDP投げるだけなので動画ぐらいの基本動作は簡単。<br>ここまで出来ればあとはいつものFirebaseの仕組みでスマートスピーカーからの音声操作もいける！ <a href="https://t.co/VmKPJzJkgF">pic.twitter.com/VmKPJzJkgF</a></p>&mdash; 田中みそ (@miso_develop) <a href="https://twitter.com/miso_develop/status/991615232870006785?ref_src=twsrc%5Etfw">2018年5月2日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



# Google Homeに話しかけて音声操作してみる
続いてTelloをGoogle Homeから操作してみましょう。
流れは以下の図のようになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/207109/8b6e8792-0d31-f700-6c3b-dd9d3e523a0f.png)

Google Homeに話しかけた内容を取得するためにIFTTTを使います。
そしてIFTTTで受け取った情報をローカルのPCに受け渡すために、FirebaseのRealtime Databaseへ書き込みます。
で、Firebaseの状態をローカルPC上のNode.jsで監視し、書き込みがあったらTelloに対し対応するコマンドを送信する、という流れです。

では以下の順序で構築していきます。

1. [Firebase Realtime Databaseの作成](#firebase-realtime-databaseの作成)
1. [IFTTTアプレットの作成](#iftttアプレットの作成)
1. [Node.jsプログラムの作成](#nodejsプログラムの作成)
1. [動かしてみる](#動かしてみる)

## Firebase Realtime Databaseの作成
まずはFirebaseにてRealtime Databaseを作成します。
手順を箇条書きで簡単に書いていきます。

* [Firebaseコンソール](https://console.firebase.google.com/)にて「プロジェクトを追加」
* 「プロジェクト名」を入力し、「国 / 地域」で日本を選択し「プロジェクトを作成」
* 遷移先の画面左のメニューより、「DEVELOP」 > 「Database」と選択
* Realtime Databaseにて「スタートガイド」
* 「テストモードで開始」を選択し「有効にする」
* 以下のようにDatabaseを作成

```
プロジェクトID
 └ googlehome
 　 └ word: ""
```

なおプロジェクトIDのちほどNode.jsでも使うのでメモしておいてください。

## IFTTTアプレットの作成
[IFTTT](https://ifttt.com/)にてTello音声操作用アプレットを作成します。

* IFTTTにログイン
* 画面上部の「My Applets」 > 画面右上の「New Applet」
* 「this」をクリック
* テキストボックスに「ass」と入力し、絞られたサービス結果から「Google Assistant」を選択
    * このとき初めて選択した場合は接続画面になるので「connect」ボタンを押し、接続するアカウントを選択してください
* 「Say a phrase with a text ingredient」を選択
* 以下のよう入力して「Create trigger」

|項目名|入力値|
|---|---|
|What do you want to say?|ドローン $|
|What do you want the Assistant to say in response?|ドローンを操作します|
|Language|Japanese|

![image.png](https://qiita-image-store.s3.amazonaws.com/0/207109/7e926755-e6ac-5250-2d34-dd4989489602.png)

* 「that」をクリック
* テキストボックスに「web」と入力し、絞られたサービス結果から「Webhooks」を選択
    * ここも初めて選択した場合は接続画面になるので「connect」ボタンを押してください
* 「Make a web request」を選択

* 以下のよう入力して「Create action」

|項目名|入力値|備考|
|---|---|---|
|URL|https://xxxxxxxx.firebaseio.com/googlehome/word.json|xxxxxxxxはFirebaseのプロジェクトID|
|Method|PUT||
|Content Type|application/json||
|Body|"drone {{TextField}}"|ダブルクォーテーションで括る！|

![image.png](https://qiita-image-store.s3.amazonaws.com/0/207109/8323f1bb-94cb-3766-e557-54008a6a645c.png)

* 最後に「Finish」ボタン



## Node.jsプログラムの作成
以下のソースを`index.js`にコピペしてください。
その後ソース中の`const firebaseProjectId = "xxxxxxxx"`の`xxxxxxxx`を先ほど作成したFirebaseのプロジェクトIDに書き換えてください。

```js:index.js
"use strict"

// firebase
const firebase = require("firebase")
const firebaseProjectId = "xxxxxxxx"
const firebasePath = "/googlehome/word"
firebase.initializeApp({databaseURL: `https://${firebaseProjectId}.firebaseio.com`})

// udp
const dgram = require("dgram")
const sock = dgram.createSocket("udp4")

// Tello
const tello = {
	address: "192.168.10.1",
	port: 8889
}

// Telloへcommand送信
const sendTello = buf => {
	const command = new Buffer(buf)
	setTimeout(() => sock.send(command, 0, command.length, tello.port, tello.address), 500)
}



// database更新時
const db = firebase.database()
db.ref(firebasePath).on("value", snapshot => {
	// 値取得
	let value = snapshot.val()
	if (!value) return
	console.log(value)
	
	// option word index
	let index = 1
	// 助詞を除外
	value = value.replace(/ ([がのをにへとでや]|より|から)/g, "")
	
	// コマンド定義
	const commandJson = {
		
		// drone
		"drone": () => {
			// 距離情報取得
			const distanceWord = / \d+ (CM|cm|センチ)/
			const distanceMatch = value.match(distanceWord)
			const distance = distanceMatch ? distanceMatch[0].match(/\d+/)[0] : 0
			value = value.replace(distanceWord, "")
			
			// 角度情報取得
			const angleWord = / \d+ (°|度|ドル)/
			const angleMatch = value.match(angleWord)
			const angle = angleMatch ? angleMatch[0].match(/\d+/)[0] : 0
			value = value.replace(angleWord, "")

			// スペースで区切られた単語を修正
			value = value.replace(" 旋 回", "旋回")
			value = value.replace("旋 回", "旋回")
			value = value.replace(" フリップ", "フリップ")
			value = value.replace(" グリップ", "フリップ")
			
			sendTello("command")
			const option = {
				"離陸": "takeoff",
				"着陸": "land",
				"上昇": `up ${distance}`,
				"上": `up ${distance}`,
				"下降": `down ${distance}`,
				"下": `down ${distance}`,
				"右": `right ${distance}`,
				"左": `left ${distance}`,
				"前": `forward ${distance}`,
				"前進": `forward ${distance}`,
				"バック": `back ${distance}`,
				"後ろ": `back ${distance}`,
				"後退": `back ${distance}`,
				"右旋回": `cw ${angle}`,
				"左旋回": `ccw ${angle}`,
				"右フリップ": "flip r",
				"左フリップ": "flip l",
				"前フリップ": "flip f",
				"バックフリップ": "flip b",
			}[value.split(" ")[index]]
			return typeof option === "string" ? () => sendTello(option) : false
		},
		
	}[value.split(" ")[0]]
	
	// コマンド取得
	if (!commandJson) return
	const command = commandJson()
	
	// コマンド実行
	if (!command) return
	command()
	
	// firebase clear
	db.ref(firebasePath).set("")
})

```


## 動かしてみる
以上で一通り準備ができたので動かしてみましょう。
このときPCのネットワークについてひとつ注意があり、インターネット接続用の口と、Tello接続用の口とで2つ必要になります。
Tello側はWi-Fiでの接続となるので、[USB Wi-Fi子機](https://www.amazon.co.jp/TP-Link-WIFI-%E7%84%A1%E7%B7%9ALAN-%E3%83%87%E3%83%A5%E3%82%A2%E3%83%AB%E3%83%A2%E3%83%BC%E3%83%89%E5%AF%BE%E5%BF%9C%E3%83%A2%E3%83%87%E3%83%AB-TL-WN725N/dp/B008IFXQFU/ref=pd_cp_147_1?_encoding=UTF8&psc=1&refRID=2RB07BGCGEXY6ERS6CWF)をTello用に挿すのがてっとり早いです。

そしてpingコマンドなりでインターネット、Tello両方につながっていることを確認したらNode.jsプログラムを実行します。

```
node index.js
```

あとはGoogle Homeに話しかけるだけです。

「OK グーグル、ドローン離陸」
「OK グーグル、ドローン前に50センチ」
「OK グーグル、ドローン90度右旋回」
「OK グーグル、ドローン左フリップ」

こんな感じでドローンを音声操作できます。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">子供の昼寝中にドローンを音声操作化！<br>途中言いしくじったところはフリップじゃなくグリップで認識されてました…<a href="https://twitter.com/hashtag/tello?src=hash&amp;ref_src=twsrc%5Etfw">#tello</a> <a href="https://t.co/TuDzpJhazU">pic.twitter.com/TuDzpJhazU</a></p>&mdash; 田中みそ (@miso_develop) <a href="https://twitter.com/miso_develop/status/991947467875758082?ref_src=twsrc%5Etfw">2018年5月3日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



# おわりに
ドローンを音声操作ってやっぱロマンがありますよね。
ある程度音声操作した後はPCそっ閉じして普通にリモコンで遊んでますが。。。

というのも現状の公式SDKだとドローンの操縦しかできず、映像が取得できないんですよね。
できれば映像取得してあんなことやこんなことしてみたいじゃないですか。
[実は非公式のGoのbot用ライブラリがあるようです。](https://www.drone-engineer.com/articles/766)
言語がGoとなり私はまずGoの勉強からとなるので、こちらもいずれ挑戦してみたいなと思います。