Creating a custom DJI Drone Controller App: Part 1
Introduction
Working with my current client has given me the incredible opportunity to program a drone path using the DJI SDK and iOS. I wanted to show a quick walkthrough of how to get setup to program your own flight paths. We’ll talk quickly about why you would want to do this, how it is done and finally show some examples.
Why Custom?
At first glance, using the DJI Go 4 app, it would appear that you have everything you could possibly need. Why would you need to create your own app when DJI provides you with one? One of the big reasons for this is that in the DJI Go 4 app you have to fly the mission on it’s own and mark the waypoints in the app. This is great if you want a repeatable mission at the same start and end points every time. For example, taking pictures and video of a field. But if you want something you can recreate no matter where you are, it can be very useful to create your own application and program your own flight paths. Let’s take a look at how to get started!
How
Getting started: Project setup
Install the DJISDK
and DJIUXSDK
useing cocoapods
. If you’re not familiar with cocoapods
please see instructions at https://cocoapods.org.
Run pod init
to start using cocoapods
with your project.
In your Podfile
add the following lines:
pod <span class="token string">'DJI-SDK-iOS'</span>
pod <span class="token string">'DJI-UXSDK-iOS'</span>
pod <span class="token string">'DJIWidget'</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span>
Your Podfile
should look something like this:
target <span class="token string">'DJIWaypointMissionExample'</span> <span class="token keyword">do</span>
<span class="token comment"># Comment the next line if you're not using Swift and don't want to use dynamic frameworks</span>
use_frameworks<span class="token operator">!</span>
<span class="token comment"># Pods for DJIWaypointMissionExample</span>
pod <span class="token string">'DJI-SDK-iOS'</span>
pod <span class="token string">'DJI-UXSDK-iOS'</span>
pod <span class="token string">'DJIWidget'</span>
<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span>
In your terminal run pod install
then open the generated .xcworkspace
file.
In your Xcode project – Build phases: add FFmpeg
to your link libraries.
Getting started: Register your app with DJI
To connect to your drone you will need to have a DJI developer account. Navigate to https://developer.dji.com to get started.
Once you have your account. Navigate to https://developer.dji.com/user/apps/ and create a new app. Name it whatever you want but make sure the app identifier matches.
Copy the api key and paste it into your apps Info.plist as DJISDKAppKey
Getting started: Connecting to the Drone
In this example we’re using a Mavic Pro, but the connections will be the same for any DJI Drone
In ViewController.swift
replace : UIViewController
with : DUXDefaultLayoutViewController
and add the following imports:
import DJISDK
import DJIUXSDK
import DJIWidget
Add the following function:
<span class="token keyword">private</span> <span class="token keyword">func</span> <span class="token function">registerWithSDK</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> appKey <span class="token operator">=</span> <span class="token builtin">Bundle</span><span class="token punctuation">.</span>main<span class="token punctuation">.</span><span class="token function">object</span><span class="token punctuation">(</span>forInfoDictionaryKey<span class="token punctuation">:</span> <span class="token constant">SDK_APP_KEY_INFO_PLIST_KEY</span><span class="token punctuation">)</span> <span class="token keyword">as</span><span class="token operator">?</span> <span class="token builtin">String</span>
<span class="token keyword">guard</span> appKey <span class="token operator">!=</span> <span class="token constant">nil</span> <span class="token operator">&&</span> appKey<span class="token operator">!</span><span class="token punctuation">.</span><span class="token builtin">isEmpty</span> <span class="token operator">==</span> <span class="token boolean">false</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token function">NSLog</span><span class="token punctuation">(</span><span class="token string">"Please enter your app key in the info.plist"</span><span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token builtin">DJISDKManager</span><span class="token punctuation">.</span><span class="token function">registerApp</span><span class="token punctuation">(</span>with<span class="token punctuation">:</span> <span class="token keyword">self</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span>
This will start the registration process and the DJISDKManagerDelegate
will be called after it has been completed. Add the following to your code:
<span class="token keyword">extension</span> <span class="token builtin">ViewController</span><span class="token punctuation">:</span> <span class="token builtin">DJISDKManagerDelegate</span> <span class="token punctuation">{</span>
<span class="token keyword">func</span> <span class="token function">appRegisteredWithError</span><span class="token punctuation">(</span><span class="token number">_</span> error<span class="token punctuation">:</span> <span class="token builtin">Error</span><span class="token operator">?</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">guard</span> error <span class="token operator">==</span> <span class="token constant">nil</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span>
<span class="token keyword">if</span> enableBridgeMode <span class="token punctuation">{</span>
<span class="token builtin">DJISDKManager</span><span class="token punctuation">.</span><span class="token function">startConnectionToProduct</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">productConnected</span><span class="token punctuation">(</span><span class="token number">_</span> product<span class="token punctuation">:</span> <span class="token builtin">DJIBaseProduct</span><span class="token operator">?</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">guard</span> product <span class="token operator">!=</span> <span class="token constant">nil</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"error connecting to product"</span><span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span>
To connect to the drone you will need a few things.
- The DJI SDK Bridge App SDK Bridge on the App Store
- The DJI Assistant (for simulations) DJI Mavic Pro – Specs, Tutorials & Guides – DJI
Steps
- Plug the remote control into your iPad or iPhone and turn the remote on.
- Open the DJII Bridge app and note the IP address listed
- Paste the IP address into
bridgeAppIP
- Run the app!
If all has gone well you will see the camera output from your drone.
Let’s Code… finally
Now that the basic connections are setup let’s start creating an actual mission. This is accomplished through the DJIMissionControl
object and we’ll build up a DJIWaypointMission
object.
Create a function that looks like this:
func createWaypointMission() -> DJIWaypointMission {}
This is where we’ll do the bulk of the work, but first let’s come up with a simple example to show that we can actually control the drone.
Location Updates
Let’s start by registering for location updaates and getting the current location. We’ll use the location to set our take-off point in the mission.
In the view controller create a private var private var currentLocation: CLLocation?
and the following function:
<span class="token keyword">func</span> <span class="token function">startListeningForLocationUpdates</span><span class="token punctuation">(</span>completion<span class="token punctuation">:</span> @escaping <span class="token punctuation">(</span><span class="token builtin">CLLocation</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Void</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">guard</span> <span class="token keyword">let</span> key <span class="token operator">=</span> <span class="token function">DJIFlightControllerKey</span><span class="token punctuation">(</span>param<span class="token punctuation">:</span> <span class="token builtin">DJIFlightControllerParamAircraftLocation</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token keyword">let</span> keyManager <span class="token operator">=</span> <span class="token builtin">DJISDKManager</span><span class="token punctuation">.</span><span class="token function">keyManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span>
keyManager<span class="token punctuation">.</span><span class="token function">getValueFor</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span> <span class="token punctuation">{</span>
value<span class="token punctuation">,</span> <span class="token number">_</span> <span class="token keyword">in</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> location <span class="token operator">=</span> value<span class="token operator">?</span><span class="token punctuation">.</span>value <span class="token keyword">as</span><span class="token operator">?</span> <span class="token builtin">CLLocation</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token keyword">self</span><span class="token punctuation">.</span>currentLocation <span class="token operator">=</span> location <span class="token function">completion</span><span class="token punctuation">(</span>location<span class="token punctuation">)</span>
<span class="token punctuation">}</span> keyManager<span class="token punctuation">.</span><span class="token function">startListeningForChanges</span><span class="token punctuation">(</span>on<span class="token punctuation">:</span> key<span class="token punctuation">,</span> withListener<span class="token punctuation">:</span> <span class="token keyword">self</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token number">_</span><span class="token punctuation">,</span> newValue <span class="token keyword">in</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> location <span class="token operator">=</span> newValue<span class="token operator">?</span><span class="token punctuation">.</span>value <span class="token keyword">as</span><span class="token operator">?</span> <span class="token builtin">CLLocation</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token keyword">self</span><span class="token punctuation">.</span>currentLocation <span class="token operator">==</span> <span class="token constant">nil</span> <span class="token punctuation">{</span> <span class="token function">completion</span><span class="token punctuation">(</span>location<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">self</span><span class="token punctuation">.</span>currentLocation <span class="token operator">=</span> location <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span>
Add the function call to viewDidLoad
.
Now that we have our location we can start our custom mission. In your createWaypointMission
function create a mutable mission and set a few basic fields.
<span class="token keyword">func</span> <span class="token function">createWaypointMission</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">DJIWaypointMission</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> mission <span class="token operator">=</span> <span class="token function">DJIMutableWaypointMission</span><span class="token punctuation">(</span><span class="token punctuation">)</span> mission<span class="token punctuation">.</span>headingMode <span class="token operator">=</span> <span class="token punctuation">.</span>usingWaypointHeading mission<span class="token punctuation">.</span>autoFlightSpeed <span class="token operator">=</span> <span class="token number">1</span>
<span class="token comment">// Set to your preference mission.maxFlightSpeed = 2</span>
<span class="token comment">// Set to your preference mission.finishedAction = .noAction mission.flightPathMode = .normal return mission</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span>
For our first mission we’re just going to take off and move to 10 meters off the ground from our current location. This will prove that everything works and give us enough to staart building out more complicated missions in the next tutorial.
First, make sure we have a location.
<span class="token keyword">guard</span> <span class="token keyword">let</span> currentLocation <span class="token operator">=</span> currentLocation <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token function">DJIWaypointMission</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span>
Next add a new waypoint to the mission:
<span class="token keyword">let</span> waypoint <span class="token operator">=</span> <span class="token function">DJIWaypoint</span><span class="token punctuation">(</span>coordinate<span class="token punctuation">:</span> currentLocation<span class="token punctuation">.</span>coordinate<span class="token punctuation">)</span> waypoint<span class="token punctuation">.</span>altitude <span class="token operator">=</span> <span class="token number">10</span> mission<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>waypoint<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span>
Your final createWaypointMission
should now look like this:
<span class="token keyword">func</span> <span class="token function">createWaypointMission</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">DJIWaypointMission</span> <span class="token punctuation">{</span>
<span class="token keyword">guard</span> <span class="token keyword">let</span> currentLocation <span class="token operator">=</span> currentLocation <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token function">DJIWaypointMission</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">let</span> mission <span class="token operator">=</span> <span class="token function">DJIMutableWaypointMission</span><span class="token punctuation">(</span><span class="token punctuation">)</span> mission<span class="token punctuation">.</span>headingMode <span class="token operator">=</span> <span class="token punctuation">.</span>usingWaypointHeading mission<span class="token punctuation">.</span>autoFlightSpeed <span class="token operator">=</span> <span class="token number">1</span>
<span class="token comment">// Set to your preference mission.maxFlightSpeed = 2</span>
<span class="token comment">// Set to your preference mission.finishedAction = .noAction mission.flightPathMode = .normal let waypoint = DJIWaypoint(coordinate: currentLocation.coordinate) waypoint.altitude = 10 mission.add(waypoint)</span>
<span class="token keyword">return</span> mission
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span>
Now all that is left is to upload the mission to the drone!
This part is a little overly complicated because of all the callbacks involved, but basically it works like this:
- Load the mission into mission control
- Start uploading the mission to the drone
- Listen for upload updataes
- Once everything is uploaded, start the mission
<span class="token keyword">func</span> <span class="token function">uploadAndStartMission</span><span class="token punctuation">(</span>mission<span class="token punctuation">:</span> <span class="token builtin">DJIWaypointMission</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">guard</span> <span class="token keyword">let</span> missionControl <span class="token operator">=</span> <span class="token builtin">DJISDKManager</span><span class="token punctuation">.</span><span class="token function">missionControl</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
missionControl<span class="token punctuation">.</span><span class="token function">waypointMissionOperator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span>mission<span class="token punctuation">)</span>
missionControl<span class="token punctuation">.</span><span class="token function">waypointMissionOperator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>uploadMission <span class="token punctuation">{</span>
<span class="token punctuation">[</span><span class="token keyword">weak</span> <span class="token keyword">self</span><span class="token punctuation">]</span> <span class="token number">_</span> <span class="token keyword">in</span> missionControl<span class="token punctuation">.</span><span class="token function">waypointMissionOperator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addListener</span><span class="token punctuation">(</span>toStarted<span class="token punctuation">:</span> <span class="token keyword">self</span><span class="token punctuation">,</span> with<span class="token punctuation">:</span> <span class="token constant">nil</span><span class="token punctuation">,</span> andBlock<span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span> missionControl<span class="token punctuation">.</span><span class="token function">waypointMissionOperator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addListener</span><span class="token punctuation">(</span>toUploadEvent<span class="token punctuation">:</span> <span class="token keyword">self</span><span class="token punctuation">,</span> with<span class="token punctuation">:</span> <span class="token constant">nil</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
event <span class="token keyword">in</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> progress <span class="token operator">=</span> event<span class="token punctuation">.</span>progress <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"progress: <span class="token interpolation"><span class="token delimiter variable">\(</span>progress<span class="token delimiter variable">)</span></span>"</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> progress<span class="token punctuation">.</span>totalWaypointCount <span class="token operator">-</span> <span class="token number">1</span> <span class="token operator">==</span> progress<span class="token punctuation">.</span>uploadedWaypointIndex<span class="token punctuation">,</span> progress<span class="token punctuation">.</span>isWaypointSummaryUploaded <span class="token punctuation">{</span>
<span class="token builtin">DispatchQueue</span><span class="token punctuation">.</span>main<span class="token punctuation">.</span><span class="token function">asyncAfter</span><span class="token punctuation">(</span>deadline<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token punctuation">.</span><span class="token function">seconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
missionControl<span class="token punctuation">.</span><span class="token function">waypointMissionOperator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">startMission</span><span class="token punctuation">(</span>completion<span class="token punctuation">:</span> <span class="token constant">nil</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span>
Now we hook everything up!
In Main.storyboard
add a button and hook it up to your view controller. Something like this:
<span class="token atrule">@IBAction</span> <span class="token keyword">func</span> <span class="token function">startButtonPressed</span><span class="token punctuation">(</span><span class="token number">_</span> sender<span class="token punctuation">:</span> <span class="token builtin">UIButton</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span>
Now add the following:
startListeningForLocationUpdates <span class="token punctuation">{</span>
location <span class="token keyword">in</span> <span class="token keyword">self</span><span class="token punctuation">.</span>currentLocation <span class="token operator">=</span> location <span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">uploadAndStartMission</span><span class="token punctuation">(</span>mission<span class="token punctuation">:</span> <span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">createWaypointMission</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token atrule">@IBAction</span> <span class="token keyword">func</span> <span class="token function">startButtonPressed</span><span class="token punctuation">(</span><span class="token number">_</span><span class="token punctuation">:</span> <span class="token builtin">UIButton</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
startListeningForLocationUpdates <span class="token punctuation">{</span>
location <span class="token keyword">in</span> <span class="token keyword">self</span><span class="token punctuation">.</span>currentLocation <span class="token operator">=</span> location <span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">uploadAndStartMission</span><span class="token punctuation">(</span>mission<span class="token punctuation">:</span> <span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">createWaypointMission</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span>
Now when you press the start button the drone will take off and ascend to 10 meters.
In the next part of this series we’ll take a look at a creating a more complex mission, some other features of the SDK, and a few lessons learned to help you avoid mistakes that I’ve had to learn from. See you next time!
Please note that I am using Mavic Pro 2 Drone and the mission is not working at all!?
Could you share that code on Github or send it to my email and I will make sure to test it again
Thank you very much!
Hi, Your example doesn’t work at all. DUXDefaultLayoutViewController is not declared
Emmanuel,thanks for catching that and my apologies. You need in to import the following:
import DJISDK
import DJIUXSDK
import DJIWidget
I’ve updated the post as well.