4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オルトプラスAdvent Calendar 2023

Day 8

Go + ドローンで最高の世界へ飛び立つ その1 導入編

Last updated at Posted at 2023-12-07

本記事は オルトプラス Advent Calendar 2023 の12/08の記事です。

はじめに

こんにちは!オルトプラスの中村です!
普段はサーバサイドがメインですが、フロントエンドやインフラ周りなども触りつつ、業務外では興味あるものがあったら突撃する雑食系です。
今回は趣味で始めたドローンで面白そうなことができそうなので、まずは導入編として書いてみます。
教育現場でもプログラミングの入り口としてドローンを使用することもあるそうです。
なので本記事ではそのような初学者あたりの方からIT関係ないワクワクしたいおじさんまで幅広く楽しんでいただけたらと思っています。

概要

Mac PCでDJI TelloのドローンをGoフレームワークGobotで操作していきます。
より応用した操作や映像周りに関しては次回の記事のGo + ドローンで最高の世界へ飛び立つ その2 操作&映像編で触れようと思います。
それぞれの環境等は、詳細の冒頭あたりで説明していきます。

詳細

環境

PC

  • OS: Mac (Ventura 13.3)
  • エディタ: VSCode (Visual Studio Code)

化石なPCじゃなければ基本なんでも良いと思います。MacでもWindowsでも。

Go

  • バージョン: go1.21.4 darwin/arm64

    できれば1.16以降のバージョンを推奨しておきます。
    パッケージ管理が1.10以前ではGOPATHモードのみだったのが、1.11からはモジュール対応モードが加わり、1.16から現在はこちらが主流となっています。
    本記事も現バージョン前提で記載しています。

    Homebrewでインストールする場合

    terminal
    $ brew install go
    

    公式のインストーラーからでもokです。
    インストールが終えたら正常に完了したかバージョンを確認してみてください。

    terminal
    $ go version
    go version go1.21.4 darwin/arm64
    

  • フレームワーク: Gobot
    Gobotは2023年現在、35のプラットフォームをサポートしたIoTフレームワークです。
    今回のドローン(DJI Tello)もプラットフォーム一覧の中にありますね!
    別のドローンもあったり、他にも様々なロボティクスなモノに対応していることが確認できます。
    急速に発展してきているAIとのシナジーも高い分野なので今後も注目されるフレームワークかなと思います。

ドローン

  • 製品: DJI Tello

  • スペック: 公式
    購入したのはTelloの無印です。
    他にTello Eduというより教育向けのシリーズもあるのですが、現在は購入できないようです。
    image.png

    DJIから販売されているドローンですが開発したのはRyze Tech社(Ryze Technology)になります。

    Ryze Technologyは、2017年に中国の深センで設立されたテック系スタートアップカンパニーです。私たちのゴールは、ドローンフライトをより楽しくワクワクするものにすることです。遊び心こそが学びの最大の基盤だと、私たちは考えています。ドローンについて学ぶこと、それは、とても楽しくて面白いことなのです。私たちの初めてのドローン「Tello」は、まさにそういったコンセプトから生まれました。DJI製のフライトコントロール システムとIntelプロセッサーを搭載したTelloは、業界をリードする技術とエンターテイメント性を詰め込んだ、かんたん操作ドローンです。


  • 規制
    ここを気にする方も多いかと思います。
    今回のTelloは重量が約80gのトイドローンです。
    2023年時点では以下のようになっています。

    「トイドローン」とは、一般的に「本体とバッテリーの重さの合計が100g未満の小型ドローン」のことです。
    100g以上のドローンは、「航空法」によって飛ばせる場所や方法が規制されていますが、100g未満のトイドローンには航空法が適用されません。

    100g未満だと小型無人機、100g以上では無人航空機という扱いだそうです。
    トイドローン(小型無人機)では以下が不要になります。

    • 特定飛行の許可申請
    • 機体登録
    • リモートIDの搭載

    リモートIDってなんなん?みたいな説明は省きますが、要はめんどくさいことはあまり考えず気軽に遊びやすいドローンって感じです。
    ただしトイドローンでも飛行禁止エリアはあるので屋外で飛ばす際は気をつけてください。

    参考: 【2023年版】ドローン規制の全体像|全5大規制をわかりやすく解説


  • 開封
    (記事を書く際に開封しようと思っていたのでやっと開封できる…!)
    サイズ比較
    IMG_2443.jpg
    箱の大きさは皆んな大好きリーダブルコードより少し大きい程度です。箱は薄め。
    同じタイミングでDJI MINI 4 PROも買ったのでサイズ感確認していきます。

    IMG_2445.jpg
    Telloの本体ちっちゃい!お寿司くらいです🍣
    MINIは折り畳んだ状態ですが比較すると結構デカいですね。
    Telloを充電しながらコードの方を準備していきます。

