Ground SDK Hello Drone Tutorial#
Ground SDK Hello Drone Tutorial is a step-by-step guide that helps you develop an Android application using Ground SDK Android 7.7. This application is able to connect to an ANAFI drone and a Skycontroller remote control, display the battery charge 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 environement
Setup your project to use Ground SDK Android 7.7
Connect to a drone
Display drone connection state
Display drone battery charge 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 charge level
The full project is available here.
Note
This tutorial is based on Ground SDK Android version 7.7.
Prerequisites#
Before starting this tutorial, you have to:
Setup project#
First you need to configure your project to use Ground SDK Android.
For this purpose, open the application app/build.gradle file, and add the Ground SDK Android dependencies:
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// GroundSdk dependencies
implementation 'com.parrot.drone.groundsdk:groundsdk:7.7.+'
runtimeOnly 'com.parrot.drone.groundsdk:arsdkengine:7.7.+'
}
This allows to downlaod and link Ground SDK AAR to the project.
To make your project compatible with Ground SDK you need to increase the minimun Android SDK version supported by your project.
In the same file:
android {
compileSdk 31
defaultConfig {
applicationId "com.parrot.hellodrone"
// Set the minimum SDK version supported by GroundSdk
minSdkVersion 24
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Your project setup is ready, let’s start coding!
Get GroundSdk session#
In order to use GroundSdk in your application, you first have to obtain a GroundSdk session at the activity creation. So open your activity file, and add:
class MainActivity : AppCompatActivity() {
/** GroundSdk instance. */
private lateinit var groundSdk: GroundSdk
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get a GroundSdk session.
groundSdk = ManagedGroundSdk.obtainSession(this)
// All references taken are linked to the activity lifecycle and
// automatically closed at its destruction.
}
}
This GroundSdk session keeps and manages all GroundSdk references, according to the Android Activity lifecycle.
Drone connection#
To connect to a drone, you should use the AutoConnection facility.
At the Activity start, get the facility and start it.
override fun onStart() {
super.onStart()
// Monitor the auto connection facility.
groundSdk.getFacility(AutoConnection::class.java) {
// Called when the auto connection facility is available and when it changes.
it?.let{
// Start auto connection.
if (it.status != AutoConnection.Status.STARTED) {
it.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? = null
override fun onStart() {
super.onStart()
// Monitor the auto connection facility.
groundSdk.getFacility(AutoConnection::class.java) {
// Called when the auto connection facility is available and when it changes.
it?.let{
// Start auto connection.
if (it.status != AutoConnection.Status.STARTED) {
it.start()
}
// If the drone has changed.
if (drone?.uid != it.drone?.uid) {
if(drone != null) {
// Stop monitoring the old drone.
stopDroneMonitors()
}
// Monitor the new drone.
drone = it.drone
if(drone != null) {
startDroneMonitors()
}
}
}
}
}
/**
* Starts drone monitors.
*/
private fun startDroneMonitors() {
}
/**
* Stops drone monitors.
*/
private fun stopDroneMonitors() {
}
Drone monitoring#
Now you will monitor and display the drone connection state and its battery state.
Drone user interface#
To display drone information, replace the default TextView by your own TextViews in your layout.
Your res/layout/activity_main.xml file should look like this:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/labelDroneControl"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Drone"/>
<TextView
android:id="@+id/droneStateTxt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:text="state"
android:textAlignment="center"/>
<TextView
android:id="@+id/droneBatteryTxt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text=""
android:textAlignment="textEnd"/>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Then get and initialize this new text views in your activity.
// User Interface:
/** Drone state text view. */
private lateinit var droneStateTxt: TextView
/** Drone battery charge level text view. */
private lateinit var droneBatteryTxt: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get user interface instances.
droneStateTxt = findViewById(R.id.droneStateTxt)
droneBatteryTxt = findViewById(R.id.droneBatteryTxt)
// Initialize user interface default values.
droneStateTxt.text = DeviceState.ConnectionState.DISCONNECTED.toString()
// Get a GroundSdk session.
groundSdk = ManagedGroundSdk.obtainSession(this)
// All references taken are linked to the activity lifecycle and
// automatically closed at its destruction.
}
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, close the drone state reference.
// Drone:
/** Current drone instance. */
private var drone: Drone? = null
/** Reference to the current drone state. */
private var droneStateRef: Ref<DeviceState>? = null
/**
* Starts drone monitors.
*/
private fun startDroneMonitors() {
// Monitor drone state.
monitorDroneState()
}
/**
* Stops drone monitors.
*/
private fun stopDroneMonitors() {
// Close all references linked to the current drone to stop their monitoring.
droneStateRef?.close()
droneStateRef = null
}
/**
* Monitor current drone state.
*/
private fun monitorDroneState() {
// Monitor current drone state.
droneStateRef = drone?.getState {
// Called at each drone state update.
it?.let {
// Update drone connection state view.
droneStateTxt.text = it.connectionState.toString()
}
}
}
Drone battery monitoring#
In order to display the drone battery charge level, monitor the drone battery info instrument, using getInstrument, then get its charge level.
// Drone:
/** Current drone instance. */
private var drone: Drone? = null
/** Reference to the current drone state. */
private var droneStateRef: Ref<DeviceState>? = null
/** Reference to the current drone battery info instrument. */
private var droneBatteryInfoRef: Ref<BatteryInfo>? = null
/**
* Starts drone monitors.
*/
private fun startDroneMonitors() {
// Monitor drone state.
monitorDroneState()
// Monitor drone battery charge level.
monitorDroneBatteryChargeLevel()
}
/**
* Stops drone monitors.
*/
private fun stopDroneMonitors() {
// Close all references linked to the current drone to stop their monitoring.
droneStateRef?.close()
droneStateRef = null
droneBatteryInfoRef?.close()
droneBatteryInfoRef = null
}
/**
* Monitors current drone battery charge level.
*/
private fun monitorDroneBatteryChargeLevel() {
// Monitor the battery info instrument.
droneBatteryInfoRef = drone?.getInstrument(BatteryInfo::class.java) {
// Called when the battery info instrument is available and when it changes.
it?.let {
// Update drone battery charge level view.
droneBatteryTxt.text = getString(R.string.percentage, it.charge)
}
}
}
Reset drone user interface#
When you stop monitoring a drone, you have to reset the drone user interface to prevent garbage display.
override fun onStart() {
super.onStart()
// Monitor the auto connection facility.
groundSdk.getFacility(AutoConnection::class.java) {
// Called when the auto connection facility is available and when it changes.
it?.let{
// Start auto connection.
if (it.status != AutoConnection.Status.STARTED) {
it.start()
}
// If the drone has changed.
if (drone?.uid != it.drone?.uid) {
if(drone != null) {
// Stop monitoring the old drone.
stopDroneMonitors()
// Reset user interface drone part.
resetDroneUi()
}
// Monitor the new drone.
drone = it.drone
if(drone != null) {
startDroneMonitors()
}
}
}
}
}
/**
* Resets drone user interface part.
*/
private fun resetDroneUi() {
// Reset drone user interface views.
droneStateTxt.text = DeviceState.ConnectionState.DISCONNECTED.toString()
droneBatteryTxt.text = ""
}
Video stream#
The next step will allow you to add a live stream video view.
Video layout#
In your res/layout/activity_main.xml layout, add a GsdkStreamView
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/labelDroneControl"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Drone"/>
<TextView
android:id="@+id/droneStateTxt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:text="state"
android:textAlignment="center"/>
<TextView
android:id="@+id/droneBatteryTxt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text=""
android:textAlignment="textEnd"/>
</LinearLayout>
</LinearLayout>
<com.parrot.drone.groundsdk.stream.GsdkStreamView
android:id="@+id/stream_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/info"
app:layout_constraintBottom_toTopOf="@id/takeOffLandBt"
tools:layout_editor_absoluteY="38dp">
</com.parrot.drone.groundsdk.stream.GsdkStreamView>
<Button
android:id="@+id/takeOffLandBt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:text="take off"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Then get it in your activity.
// User Interface:
/** Video stream view. */
private lateinit var streamView: GsdkStreamView
/** Drone state text view. */
private lateinit var droneStateTxt: TextView
/** Drone battery charge level text view. */
private lateinit var droneBatteryTxt: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get user interface instances.
streamView = findViewById(R.id.stream_view)
droneStateTxt = findViewById(R.id.droneStateTxt)
droneBatteryTxt = findViewById(R.id.droneBatteryTxt)
// Initialize user interface default values.
droneStateTxt.text = DeviceState.ConnectionState.DISCONNECTED.toString()
// Get a GroundSdk session.
groundSdk = ManagedGroundSdk.obtainSession(this)
// All references taken are linked to the activity lifecycle and
// automatically closed at its destruction.
}
Video display#
In order to display the live video stream in the GsdkStreamView, you need to:
Monitor the stream server peripheral
Start to play the stream
Detach the stream from the GsdkStreamView when you want to stop rendering the stream
// Drone:
/** Current drone instance. */
private var drone: Drone? = null
/** Reference to the current drone state. */
private var droneStateRef: Ref<DeviceState>? = null
/** Reference to the current drone battery info instrument. */
private var droneBatteryInfoRef: Ref<BatteryInfo>? = null
/** Reference to a current drone piloting interface. */
private var pilotingItfRef: Ref<ManualCopterPilotingItf>? = null
/** Reference to the current drone stream server Peripheral. */
private var streamServerRef: Ref<StreamServer>? = null
/** Reference to the current drone live stream. */
private var liveStreamRef: Ref<CameraLive>? = null
/** Current drone live stream. */
private var liveStream: CameraLive? = null
/**
* Resets drone user interface part.
*/
private fun resetDroneUi() {
// Reset drone user interface views.
droneStateTxt.text = DeviceState.ConnectionState.DISCONNECTED.toString()
droneBatteryTxt.text = ""
takeOffLandBt.isEnabled = false
// Stop rendering the stream
streamView.setStream(null)
}
/**
* Starts drone monitors.
*/
private fun startDroneMonitors() {
// Monitor drone state.
monitorDroneState()
// Monitor drone battery charge level.
monitorDroneBatteryChargeLevel()
// Monitor piloting interface.
monitorPilotingInterface()
// Start video stream.
startVideoStream()
}
/**
* Stops drone monitors.
*/
private fun stopDroneMonitors() {
// Close all references linked to the current drone to stop their monitoring.
droneStateRef?.close()
droneStateRef = null
droneBatteryInfoRef?.close()
droneBatteryInfoRef = null
pilotingItfRef?.close()
pilotingItfRef = null
liveStreamRef?.close()
liveStreamRef = null
streamServerRef?.close()
streamServerRef = null
liveStream = null
}
/**
* Starts the video stream.
*/
private fun startVideoStream() {
// Monitor the stream server.
streamServerRef = drone?.getPeripheral(StreamServer::class.java) { streamServer ->
// Called when the stream server is available and when it changes.
if (streamServer != null) {
// Enable Streaming
if(!streamServer.streamingEnabled()) {
streamServer.enableStreaming(true)
}
// Monitor the live stream.
if (liveStreamRef == null) {
liveStreamRef = streamServer.live { liveStream ->
// Called when the live stream is available and when it changes.
if (liveStream != null) {
if (this.liveStream == null) {
// It is a new live stream.
// Set the live stream as the stream
// to be render by the stream view.
streamView.setStream(liveStream)
}
// Play the live stream.
if (liveStream.playState() != CameraLive.PlayState.PLAYING) {
liveStream.play()
}
} else {
// Stop rendering the stream
streamView.setStream(null)
}
// Keep the live stream to know if it is a new one or not.
this.liveStream = liveStream
}
}
} else {
// Stop monitoring the live stream
liveStreamRef?.close()
liveStreamRef = null
// Stop rendering the stream
streamView.setStream(null)
}
}
}
Remote control#
In this section you will see how to connect to a remote control, display its connection state and battery charge level.
Remote control connection#
You can use the auto connection facility as with the drone, and get the remote control from it.
// Remote control:
/** Current remote control instance. */
private var rc: RemoteControl? = null
override fun onStart() {
super.onStart()
// Monitor the auto connection facility.
groundSdk.getFacility(AutoConnection::class.java) {
// Called when the auto connection facility is available and when it changes.
it?.let{
// Start auto connection.
if (it.status != AutoConnection.Status.STARTED) {
it.start()
}
// If the drone has changed.
if (drone?.uid != it.drone?.uid) {
if(drone != null) {
// Stop monitoring the old drone.
stopDroneMonitors()
// Reset user interface drone part.
resetDroneUi()
}
// Monitor the new drone.
drone = it.drone
if(drone != null) {
startDroneMonitors()
}
}
// If the remote control has changed.
if (rc?.uid != it.remoteControl?.uid) {
if(rc != null) {
// Stop monitoring the old remote.
stopRcMonitors()
// Reset user interface Remote part.
resetRcUi()
}
// Monitor the new remote.
rc = it.remoteControl
if(rc != null) {
startRcMonitors()
}
}
}
}
}
/**
* Resets remote user interface part.
*/
private fun resetRcUi() {
}
/**
* Starts remote control monitors.
*/
private fun startRcMonitors() {
}
/**
* Stops remote control monitors.
*/
private fun stopRcMonitors() {
}
Remote control user interface#
To display remote control information, add TextViews in the res/layout/activity_main.xml layout.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/labelDroneControl"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Drone"/>
<TextView
android:id="@+id/droneStateTxt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:text="state"
android:textAlignment="center"/>
<TextView
android:id="@+id/droneBatteryTxt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text=""
android:textAlignment="textEnd"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/labelRemoteControl"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Remote"/>
<TextView
android:id="@+id/rcStateTxt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:text="state"
android:textAlignment="center"/>
<TextView
android:id="@+id/rcBatteryTxt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text=""
android:textAlignment="textEnd"/>
</LinearLayout>
</LinearLayout>
<com.parrot.drone.groundsdk.stream.GsdkStreamView
android:id="@+id/stream_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/info"
app:layout_constraintBottom_toTopOf="@id/takeOffLandBt"
tools:layout_editor_absoluteY="38dp">
</com.parrot.drone.groundsdk.stream.GsdkStreamView>
<Button
android:id="@+id/takeOffLandBt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:text="take off"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Then get, initialize and reset this new text views in your activity.
// User Interface:
/** Video stream view. */
private lateinit var streamView: GsdkStreamView
/** Drone state text view. */
private lateinit var droneStateTxt: TextView
/** Drone battery charge level text view. */
private lateinit var droneBatteryTxt: TextView
/** Remote state level text view. */
private lateinit var rcStateTxt: TextView
/** Remote battery charge level text view. */
private lateinit var rcBatteryTxt: TextView
/** Take off / land button. */
private lateinit var takeOffLandBt: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get user interface instances.
streamView = findViewById(R.id.stream_view)
droneStateTxt = findViewById(R.id.droneStateTxt)
droneBatteryTxt = findViewById(R.id.droneBatteryTxt)
rcStateTxt = findViewById(R.id.rcStateTxt)
rcBatteryTxt = findViewById(R.id.rcBatteryTxt)
takeOffLandBt = findViewById(R.id.takeOffLandBt)
takeOffLandBt.setOnClickListener {onTakeOffLandClick()}
// Initialize user interface default values.
droneStateTxt.text = DeviceState.ConnectionState.DISCONNECTED.toString()
rcStateTxt.text = DeviceState.ConnectionState.DISCONNECTED.toString()
// Get a GroundSdk session.
groundSdk = ManagedGroundSdk.obtainSession(this)
// All references taken are linked to the activity lifecycle and
// automatically closed at its destruction.
}
/**
* Resets remote user interface part.
*/
private fun resetRcUi() {
// Reset remote control user interface views.
rcStateTxt.text = DeviceState.ConnectionState.DISCONNECTED.toString()
rcBatteryTxt.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 charge level.
Finally, close the remote control references to stop monitoring them.
// Remote control:
/** Current remote control instance. */
private var rc: RemoteControl? = null
/** Reference to the current remote control state. */
private var rcStateRef: Ref<DeviceState>? = null
/** Reference to the current remote control battery info instrument. */
private var rcBatteryInfoRef: Ref<BatteryInfo>? = null
/**
* Starts remote control monitors.
*/
private fun startRcMonitors() {
// Monitor remote state
monitorRcState()
// Monitor remote battery charge level
monitorRcBatteryChargeLevel()
}
/**
* Stops remote control monitors.
*/
private fun stopRcMonitors() {
// Close all references linked to the current remote to stop their monitoring.
rcStateRef?.close()
rcStateRef = null
rcBatteryInfoRef?.close()
rcBatteryInfoRef = null
}
/**
* Monitor current remote control state.
*/
private fun monitorRcState() {
// Monitor current drone state.
rcStateRef = rc?.getState {
// Called at each remote state update.
it?.let {
// Update remote connection state view.
rcStateTxt.text = it.connectionState.toString()
}
}
}
/**
* Monitors current remote control battery charge level.
*/
private fun monitorRcBatteryChargeLevel() {
// Monitor the battery info instrument.
rcBatteryInfoRef = rc?.getInstrument(BatteryInfo::class.java) {
// Called when the battery info instrument is available and when it changes.
it?.let {
// Update drone battery charge level view.
rcBatteryTxt.text = getString(R.string.percentage, it.charge)
}
}
}
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!