package org.droidplanner.services.android.impl.core.gcs; import android.os.Bundle; import com.o3dr.services.android.lib.coordinate.LatLongAlt; import com.o3dr.services.android.lib.drone.attribute.AttributeEvent; import com.o3dr.services.android.lib.drone.attribute.AttributeEventExtra; import com.o3dr.services.android.lib.drone.attribute.AttributeType; import com.o3dr.services.android.lib.drone.property.DroneAttribute; import com.o3dr.services.android.lib.drone.property.Home; import com.o3dr.services.android.lib.gcs.returnToMe.ReturnToMeState; import com.o3dr.services.android.lib.model.AbstractCommandListener; import com.o3dr.services.android.lib.model.ICommandListener; import com.o3dr.services.android.lib.model.action.Action; import org.droidplanner.services.android.impl.core.MAVLink.command.doCmd.MavLinkDoCmds; import org.droidplanner.services.android.impl.core.drone.DroneInterfaces; import org.droidplanner.services.android.impl.core.drone.DroneInterfaces.AttributeEventListener; import org.droidplanner.services.android.impl.core.drone.autopilot.MavLinkDrone; import org.droidplanner.services.android.impl.core.drone.manager.MavLinkDroneManager; import org.droidplanner.services.android.impl.core.gcs.location.Location; import org.droidplanner.services.android.impl.utils.CommonApiUtils; import java.util.concurrent.atomic.AtomicBoolean; import timber.log.Timber; /** * Return to me implementation. * If enabled, listen for user's gps location updates, and accordingly updates the vehicle RTL location. * Created by Fredia Huya-Kouadio on 9/21/15. */ public class ReturnToMe implements DroneInterfaces.OnDroneListener<MavLinkDrone>, Location.LocationReceiver { public static final int UPDATE_MINIMAL_DISPLACEMENT = 5; //meters private static final String TAG = ReturnToMe.class.getSimpleName(); private final static Action requestHomeUpdateAction = new Action(MavLinkDrone.ACTION_REQUEST_HOME_UPDATE); private final AtomicBoolean isEnabled = new AtomicBoolean(false); private final ReturnToMeState currentState; private final MavLinkDroneManager droneMgr; private final Location.LocationFinder locationFinder; private final AttributeEventListener attributeListener; private ICommandListener commandListener; public ReturnToMe(MavLinkDroneManager droneMgr, Location.LocationFinder locationFinder, AttributeEventListener listener) { this.droneMgr = droneMgr; this.locationFinder = locationFinder; this.attributeListener = listener; this.currentState = new ReturnToMeState(); final MavLinkDrone drone = droneMgr.getDrone(); drone.addDroneListener(this); } public void enable(ICommandListener listener) { if (isEnabled.compareAndSet(false, true)) { this.commandListener = listener; final Home droneHome = getHome(); if (droneHome.isValid()) { currentState.setOriginalHomeLocation(droneHome.getCoordinate()); } //Enable return to me Timber.i("Enabling return to me."); locationFinder.enableLocationUpdates(TAG, this); updateCurrentState(ReturnToMeState.STATE_WAITING_FOR_VEHICLE_GPS); } } public void disable() { if (isEnabled.compareAndSet(true, false)) { //Disable return to me Timber.i("Disabling return to me."); locationFinder.disableLocationUpdates(TAG); currentState.setCurrentHomeLocation(null); //Reset the original home location final LatLongAlt originalHomeLocation = currentState.getOriginalHomeLocation(); if(originalHomeLocation != null){ MavLinkDoCmds.setVehicleHome(droneMgr.getDrone(), originalHomeLocation, new AbstractCommandListener(){ @Override public void onSuccess() { Timber.i("Updated vehicle home location to %s", originalHomeLocation.toString()); droneMgr.getDrone().executeAsyncAction(requestHomeUpdateAction, null); } @Override public void onError(int executionError) { Timber.e("Unable to update vehicle home location: %d", executionError); } @Override public void onTimeout() { Timber.w("Vehicle home update timed out!"); } }); } updateCurrentState(ReturnToMeState.STATE_IDLE); this.commandListener = null; } } @Override public void onLocationUpdate(Location location) { if (location.isAccurate()) { final Home home = getHome(); if (!home.isValid()) { updateCurrentState(ReturnToMeState.STATE_WAITING_FOR_VEHICLE_GPS); return; } final LatLongAlt homePosition = home.getCoordinate(); //Calculate the displacement between the home location and the user location. final LatLongAlt locationCoord = location.getCoord(); final float[] results = new float[3]; android.location.Location.distanceBetween(homePosition.getLatitude(), homePosition.getLongitude(), locationCoord.getLatitude(), locationCoord.getLongitude(), results); final float displacement = results[0]; if (displacement >= UPDATE_MINIMAL_DISPLACEMENT) { MavLinkDoCmds.setVehicleHome(droneMgr.getDrone(), new LatLongAlt(locationCoord.getLatitude(), locationCoord.getLongitude(), homePosition.getAltitude()), new AbstractCommandListener() { @Override public void onSuccess() { Timber.i("Updated vehicle home location to %s", locationCoord.toString()); droneMgr.getDrone().executeAsyncAction(requestHomeUpdateAction, null); CommonApiUtils.postSuccessEvent(commandListener); updateCurrentState(ReturnToMeState.STATE_UPDATING_HOME); } @Override public void onError(int executionError) { Timber.e("Unable to update vehicle home location: %d", executionError); CommonApiUtils.postErrorEvent(executionError, commandListener); updateCurrentState(ReturnToMeState.STATE_ERROR_UPDATING_HOME); } @Override public void onTimeout() { Timber.w("Vehicle home update timed out!"); CommonApiUtils.postTimeoutEvent(commandListener); updateCurrentState(ReturnToMeState.STATE_ERROR_UPDATING_HOME); } }); } } else { updateCurrentState(ReturnToMeState.STATE_USER_LOCATION_INACCURATE); } } private Home getHome() { return (Home) droneMgr.getDrone().getAttribute(AttributeType.HOME); } @Override public void onLocationUnavailable() { if (isEnabled.get()) { updateCurrentState(ReturnToMeState.STATE_USER_LOCATION_UNAVAILABLE); disable(); } } @Override public void onDroneEvent(DroneInterfaces.DroneEventsType event, MavLinkDrone drone) { switch (event) { case DISCONNECTED: //Stops updating the vehicle RTL location disable(); break; case HOME: if (isEnabled.get()) { final LatLongAlt homeCoord = getHome().getCoordinate(); if (currentState.getOriginalHomeLocation() == null) currentState.setOriginalHomeLocation(homeCoord); else { currentState.setCurrentHomeLocation(homeCoord); } } break; } } private void updateCurrentState(@ReturnToMeState.ReturnToMeStates int state) { this.currentState.setState(state); if (attributeListener != null) { final Bundle eventInfo = new Bundle(); eventInfo.putInt(AttributeEventExtra.EXTRA_RETURN_TO_ME_STATE, state); attributeListener.onAttributeEvent(AttributeEvent.RETURN_TO_ME_STATE_UPDATE, eventInfo); } } public DroneAttribute getState() { return currentState; } }