操作していく

準備

  • ディレクトリの作成
    この辺りは自由ですが、今回は自分の作業用ディレクトリにドローン用のディレクトリを作成するような想定でいこうと思います。
    作成したドローンディレクトリに移動しておきます。
    terminal
    [~/my_app]
    $ mkdir drone_tello_app && cd drone_tello_app
    
    [~/my_app/drone_tello_app]
    $ 
    
    このタイミングくらいでVSCodeで開こうと思います。
    image.png

  • Goモジュールの初期化
    go mod init {project_name}を実行します。

    terminal
    [~/my_app/drone_tello_app]
    $ go mod init drone_tello_app
    

    実行後、サイドバーのツリーにgo.modが作成されたらokです。
    image.png

    GoではGo Modulesによってパッケージを管理します。
    Node.jsならnpm、Pythonならpipで管理しているアレです。
    Go Modulesを利用する際にはgo modコマンドを使用します。
    作成されたgo.modファイルにはモジュール名や他のモジュールとの依存関係が定義されています。
    外部パッケージを利用するとgo.sumファイルも作成されます。(後で作成します)


  • TELLO APPのインストール (スキップ可)
    App Storeにてアプリをインストールします。
      image.png

    アプリを起動し、規約をチェックしたり権限周りを許可していくと接続方法の画面が表示されました。
    image.png

    方法に沿って本体の電源を入れた後にTelloに接続します。
    接続後、ガイドが表示され、ガイドを閉じるとドローンのカメラ映像とActivationのダイアログが表示されたのでそのままActivateしました。
    image.png
    image.png
    カメラ映像の画質は荒めですが、オモチャとして考えれば十分すぎる気がします。
    設定の詳細にファームウェアのアップデートが出来たのでついでにしておきました。
    もうなんかアプリからのボタン操作でドローンが動きそうな気配しかしないですが、初めての離陸はGoに譲りたいため我慢します…!
    アプリの方は終了しておきます。これで準備は一通り完了したのかなと思います。

Gobotによる操作

いよいよ飛ばしていきます!
GobotのプラットフォームにあるDJI Telloページを確認しつつ試していきたいと思います。

