package com.o3dr.android.client.apis; import android.net.Uri; import android.os.Bundle; import android.support.annotation.Nullable; import com.o3dr.android.client.Drone; import com.o3dr.services.android.lib.drone.mission.Mission; import com.o3dr.services.android.lib.drone.mission.MissionItemType; import com.o3dr.services.android.lib.drone.mission.item.MissionItem; import com.o3dr.services.android.lib.model.AbstractCommandListener; import com.o3dr.services.android.lib.model.action.Action; import java.util.concurrent.ConcurrentHashMap; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.ACTION_BUILD_COMPLEX_MISSION_ITEM; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.ACTION_CHANGE_MISSION_SPEED; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.ACTION_GENERATE_DRONIE; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.ACTION_GOTO_WAYPOINT; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.ACTION_LOAD_MISSION; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.ACTION_LOAD_WAYPOINTS; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.ACTION_SAVE_MISSION; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.ACTION_SET_MISSION; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.ACTION_START_MISSION; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.EXTRA_FORCE_ARM; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.EXTRA_FORCE_MODE_CHANGE; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.EXTRA_LOAD_MISSION_URI; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.EXTRA_MISSION; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.EXTRA_MISSION_ITEM_INDEX; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.EXTRA_MISSION_SPEED; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.EXTRA_PUSH_TO_DRONE; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.EXTRA_SAVE_MISSION_URI; import static com.o3dr.services.android.lib.drone.mission.action.MissionActions.EXTRA_SET_LOADED_MISSION; /** * Provides access to missions specific functionality. * Created by Fredia Huya-Kouadio on 1/19/15. */ public class MissionApi extends Api { private static final ConcurrentHashMap<Drone, MissionApi> missionApiCache = new ConcurrentHashMap<>(); private static final Builder<MissionApi> apiBuilder = new Builder<MissionApi>() { @Override public MissionApi build(Drone drone) { return new MissionApi(drone); } }; /** * Retrieves a MissionApi instance. * @param drone Target vehicle * @return a MissionApi instance. */ public static MissionApi getApi(final Drone drone) { return getApi(drone, missionApiCache, apiBuilder); } private final Drone drone; private MissionApi(Drone drone){ this.drone = drone; } /** * Generate action to create a dronie mission, and upload it to the connected drone. */ public void generateDronie() { drone.performAsyncAction(new Action(ACTION_GENERATE_DRONIE)); } /** * Generate action to update the mission property for the drone model in memory. * * @param mission mission to upload to the drone. * @param pushToDrone if true, upload the mission to the connected device. */ public void setMission(Mission mission, boolean pushToDrone) { Bundle params = new Bundle(); params.putParcelable(EXTRA_MISSION, mission); params.putBoolean(EXTRA_PUSH_TO_DRONE, pushToDrone); drone.performAsyncAction(new Action(ACTION_SET_MISSION, params)); } /** * Starts the mission. The vehicle will only accept this command if armed and in Auto mode. * note: This command is only supported by APM:Copter V3.3 and newer. * * @param forceModeChange Change to Auto mode if not in Auto. * @param forceArm Arm the vehicle if it is disarmed. * @param listener */ public void startMission(boolean forceModeChange, boolean forceArm, AbstractCommandListener listener){ Bundle params = new Bundle(); params.putBoolean(EXTRA_FORCE_MODE_CHANGE, forceModeChange); params.putBoolean(EXTRA_FORCE_ARM, forceArm); drone.performAsyncActionOnDroneThread(new Action(ACTION_START_MISSION, params), listener); } /** * Jump to the desired command in the mission list. Repeat this action only the specified number of times * @param waypoint command to jump to * @param listener */ public void gotoWaypoint(int waypoint, AbstractCommandListener listener){ Bundle params = new Bundle(); params.putInt(EXTRA_MISSION_ITEM_INDEX, waypoint); drone.performAsyncActionOnDroneThread(new Action(ACTION_GOTO_WAYPOINT, params), listener); } /** * Load waypoints from the target vehicle. */ public void loadWaypoints() { drone.performAsyncAction(new Action(ACTION_LOAD_WAYPOINTS)); } /** * Loads the mission from the given source uri * @param sourceUri * @param loadingCallback Invoked when the loading operation completes. * @since 3.0.0 */ @Nullable public void loadMission(Uri sourceUri, LoadingCallback<Mission> loadingCallback) { loadAndSetMission(sourceUri, false, loadingCallback); } /** * Loads and sets the mission retrieved from the source uri. * @param sourceUri * @param loadingCallback Invoked when the loading operation completes. * @since 3.0.0 */ public void loadAndSetMission(Uri sourceUri, LoadingCallback<Mission> loadingCallback){ loadAndSetMission(sourceUri, true, loadingCallback); } private void loadAndSetMission(final Uri sourceUri, final boolean setMission, final LoadingCallback<Mission> loadingCallback){ if(sourceUri == null){ throw new NullPointerException("Mission source uri must be non null."); } if(!setMission && loadingCallback == null){ // No point to load the mission if no one is listening for it. return; } drone.getAsyncScheduler().execute(new Runnable() { @Override public void run() { postLoadingStart(loadingCallback); Bundle params = new Bundle(); params.putParcelable(EXTRA_LOAD_MISSION_URI, sourceUri); params.putBoolean(EXTRA_SET_LOADED_MISSION, setMission); Action loadAction = new Action(ACTION_LOAD_MISSION, params); boolean result = drone.performAction(loadAction); if (loadingCallback != null) { if (result) { final Mission loadedMission = loadAction.getData().getParcelable(EXTRA_MISSION); if (loadedMission == null) { postLoadingFailed(loadingCallback); } else { postLoadingComplete(loadedMission, loadingCallback); } } else { postLoadingFailed(loadingCallback); } } } }); } private void postLoadingStart(final LoadingCallback<?> callback) { if(callback != null){ drone.getHandler().post(new Runnable() { @Override public void run() { callback.onLoadingStart(); } }); } } private void postLoadingFailed(final LoadingCallback<?> callback){ if(callback != null){ drone.getHandler().post(new Runnable() { @Override public void run() { callback.onLoadingFailed(); } }); } } private <T> void postLoadingComplete(final T loaded, final LoadingCallback<T> callback) { if(callback != null){ drone.getHandler().post(new Runnable() { @Override public void run() { callback.onLoadingComplete(loaded); } }); } } /** * Saves a mission to the given save uri. * @param mission Mission to save * @param saveUri Destination uri for the mission * @param listener * * @since 3.0.0 */ public void saveMission(Mission mission, Uri saveUri, AbstractCommandListener listener){ if(mission == null){ throw new NullPointerException("Mission must be non null."); } if(saveUri == null){ throw new NullPointerException("Mission destination uri must be non null."); } Bundle params = new Bundle(); params.putParcelable(EXTRA_MISSION, mission); params.putParcelable(EXTRA_SAVE_MISSION_URI, saveUri); drone.performAsyncActionOnDroneThread(new Action(ACTION_SAVE_MISSION, params), listener); } /** * Build and return complex mission item. * @param itemBundle bundle containing the complex mission item to update. */ private Action buildComplexMissionItem(Bundle itemBundle) { Action payload = new Action(ACTION_BUILD_COMPLEX_MISSION_ITEM, itemBundle); boolean result = drone.performAction(payload); if(result) return payload; else return null; } /** * Builds and validates a complex mission item against the target vehicle. * @param complexItem Mission item to build. * @return an updated mission item. */ public <T extends MissionItem> T buildMissionItem(MissionItem.ComplexItem<T> complexItem){ T missionItem = (T) complexItem; Bundle payload = missionItem.getType().storeMissionItem(missionItem); if (payload == null) return null; Action result = buildComplexMissionItem(payload); if(result != null){ T updatedItem = MissionItemType.restoreMissionItemFromBundle(result.getData()); complexItem.copy(updatedItem); return (T) complexItem; } else return null; } /** * Stops the vehicle at the current location. The vehicle will remain in Auto mode * @param listener * * @since 2.8.0 */ public void pauseMission(AbstractCommandListener listener) { Bundle params = new Bundle(); params.putFloat(EXTRA_MISSION_SPEED, 0); drone.performAsyncActionOnDroneThread(new Action(ACTION_CHANGE_MISSION_SPEED, params), listener); } /** * Sets the mission to a specified speed * @param speed Speed to set mission in m/s * @param listener * * @since 2.8.0 */ public void setMissionSpeed(float speed, AbstractCommandListener listener) { Bundle params = new Bundle(); params.putFloat(EXTRA_MISSION_SPEED, speed); drone.performAsyncActionOnDroneThread(new Action(ACTION_CHANGE_MISSION_SPEED, params), listener); } public interface LoadingCallback<T> { void onLoadingStart(); void onLoadingComplete(T loaded); void onLoadingFailed(); } }