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 'DJI-SDK-iOS'
pod 'DJI-UXSDK-iOS'
pod 'DJIWidget'

Your Podfile should look something like this:

target 'DJIWaypointMissionExample' do
    # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
    use_frameworks!
    # Pods for DJIWaypointMissionExample
    pod 'DJI-SDK-iOS'
    pod 'DJI-UXSDK-iOS'
    pod 'DJIWidget'
end

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.

Add the following function:

private func registerWithSDK() { 
    let appKey = Bundle.main.object(forInfoDictionaryKey: SDK_APP_KEY_INFO_PLIST_KEY) as? String
    guard appKey != nil && appKey!.isEmpty == false else {
        NSLog("Please enter your app key in the info.plist")
        return
    }
    
    DJISDKManager.registerApp(with: self)
}

This will start the registration process and the DJISDKManagerDelegate will be called after it has been completed. Add the following to your code:

extension ViewController: DJISDKManagerDelegate {
     func appRegisteredWithError(_ error: Error?) {
        guard error == nil else { return }
        if enableBridgeMode {
            DJISDKManager.startConnectionToProduct()
        }
    }
    func productConnected(_ product: DJIBaseProduct?) {
        guard product != nil else {
            print("error connecting to product")
            return

        }
    }
}

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() -> 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:

func startListeningForLocationUpdates(completion: @escaping (CLLocation) -> Void) {
    guard let key = DJIFlightControllerKey(param: DJIFlightControllerParamAircraftLocation),
        let keyManager = DJISDKManager.keyManager() else { return }
    keyManager.getValueFor(key) {
        value, _ in guard let location = value?.value as? CLLocation else { return } self.currentLocation = location completion(location)
    } keyManager.startListeningForChanges(on: key, withListener: self) { _, newValue in guard let location = newValue?.value as? CLLocation else { return } if self.currentLocation == nil { completion(location) } self.currentLocation = location }
}

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.

func createWaypointMission() -> DJIWaypointMission {
    let mission = DJIMutableWaypointMission() mission.headingMode = .usingWaypointHeading mission.autoFlightSpeed = 1
    // Set to your preference mission.maxFlightSpeed = 2
    // Set to your preference mission.finishedAction = .noAction mission.flightPathMode = .normal return mission
}

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.

guard let currentLocation = currentLocation else {
    return DJIWaypointMission()
}

Next add a new waypoint to the mission:

let waypoint = DJIWaypoint(coordinate: currentLocation.coordinate) waypoint.altitude = 10 mission.add(waypoint)

Your final createWaypointMission should now look like this:

func createWaypointMission() -> DJIWaypointMission {
    guard let currentLocation = currentLocation else {
        return DJIWaypointMission()
    }
    let mission = DJIMutableWaypointMission() mission.headingMode = .usingWaypointHeading mission.autoFlightSpeed = 1
    // Set to your preference mission.maxFlightSpeed = 2
    // Set to your preference mission.finishedAction = .noAction mission.flightPathMode = .normal let waypoint = DJIWaypoint(coordinate: currentLocation.coordinate) waypoint.altitude = 10 mission.add(waypoint)
    return mission
}

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
func uploadAndStartMission(mission: DJIWaypointMission) {
    guard let missionControl = DJISDKManager.missionControl() else {
        return
    }
    missionControl.waypointMissionOperator().load(mission)
    missionControl.waypointMissionOperator().uploadMission {
        [weak self] _ in missionControl.waypointMissionOperator().addListener(toStarted: self, with: nil, andBlock: {})
    } missionControl.waypointMissionOperator().addListener(toUploadEvent: self, with: nil) {
        event in guard let progress = event.progress else {
            return
        }
        print("progress: \(progress)")
        if progress.totalWaypointCount - 1 == progress.uploadedWaypointIndex, progress.isWaypointSummaryUploaded {
            DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
                missionControl.waypointMissionOperator().startMission(completion: nil)
            }
        }
    }
}

Now we hook everything up!

In Main.storyboard add a button and hook it up to your view controller. Something like this:

@IBAction func startButtonPressed(_ sender: UIButton) { }

Now add the following:

startListeningForLocationUpdates {
    location in self.currentLocation = location self.uploadAndStartMission(mission: self.createWaypointMission())
}

@IBAction func startButtonPressed(_: UIButton) {
    startListeningForLocationUpdates {
        location in self.currentLocation = location self.uploadAndStartMission(mission: self.createWaypointMission())
    }
}

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

Elvin Bearden profile.

Elvin Bearden

Sr. Consultant

Elvin has experience in Swift, Objective-C, Java, PHP and restful API’s. He has spent 5 years developing native iOS applications in a variety of fields. When not working on iOS, Elvin enjoys playing guitar, listening to music, playing video games and reading.

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

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Blog Posts
Up to Spec: JavaScript Numeric Separators
Let's take a look at the proposal to add Numeric Separators to the JavaScript specification.
Using Conftest to Validate Configuration Files
Conftest is a utility within the Open Policy Agent ecosystem that helps simplify writing validation tests against configuration files. In a previous blog post, I wrote about using the Open Policy Agent utility directly to […]
SwiftGen with Image & Color Asset Catalogs
You might remember back in 2015 when iOS 9 was introduced, and we were finally given a way to manage all of our assets in one place with Asset Catalogs. A few years later, support […]
Tracking Original URL Through Authentication
If you read my other post about refreshing AWS tokens, then you probably have a use case for keeping track of the original requested resource while the user goes through authentication so you can route […]