Skip to content

Commit

Permalink
Added a mission example application
Browse files Browse the repository at this point in the history
  • Loading branch information
RyanHurst authored and JonasVautherin committed Apr 11, 2019
1 parent 3b837d0 commit fb9d8b4
Show file tree
Hide file tree
Showing 14 changed files with 428 additions and 81 deletions.
33 changes: 10 additions & 23 deletions examples/android-client/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
apply plugin: 'com.android.application'

repositories {
jcenter()
}

configurations {
checkstyleClasspath
}

android {
compileSdkVersion 27
compileSdkVersion 28

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand All @@ -18,14 +14,12 @@ android {

defaultConfig {
applicationId "io.dronecore.dronecoreclient"
minSdkVersion 14
targetSdkVersion 27
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
Expand Down Expand Up @@ -58,20 +52,13 @@ build.dependsOn 'checkstyle'

dependencies {
checkstyleClasspath 'com.puppycrawl.tools:checkstyle:8.5'

implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.google.android.gms:play-services-maps:16.1.0'
implementation "android.arch.lifecycle:extensions:1.1.1"
implementation 'org.dronecode.sdk:dronecode-sdk:0.1.0'

implementation 'com.android.support:appcompat-v7:27.0.2'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.github.tony19:logback-android-core:1.1.1-6'
implementation('com.github.tony19:logback-android-classic:1.1.1-6') {
// workaround logback-android issue #73
exclude group: 'com.google.android', module: 'android'
}
implementation 'org.slf4j:slf4j-api:1.7.25'

testImplementation 'junit:junit:4.12'

androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
28 changes: 20 additions & 8 deletions examples/android-client/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.dronecore.dronecoreclient">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="false"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning"
tools:targetApi="donut">
<activity android:name=".MainActivity">
android:theme="@style/AppTheme">

<!--
The API key for Google Maps-based APIs is defined as a string resource.
(See the file "res/values/google_maps_api.xml").
Note that the API key is linked to the encryption key used to sign the APK.
You need a different API key for each encryption key, including the release key that is used to
sign the APK for publishing.
You can define the keys for the debug and release targets in src/debug/ and src/release/.
-->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />

<activity
android:name=".MapsActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package io.dronecore.dronecoreclient;

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

import java.util.ArrayList;
import java.util.List;

import io.dronecode_sdk.action.Action;
import io.dronecode_sdk.mission.Mission;
import io.dronecode_sdk.telemetry.Telemetry;
import io.reactivex.disposables.Disposable;

import static com.google.android.gms.maps.GoogleMap.MAP_TYPE_HYBRID;

/**
* Main Activity to display map and create missions.
* 1. Take off
* 2. Long click on map to add a waypoint
* 3. Touch an existing waypoint to delete it
* 4. Hit play to start mission.
*/
public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback, GoogleMap.OnMarkerDragListener {

public static final String TAG = "MapsActivity";
public static final String BACKEND_IP_ADDRESS = "10.42.0.65"; // todo update this to ip address of device running the backend. remove this once the backend is included in the app

private GoogleMap map = null;
private MapsViewModel viewModel;
private Marker currentPositionMarker;
private final List<Marker> markers = new ArrayList<>();
private Action action;
private Mission mission;
private Telemetry telemetry;
private final List<Disposable> disposables = new ArrayList<>();

private Observer<LatLng> currentPositionObserver = this::updateVehiclePosition;
private Observer<List<LatLng>> currentMissionPlanObserver = this::updateMarkers;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
viewModel = ViewModelProviders.of(this).get(MapsViewModel.class);

// Obtain the SupportMapFragment and get notified when the map is ready to be used.
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
mapFragment.getMapAsync(this);

FloatingActionButton floatingActionButton = findViewById(R.id.fab);
floatingActionButton.setOnClickListener(v -> viewModel.startMission(mission));
}

@Override
public void onResume() {
super.onResume();
viewModel.currentPositionLiveData.observe(this, currentPositionObserver);
viewModel.currentMissionPlanLiveData.observe(this, currentMissionPlanObserver);
action = new Action(BACKEND_IP_ADDRESS, 50051); // TODO: 4/4/19 50051 should be a constant in the sdk somewhere
mission = new Mission(BACKEND_IP_ADDRESS, 50051);
telemetry = new Telemetry(BACKEND_IP_ADDRESS, 50051);
disposables.add(telemetry.getFlightMode().distinct()
.subscribe(flightMode -> Log.d(TAG, "flight mode: " + flightMode)));
disposables.add(telemetry.getArmed().distinct()
.subscribe(armed -> Log.d(TAG, "armed: " + armed)));
disposables.add(telemetry.getPosition().subscribe(position -> {
LatLng latLng = new LatLng(position.getLatitudeDeg(), position.getLongitudeDeg());
viewModel.currentPositionLiveData.postValue(latLng);
}));
}

@Override
public void onPause() {
super.onPause();
viewModel.currentPositionLiveData.removeObserver(currentPositionObserver);
viewModel.currentMissionPlanLiveData.removeObserver(currentMissionPlanObserver);
for (Disposable disposable : disposables) {
disposable.dispose();
}

// TODO: 4/10/19 close these channels properly
action = null;
mission = null;
telemetry = null;
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_maps, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.disarm:
action.kill().subscribe();
break;
case R.id.land:
action.land().subscribe();
break;
case R.id.return_home:
action.returnToLaunch().subscribe();
break;
case R.id.takeoff:
action.arm().andThen(action.takeoff()).subscribe();
default:
return super.onOptionsItemSelected(item);
}
return true;
}

/**
* Update [currentPositionMarker] position with a new [position]
*
* @param newLatLng new position of the vehicle
*/
private void updateVehiclePosition(@Nullable LatLng newLatLng) {
if (newLatLng != null && map != null) {
// Add a vehicle marker and move the camera
if (currentPositionMarker == null) {
currentPositionMarker = map.addMarker(new MarkerOptions().position(newLatLng)
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_CYAN)));
map.moveCamera(CameraUpdateFactory.newLatLngZoom(newLatLng, 20.0f));
} else {
currentPositionMarker.setPosition(newLatLng);
}
}
}

/**
* Update the [map] with the current mission plan waypoints
*
* @param latLngs current mission waypoints
*/
private void updateMarkers(@Nullable List<LatLng> latLngs) {
if (map == null) {
return;
}
for (Marker marker : markers) {
marker.remove();
}
markers.clear();
if (latLngs != null) {
Log.d("Waypoints", "received mission plan with size " + latLngs.size());
for (LatLng latLng : latLngs) {
markers.add(map.addMarker(new MarkerOptions()
.position(latLng)
.draggable(true)));
}
}
}

@Override
public void onMarkerDragEnd(Marker marker) {
if (marker == null) {
return;
}

int index = markers.indexOf(marker);
viewModel.replaceWaypoint(index, marker.getPosition());
}

@Override
public void onMarkerDragStart(Marker marker) {
}

@Override
public void onMarkerDrag(Marker marker) {
}

/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the camera.
* If Google Play services is not installed on the device, the user will be prompted to install
* it inside the SupportMapFragment. This method will only be triggered once the user has
* installed Google Play services and returned to the app.
*/
@Override
public void onMapReady(GoogleMap googleMap) {
map = googleMap;
map.setOnMapLongClickListener(latLng -> viewModel.addWaypoint(markers.size(), latLng));

map.setOnMarkerClickListener(marker -> {
int index = markers.indexOf(marker);
if (index != -1) {
viewModel.removeWaypoint(index);
}
return true;
});

map.setMapType(MAP_TYPE_HYBRID);
map.setOnMarkerDragListener(this);
updateMarkers(viewModel.currentMissionPlanLiveData.getValue());
}
}
Loading

0 comments on commit fb9d8b4

Please sign in to comment.