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

  1. Install the DJISDK and DJIUXSDK using 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 pod file 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.

  1. In your Xcode project - Build phases: add FFmpeg to your link libraries.

Getting started: Register your app with DJI

  1. To connect to your drone you will need to have a DJI developer account. Navigate to https://developer.dji.com to get started.
  2. 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.
  3. 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

  1. In ViewController.swift replace : UIViewController with : DUXDefaultLayoutViewController.
  2. 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 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 DJI 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.

Lets 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 to 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 updates 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, error) in
            guard let location = value?.value as? CLLocation else { return }
            self.currentLocation = location
            completion(location)
        }
 
        keyManager.startListeningForChanges(on: key, withListener: self) { (oldValue, 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 start 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 it basically works like this:

  1. Load the mission into mission control
  2. Start uploading the mission to the drone
  3. Listen for upload updates
  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] (error) 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), execute: {
                    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(_ sender: 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 create 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!

Leave a Reply

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

*

*