DJI Mobile SDK V5を利用して、自動でスクエア飛行をしてみた！

# はじめに
DJI Mobile SDK V5を使用して、スクエア飛行を自動で行いました。
サンプルコードをベースに機能を追加していきます。

# スクエア飛行とは
スクエア飛行とは、ドローンを、四隅を順に飛行させることであり、操縦の練習方法の一つとして用いられています。
ドローンの国家資格(免許)である無⼈航空機操縦士の実技試験でも実施されており、初心者が必ず実施する飛行の一つといってもよいでしょう。

▼スクエア飛行の図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2584877/bb7a8a51-736f-1f02-5df4-f621e4c1ebe1.png)
出典: 国土交通省航空局安全部無人航空機安全課(2022)

# 前提条件
* [Android Studio Flamingo](https://developer.android.com/studio)
* [DJI Mini 3](https://www.dji.com/jp/mini-3)
* [Huawei Mate 20](https://www.gsmarena.com/huawei_mate_20-9367.php)
* [Mobile SDK V5](https://github.com/dji-sdk/Mobile-SDK-Android-V5)
* サンプルコード(APIキー連携済み)
    * APIキー連携の実施方法は以下を確認してください

https://qiita.com/TEMMIEHOIHOI/items/cee3ae75d4d88d322ee3

# 画面の作成
### ボタンのテキストの追加
```diff:android-sdk-v5-sample\module-common\src\main\res\values\strings.xml
+    <string name="btn_square" translatable="false">Square</string>
```

### ボタンの作成
Testing Tool-> Virtual Stickの画面に起動用のボタンを追加します。
```diff:android-sdk-v5-sample\module-aircraft\src\main\res\layout\frag_virtual_stick_page.xml
            <Button
                android:id="@+id/btn_set_virtual_stick_speed_level"
                style="@style/main_fragment_btn"
                android:text="@string/btn_set_virtual_stick_speed_level"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/btn_disable_virtual_stick" />

+            <Button
+                android:id="@+id/btn_square"
+                style="@style/main_fragment_btn"
+                android:text="@string/btn_square"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/btn_set_virtual_stick_speed_level" />
```


▼プレビュー
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2584877/739dc126-a19b-c913-a4cd-fb18181ba66b.png)



# ボタンの処理を追加
ボタンを押下した際の処理を記述します。
```diff:android-sdk-v5-sample\module-aircraft\src\main\java\dji\sampleV5\moduleaircraft\pages\VirtualStickFragment.kt
    private val simulatorVM: SimulatorVM by activityViewModels()
    private val deviation: Double = 0.02

+    private val handler: Handler = Handler(Looper.getMainLooper())
(中略)

        btn_set_virtual_stick_speed_level.setOnClickListener {
            val speedLevels = doubleArrayOf(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0)
            initPopupNumberPicker(Helper.makeList(speedLevels)) {
                virtualStickVM.setSpeedLevel(speedLevels[indexChosen[0]])
                resetIndex()
            }
        }

+        //メッセージをポップアップ表示した後、送信機の右スティックを操作する
+        btn_square.setOnClickListener {
+            class GotoPos(
+                val launch_message: String,
+                val right_horizontal: Float,
+                val right_vertical: Float
+            ) : Runnable {
+                override fun run() {
+                    if (launch_message != "")
+                        //メッセージのポップアップ
+                        ToastUtils.showToast(launch_message)
+                    //右スティックの状態を更新する
+                    virtualStickVM.setRightPosition(
+                        //垂直方向
+                        (right_horizontal * Stick.MAX_STICK_POSITION_ABS).toInt(),
+                        //鉛直方向
+                        (right_vertical * Stick.MAX_STICK_POSITION_ABS).toInt()
+                    )
+                }
+            }
+            
+            //離陸
+            ToastUtils.showToast("Takeoff")
+            basicAircraftControlVM.startTakeOff(object :
+                CommonCallbacks.CompletionCallbackWithParam<EmptyMsg> {
+                override fun onSuccess(t: EmptyMsg?) {
+                    ToastUtils.showToast("start takeOff onSuccess.")
+                }
+
+                override fun onFailure(error: IDJIError) {
+                    ToastUtils.showToast("start takeOff onFailure,$error")
+                }
+            })
+            
+            //Goto Pos1
+            handler.postDelayed(GotoPos("Go to Position1", 0f, -0.1f), 4000)
+            //停止させるために進行方向と逆方向にスティックを倒す
+            handler.postDelayed(GotoPos("", 0f, 0.01f), 8000)
+            //スティックを戻す
+            handler.postDelayed(GotoPos("", 0f, 0f), 8100)
+
+            //Goto Pos2
+            handler.postDelayed(GotoPos("Go to Position2", -0.1f, 0f), 8100)
+            handler.postDelayed(GotoPos("", 0.01f, 0f), 12100)
+            handler.postDelayed(GotoPos("", 0f, 0f), 12200)
+
+            //Goto Pos3
+            handler.postDelayed(GotoPos("Go to Position3", 0f, 0.1f), 12200)
+            handler.postDelayed(GotoPos("", 0f, -0.01f), 20200)
+            handler.postDelayed(GotoPos("", 0f, 0f), 20300)
+
+            //Goto Pos4
+            handler.postDelayed(GotoPos("Go to Position4", 0.1f, 0f), 20300)
+            handler.postDelayed(GotoPos("", -0.01f, 0f), 28300)
+            handler.postDelayed(GotoPos("", 0f, 0f), 28400)
+
+            //Goto Pos5
+            handler.postDelayed(GotoPos("Go to Position5", 0f, -0.1f), 28400)
+            handler.postDelayed(GotoPos("", 0f, 0.01f), 36400)
+            handler.postDelayed(GotoPos("", 0f, 0f), 36500)
+
+            //Goto Pos1
+            handler.postDelayed(GotoPos("Go to Position1", -0.1f, 0f), 36500)
+            handler.postDelayed(GotoPos("", 0.01f, 0f), 40500)
+            handler.postDelayed(GotoPos("", 0f, 0f), 40600)
+
+            //Goto Home
+            handler.postDelayed(GotoPos("Go to Home", 0f, 0.1f), 40600)
+            handler.postDelayed(GotoPos("", 0f, -0.01f), 44600)
+            handler.postDelayed(GotoPos("", 0f, 0f), 44700)
+
+            //着陸
+            handler.postDelayed(
+                object : Runnable {
+                    override fun run() {
+                        basicAircraftControlVM.startLanding(object :
+                            CommonCallbacks.CompletionCallbackWithParam<EmptyMsg> {
+                            override fun onSuccess(t: EmptyMsg?) {
+                                ToastUtils.showToast("start landing onSuccess.")
+                            }
+
+                            override fun onFailure(error: IDJIError) {
+                                ToastUtils.showToast("start landing onFailure,$error")
+                            }
+                        })
+                    }
+                },
+                48600)
+        }
```



# 動作確認
:::note warn
確認の都合上、上記のコードと以下の点で異なっております。
* virtual stickが削除されています
* SimulatorのON/OFFボタンが追加されています
    * 「Testing Tools」->「Simulator」より同様の機能が利用できます
:::
<iframe width="560" height="720" src="https://www.youtube.com/embed/7BeTrQG7mx8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>


# 課題点
~~着陸の際に、一定の高度で停止してしまう事象が発生している。~~
解決しました

https://qiita.com/TEMMIEHOIHOI/items/3e68aa61aae9177566bf

# 参考文献
国土交通省航空局安全部無人航空機安全課, [二等無⼈航空機操縦士実地試験実施細則回転翼航空機（マルチローター）](https://www.mlit.go.jp/common/001516517.pdf), 2022, p22