Ground SDK Hello Drone Tutorial#

Ground SDK Hello Drone Tutorial is a step-by-step guide that helps you develop an iOS application using Ground SDK iOS 7.7. This application is able to connect to an ANAFI drone and a Skycontroller remote control, display the battery level and video stream, and take off or land the drone.

At the end of this tutorial, you will be able to:

  • Setup your developement environment

  • Setup your project to use Ground SDK iOS 7.7

  • Connect to a drone

  • Display drone connection state

  • Display drone battery level

  • Make take off and land the drone

  • Display the video live stream

  • Connect to a remote control

  • Display remote control connection state

  • Display remote control battery level

The full project is available here.

Note

This tutorial is based on Ground SDK iOS version 7.7.

Prerequisites#

Before starting this tutorial, you have to:

Setup project#

First you need to configure your project to use Ground SDK iOS Pods.

Cocoapods install#

To install Cocoapods, open a Terminal and execute the following command.

$ sudo gem install cocoapods

Adding Ground SDK Pods#

Open Terminal and navigate to the directory that contains your HelloDrone by using the cd command:

$ cd ~/Path/To/Folder/Containing/HelloDrone

Next, enter the following command:

$ pod init

This creates a Podfile for your project.

Open the Podfile using Xcode for editing:

$ open -a Xcode Podfile

Replace all content with:

platform :ios, '12.0'

target 'HelloDrone' do
    use_frameworks!
    pod 'GroundSdk', '~> 7.7'
    pod 'ArsdkEngine', '~> 7.7'
    pod 'SdkCore', '~> 7.7'
end

Note

replace HelloDrone by your own project name.

Save and close the Podfile.

You now need to tell CocoaPods to install the dependencies for your project. Enter the following command in Terminal, after ensuring you are still in the directory containing the HelloDrone project and Podfile:

$ pod install

Open the project folder using Finder, and open the new `HelloDrone.xcworkspace` file created by CocoaPods.

Allow Network Discovery#

You need to setup your project to allow device network discovery.

Add to your Info.plist file a new array with Bonjour services as key and add string items with services types to allow as value.

  • _arsdk-0914._udp. for Anafi 4K

  • _arsdk-0919._udp. for Anafi Thermal

  • _arsdk-091b._udp. for Anafi UA

  • _arsdk-091e._udp. for Anafi USA

  • _arsdk-091a._udp. for Anafi Ai

Your Info.plist file should now look like this:

_images/bonjour_services_plist.png

Your project setup is ready, let’s start coding!

Ground SDK instance#

In order to use Ground SDK in your application, you first have to create an keep a GroundSdk instance. So open your ViewController file, and add:

import UIKit
// import GroundSdk library.
import GroundSdk

class ViewController: UIViewController {

    /// Ground SDk instance.
    private let groundSdk = GroundSdk()

    override func viewDidLoad() {
        super.viewDidLoad()

    }
}

This GroundSdk instance keeps and manages all GroundSdk references.

Drone connection#

To connect to a drone, you should use the AutoConnection facility.

In viewDidLoad, get the facility and start it.

    /// Ground SDk instance.
    private let groundSdk = GroundSdk()
    /// Reference to auto connection.
    private var autoConnectionRef: Ref<AutoConnection>?

    override func viewDidLoad() {
        super.viewDidLoad()

        // Monitor the auto connection facility.
        // Keep the reference to be notified on update.
        autoConnectionRef = groundSdk.getFacility(Facilities.autoConnection) { [weak self] autoConnection in
            // Called when the auto connection facility is available and when it changes.

            if let self = self, let autoConnection = autoConnection {
                // Start auto connection.
                if (autoConnection.state != AutoConnectionState.started) {
                    autoConnection.start()
                }
            }
        }
    }

Auto connection will automatically select and connect the device.

