Aug 29, 2019

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">&amp;&amp;</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.

  1. The DJI SDK Bridge App SDK Bridge on the App Store
  2. The DJI Assistant (for simulations) DJI Mavic Pro – Specs, Tutorials & Guides – DJI

Steps

  1. Plug the remote control into your iPad or iPhone and turn the remote on.
  2. Open the DJII Bridge app and note the IP address listed
  3. Paste the IP address into bridgeAppIP
  4. 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() -&gt; 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">&gt;</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">&gt;</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">&gt;</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:

  1. Load the mission into mission control
  2. Start uploading the mission to the drone
  3. Listen for upload updataes
  4. 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!

About the Author

Object Partners profile.

One thought on “Creating a custom DJI Drone Controller App: Part 1

  1. Mohamad Ghaith Alzin says:

    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!

  2. Emmanuel says:

    Hi, Your example doesn’t work at all. DUXDefaultLayoutViewController is not declared

    1. 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.

Leave a Reply

Your email address will not be published.

Related Blog Posts
Natively Compiled Java on Google App Engine
Google App Engine is a platform-as-a-service product that is marketed as a way to get your applications into the cloud without necessarily knowing all of the infrastructure bits and pieces to do so. Google App […]
Building Better Data Visualization Experiences: Part 2 of 2
If you don't have a Ph.D. in data science, the raw data might be difficult to comprehend. This is where data visualization comes in.
Unleashing Feature Flags onto Kafka Consumers
Feature flags are a tool to strategically enable or disable functionality at runtime. They are often used to drive different user experiences but can also be useful in real-time data systems. In this post, we’ll […]
A security model for developers
Software security is more important than ever, but developing secure applications is more confusing than ever. TLS, mTLS, RBAC, SAML, OAUTH, OWASP, GDPR, SASL, RSA, JWT, cookie, attack vector, DDoS, firewall, VPN, security groups, exploit, […]