GroundSdk Hello Drone Tutorial

GroundSdk Hello Drone Tutorial is a step-by-step guide that helps you develop an iOS application using GroundSdk iOS 1.1.1. This application is able to connect to an Anafi drone and a Skycontroller 3 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 GroundSdk iOS 1.1.1
  • 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 GroundSdk iOS version 1.1.1.

Prerequisites

Before starting this tutorial, you have to:

Setup project

First you need to configure your project to use GroundSdk iOS Pods.

Cocoapods install

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

$ sudo gem install cocoapods

Adding GroundSdk 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, '10.0'

target 'HelloDrone' do
    use_frameworks!
    pod 'GroundSdk', '1.1.1'
    pod 'ArsdkEngine', '1.1.1'
    pod 'SdkCore', '1.1.1'
end

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.

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

GroundSdk instance

In order to use GroundSdk 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 GroundSdk!