You need to monitor the drone change to stop using the old one and start using the new one.

    // Drone:
    /// Current drone instance.
    private var drone: Drone?

    override func viewDidLoad() {
        super.viewDidLoad()

        // Monitor the auto connection facility.
        // Keep the reference to be notified on update.
        autoConnectionRef = groundSdk.getFacility(Facilities.autoConnection) { [weak self] autoConnection in
            // Called when the auto connection facility is available and when it changes.

            if let self = self, let autoConnection = autoConnection {
                // Start auto connection.
                if (autoConnection.state != AutoConnectionState.started) {
                    autoConnection.start()
                }

                // If the drone has changed.
                if (self.drone?.uid != autoConnection.drone?.uid) {
                    if (self.drone != nil) {
                        // Stop to monitor the old drone.
                        self.stopDroneMonitors()
                    }

                    // Monitor the new drone.
                    self.drone = autoConnection.drone
                    if (self.drone != nil) {
                        self.startDroneMonitors()
                    }
                }
            }
        }
    }

    /// Starts drone monitors.
    private func startDroneMonitors() {
    }

    /// Stops drone monitors.
    private func stopDroneMonitors() {
    }

Drone monitoring#

Now you will monitor and display the drone connection state and its battery state.

Drone user interface#

To display drone information, add two UILabels in your storyboard. One for the Drone connection state, referencing to a droneStateTxt UILabel IBOutlet and one for the Drone battery level, referencing to a droneBatteryTxt UILabel IBOutlet`.

Your storyboard should look like this:

_images/drone_ui.png

And your addings in your ViewController should be:

    // Drone:
    /// Current drone instance.
    private var drone: Drone?

    // User Interface:
    /// Drone state text view.
    @IBOutlet weak var droneStateTxt: UILabel!
    /// Drone battery level text view.
    @IBOutlet weak var droneBatteryTxt: UILabel!

Drone state monitoring#

In order to display the drone connection state, set an observer on the drone state, and get its ConnectionState.

When you have finished with it and you want to stop monitoring it, set the drone state reference to nil.

    // Drone:
    /// Current drone instance.
    private var drone: Drone?
    /// Reference to the current drone state.
    private var droneStateRef: Ref<DeviceState>?

    // User Interface:
    /// Drone state text view.
    @IBOutlet weak var droneStateTxt: UILabel!
    /// Drone battery level text view.
    @IBOutlet weak var droneBatteryTxt: UILabel!

    private func startDroneMonitors() {
        // Monitor drone state.
        monitorDroneState()
    }

    /// Stops drone monitors.
    private func stopDroneMonitors() {
        // Forget references linked to the current drone to stop their monitoring.

        droneStateRef = nil
    }

    /// Monitor current drone state.
    private func monitorDroneState() {
        // Monitor current drone state.
        droneStateRef = drone?.getState { [weak self] state in
            // Called at each drone state update.

            if let self = self, let state = state {
                // Update drone state view.
                self.droneStateTxt.text = state.connectionState.description
            }
        }
    }

Drone battery monitoring#

In order to display the drone battery level, monitor the drone battery info instrument, using getInstrument, then get its batteryLevel.

    // Drone:
    /// Current drone instance.
    private var drone: Drone?
    /// Reference to the current drone state.
    private var droneStateRef: Ref<DeviceState>?
    /// Reference to the current drone battery info instrument.
    private var droneBatteryInfoRef: Ref<BatteryInfo>?

    // User Interface:
    /// Drone state text view.
    @IBOutlet weak var droneStateTxt: UILabel!
    /// Drone battery level text view.
    @IBOutlet weak var droneBatteryTxt: UILabel!

    private func startDroneMonitors() {
        // Monitor drone state.
        monitorDroneState()

        // Monitor drone battery level.
        monitorDroneBatteryLevel()
    }

    /// Stops drone monitors.
    private func stopDroneMonitors() {
        // Forget references linked to the current drone to stop their monitoring.

        droneStateRef = nil
        droneBatteryInfoRef = nil
    }

    /// Monitors current drone battery level.
    private func monitorDroneBatteryLevel() {
        // Monitor the battery info instrument.
        droneBatteryInfoRef = drone?.getInstrument(Instruments.batteryInfo) { [weak self] batteryInfo in
            // Called when the battery info instrument is available and when it changes.

            if let self = self, let batteryInfo = batteryInfo {
                // Update drone battery level view.
                self.droneBatteryTxt.text = "\(batteryInfo.batteryLevel)%"
            }
        }
    }

Reset drone user interface#

When view did load and when you stop monitoring a drone, you have to reset the drone user interface to prevent garbage display.

    override func viewDidLoad() {
        super.viewDidLoad()

        // Reset user interface
        resetDroneUi()

        // Monitor the auto connection facility.
        // Keep the reference to be notified on update.
        autoConnectionRef = groundSdk.getFacility(Facilities.autoConnection) { [weak self] autoConnection in
            // Called when the auto connection facility is available and when it changes.

            if let self = self, let autoConnection = autoConnection {
                // Start auto connection.
                if (autoConnection.state != AutoConnectionState.started) {
                    autoConnection.start()
                }

                // If the drone has changed.
                if (self.drone?.uid != autoConnection.drone?.uid) {
                    if (self.drone != nil) {
                        // Stop to monitor the old drone.
                        self.stopDroneMonitors()

                        // Reset user interface drone part.
                        self.resetDroneUi()
                    }

                    // Monitor the new drone.
                    self.drone = autoConnection.drone
                    if (self.drone != nil) {
                        self.startDroneMonitors()
                    }
                }
            }
        }
    }

    /// Resets drone user interface part.
    private func resetDroneUi() {
        // Reset drone user interface views.
        droneStateTxt.text = DeviceState.ConnectionState.disconnected.description
        droneBatteryTxt.text = ""
    }

Take off / land button#

Now you will add a button to enable take off and landing.

Button layout#

In your layout, add a button at the bottom of the screen. Make it referencing to a takeOffLandBt UIButton IBOutlet. Bind it Touch Up Inside with a takeOffLandBtAction function.

Your storyboard should look like this:

_images/bt_ui.png

And in your ViewController you should have:

    // User Interface:
    /// Drone state text view.
    @IBOutlet weak var droneStateTxt: UILabel!
    /// Drone battery level text view.
    @IBOutlet weak var droneBatteryTxt: UILabel!
    /// Takeoff / land button.
    @IBOutlet weak var takeOffLandBt: UIButton!

    /// Called on takeOff/land button click.
    @IBAction func takeOffLandBtAction(_ sender: Any) {
    }

Manual piloting monitor#

In order to pilot the drone, you have to use the Manual Copter Piloting Interface.

Monitor it using getPilotingItf, and update the button view according to the availability of these actions (canTakeOff / canLand).

    // Drone:
    /// Current drone instance.
    private var drone: Drone?
    /// Reference to the current drone state.
    private var droneStateRef: Ref<DeviceState>?
    /// Reference to the current drone battery info instrument.
    private var droneBatteryInfoRef: Ref<BatteryInfo>?
    /// Reference to a current drone piloting interface.
    private var pilotingItfRef: Ref<ManualCopterPilotingItf>?

    // User Interface:
    /// Drone state text view.
    @IBOutlet weak var droneStateTxt: UILabel!
    /// Drone battery level text view.
    @IBOutlet weak var droneBatteryTxt: UILabel!
    /// Takeoff / land button.
    @IBOutlet weak var takeOffLandBt: UIButton!

    private func startDroneMonitors() {
        // Monitor drone state.
        monitorDroneState()

        // Monitor drone battery level.
        monitorDroneBatteryLevel()

        // Monitor piloting interface.
        monitorPilotingInterface()
    }

    /// Stops drone monitors.
    private func stopDroneMonitors() {
        // Forget references linked to the current drone to stop their monitoring.

        droneStateRef = nil
        droneBatteryInfoRef = nil
        pilotingItfRef = nil
    }

    /// Monitors current drone piloting interface.
    private func monitorPilotingInterface() {
        // Monitor a piloting interface.
        pilotingItfRef = drone?.getPilotingItf(PilotingItfs.manualCopter) { [weak self] itf in
            // Called when the manual copter piloting Interface is available and when it changes.

            if let itf = itf {
                self?.managePilotingItfState(itf: itf)
            } else {
                // Disable the button if the piloting interface is not available.
                self?.takeOffLandBt.isEnabled = false
            }
        }
    }

    /// Manage piloting interface state
    ///
    /// - Parameter itf: the piloting interface
    private func managePilotingItfState(itf: ManualCopterPilotingItf) {
        switch itf.state {
        case ActivablePilotingItfState.unavailable:
            // Piloting interface is unavailable.
            takeOffLandBt.isEnabled = false

        case ActivablePilotingItfState.idle:
            // Piloting interface is idle.
            takeOffLandBt.isEnabled = false

            // Activate the interface.
            _ = itf.activate()

        case ActivablePilotingItfState.active:
            // Piloting interface is active.

            if itf.canTakeOff {
                // Drone can takeOff.
                takeOffLandBt.isEnabled = true
                takeOffLandBt.setTitle("TakeOff", for: .normal)
            } else if itf.canLand {
                // Drone can land.
                takeOffLandBt.isEnabled = true
                takeOffLandBt.setTitle("Land", for: .normal)
            } else {
                // Disable the button.
                takeOffLandBt.isEnabled = false
            }
        }
    }

Take off / landing requests#

Now you need to take off or land the drone when the button is clicked, according to their availabilities.

    /// Called on takeOff/land button click.
    @IBAction func takeOffLandBtAction(_ sender: Any) {
        // Get the piloting interface from its reference.
        if let itf = pilotingItfRef?.value {
            // Do the action according to the interface capabilities
            if itf.canTakeOff {
                // Takeoff
                itf.takeOff()
            } else if itf.canLand {
                // Land
                itf.land()
            }
        }
    }

Video stream#

The next step will allow you to add a live stream video view.

Video layout#

In your storyboard, add a GLKit View with GSStreamView as custom class. (GSStreamView is the Objective-C name of StreamView)

Make it referencing to a streamView StreamView IBOutlet.

Your storyboard should now look like this:

_images/stream_ui.png

And in your ViewController you should have:

    // User Interface:
    /// Video stream view.
    @IBOutlet weak var streamView: StreamView!
    /// Drone state text view.
    @IBOutlet weak var droneStateTxt: UILabel!
    /// Drone battery level text view.
    @IBOutlet weak var droneBatteryTxt: UILabel!
    /// Takeoff / land button.
    @IBOutlet weak var takeOffLandBt: UIButton!

Video display#

In order to display the live video stream in the StreamView, you need to:

    // Drone:
    /// Current drone instance.
    private var drone: Drone?
    /// Reference to the current drone state.
    private var droneStateRef: Ref<DeviceState>?
    /// Reference to the current drone battery info instrument.
    private var droneBatteryInfoRef: Ref<BatteryInfo>?
    /// Reference to a current drone piloting interface.
    private var pilotingItfRef: Ref<ManualCopterPilotingItf>?
    /// Reference to the current drone stream server Peripheral.
    private var streamServerRef: Ref<StreamServer>?
    /// Reference to the current drone live stream.
    private var liveStreamRef: Ref<CameraLive>?

    // User Interface:
    /// Video stream view.
    @IBOutlet weak var streamView: StreamView!
    /// Drone state text view.
    @IBOutlet weak var droneStateTxt: UILabel!
    /// Drone battery level text view.
    @IBOutlet weak var droneBatteryTxt: UILabel!
    /// Takeoff / land button.
    @IBOutlet weak var takeOffLandBt: UIButton!

    /// Resets drone user interface part.
    private func resetDroneUi() {
        // Reset drone user interface views.
        droneStateTxt.text = DeviceState.ConnectionState.disconnected.description
        droneBatteryTxt.text = ""
        takeOffLandBt.isEnabled = false
        // Stop rendering the stream
        streamView.setStream(stream: nil)
    }

    /// Starts drone monitors.
    private func startDroneMonitors() {
        // Monitor drone state.
        monitorDroneState()

        // Monitor drone battery level.
        monitorDroneBatteryLevel()

        // Monitor piloting interface.
        monitorPilotingInterface()

        // Start video stream.
        startVideoStream()
    }

    /// Stops drone monitors.
    private func stopDroneMonitors() {
        // Forget references linked to the current drone to stop their monitoring.

        droneStateRef = nil
        droneBatteryInfoRef = nil
        pilotingItfRef = nil
        liveStreamRef = nil
        streamServerRef = nil
    }

    /// Starts the video stream.
    private func startVideoStream() {
        // Monitor the stream server.
        streamServerRef = drone?.getPeripheral(Peripherals.streamServer) { [weak self] streamServer in
            // Called when the stream server is available and when it changes.

            if let self = self, let streamServer = streamServer {
                // Enable Streaming
                streamServer.enabled = true
                self.liveStreamRef = streamServer.live { liveStream in
                    // Called when the live stream is available and when it changes.

                    if let liveStream = liveStream {
                        // Set the live stream as the stream to be render by the stream view.
                        self.streamView.setStream(stream: liveStream)
                        // Play the live stream.
                        _ = liveStream.play()
                    }
                }
            }
        }
    }

Remote control#

In this section you will see how to connect to a remote control, display its connection state and battery level.

Setup project#

You need to setup your project to support remote controls.

Add to your Info.plist file a new array with Supported external accessory protocols as key and add to it a string item with the value com.parrot.dronesdk

Your Info.plist file should now look like this:

_images/remote_plist.png

Remote control connection#

You can use the auto connection facility as with the drone, and get the remote control from it.

    /// Ground SDk instance.
    private let groundSdk = GroundSdk()
    /// Reference to auto connection.
    private var autoConnectionRef: Ref<AutoConnection>?

    // Remote control:
    /// Current remote control instance.
    private var remote: RemoteControl?

    override func viewDidLoad() {
        super.viewDidLoad()

        // Reset user interface
        resetDroneUi()

        // Monitor the auto connection facility.
        // Keep the reference to be notified on update.
        autoConnectionRef = groundSdk.getFacility(Facilities.autoConnection) { [weak self] autoConnection in
            // Called when the auto connection facility is available and when it changes.

            if let self = self, let autoConnection = autoConnection {
                // Start auto connection.
                if (autoConnection.state != AutoConnectionState.started) {
                    autoConnection.start()
                }

                // If the drone has changed.
                if (self.drone?.uid != autoConnection.drone?.uid) {
                    if (self.drone != nil) {
                        // Stop to monitor the old drone.
                        self.stopDroneMonitors()

                        // Reset user interface drone part.
                        self.resetDroneUi()
                    }

                    // Monitor the new drone.
                    self.drone = autoConnection.drone
                    if (self.drone != nil) {
                        self.startDroneMonitors()
                    }
                }

                // If the remote control has changed.
                if (self.remote?.uid != autoConnection.remoteControl?.uid) {
                    if (self.remote != nil) {
                        // Stop to monitor the old remote.
                        self.stopRemoteMonitors()
                    }

                    // Monitor the new remote.
                    self.remote = autoConnection.remoteControl
                    if (self.remote != nil) {
                        self.startRemoteMonitors()
                    }
                }
            }
        }
    }

    /// Starts remote control monitors.
    private func startRemoteMonitors() {
    }

    /// Stops remote control monitors.
    private func stopRemoteMonitors() {
    }

Remote control user interface#

To display remote control information, add UILabels in your storyboard. One for the remote control connection state, referencing to a remoteStateTxt UILabel IBOutlet and one for the remote control battery level, referencing to a remoteBatteryTxt UILabel IBOutlet`.