上記ページにてHow to Installとしてgo get -d -u gobot.io/x/gobot/...を実行してねと書いてありますが、今回の記事では実行せず後でgo mod tidyを実行したいと思います。
go getgo installコマンドについては触れません(たぶん)

  • サンプルコードの実施
    実行用のファイルとしてmain.goを作成します。
    image.png

    Telloのプラットフォームページにある「How to Use」のサンプルコードを参考にmain.goのファイルに貼ります。
    fmtは今のところ不要なので削除。(インデントが多い…)

    main.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()
      }
    

    main.goにコードを貼ったら保存してgo mod tidyを実行してください。
    するとgo.sumファイルが作成され、go.modファイルも内容が更新されたかと思います。
    image.png

    go mod tidyはソースコードでimportして使用しているものがgo.modgo.sumに依存関係が定義されていないものがあれば追加し、逆にgo.modgo.sumに定義しているがimportで使用していないものがあれば削除します。
    なので必要最低限の依存関係で整理整頓してくれるありがたいコマンドです。

    では、main.goを実行していきたいと思います。
    実行前に以下をチェックしてください。

    • telloの電源が入っていること
    • telloのWi-Fiに接続できていること
    • telloを床など安定した所に置いていること

    もろもろokなら以下を実行します。

    terminal
    [~/my_app/drone_tello_app]
    $ go run main.go
    

    とんだァ!!!
    離陸と着陸のみですが地点がそこそこズレたので、なるべく周囲に何も無いところで実行したほうが良いかもです。
    main.goを実行してもドローンが反応しない場合は、TELLO APPのせいかもしれませんのでTELLOのアプリを終了してみてください。


  • 処理内容の確認
    あっさりしたコードに見えますが何をしていたのでしょうか。
    main関数で何をしているか少しだけみていきます。
    drone := tello.NewDriver("8888")

    driver.go
      func NewDriver(port string) *Driver {
      	d := &Driver{name: gobot.DefaultName("Tello"),
      		reqAddr:   "192.168.10.1:8889",
      		respPort:  port,
      		videoPort: "11111",
      		Eventer:   gobot.NewEventer(),
      		doneCh:    make(chan struct{}, 1),
      	}
      
      	d.AddEvent(ConnectedEvent)
      	d.AddEvent(FlightDataEvent)
      	d.AddEvent(TakeoffEvent)
      	d.AddEvent(LandingEvent)
      	d.AddEvent(PalmLandingEvent)
      	d.AddEvent(BounceEvent)
      	d.AddEvent(FlipEvent)
      	d.AddEvent(TimeEvent)
      	d.AddEvent(LogEvent)
      	d.AddEvent(WifiDataEvent)
      	d.AddEvent(LightStrengthEvent)
      	d.AddEvent(SetExposureEvent)
      	d.AddEvent(VideoFrameEvent)
      	d.AddEvent(SetVideoEncoderRateEvent)
      
      	return d
      }
    

    droneの変数は短縮記法:=によってtelloパッケージのNewDriver関数を呼び、Driverポインタ型の値が代入されるとともに型も定めています。型推論的なやつです。
    引数に入れていたのはレスポンスのポートだったんですね。

    :=はセイウチ演算子とも呼ばれています。横向きですが目とキバに見えるからです。
    ちょっと気持ち悪い記法だと思えていたのがちょっと可愛く見えてきます。

    次が実質メインの処理で、ドローンに何をさせるかが書いてあります。
    ざっくり、離陸して着陸するって感じでしょうか。

    main.go
    work := func() {
            drone.TakeOff()
    
            gobot.After(5*time.Second, func() {
                    drone.Land()
            })
    }
    

    離陸させるTakeOffのメソッドはどんな感じなのか確認します。

    driver.go
      func (d *Driver) TakeOff() (err error) {
      	buf, _ := d.createPacket(takeoffCommand, 0x68, 0)
      	d.seq++
      	binary.Write(buf, binary.LittleEndian, d.seq)
      	binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes()))
      
      	_, err = d.cmdConn.Write(buf.Bytes())
      	return
      }
    

    このメソッドは(d *Driver)のポインタレシーバを持っています。
    なので*Driverのインスタンスに対して呼び出すことができます。
    今回だと最初に定義したdroneのインスタンスに対して呼べるのでdrone.TakeOff()となっていたんですね。
    とりあえずdroneのインスタンスにメソッドチェーンでドローンを動かすメソッドを書けば動く!ってのが分かりました。

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

    ここではdroneworkを元にtelloのインスタンスであるrobotを定義し、そのインスタンスでStartメソッドを呼び出しています。上記でやっていたTakeOffと同じような感じですね。


  • キャリブレーションしてみる
    離陸と着陸地点に差異があったので調整できるか調べたところ、以下とのことなのでやってみます。

    機体のフライトが安定しない(ふらつく、離陸時まっすぐ上昇しない、機体が流れる、離陸できない等)場合には、Telloアプリを利用しIMUキャリブレーション作業を行う事で改善可能な場合があります。

    公式の動画一覧の「Calibrating the IMU」にて動画でも方法が確認できます。
    尚、キャリブレーション作業はTELLO APPからのみ可能です。

    IMG_2473.jpg
    ただプロペラ外しただけなのにイジってるぜ感高まります。

    image.png
    アプリでガイドしてもらいながら簡単にキャリブレーションとやらが出来ました。

    ベストを追求してキャリブレしまくってたら一時飛ばなくなって焦りました。
    結局ブレます!よほど狂った飛行しない限りはやらなくてokかもです。


  • ドローンの他のアクションを試す
    今は離陸と着陸しかないので他のアクションも試していきます。
    他にどんなアクションがあるかは、driver.goファイルのメソッドを直接確認していくか、
    telloのパッケージのドキュメントを確認すれば良いかと思います。

    私はとりあえずこのフリップアクションあたりが面白そうなのでFrontFlipBackFlipを試してみます…!

    driver.go
      // Flip tells drone to flip
      func (d *Driver) Flip(direction FlipType) (err error) {
      	buf, _ := d.createPacket(flipCommand, 0x70, 1)
      	d.seq++
      	binary.Write(buf, binary.LittleEndian, d.seq)
      	binary.Write(buf, binary.LittleEndian, byte(direction))
      	binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes()))
      
      	_, err = d.cmdConn.Write(buf.Bytes())
      	return
      }
      
      // FrontFlip tells the drone to perform a front flip.
      func (d *Driver) FrontFlip() (err error) {
      	return d.Flip(FlipFront)
      }
      
      // BackFlip tells the drone to perform a back flip.
      func (d *Driver) BackFlip() (err error) {
      	return d.Flip(FlipBack)
      }
      
      // RightFlip tells the drone to perform a flip to the right.
      func (d *Driver) RightFlip() (err error) {
      	return d.Flip(FlipRight)
      }
      
      // LeftFlip tells the drone to perform a flip to the left.
      func (d *Driver) LeftFlip() (err error) {
      	return d.Flip(FlipLeft)
      }
    

    関数型work変数の無名関数内を修正します。

    main.go
      work := func() {
      	drone.TakeOff()
    
      	gobot.After(5*time.Second, func() {
      		drone.FrontFlip()
      	})
    
      	gobot.After(10*time.Second, func() {
      		drone.BackFlip()
      	})
    
      	gobot.After(15*time.Second, func() {
      		drone.Land()
      	})
      }
    

    離陸から5秒後にフロントフリップ、10秒後にバックフリップ、15秒後に着陸としてみました。
    荒ぶりそうですがどうなるでしょうか。。

    また、あわせて様々なフライトデータが定義してあったので取得してみます。

    driver.go
      // FlightData packet returned by the Tello
      type FlightData struct {
      	BatteryLow               bool
      	BatteryLower             bool
      	BatteryPercentage        int8
      	BatteryState             bool
      	CameraState              int8
      	DownVisualState          bool
      	DroneBatteryLeft         int16
      	DroneFlyTimeLeft         int16
      	DroneHover               bool
      	EmOpen                   bool
      	Flying                   bool
      	OnGround                 bool
      	EastSpeed                int16
      	ElectricalMachineryState int16
      	FactoryMode              bool
      	FlyMode                  int8
      	FlyTime                  int16
      	FrontIn                  bool
      	FrontLSC                 bool
      	FrontOut                 bool
      	GravityState             bool
      	VerticalSpeed            int16
      	Height                   int16
      	ImuCalibrationState      int8
      	ImuState                 bool
      	LightStrength            int8
      	NorthSpeed               int16
      	OutageRecording          bool
      	PowerState               bool
      	PressureState            bool
      	SmartVideoExitMode       int16
      	TemperatureHigh          bool
      	ThrowFlyTimer            int8
      	WindState                bool
      }
    

    FlightDataはイベントハンドラーで取得できるようです。
    FlightDataEventは先ほどtelloパッケージのNewDriverの関数を確認した時にd.AddEvent(FlightDataEvent)とありましたね。
    今回は飛行高さHeightとバッテリー残量(%)BatteryPercentageを取得してターミナルに表示したいと思います。

    最終的にコードは以下になりました。

    main.go
      package main
      
      import (
      	"fmt"
      	"time"
      
      	"gobot.io/x/gobot"
      	"gobot.io/x/gobot/platforms/dji/tello"
      )
      
      func main() {
      	drone := tello.NewDriver("8888")
      	var flightData *tello.FlightData
      	var battery int8
      
      	work := func() {
      		fmt.Println("TakeOff")
      		drone.TakeOff()
      
      		drone.On(tello.FlightDataEvent, func(data interface{}) {
      			flightData = data.(*tello.FlightData)
      			battery = flightData.BatteryPercentage
      			fmt.Println("Height:", flightData.Height)
      		})
      
      		gobot.After(5*time.Second, func() {
      			fmt.Println("FrontFlip")
      			drone.FrontFlip()
      		})
      
      		gobot.After(10*time.Second, func() {
      			fmt.Println("BackFlip")
      			drone.BackFlip()
      		})
      
      		gobot.After(15*time.Second, func() {
      			fmt.Println("Land")
      			drone.Land()
      
      			fmt.Println("Battery:", battery)
      		})
      	}
      
      	robot := gobot.NewRobot("tello",
      		[]gobot.Connection{},
      		[]gobot.Device{drone},
      		work,
      	)
      
      	robot.Start()
      }
    

    まわったァ!!!
    Gobotのコード見る前は半フリップで逆さ飛行とか試したいと思っていましたがそれは無理そうですね。。

    フライトデータはこんな感じでした。(バックフリップ以降)

    terminal
      BackFlip
      Height: 9
      Height: 9
      Height: 9
      Height: 10
      Height: 11
      Height: 11
      Height: 10
      Height: 9
      Height: 8
      Height: 8
      Height: 8
      Height: 8
      Height: 8
      Height: 8
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 10
      Height: 10
      Height: 10
      Height: 10
      Height: 10
      Height: 10
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Land
      Battery: 67
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 9
      Height: 8
      Height: 8
      Height: 8
      Height: 7
      Height: 6
      Height: 6
      Height: 5
      Height: 4
      Height: 4
      Height: 3
      Height: 3
      Height: 2
      Height: 2
      Height: 2
      Height: 1
      Height: 1
      Height: 0
      Height: 0
      Height: 0
      Height: 0
    

    思っていたほどバックフリップ時のHeight値は変動しなかったです。
    こんな感じでアクション系のメソッドを色々試してみてください。

    いったん今回の記事ではここまでにしたいと思います!

まとめ

今回は導入編ということで、

  • Goのインストールやパッケージ管理
  • Gobotフレームワークの活用
  • ドローン(Tello)について
  • プログラミングによる飛行体験

この辺りが分かったんじゃないかなと思います…!ワクワクしてもらえてたら幸いです。
次回はドローンの操作方法をより直感的にしてみたり、ドローンのカメラ側についても触れていけたらと思います。
それでは、最後までご覧いただきありがとうございました。

次回の記事はこちら
Go + ドローンで最高の世界へ飛び立つ その2 操作&映像編

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?