package org.droidplanner.services.android.impl.core.drone.manager;
import android.os.Handler;
import android.os.RemoteException;
import com.MAVLink.Messages.MAVLinkMessage;
import com.MAVLink.common.msg_command_ack;
import com.MAVLink.common.msg_command_long;
import com.MAVLink.common.msg_set_mode;
import com.o3dr.services.android.lib.model.ICommandListener;
import java.util.concurrent.ConcurrentHashMap;
import timber.log.Timber;
/**
* Handles tracking and dispatching of the command listener events.
* Created by Fredia Huya-Kouadio on 6/24/15.
*/
public class DroneCommandTracker {
private static final long COMMAND_TIMEOUT_PERIOD = 2000l; //2 seconds
private final Handler handler;
private final ConcurrentHashMap<Integer, CallbackKey> keyStore = new ConcurrentHashMap<>();
private final ConcurrentHashMap<CallbackKey, AckCallback> callbackStore = new ConcurrentHashMap<>();
DroneCommandTracker(Handler handler) {
this.handler = handler;
}
public void onCommandSubmitted(MAVLinkMessage command, ICommandListener listener) {
if (command == null || listener == null)
return;
if (command instanceof msg_command_long) {
onCommandSubmittedImpl((msg_command_long) command, listener);
} else if (command instanceof msg_set_mode) {
onCommandSubmittedImpl((msg_set_mode) command, listener);
}
}
private void onCommandSubmittedImpl(msg_command_long command, ICommandListener listener) {
final int commandId = command.command;
final CallbackKey<msg_command_ack> key = new CallbackKey<msg_command_ack>(commandId) {
@Override
public int checkAckResult(msg_command_ack result) {
return result.result;
}
};
final AckCallback callback = new AckCallback(listener, commandId);
keyStore.put(commandId, key);
callbackStore.put(key, callback);
handler.postDelayed(callback, COMMAND_TIMEOUT_PERIOD);
}
private void onCommandSubmittedImpl(final msg_set_mode command, ICommandListener listener) {
final int commandId = command.msgid;
final CallbackKey<msg_command_ack> key = new CallbackKey<msg_command_ack>(commandId) {
@Override
public int checkAckResult(msg_command_ack result) {
return result.result;
}
};
final AckCallback callback = new AckCallback(listener, commandId);
keyStore.put(commandId, key);
callbackStore.put(key, callback);
handler.postDelayed(callback, COMMAND_TIMEOUT_PERIOD);
}
public void onCommandAck(int commandId, Object ack) {
switch (commandId) {
case msg_command_ack.MAVLINK_MSG_ID_COMMAND_ACK:
onCommandAckImpl((msg_command_ack) ack);
break;
}
}
private void onCommandAckImpl(msg_command_ack ack) {
final CallbackKey<msg_command_ack> key = keyStore.get(ack.command);
if (key == null)
return;
final AckCallback callback = callbackStore.remove(key);
if (callback != null) {
handler.removeCallbacks(callback);
callback.setAckResult(key.checkAckResult(ack));
handler.post(callback);
}
}
private static abstract class CallbackKey<T> {
private final int commandId;
CallbackKey(int commandId) {
this.commandId = commandId;
}
public abstract int checkAckResult(T result);
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof CallbackKey))
return false;
CallbackKey that = (CallbackKey) o;
return this.commandId == that.commandId;
}
@Override
public int hashCode() {
return this.commandId;
}
}
private class AckCallback implements Runnable {
private static final int COMMAND_TIMED_OUT = -1;
private static final int COMMAND_SUCCEED = 0;
private int ackResult = COMMAND_TIMED_OUT;
private final ICommandListener listener;
private final int ackId;
AckCallback(ICommandListener listener, int ackId) {
this.listener = listener;
this.ackId = ackId;
}
void setAckResult(int result) {
this.ackResult = result;
}
@Override
public void run() {
if (listener == null)
return;
final CallbackKey key = keyStore.remove(ackId);
if (key != null)
callbackStore.remove(key);
Timber.d("Callback with ack result %d", ackResult);
try {
switch (ackResult) {
case COMMAND_TIMED_OUT:
listener.onTimeout();
break;
case COMMAND_SUCCEED:
listener.onSuccess();
break;
default:
listener.onError(ackResult);
break;
}
} catch (RemoteException e) {
Timber.e(e, e.getMessage());
}
}
}
}