本記事は オルトプラス 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というより教育向けのシリーズもあるのですが、現在は購入できないようです。
DJIから販売されているドローンですが開発したのはRyze Tech社(Ryze Technology)になります。
Ryze Technologyは、2017年に中国の深センで設立されたテック系スタートアップカンパニーです。私たちのゴールは、ドローンフライトをより楽しくワクワクするものにすることです。遊び心こそが学びの最大の基盤だと、私たちは考えています。ドローンについて学ぶこと、それは、とても楽しくて面白いことなのです。私たちの初めてのドローン「Tello」は、まさにそういったコンセプトから生まれました。DJI製のフライトコントロール システムとIntelプロセッサーを搭載したTelloは、業界をリードする技術とエンターテイメント性を詰め込んだ、かんたん操作ドローンです。
-
規制
ここを気にする方も多いかと思います。
今回のTelloは重量が約80gのトイドローンです。
2023年時点では以下のようになっています。「トイドローン」とは、一般的に「本体とバッテリーの重さの合計が100g未満の小型ドローン」のことです。
100g以上のドローンは、「航空法」によって飛ばせる場所や方法が規制されていますが、100g未満のトイドローンには航空法が適用されません。100g未満だと小型無人機、100g以上では無人航空機という扱いだそうです。
トイドローン(小型無人機)では以下が不要になります。- 特定飛行の許可申請
- 機体登録
- リモートIDの搭載
リモートIDってなんなん?みたいな説明は省きますが、要はめんどくさいことはあまり考えず気軽に遊びやすいドローンって感じです。
ただしトイドローンでも飛行禁止エリアはあるので屋外で飛ばす際は気をつけてください。
-
開封
(記事を書く際に開封しようと思っていたのでやっと開封できる…!)
サイズ比較
箱の大きさは皆んな大好きリーダブルコードより少し大きい程度です。箱は薄め。
同じタイミングでDJI MINI 4 PROも買ったのでサイズ感確認していきます。
Telloの本体ちっちゃい!お寿司くらいです🍣
MINIは折り畳んだ状態ですが比較すると結構デカいですね。
Telloを充電しながらコードの方を準備していきます。
操作していく
準備
- ディレクトリの作成
この辺りは自由ですが、今回は自分の作業用ディレクトリにドローン用のディレクトリを作成するような想定でいこうと思います。
作成したドローンディレクトリに移動しておきます。terminal[~/my_app] $ mkdir drone_tello_app && cd drone_tello_app [~/my_app/drone_tello_app] $
-
Goモジュールの初期化
go mod init {project_name}
を実行します。terminal[~/my_app/drone_tello_app] $ go mod init drone_tello_app
実行後、サイドバーのツリーにgo.modが作成されたらokです。
GoではGo Modulesによってパッケージを管理します。
Node.jsならnpm、Pythonならpipで管理しているアレです。
Go Modulesを利用する際にはgo mod
コマンドを使用します。
作成されたgo.modファイル
にはモジュール名や他のモジュールとの依存関係が定義されています。
外部パッケージを利用するとgo.sumファイル
も作成されます。(後で作成します)
-
TELLO APPのインストール (スキップ可)
App Storeにてアプリをインストールします。
アプリを起動し、規約をチェックしたり権限周りを許可していくと接続方法の画面が表示されました。
方法に沿って本体の電源を入れた後にTelloに接続します。
接続後、ガイドが表示され、ガイドを閉じるとドローンのカメラ映像とActivationのダイアログが表示されたのでそのままActivateしました。
カメラ映像の画質は荒めですが、オモチャとして考えれば十分すぎる気がします。
設定の詳細にファームウェアのアップデートが出来たのでついでにしておきました。
もうなんかアプリからのボタン操作でドローンが動きそうな気配しかしないですが、初めての離陸はGoに譲りたいため我慢します…!
アプリの方は終了しておきます。これで準備は一通り完了したのかなと思います。
Gobotによる操作
いよいよ飛ばしていきます!
GobotのプラットフォームにあるDJI Telloページを確認しつつ試していきたいと思います。
上記ページにてHow to Installとしてgo get -d -u gobot.io/x/gobot/...
を実行してねと書いてありますが、今回の記事では実行せず後でgo mod tidy
を実行したいと思います。
go get
やgo install
コマンドについては触れません(たぶん)
-
サンプルコードの実施
実行用のファイルとしてmain.go
を作成します。
Telloのプラットフォームページにある「How to Use」のサンプルコードを参考に
main.go
のファイルに貼ります。
fmtは今のところ不要なので削除。(インデントが多い…)main.gopackage 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ファイル
も内容が更新されたかと思います。
go mod tidy
はソースコードでimportして使用しているものがgo.mod
やgo.sum
に依存関係が定義されていないものがあれば追加し、逆にgo.mod
やgo.sum
に定義しているがimportで使用していないものがあれば削除します。
なので必要最低限の依存関係で整理整頓してくれるありがたいコマンドです。では、
main.go
を実行していきたいと思います。
実行前に以下をチェックしてください。- telloの電源が入っていること
- telloのWi-Fiに接続できていること
- telloを床など安定した所に置いていること
もろもろokなら以下を実行します。
terminal[~/my_app/drone_tello_app] $ go run main.go
— ka-na (@nakamu_ka) November 26, 2023
とんだァ!!!
離陸と着陸のみですが地点がそこそこズレたので、なるべく周囲に何も無いところで実行したほうが良いかもです。
main.go
を実行してもドローンが反応しない場合は、TELLO APPのせいかもしれませんのでTELLOのアプリを終了してみてください。
-
処理内容の確認
あっさりしたコードに見えますが何をしていたのでしょうか。
main関数で何をしているか少しだけみていきます。
drone := tello.NewDriver("8888")
driver.gofunc 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.gowork := func() { drone.TakeOff() gobot.After(5*time.Second, func() { drone.Land() }) }
離陸させるTakeOffのメソッドはどんな感じなのか確認します。
driver.gofunc (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.gorobot := gobot.NewRobot("tello", []gobot.Connection{}, []gobot.Device{drone}, work, ) robot.Start()
ここでは
drone
とwork
を元にtelloのインスタンスであるrobot
を定義し、そのインスタンスでStart
メソッドを呼び出しています。上記でやっていたTakeOff
と同じような感じですね。
-
キャリブレーションしてみる
離陸と着陸地点に差異があったので調整できるか調べたところ、以下とのことなのでやってみます。機体のフライトが安定しない(ふらつく、離陸時まっすぐ上昇しない、機体が流れる、離陸できない等)場合には、Telloアプリを利用しIMUキャリブレーション作業を行う事で改善可能な場合があります。
公式の動画一覧の「Calibrating the IMU」にて動画でも方法が確認できます。
尚、キャリブレーション作業はTELLO APPからのみ可能です。
アプリでガイドしてもらいながら簡単にキャリブレーションとやらが出来ました。ベストを追求してキャリブレしまくってたら一時飛ばなくなって焦りました。
結局ブレます!よほど狂った飛行しない限りはやらなくてokかもです。
-
ドローンの他のアクションを試す
今は離陸と着陸しかないので他のアクションも試していきます。
他にどんなアクションがあるかは、driver.goファイル
のメソッドを直接確認していくか、
telloのパッケージのドキュメントを確認すれば良いかと思います。私はとりあえずこのフリップアクションあたりが面白そうなので
FrontFlip
とBackFlip
を試してみます…!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.gowork := 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.gopackage 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() }
— ka-na (@nakamu_ka) November 26, 2023
まわったァ!!!
Gobotのコード見る前は半フリップで逆さ飛行とか試したいと思っていましたがそれは無理そうですね。。フライトデータはこんな感じでした。(バックフリップ以降)
terminalBackFlip 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 操作&映像編