Jun 20, 2024

MAVSDK on Android

MAVSDK on Android

Learn to develop Android apps to control MAVLink drones using MAVSDK. Set up the environment, write Java code, and create missions with telemetry data.

If you’ve landed here, chances are you know that MAVLink is the most widely used drone communication protocol outside of DJI. The MAVSDK provides collection of libraries designed to interface with MAVLink systems such as drones, cameras, or ground control systems (GCS). It provides a simple API for managing one or more vehicles, offering programmatic access to vehicle information, telemetry, and control over missions, movement, and other operations. Quite simply, you can use it to build apps to control your drone (and other cool autonomous systems). At a recent gathering of the Drone Software Meetup Group, Godfrey Nolan, President of RIIS LLC. showed attendees how to get started with the MAVSDK on Android. Feel free to follow along with the video or read the blog below to jump start your MAVSDK development. This post includes both a “Stupid Simple Example” as well as a more hand’s on tutorial if you want to roll up your sleeves and get your hands dirty.



Stupid Simple Setup

For our Stupid Simple Example, we will be using Java for a Software in the Loop (SITL) simulation. By coding with the MAVSDK we are skipping over QGroundControl (QGC). Although QGC has a lot of benefits and a great suite of tools, it’s a whole other tutorial, and we want to keep things simple. For our example we will be using PX4, but Ardupilot can also be used.

The SITL setup doesn’t require much in terms of hardware. In fact, this is a pretty bare bones setup for this tutorial.

However, you do need to follow several steps to ensure that your development environment is correctly configured.

Prerequisites

  1. Docker: Ensure Docker is installed on your machine. Docker will be used to run the headless simulator.

  2. Android Studio: Install Android Studio for developing the Android client.

  3. MAVSDK-Java: Clone the MAVSDK-Java repository from GitHub.

Step-by-Step Setup

1. Clone the MAVSDK-Java Repository

git clone https://github.com/mavlink/MAVSDK-Java.git
cd

2. Set up Docker

docker run --rm -it -p 8554:8554 jonasvautherin/px4-gazebo-headless:1.13.2 -v

Special thanks to Jonas Vautherin, Software Engineer, Auterion for providing this ^^

3. Edit MapsActivity.java

Open the android-client in the examples folder in Android Studio. Also, make sure your Android Gradle Plugin version is 4.0.1 and your Gradle Version is 6.5.

Look for the runMavsdkServer().

Then change this:

int mavsdkServerPort=mavskdServer.run();

To this:

int mavsdkServerPort=mavskdServer.run("udp://14550");

If you were paying attention to the SITL diagram earlier you will notice that the port we are running the mavSDKServer on is the one that points to the GCS.

Sample Client

Make sure your phone is running is connected to your computer via USB and set to debug mode, then hit the play button in Android Studio to see what happens.


A More Hand’s On Example

A 2-Minute Crash Course on Android Development

For an Android app you need a layout file, activity_main.xml and you need a main activity file, MainActivity.java. Your layout is controlled by layout a file, and the main activity file is were things get done, like buttons, functions etc.

Creating a Layout

For simplicities sake, we are only trying to do three things, connect to the server, take off, and land, so we need to build a layout within activity_main.xml that includes three buttons, one for each of those functions.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <Button
        android:id="@+id/btn_server"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="MAVSDK Server" />

    <Button
        android:id="@+id/btn_takeoff"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Take Off"
        android:layout_below="@id/btn_server" 
        android:layout_marginTop="16dp" />

    <Button
        android:id="@+id/btn_land"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Land"
        android:layout_below="@id/btn_takeoff" 
        android:layout_marginTop="16dp"/>

Writing the Functions and Listeners in MainActivity.java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnServer = findViewById(R.id.btn_server);
        btnTakeoff = findViewById(R.id.btn_takeoff);
        btnLand = findViewById(R.id.btn_land);

        btnServer.setOnClickListener(v -> initializeServerAndDrone());
        btnTakeoff.setOnClickListener(v -> takeOff());
        btnLand.setOnClickListener(v -> land());
    }

    private void initializeServerAndDrone() {
        mavsdkServer = new MavsdkServer();
        int mavsdkServerPort = mMavsdkServer.run("udp://:14540");
        mDrone = new System(MAVSDK_SERVER_IP, mavsdkServerPort);
    }

    private void takeOff() {
        mDrone.getAction.arm().andThen(mDrone.getAction().takeoff()).subscribe();
    }

    private void land() {
        mDrone.getAction().arm().andThen(mDrone.getAction().land()).subscribe();
    }

}

In MainActivity.java add the below code within the MainActivity class.

What we are doing here is really simple. We create the btn variables and connect them to our layout buttons by using the findViewById method. Then we add listeners to each of those variables which will invoke the functions at the bottom of the code for each of the corresponding buttons.

Let’s breakdown the functions more.

initializeServerAndDrone()

The first line creates a new instance of the MavsdkServer class, which is responsible for handling the communication between the application and the drone. Then we bind the server to the UDP port. Lastly we create a new System object which represents the drone.

takeoff()

This arms the drone, issues a takeoff command and then subscribes to the observable stream created by the chained commands.

land()

Land functions similarly except it issues a land command.

Updating the Dependencies

