GoでトイドローンDJI Telloを制御する #golangjp


こんにちは、のびすけです。[Go合宿2018](https://go-beginners.connpass.com/event/86555/)にて記事書いてます。

[![](https://i.gyazo.com/2dc039ec559d586d3f0868a666fe65fc.jpg)](https://www.instagram.com/p/Bk9TRYVhFpF/)

ということで、今日はGoでドローン制御してみます。

>ちなみに環境はmacos high sierra

## DJI Tello

DJIとインテルが共同で作ったすごいドローン。最近話題。

> 参考:[最新の超軽量ドローン「Tello」でオフィスの日常を撮影してみた](https://dotstud.io/blog/toy-drone-tello-using/)

## 制御方法

他のトイドローンはBLEなどが主流でしたが、Telloの場合Wi-Fiのアドホック通信でUDPパケットを送る仕様になっています。なので割とどんな言語でもライブラリなど無しで叩けます。

> 参考:[NefryBT(ESP32)でTelloを制御。1回だけ離陸して着陸する](https://qiita.com/n0bisuke/items/0dc2039da20a5f12f2d5)

ただしPCのWi-FiがTelloの接続に取られてしまうのでWi-Fi経由でインターネットにはアクセス出来なくなります。

**TelloをPCで制御しつつインターネットに繋ぎたい場合はLANケーブル、Bluetoothテザリング、USBテザリングなどを使いましょう。**

## GoBotがTello対応

GoBotはGoでハードウェア制御する系フレームワークです。ChatBot的なものではないです。

対象デバイスはスマートトイ系が多いイメージ。

https://gobot.io/documentation/platforms/tello/

![](https://i.gyazo.com/b2bf515dfe8dab920446e3b1c85f9dcf.png)

これで準備完了です。

```
go get -d -u gobot.io/x/gobot/..
```

### GoBotでTelloを飛ばす

以下のコードで`離陸して5秒後に着陸`します。

```go
  package main

  import (
      "time"

      "gobot.io/x/gobot"
      "gobot.io/x/gobot/platforms/dji/tello"
  )

  func main() {
      drone := tello.NewDriver("8888")

      work := func() {
          drone.TakeOff()

          gobot.After(5*time.Second, func() {
              drone.Land()
          })
      }

      robot := gobot.NewRobot("tello",
          []gobot.Connection{},
          []gobot.Device{drone},
          work,
      )

      robot.Start()
  }
```

UDPパケットを内部で送ってますがTelloのSDKに仕様が書いています。

> 参考: https://dl-cdn.ryzerobotics.com/downloads/tello/0228/Tello+SDK+Readme.pdf

### GoBotでTelloのカメラにアクセス

TelloのSDKにはカメラアクセスについては記載が無いですが、GoBotのドライバには何故かあります。他の言語でもTello制御はできますが、これだけでGo使う意義ありそう。

> 参考:[Hello, Tello - Hacking Drones With Go ](https://gobot.io/blog/2018/04/20/hello-tello-hacking-drones-with-go/)

サンプルコードがmplayerを内部で起動してパケットを流し込んでるのでmplayerをインストールします。

```
$ brew install mplayer
```

```go
package main

import (
	"fmt"
	"os/exec"
	"time"

	"gobot.io/x/gobot"
	"gobot.io/x/gobot/platforms/dji/tello"
)

func main() {
	drone := tello.NewDriver("8890")

	work := func() {
		mplayer := exec.Command("mplayer", "-fps", "25", "-")
		mplayerIn, _ := mplayer.StdinPipe()
		if err := mplayer.Start(); err != nil {
			fmt.Println(err)
			return
		}

		drone.On(tello.ConnectedEvent, func(data interface{}) {
			fmt.Println("Connected")
			drone.StartVideo()
			drone.SetVideoEncoderRate(4)
			gobot.Every(100*time.Millisecond, func() {
				drone.StartVideo()
			})
		})

		drone.On(tello.VideoFrameEvent, func(data interface{}) {
			pkt := data.([]byte)
			if _, err := mplayerIn.Write(pkt); err != nil {
				fmt.Println(err)
			}
		})
	}

	robot := gobot.NewRobot("tello",
		[]gobot.Connection{},
		[]gobot.Device{drone},
		work,
	)

	robot.Start()
}
```

実行するとこんな感じ。ノイズが入りますがまぁ分かる。

[![](https://i.gyazo.com/27d74a0942d088a9259b107a19631f5c.jpg)](https://www.instagram.com/p/Bk8JSTAhS9C/?taken-by=n0bisuke)

### キー入力でドローンを制御しながら映像取得

ジョイスティックなどがあればよかったですが今回は持ち合わせがないのでキーボードで。
GoBotのドライバーでキー入力を取得できるのでこちらを併用します。

> 参考: [トイドローンTelloでGo言語プログラミング！フロントカメラのビデオ映像も取得成功！](https://www.drone-engineer.com/articles/766)
> 参考: https://godoc.org/gobot.io/x/gobot/platforms/dji/tello
> 参考: https://godoc.org/gobot.io/x/gobot/platforms/keyboard

```go
package main

import (
	"fmt"
	"os/exec"
	"time"

	"gobot.io/x/gobot"
	"gobot.io/x/gobot/platforms/keyboard"
	"gobot.io/x/gobot/platforms/dji/tello"
)

func main() {
	drone := tello.NewDriver("8890")
	keys := keyboard.NewDriver()

	work := func() {
		/**
		ドローンカメラアクセス
		*/
		mplayer := exec.Command("mplayer", "-fps", "25", "-")
		mplayerIn, _ := mplayer.StdinPipe()
		if err := mplayer.Start(); err != nil {
			fmt.Println(err)
			return
		}

		drone.On(tello.ConnectedEvent, func(data interface{}) {
			fmt.Println("Connected")
			drone.StartVideo()
			drone.SetVideoEncoderRate(4)
			gobot.Every(100*time.Millisecond, func() {
				drone.StartVideo()
			})
		})

		drone.On(tello.VideoFrameEvent, func(data interface{}) {
			pkt := data.([]byte)
			if _, err := mplayerIn.Write(pkt); err != nil {
				fmt.Println(err)
			}
		})

		/**
		ドローン制御
		*/
		keys.On(keyboard.Key, func(data interface{}) {
			key := data.(keyboard.KeyEvent)

			if key.Key == keyboard.C {
				fmt.Println("Command Test")
			} else if key.Key == keyboard.T{
				fmt.Println("Take Off!")
				drone.TakeOff() //離陸
			} else if key.Key == keyboard.L{
				fmt.Println("Land")
				drone.TakeLand() //着陸
			} else if key.Char == keyboard.ArrowUp{
				fmt.Println("↑")
				drone.Forward(10) //前進
			} else if key.Char == keyboard.ArrowDown{
				fmt.Println("↓")
				drone.Backward(10) //後退
			} else if key.Char == keyboard.ArrowRight{
				fmt.Println("→")
				drone.Right(10) //右へ
			} else if key.Char == keyboard.ArrowLeft{
				fmt.Println("←")
				drone.Left(10) //左へ
			} else if key.Key == keyboard.U{
				fmt.Println("Up")
				drone.Up(10) //上昇
			} else if key.Key == keyboard.D{
				fmt.Println("Down")
				drone.Down(10) //下降
			} else if key.Key == keyboard.F{
				fmt.Println("Front Flip")
				drone.FrontFlip() //フリップ
			} else if key.Key == keyboard.B{
				fmt.Println("Back Flip")
				drone.BackFlip() //バックフリップ
			} else {
				fmt.Println("keyboard event!", key, key.Char)
			}
		})
	}

	robot := gobot.NewRobot("tello",
		[]gobot.Connection{},
		[]gobot.Device{drone, keys}, //droneとkeysの二つのアダプタ
		work,
	)

	robot.Start()
}
```

## 所感

Tello楽しい。映像ストリーミングはWebRTCにつなげて配信とかしてみたいですね。
Wi-Fiのポートが占有されてしまうのが少し扱いが難しいですね。

WebRTCはJS実装が多いので、Node.jsでも無いかなぁと思ったら[SovGVD/nodetello](https://github.com/SovGVD/nodetello)があったのでこちらも試してみたい。

毎回合宿でドローン触ってる気がする。