Go
golang
drone
Gobot
tello

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

こんにちは、のびすけです。Go合宿2018にて記事書いてます。

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

ちなみに環境はmacos high sierra

DJI Tello

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

参考:最新の超軽量ドローン「Tello」でオフィスの日常を撮影してみた

制御方法

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

参考:NefryBT(ESP32)でTelloを制御。1回だけ離陸して着陸する

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

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

GoBotがTello対応

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

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

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

これで準備完了です。

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

GoBotでTelloを飛ばす

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

  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

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

$ brew install mplayer
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()
}

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

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

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

参考: トイドローンTelloでGo言語プログラミング!フロントカメラのビデオ映像も取得成功!
参考: https://godoc.org/gobot.io/x/gobot/platforms/dji/tello
参考: https://godoc.org/gobot.io/x/gobot/platforms/keyboard

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があったのでこちらも試してみたい。

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