Add the following changes within dependencies{}{ in the build.gradle file:

dependencies {
    implementation 'io.mavsdk:mavsdk'
    implementation 'io.mavsdk:mavsdk-server'
}

Run the App for the First Time

In the terminal run docker run --rm -it jonasvautherin/px4-gazebo-headless:1.11.0.

Make sure your Android device is connected via USB with debugging enabled. Run the app on the device and it should connect to the simulator on your machine.

Take Off!

Hit the Take off button in the app on the Android device and you should see this in the command line:

All the log info here is from the thread you subscribed to in the takeoff().

Landing!

Hitting the landing button should give you this set of logs:

What Next?

We can make drone go up and make drone go down, but what if we want to do something more interesting or useful. In order to make a drone do anything interesting we will need telemetry information. Here is a non-comprehensive list of the telemetry data you can receive from the drone.

Why don’t we start by getting some altitude data. Add this code to MainActivity.java:

private void getAltitude() {
	// get the altitude of the drone
	Flowable<Float> altitude = mDrone.getTelemetry().getPosition().map(new Function<Telemetry.Position position) throws Exception {
		return position.getRelativeAltitudeM();
	}
});
// subscribe to the altitude flowable
	altitude.subscribeOn(Schedulers.io())
		.observeOn(Schedulers.single())
		.subscribe(new io.reactivex.functions.Consumer<Float>() {
			@Override
			public void accept(Float aFloat) throws Exception {
				Log.d("Altitude", "Altitude: " + aFloat);
			}
		});		
}

This code is using RxJava to retrieve the altitude of a drone from its telemetry data in a reactive way. mDrone.getTelemetry().getPosition() returns a Flowable that emits Telemetry.Position objects containing the drone's position data. The map operator is used to transform each Telemetry.Position object into a Float representing the relative altitude in meters, using the provided lambda function: position -> position.getRelativeAltitudeM() Then the subscribeOn operator is used to specify the Scheduler on which the source Flowable should execute its work while the he observeOn operator is used to specify the Scheduler on which the downstream operators (after observeOn) execute.

More simply, this code retrieves the drone's position data from the telemetry on the IO Scheduler thread pool, transforms each position object into a Float representing the relative altitude, and emits the altitude values on a single-threaded Scheduler. Essentially, we are logging whatever altitude data comes streaming in.

Adding Latitude and Longitude

Going up and down is nice, but it might be useful to go North, South, East, and West also. Let’s add some Latitude and Longitude data to get a better idea of our drone’s position in the world.

private void getLatitudeLongitude() {
	// get the latitude and longitude of the drone
	Flowable<Telemetry.Position> position = mDrone.getTelemetry().getPosition();
	// subscribe to the position flowable
	position.subscribeOn(Schedulers.io())
		.observeOn(Schedulers.single())
		.subscribe(new io.reactivex.functions.Consumer<Telemetry.Position>() {
			@Override
			public void accept(Telemetry.Position position) throws Exception {
				Log.d("Position", "Latitude: " + position.getLatitudeDeg() + " Longitude: " + position.getLongitudeDef());
			}
		});
}

This is very similar to our getAltitude() with but with an important key difference. Here we are no longer transforming the position data into a float.

Before moving on, if you haven’t already, be sure to add Get Altitude and Get Position buttons to the activity_main.xml file.

Now if you hit the Get Position button you should see Latitude and Longitude data in the console.

Creating a Simple Mission

First, add a ‘Start orbit flight’ button within activity_main.xml.

Then add the following code to MainActivity.java:

private void createOrbitFlight() {
	Mission.MissionItem missionItem = new Mission.MissionItem(
		47.39803985999999997,
		8.545572540000000002,
		10F,
		10F,
		true,
		Float.NaN,
		Float.NaN,
		Mission.MissionItem.CameraAction.NONE,
		Float.NaN,
		Double.NaN,
		10F,
		Float.NaN,
		Float.NaN
	);
	List<Mission.MissionItem> missionItems = Collections.singletonList(missionItem);
	Mission.MissionPlan missionPlan = new Mission.MissionPlan(missionItems);
	
	mDrone.getMission().setReturToLaunchAfterMission(true);
	mDrone.getMission().uploadMission(missionPlan)
		.andThen(mDrone.getMission().startMisssion())
		.subscribe();
}

We’ve prefilled our mission with some data, but what does it all mean. The first two numbers are our latitude and longitude. The first 10F is our relative altitude and our second 10F is our speed. The true sets isFlyThrough to on. The last 10F is a relative radius to make sure the drone orbits.

This whole list is actually just one mission item.

After the mission is over we have the drone return to its launch position with mDrone.getMission().setReturToLaunchAfterMission(true); and then we upload the mission and reset the drone’s mission status.

Hit the Play button in Android Studio to build and run.

Assuming this works you should see logs like this in the console.


Final Thoughts

By following this comprehensive tutorial, you should now have the skills to develop Android applications that can interface with and control drones and other autonomous systems using the MAVSDK (MAVLink Software Development Kit). You learned how to set up the required development environment, including installing Docker, Android Studio, and cloning the MAVSDK-Java repository. The tutorial guided you through creating a basic Android app with user interface elements like buttons to connect to a simulated drone server, arm the drone, initiate takeoff and landing commands, and subscribe to telemetry data streams like altitude, latitude, and longitude using RxJava. You gained hands-on experience creating a simple missions and you are now equipped to build more advanced drone applications leveraging the MAVSDK's awesome capabilities.

If you want to learn more about drone development, be sure to join the Drone Software Meetup Group, where you can find more cool projects like this one every month.