Your storyboard should look like this:

_images/remote_ui.png

And with the reset of the remote user interface, your ViewController you should have:

    // User Interface:
    /// Video stream view.
    @IBOutlet weak var streamView: StreamView!
    /// Drone state text view.
    @IBOutlet weak var droneStateTxt: UILabel!
    /// Drone battery level text view.
    @IBOutlet weak var droneBatteryTxt: UILabel!
    /// Remote state level text view.
    @IBOutlet weak var remoteStateTxt: UILabel!
    /// Remote battery level text view.
    @IBOutlet weak var remoteBatteryTxt: UILabel!
    /// Takeoff / land button.
    @IBOutlet weak var takeOffLandBt: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Reset user interface
        resetDroneUi()
        resetRemoteUi()

        // Monitor the auto connection facility.
        // Keep the reference to be notified on update.
        autoConnectionRef = groundSdk.getFacility(Facilities.autoConnection) { [weak self] autoConnection in
            // Called when the auto connection facility is available and when it changes.

            if let self = self, let autoConnection = autoConnection {
                // Start auto connection.
                if (autoConnection.state != AutoConnectionState.started) {
                    autoConnection.start()
                }

                // If the drone has changed.
                if (self.drone?.uid != autoConnection.drone?.uid) {
                    if (self.drone != nil) {
                        // Stop to monitor the old drone.
                        self.stopDroneMonitors()

                        // Reset user interface drone part.
                        self.resetDroneUi()
                    }

                    // Monitor the new drone.
                    self.drone = autoConnection.drone
                    if (self.drone != nil) {
                        self.startDroneMonitors()
                    }
                }

                // If the remote control has changed.
                if (self.remote?.uid != autoConnection.remoteControl?.uid) {
                    if (self.remote != nil) {
                        // Reset user interface Remote part.
                        self.resetRemoteUi()

                        // Stop to monitor the old remote.
                        self.stopRemoteMonitors()
                    }

                    // Monitor the new remote.
                    self.remote = autoConnection.remoteControl
                    if (self.remote != nil) {
                        self.startRemoteMonitors()
                    }
                }
            }
        }
    }

    /// Resets remote user interface part.
    private func resetRemoteUi() {
        // Reset remote control user interface views.
        remoteStateTxt.text = DeviceState.ConnectionState.disconnected.description
        remoteBatteryTxt.text = ""
    }

