package org.droidplanner.services.android.impl.core.drone;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import com.o3dr.services.android.lib.drone.action.ControlActions;
import com.o3dr.services.android.lib.drone.action.GimbalActions;
import com.o3dr.services.android.lib.drone.attribute.error.CommandExecutionError;
import com.o3dr.services.android.lib.drone.connection.ConnectionParameter;
import com.o3dr.services.android.lib.drone.connection.ConnectionType;
import com.o3dr.services.android.lib.drone.property.DroneAttribute;
import com.o3dr.services.android.lib.drone.property.Parameter;
import com.o3dr.services.android.lib.gcs.link.LinkConnectionStatus;
import com.o3dr.services.android.lib.model.ICommandListener;
import com.o3dr.services.android.lib.model.action.Action;
import org.droidplanner.services.android.impl.api.DroneApi;
import org.droidplanner.services.android.impl.communication.model.DataLink;
import org.droidplanner.services.android.impl.core.drone.autopilot.Drone;
import org.droidplanner.services.android.impl.core.drone.autopilot.apm.solo.ArduSolo;
import org.droidplanner.services.android.impl.core.drone.autopilot.apm.solo.SoloComp;
import org.droidplanner.services.android.impl.core.drone.manager.MavLinkDroneManager;
import org.droidplanner.services.android.impl.utils.CommonApiUtils;
import java.util.concurrent.ConcurrentHashMap;
/**
* Bridge between the communication channel, the drone instance(s), and the connected client(s).
*/
public class DroneManager<T extends Drone, D> implements DataLink.DataLinkListener<D>, DroneInterfaces.OnDroneListener,
DroneInterfaces.OnParameterManagerListener, LogMessageListener, DroneInterfaces.AttributeEventListener {
private static final String TAG = DroneManager.class.getSimpleName();
public static final String EXTRA_CLIENT_APP_ID = "extra_client_app_id";
protected final ConcurrentHashMap<String, DroneApi> connectedApps = new ConcurrentHashMap<>();
protected final Context context;
protected final Handler handler;
protected T drone;
protected final ConnectionParameter connectionParameter;
public static DroneManager generateDroneManager(Context context, ConnectionParameter connParams, Handler handler) {
switch (connParams.getConnectionType()) {
default:
return new MavLinkDroneManager(context, connParams, handler);
}
}
protected DroneManager(Context context, ConnectionParameter connParams, Handler handler) {
this.context = context;
this.handler = handler;
this.connectionParameter = connParams;
}
private void destroyAutopilot() {
if (drone == null) {
return;
}
drone.destroy();
drone = null;
}
public void destroy() {
Log.d(TAG, "Destroying drone manager.");
disconnect();
destroyAutopilot();
connectedApps.clear();
}
public synchronized void connect(String appId, DroneApi listener, ConnectionParameter connParams) {
if (listener == null || TextUtils.isEmpty(appId)) {
return;
}
connectedApps.put(appId, listener);
doConnect(appId, listener, connParams);
}
protected void doConnect(String appId, DroneApi listener, ConnectionParameter connParams) {
}
private void disconnect() {
if (!connectedApps.isEmpty()) {
for (DroneApi client : connectedApps.values()) {
disconnect(client.getClientInfo());
}
}
}
/**
* @return True if we can expect to find a companion computer on the connected channel.
*/
protected boolean isCompanionComputerEnabled() {
final int connectionType = connectionParameter.getConnectionType();
return drone instanceof ArduSolo
|| (connectionType == ConnectionType.TYPE_UDP && SoloComp.isAvailable(context))
|| connectionType == ConnectionType.TYPE_SOLO;
}
public int getConnectedAppsCount() {
return connectedApps.size();
}
public void disconnect(DroneApi.ClientInfo clientInfo) {
String appId = clientInfo.appId;
if (TextUtils.isEmpty(appId)) {
return;
}
Log.d(TAG, "Disconnecting client " + appId);
DroneApi listener = connectedApps.remove(appId);
doDisconnect(appId, listener);
}
protected void doDisconnect(String appId, DroneApi listener) {
if (isConnected() && listener != null) {
listener.onDroneEvent(DroneInterfaces.DroneEventsType.DISCONNECTED, drone);
}
if (connectedApps.isEmpty()) {
//Reset the gimbal mount mode
executeAsyncAction(null, new Action(GimbalActions.ACTION_RESET_GIMBAL_MOUNT_MODE), null);
}
}
protected void notifyDroneEvent(DroneInterfaces.DroneEventsType event) {
if (drone != null) {
drone.notifyDroneEvent(event);
}
}
@Override
public void notifyReceivedData(D data) {
}
@Override
public void onConnectionStatus(LinkConnectionStatus connectionStatus) {
switch (connectionStatus.getStatusCode()) {
case LinkConnectionStatus.DISCONNECTED:
notifyDroneEvent(DroneInterfaces.DroneEventsType.DISCONNECTED);
break;
case LinkConnectionStatus.CONNECTING:
notifyDroneEvent(DroneInterfaces.DroneEventsType.CONNECTING);
break;
}
if (connectedApps.isEmpty()) {
return;
}
for (DroneApi droneEventsListener : connectedApps.values()) {
droneEventsListener.onConnectionStatus(connectionStatus);
}
}
public T getDrone() {
return this.drone;
}
public boolean isConnected() {
return drone != null && drone.isConnected();
}
public DroneAttribute getAttribute(DroneApi.ClientInfo clientInfo, String attributeType) {
switch (attributeType) {
default:
return drone == null ? null : drone.getAttribute(attributeType);
}
}
protected boolean executeAsyncAction(Action action, ICommandListener listener) {
String type = action.getType();
switch (type) {
//***************** CONTROL ACTIONS *****************//
case ControlActions.ACTION_ENABLE_MANUAL_CONTROL:
if (drone != null) {
drone.executeAsyncAction(action, listener);
} else {
CommonApiUtils.postErrorEvent(CommandExecutionError.COMMAND_FAILED, listener);
}
return true;
default:
if (drone != null) {
return drone.executeAsyncAction(action, listener);
} else {
CommonApiUtils.postErrorEvent(CommandExecutionError.COMMAND_FAILED, listener);
return true;
}
}
}
public boolean executeAsyncAction(DroneApi.ClientInfo clientInfo, Action action, ICommandListener listener) {
String type = action.getType();
Bundle data = action.getData();
switch (type) {
case ControlActions.ACTION_ENABLE_MANUAL_CONTROL:
data.putString(EXTRA_CLIENT_APP_ID, clientInfo.appId);
break;
}
return executeAsyncAction(action, listener);
}
protected void notifyDroneAttributeEvent(String attributeEvent, Bundle eventInfo) {
if (TextUtils.isEmpty(attributeEvent) || connectedApps.isEmpty()) {
return;
}
for (DroneApi listener : connectedApps.values()) {
listener.onAttributeEvent(attributeEvent, eventInfo);
}
}
@Override
public void onDroneEvent(DroneInterfaces.DroneEventsType event, Drone drone) {
switch (event) {
case HEARTBEAT_FIRST:
case CONNECTED:
event = DroneInterfaces.DroneEventsType.CONNECTED;
break;
}
if (connectedApps.isEmpty()) {
return;
}
for (DroneApi droneEventsListener : connectedApps.values()) {
droneEventsListener.onDroneEvent(event, drone);
}
}
@Override
public void onBeginReceivingParameters() {
if (connectedApps.isEmpty()) {
return;
}
for (DroneApi droneEventsListener : connectedApps.values()) {
droneEventsListener.onBeginReceivingParameters();
}
}
@Override
public void onParameterReceived(Parameter parameter, int index, int count) {
if (connectedApps.isEmpty()) {
return;
}
for (DroneApi droneEventsListener : connectedApps.values()) {
droneEventsListener.onParameterReceived(parameter, index, count);
}
}
@Override
public void onEndReceivingParameters() {
if (connectedApps.isEmpty()) {
return;
}
for (DroneApi droneEventsListener : connectedApps.values()) {
droneEventsListener.onEndReceivingParameters();
}
}
public ConnectionParameter getConnectionParameter() {
return connectionParameter;
}
@Override
public void onMessageLogged(int logLevel, String message) {
if (connectedApps.isEmpty()) {
return;
}
for (DroneApi listener : connectedApps.values()) {
listener.onMessageLogged(logLevel, message);
}
}
@Override
public void onAttributeEvent(String attributeEvent, Bundle eventInfo) {
notifyDroneAttributeEvent(attributeEvent, eventInfo);
}
}