package org.droidplanner.services.android.impl.core.drone.profiles; import android.content.Context; import android.os.Handler; import android.text.TextUtils; import android.util.SparseBooleanArray; import com.MAVLink.Messages.MAVLinkMessage; import com.MAVLink.common.msg_param_value; import com.o3dr.services.android.lib.drone.property.Parameter; import org.droidplanner.services.android.impl.core.MAVLink.MavLinkParameters; import org.droidplanner.services.android.impl.core.drone.DroneInterfaces; import org.droidplanner.services.android.impl.core.drone.DroneInterfaces.DroneEventsType; import org.droidplanner.services.android.impl.core.drone.DroneInterfaces.OnDroneListener; import org.droidplanner.services.android.impl.core.drone.DroneVariable; import org.droidplanner.services.android.impl.core.drone.autopilot.MavLinkDrone; import org.droidplanner.services.android.impl.utils.file.IO.ParameterMetadataLoader; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import timber.log.Timber; /** * Class to manage the communication of parameters to the MAV. * <p/> * Should be initialized with a MAVLink Object, so the manager can send messages * via the MAV link. The function processMessage must be called with every new * MAV Message. */ public class ParameterManager extends DroneVariable<MavLinkDrone> implements OnDroneListener<MavLinkDrone> { private static final long TIMEOUT = 1000l; //milliseconds private final Runnable parametersReceiptStartNotification = new Runnable() { @Override public void run() { if (parameterListener != null) parameterListener.onBeginReceivingParameters(); } }; public final Runnable watchdogCallback = new Runnable() { @Override public void run() { onParameterStreamStopped(); } }; private final Runnable parametersReceiptEndNotification = new Runnable() { @Override public void run() { if (parameterListener != null) parameterListener.onEndReceivingParameters(); } }; private final AtomicBoolean isRefreshing = new AtomicBoolean(false); private final ConcurrentLinkedQueue<String> paramsToReadFromVehicle = new ConcurrentLinkedQueue<>(); private int expectedParams; private final SparseBooleanArray paramsRollCall = new SparseBooleanArray(); private final ConcurrentHashMap<String, Parameter> parameters = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, ParameterMetadata> parametersMetadata = new ConcurrentHashMap<>(); private DroneInterfaces.OnParameterManagerListener parameterListener; private final Handler watchdog; private final Context context; public ParameterManager(MavLinkDrone myDrone, Context context, Handler handler) { super(myDrone); this.context = context; this.watchdog = handler; myDrone.addDroneListener(this); refreshParametersMetadata(); } public void refreshParameters() { if (isRefreshing.compareAndSet(false, true)) { paramsToReadFromVehicle.clear(); expectedParams = 0; parameters.clear(); paramsRollCall.clear(); notifyParametersReceiptStart(); MavLinkParameters.requestParametersList(myDrone); resetWatchdog(); } } public Map<String, Parameter> getParameters() { //Update the cache if it's stale. Parameters download is expensive, but we assume the caller knows what it's // doing. if (parameters.isEmpty()) refreshParameters(); return parameters; } /** * Try to process a Mavlink message if it is a parameter related message * * @param msg Mavlink message to process * @return Returns true if the message has been processed */ public boolean processMessage(MAVLinkMessage msg) { if (msg.msgid == msg_param_value.MAVLINK_MSG_ID_PARAM_VALUE) { processReceivedParam((msg_param_value) msg); return true; } return false; } protected void processReceivedParam(msg_param_value m_value) { String paramName = m_value.getParam_Id(); if(!paramsToReadFromVehicle.remove(paramName) && !isRefreshing.get()) return; // collect params in parameter list Parameter param = new Parameter(paramName, m_value.param_value, m_value.param_type); loadParameterMetadata(param); int paramIndex = m_value.param_index; parameters.put(param.getName().toLowerCase(Locale.US), param); if (paramIndex == -1) { // update listener notifyParameterReceipt(param, 0, 1); notifyParametersReceiptEnd(); return; } paramsRollCall.append(paramIndex, true); expectedParams = m_value.param_count; // update listener notifyParameterReceipt(param, paramIndex, m_value.param_count); // Are all parameters here? Notify the listener with the parameters if (parameters.size() >= m_value.param_count) { if(isRefreshing.compareAndSet(true, false)) { killWatchdog(); } notifyParametersReceiptEnd(); } else { resetWatchdog(); } } private void reRequestMissingParams(int howManyParams) { for (int i = 0; i < howManyParams; i++) { if (!paramsRollCall.get(i)) { MavLinkParameters.readParameter(myDrone, i); } } } public void sendParameter(Parameter parameter) { paramsToReadFromVehicle.add(parameter.getName()); MavLinkParameters.sendParameter(myDrone, parameter); } public void readParameter(String name) { paramsToReadFromVehicle.add(name); MavLinkParameters.readParameter(myDrone, name); } public Parameter getParameter(String name) { if (TextUtils.isEmpty(name)) return null; return parameters.get(name.toLowerCase(Locale.US)); } private void onParameterStreamStopped() { if (expectedParams > 0) { reRequestMissingParams(expectedParams); resetWatchdog(); } else { isRefreshing.set(false); } } private void resetWatchdog() { watchdog.removeCallbacks(watchdogCallback); watchdog.postDelayed(watchdogCallback, TIMEOUT); } private void killWatchdog() { watchdog.removeCallbacks(watchdogCallback); isRefreshing.set(false); } @Override public void onDroneEvent(DroneEventsType event, MavLinkDrone drone) { switch (event) { case HEARTBEAT_FIRST: refreshParameters(); break; case DISCONNECTED: case HEARTBEAT_TIMEOUT: killWatchdog(); break; case TYPE: refreshParametersMetadata(); break; default: break; } } private void refreshParametersMetadata() { //Reload the vehicle parameters metadata String metadataType = myDrone.getFirmwareType().getParameterMetadataGroup(); if (!TextUtils.isEmpty(metadataType)) { try { ParameterMetadataLoader.load(context, metadataType, this.parametersMetadata); } catch (Exception e) { Timber.e(e, e.getMessage()); } } if (parametersMetadata.isEmpty() || parameters.isEmpty()) return; for (Parameter parameter : parameters.values()) { loadParameterMetadata(parameter); } } private void loadParameterMetadata(Parameter parameter){ ParameterMetadata metadata = parametersMetadata.get(parameter.getName()); if (metadata != null) { parameter.setDisplayName(metadata.getDisplayName()); parameter.setDescription(metadata.getDescription()); parameter.setUnits(metadata.getUnits()); parameter.setRange(metadata.getRange()); parameter.setValues(metadata.getValues()); } } public void setParameterListener(DroneInterfaces.OnParameterManagerListener parameterListener) { this.parameterListener = parameterListener; } private void notifyParametersReceiptStart() { if (parameterListener != null) watchdog.post(parametersReceiptStartNotification); } private void notifyParametersReceiptEnd() { if (parameterListener != null) watchdog.post(parametersReceiptEndNotification); } private void notifyParameterReceipt(final Parameter parameter, final int index, final int count) { if (parameterListener != null) { watchdog.post(new Runnable() { @Override public void run() { if (parameterListener != null) parameterListener.onParameterReceived(parameter, index, count); } }); } } }