Remote control state and battery#

As with the drone, set an observer on the remote control state to display its connectionState.

Then monitor the battery info instrument, using getInstrument and display its batteryLevel.

Finally, set to nil the remote control references to stop monitoring them.

    // Remote control:
    /// Current remote control instance.
    private var remote: RemoteControl?
    /// Reference to the current remote control state.
    private var remoteStateRef: Ref<DeviceState>?
    /// Reference to the current remote control battery info instrument.
    private var remoteBatteryInfoRef: Ref<BatteryInfo>?

    /// Starts remote control monitors.
    private func startRemoteMonitors() {
        // Monitor remote state
        monitorRemoteState()

        // Monitor remote battery level
        monitorRemoteBatteryLevel()
    }

    /// Stops remote control monitors.
    private func stopRemoteMonitors() {
        // Forget all references linked to the current remote to stop their monitoring.

        remoteStateRef = nil
        remoteBatteryInfoRef = nil
    }

    /// Monitor current remote control state.
    private func monitorRemoteState() {
        // Monitor current drone state.
        remoteStateRef = remote?.getState { [weak self] state in
            // Called at each remote state update.

            if let self = self, let state = state {
                // Update remote state view.
                self.remoteStateTxt.text = state.connectionState.description
            }
        }
    }

    /// Monitors current remote control battery level.
    private func monitorRemoteBatteryLevel() {
        // Monitor the battery info instrument.
        remoteBatteryInfoRef = remote?.getInstrument(Instruments.batteryInfo) { [weak self] batteryInfo in
            // Called when the battery info instrument is available and when it changes.

            if let self = self, let batteryInfo = batteryInfo {
                // Update drone battery level view.
                self.remoteBatteryTxt.text = "\(batteryInfo.batteryLevel)%"
            }
        }
    }

Full project sources#

Thank you for having followed this tutorial. Hoping it was helpful to you.

You can find the full project on github.

Please feel free to ask questions on the Parrot forum for developers.

Wish you all the best with Ground SDK!