1
0
mirror of https://bitbucket.org/librepilot/librepilot.git synced 2025-02-20 10:54:14 +01:00

Merge branch 'android_hid' into android_revo

Conflicts:
	androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java
This commit is contained in:
James Cotton 2012-08-11 14:53:04 -05:00
commit 6fe2cb7738
12 changed files with 1521 additions and 541 deletions

View File

@ -10,17 +10,26 @@
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.usb.host" />
<application android:icon="@drawable/ic_logo" android:label="@string/app_name" android:theme="@android:style/Theme.Holo">
<!-- for map overlay -->
<uses-library android:name="com.google.android.maps" />
<!-- Object browser - main activity at the moment -->
<activity android:name="HomePage" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- <intent-filter> -->
<!-- <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> -->
<!-- </intent-filter> -->
<!-- <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" -->
<!-- android:resource="@xml/device_filter" /> -->
</activity>
<activity android:name="ObjectBrowser" android:label="@string/object_browser_name" />

View File

@ -5,11 +5,13 @@
<item>Fake</item>
<item>Bluetooth</item>
<item>Network</item>
<item>HID</item>
</string-array>
<string-array name="connectTypeValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
<item>4</item>
</string-array>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="8352" product-id="16730" />
<usb-device vendor-id="8352" product-id="16731" />
<usb-device vendor-id="8352" product-id="16732" />
<usb-device vendor-id="8352" product-id="16733" />
<usb-device vendor-id="8352" product-id="16734" />
</resources>

View File

@ -35,7 +35,6 @@ import org.openpilot.uavtalk.UAVObject;
import org.openpilot.uavtalk.UAVObjectField;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
@ -72,8 +71,6 @@ public class Controller extends ObjectManagerActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.controller);
TextView manualView = (TextView) findViewById(R.id.manualControlValues);
if(manualView != null)
manualView.setText("Hello");
}
Observer settingsUpdated = new Observer() {
@ -93,19 +90,16 @@ public class Controller extends ObjectManagerActivity {
void onOPConnected() {
super.onOPConnected();
Log.d(TAG, "onOPConnected()");
// Subscribe to updates from ManualControlCommand and show the values for crude feedback
UAVDataObject manualControl = (UAVDataObject) objMngr.getObject("ManualControlCommand");
if(manualControl != null) {
manualControl.addUpdatedObserver(updatedObserver);
}
registerObjectUpdates(manualControl);
UAVDataObject manualControlSettings = (UAVDataObject) objMngr.getObject("ManualControlSettings");
if(manualControlSettings != null) {
Log.d(TAG, "Requested settings update");
manualControlSettings.addUpdatedObserver(updatedObserver);
manualControlSettings.updateRequested();
}
// Request a one time update before configuring for GCS control mode
UAVDataObject manualSettings = (UAVDataObject) objMngr.getObject("ManualControlSettings");
manualSettings.addUpdatedObserver(settingsUpdated);
manualSettings.updateRequested();
final double MOVEMENT_RANGE = 50.0;
DualJoystickView joystick = (DualJoystickView) findViewById(R.id.dualjoystickView);
@ -138,7 +132,9 @@ public class Controller extends ObjectManagerActivity {
public void OnReleased() { rightJoystickHeld = false; throttle = -1; updated = true; }
@Override
public void OnReturnedToCenter() { }
}) ;
});
//! This timer task actually periodically sends updates to the UAV
TimerTask controllerTask = new TimerTask() {
@Override
public void run() {
@ -175,36 +171,19 @@ public class Controller extends ObjectManagerActivity {
});
}
};
sendTimer.schedule(controllerTask, 500, 100);
sendTimer.schedule(controllerTask, 500, 10);
}
/**
* The callbacks from the UAVOs must run in the correct thread to update the
* UI. This is what using a runnable does.
*/
final Handler uavobjHandler = new Handler();
final Runnable updateText = new Runnable() {
@Override
public void run() {
updateManualControl();
}
};
private final Observer updatedObserver = new Observer() {
@Override
public void update(Observable observable, Object data) {
uavobjHandler.post(updateText);
}
};
/**
* Show the string description of manual control command
*/
private void updateManualControl() {
UAVDataObject manualControl = (UAVDataObject) objMngr.getObject("ManualControlCommand");
TextView manualView = (TextView) findViewById(R.id.manualControlValues);
if (manualView != null && manualControl != null)
manualView.setText(manualControl.toStringData());
@Override
protected void objectUpdated(UAVObject obj) {
if (obj.getName().compareTo("ManualControlCommand") == 0) {
TextView manualView = (TextView) findViewById(R.id.manualControlValues);
if (manualView != null)
manualView.setText(obj.toStringData());
}
}
/**

View File

@ -0,0 +1,705 @@
package org.openpilot.androidgcs.telemetry;
// Code based on notes from http://torvafirmus-android.blogspot.com/2011/09/implementing-usb-hid-interface-in.html
// Taken 2012-08-10
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Iterator;
import junit.framework.Assert;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbRequest;
import android.util.Log;
public class HidUAVTalk extends TelemetryTask {
private static final String TAG = HidUAVTalk.class.getSimpleName();
public static int LOGLEVEL = 0;
public static boolean WARN = LOGLEVEL > 1;
public static boolean DEBUG = LOGLEVEL > 0;
//! USB constants
static final int OPENPILOT_VENDOR_ID = 0x20A0;
static final int USB_PRODUCT_ID_OPENPILOT_MAIN = 0x415A;
static final int USB_PRODUCT_ID_COPTERCONTROL = 0x415B;
static final int USB_PRODUCT_ID_PIPXTREME = 0x415C;
static final int USB_PRODUCT_ID_CC3D = 0x415D;
static final int USB_PRODUCT_ID_REVOLUTION = 0x415E;
static final int USB_PRODUCT_ID_OSD = 0x4194;
static final int USB_PRODUCT_ID_SPARE = 0x4195;
private static final String ACTION_USB_PERMISSION = "com.access.device.USB_PERMISSION";
UsbDevice currentDevice;
public HidUAVTalk(OPTelemetryService service) {
super(service);
}
@Override
public void disconnect() {
CleanUpAndClose();
//hostDisplayActivity.unregisterReceiver(usbReceiver);
telemService.unregisterReceiver(usbPermissionReceiver);
((TalkInputStream)inStream).stop();
((TalkOutputStream)outStream).stop();
super.disconnect();
try {
readThread.join();
writeThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (readRequest != null) {
readRequest.cancel();
readRequest.close();
readRequest = null;
}
if (writeRequest != null) {
writeRequest.cancel();
writeRequest.close();
writeRequest = null;
}
}
@Override
boolean attemptConnection() {
if (DEBUG) Log.d(TAG, "connect()");
// Register to get permission requested dialog
usbManager = (UsbManager) telemService.getSystemService(Context.USB_SERVICE);
permissionIntent = PendingIntent.getBroadcast(telemService, 0, new Intent(ACTION_USB_PERMISSION), 0);
permissionFilter = new IntentFilter(ACTION_USB_PERMISSION);
telemService.registerReceiver(usbPermissionReceiver, permissionFilter);
// Go through all the devices plugged in
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
if (DEBUG) Log.d(TAG, "Found " + deviceList.size() + " devices");
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while(deviceIterator.hasNext()){
UsbDevice dev = deviceIterator.next();
if (DEBUG) Log.d(TAG, "Testing device: " + dev);
usbManager.requestPermission(dev, permissionIntent);
}
if (DEBUG) Log.d(TAG, "Registered the deviceAttachedFilter");
return deviceList.size() > 0;
}
/*
* Receives a requested broadcast from the operating system.
* In this case the following actions are handled:
* USB_PERMISSION
* UsbManager.ACTION_USB_DEVICE_DETACHED
* UsbManager.ACTION_USB_DEVICE_ATTACHED
*/
private final BroadcastReceiver usbPermissionReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
if (DEBUG) Log.d(TAG,"Broadcast receiver caught intent: " + intent);
String action = intent.getAction();
// Validate the action against the actions registered
if (ACTION_USB_PERMISSION.equals(action))
{
// A permission response has been received, validate if the user has
// GRANTED, or DENIED permission
synchronized (this)
{
UsbDevice deviceConnected = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (DEBUG) Log.d(TAG, "Device Permission requested" + deviceConnected);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
{
// Permission has been granted, so connect to the device
// If this fails, then keep looking
if (deviceConnected != null)
{
// call method to setup device communication
currentDevice = deviceConnected;
if (DEBUG) Log.d(TAG, "Device Permission Acquired" + currentDevice);
if (!ConnectToDeviceInterface(currentDevice))
{
if (DEBUG) Log.d(TAG, "Unable to connect to device");
}
}
}
else
{
// Permission has not been granted, so keep looking for another
// device to be attached....
if (DEBUG) Log.d(TAG, "Device Permission Denied" + deviceConnected);
currentDevice = null;
}
}
}
}
};
private final BroadcastReceiver usbReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
if (DEBUG) Log.d(TAG,"Broadcast receiver taught intent: " + intent);
String action = intent.getAction();
// Validate the action against the actions registered
if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action))
{
// A device has been detached from the device, so close all the connections
// and restart the search for a new device being attached
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if ((device != null) && (currentDevice != null))
{
if (device.equals(currentDevice))
{
// call your method that cleans up and closes communication with the device
CleanUpAndClose();
}
}
}
else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action))
{
// A device has been attached. If not already connected to a device,
// validate if this device is supported
UsbDevice searchDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (DEBUG) Log.d(TAG, "Device found" + searchDevice);
if ((searchDevice != null) && (currentDevice == null))
{
// call your method that cleans up and closes communication with the device
ValidateFoundDevice(searchDevice);
}
}
}
};
private UsbEndpoint usbEndpointRead;
private UsbEndpoint usbEndpointWrite;
private UsbManager usbManager;
private PendingIntent permissionIntent;
private UsbDeviceConnection connectionRead;
private UsbDeviceConnection connectionWrite;
private IntentFilter deviceAttachedFilter;
private IntentFilter permissionFilter;
protected void CleanUpAndClose() {
if (UsingSingleInterface) {
if(connectionRead != null && usbInterfaceRead != null)
connectionRead.releaseInterface(usbInterfaceRead);
usbInterfaceRead = null;
}
else {
if(connectionRead != null && usbInterfaceRead != null)
connectionRead.releaseInterface(usbInterfaceRead);
if(connectionWrite != null && usbInterfaceWrite != null)
connectionWrite.releaseInterface(usbInterfaceWrite);
usbInterfaceWrite = null;
usbInterfaceRead = null;
}
}
//Validating the Connected Device - Before asking for permission to connect to the device, it is essential that you ensure that this is a device that you support or expect to connect to. This can be done by validating the devices Vendor ID and Product ID.
boolean ValidateFoundDevice(UsbDevice searchDevice) {
//A vendor id is a global identifier for the manufacturer. A product id refers to the product itself, and is unique to the manufacturer. The vendor id, product id combination refers to a particular product manufactured by a vendor.
if (DEBUG) Log.d(TAG, "ValidateFoundDevice: " + searchDevice );
if ( searchDevice.getVendorId() == OPENPILOT_VENDOR_ID ) {
//Requesting permission
if (DEBUG) Log.d(TAG, "Device: " + searchDevice );
usbManager.requestPermission(searchDevice, permissionIntent);
return true;
}
else
return false;
}
private UsbInterface usbInterfaceRead = null;
private UsbInterface usbInterfaceWrite = null;
private final boolean UsingSingleInterface = true;
private UsbDevice connectedDevice;
boolean ConnectToDeviceInterface(UsbDevice connectDevice) {
// Connecting to the Device - If you are reading and writing, then the device
// can either have two end points on a single interface, or two interfaces
// each with a single end point. Either way, it is best if you know which interface
// you need to use and which end points
if (DEBUG) Log.d(TAG, "ConnectToDeviceInterface:");
UsbEndpoint ep1 = null;
UsbEndpoint ep2 = null;
if (UsingSingleInterface)
{
// Using the same interface for reading and writing
usbInterfaceRead = connectDevice.getInterface(0x2);
usbInterfaceWrite = usbInterfaceRead;
if (usbInterfaceRead.getEndpointCount() == 2)
{
ep1 = usbInterfaceRead.getEndpoint(0);
ep2 = usbInterfaceRead.getEndpoint(1);
}
}
else // if (!UsingSingleInterface)
{
usbInterfaceRead = connectDevice.getInterface(0x01);
usbInterfaceWrite = connectDevice.getInterface(0x02);
if ((usbInterfaceRead.getEndpointCount() == 1) && (usbInterfaceWrite.getEndpointCount() == 1))
{
ep1 = usbInterfaceRead.getEndpoint(0);
ep2 = usbInterfaceWrite.getEndpoint(0);
}
}
if ((ep1 == null) || (ep2 == null))
{
if (DEBUG) Log.d(TAG, "Null endpoints");
return false;
}
// Determine which endpoint is the read, and which is the write
if (ep1.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)
{
if (ep1.getDirection() == UsbConstants.USB_DIR_IN)
{
usbEndpointRead = ep1;
}
else if (ep1.getDirection() == UsbConstants.USB_DIR_OUT)
{
usbEndpointWrite = ep1;
}
}
if (ep2.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)
{
if (ep2.getDirection() == UsbConstants.USB_DIR_IN)
{
usbEndpointRead = ep2;
}
else if (ep2.getDirection() == UsbConstants.USB_DIR_OUT)
{
usbEndpointWrite = ep2;
}
}
if ((usbEndpointRead == null) || (usbEndpointWrite == null))
{
if (DEBUG) Log.d(TAG, "Endpoints wrong way around");
return false;
}
connectionRead = usbManager.openDevice(connectDevice);
connectionRead.claimInterface(usbInterfaceRead, true);
if (UsingSingleInterface)
{
connectionWrite = connectionRead;
}
else // if (!UsingSingleInterface)
{
connectionWrite = usbManager.openDevice(connectDevice);
connectionWrite.claimInterface(usbInterfaceWrite, true);
}
connectedDevice = connectDevice;
if (DEBUG) Log.d(TAG, "Opened endpoints");
// Create the USB requests
readRequest = new UsbRequest();
readRequest.initialize(connectionRead, usbEndpointRead);
writeRequest = new UsbRequest();
writeRequest.initialize(connectionWrite, usbEndpointWrite);
handler.post(new Runnable() {
@Override
public void run() {
inStream = new TalkInputStream();
outStream = new TalkOutputStream();
attemptSucceeded();
}
});
readThread = new Thread(new Runnable() {
@Override
public void run() {
while (!shutdown) {
readData();
sendData();
}
}
});
readThread.start();
writeThread = new Thread(new Runnable() {
@Override
public void run() {
while (!shutdown) {
sendData();
}
}
});
//writeThread.start();
return true;
}
Thread readThread;
Thread writeThread;
private int byteToInt(byte b) { return b & 0x000000ff; }
private class TalkInputStream extends InputStream {
ByteFifo data = new ByteFifo();
boolean stopped = false;
TalkInputStream() {
}
@Override
public int read() {
try {
return data.getByteBlocking();
} catch (InterruptedException e) {
e.printStackTrace();
}
return -1;
}
public void stop() {
stopped = true;
}
public void put(byte b) {
}
public void write(byte[] b) {
data.put(b);
}
};
private class ByteFifo {
//! The maximum size of the fifo
private final int MAX_SIZE = 1024;
//! The number of bytes in the buffer
private int size = 0;
//! Internal buffer
private final ByteBuffer buf;
ByteFifo() {
buf = ByteBuffer.allocate(MAX_SIZE);
size = 0;
}
public boolean put(byte[] dat) {
if ((size + dat.length) > MAX_SIZE)
return false;
// Place data at the end of the buffer
synchronized(buf) {
buf.position(size);
buf.put(dat);
size = size + dat.length;
buf.notify();
}
return true;
}
public byte[] get(int size) throws InterruptedException {
size = Math.min(size, this.size);
if (size > 0) {
synchronized(buf) {
byte[] dst = new byte[size];
buf.position(0);
buf.get(dst,0,size);
buf.compact();
this.size = this.size - size;
Assert.assertEquals(this.size, buf.position());
buf.wait();
}
}
return new byte[0];
}
public int getByteBlocking() throws InterruptedException {
synchronized(buf) {
if (size == 0)
buf.wait();
int val = byteToInt(buf.get(0));
buf.position(1);
buf.compact();
size--;
return val;
}
}
}
/**
* Gets a report from HID, extract the meaningful data and push
* it to the input stream
*/
UsbRequest readRequest = null;
public int readData() {
int bufferDataLength = usbEndpointRead.getMaxPacketSize();
ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1);
// queue a request on the interrupt endpoint
if(!readRequest.queue(buffer, bufferDataLength)) {
if (DEBUG) Log.d(TAG, "Failed to queue request");
return 0;
}
if (DEBUG) Log.d(TAG, "Request queued");
int dataSize;
// wait for status event
if (connectionRead.requestWait() == readRequest) {
// Packet format:
// 0: Report ID (1)
// 1: Number of valid bytes
// 2:63: Data
dataSize = buffer.get(1); // Data size
//Assert.assertEquals(1, buffer.get()); // Report ID
//Assert.assertTrue(dataSize < buffer.capacity());
if (buffer.get(0) != 1 || buffer.get(1) < 0 || buffer.get(2) > (buffer.capacity() - 2)) {
if (DEBUG) Log.d(TAG, "Badly formatted HID packet");
} else {
byte[] dst = new byte[dataSize];
buffer.position(2);
buffer.get(dst, 0, dataSize);
if (DEBUG) Log.d(TAG, "Entered read");
((TalkInputStream)inStream).write(dst);
if (DEBUG) Log.d(TAG, "Got read: " + dataSize + " bytes");
}
} else
return 0;
return dataSize;
}
private class TalkOutputStream extends OutputStream {
ByteBuffer data = ByteBuffer.allocate(1024);
boolean stopped = false;
int writePosition = 0;
public int read() throws IOException {
if (!stopped)
while(!stopped) {
synchronized(data) {
if(data.hasRemaining())
return data.get();
else
try {
data.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
throw new IOException();
}
return 0;
}
public void stop() {
stopped = true;
}
@Override
public void write(int oneByte) throws IOException {
if (stopped)
throw new IOException();
synchronized(data) {
data.put((byte) oneByte);
data.notify();
}
}
@Override
public void write(byte[] b) throws IOException {
if (stopped)
throw new IOException();
synchronized(data) {
// Move the cursor to the end of the byte array to append
data.position(writePosition);
if (b.length < data.remaining()) {
data.put(b);
writePosition = data.position();
}
data.notify();
}
}
public void packetizeData() {
ByteBuffer packet;
synchronized(data) {
// Determine how much data to put in the packet
int size = Math.min(writePosition, MAX_HID_PACKET_SIZE - 2);
if (size <= 0)
return;
// Format into a HID packet
packet = ByteBuffer.allocate(MAX_HID_PACKET_SIZE);
packet.put(0,(byte) 2); // Report ID
packet.put(1,(byte) size); // The number of bytes of data
data.position(0);
data.get(packet.array(), 2, size); // Copy data into the other array
// Remove that data from the write buffer
data.compact();
writePosition -= size;
if (DEBUG) Log.d(TAG, "packetizeData(): size="+size);
}
WriteToDevice(packet);
}
};
private static final int MAX_HID_PACKET_SIZE = 64;
/**
* Send a packet or wait for data to be available
*/
public void sendData() {
TalkOutputStream o = (TalkOutputStream) outStream;
synchronized(o.data){
if (o.writePosition > 0)
o.packetizeData();
else {
o.data.notify();
o.packetizeData();
}
}
}
UsbRequest writeRequest = null;
boolean WriteToDevice(ByteBuffer DataToSend) {
if (DEBUG) Log.d(TAG, "Writing to device()");
//The report must be formatted correctly for the device being connected to. On some devices, this requires that a specific value must be the first byte in the report. This can be followed by the length of the data in the report. This format is determined by the device, and isn't specified here.
int bufferDataLength = usbEndpointWrite.getMaxPacketSize();
ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1);
if(writeRequest == null) {
writeRequest = new UsbRequest();
writeRequest.initialize(connectionWrite, usbEndpointWrite);
}
buffer.put(DataToSend);
writeRequest.queue(buffer, bufferDataLength);
try
{
if (writeRequest.equals(connectionWrite.requestWait()))
return true;
}
catch (Exception ex)
{
// An exception has occured
return false;
}
if (false) {
writeRequest.cancel();
writeRequest.close();
}
return false;
}
//Reading from the Device - As USB devices work with reports of a specific length. The data received from the device will always be the size specified by the report length. Even if there isn't enough data to fill the report. Some devices require the controlTransfer method for reading data. I don't cover this command in this blog.
void ReadFromDevice() {
//If you are expecting unsolicited data from the device, then a read thread should be started so that the data can be processed as soon as it arrives.
int bufferDataLength = usbEndpointRead.getMaxPacketSize();
ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1);
UsbRequest requestQueued = null;
UsbRequest request = new UsbRequest();
request.initialize(connectionRead, usbEndpointRead);
try
{
while (!getStopping())
{
request.queue(buffer, bufferDataLength);
requestQueued = connectionRead.requestWait();
if (request.equals(requestQueued))
{
byte[] byteBuffer = new byte[bufferDataLength + 1];
buffer.get(byteBuffer, 0, bufferDataLength);
// Handle data received
buffer.clear();
}
else
{
Thread.sleep(20);
}
}
}
catch (Exception ex)
{
// An exception has occured
}
try
{
request.cancel();
request.close();
}
catch (Exception ex)
{
// An exception has occured
}
}
private boolean getStopping() {
// TODO Auto-generated method stub
return false;
}
}

View File

@ -56,9 +56,9 @@ public class OPTelemetryService extends Service {
// Logging settings
private final String TAG = OPTelemetryService.class.getSimpleName();
public static int LOGLEVEL = 0;
public static boolean WARN = LOGLEVEL > 1;
public static boolean DEBUG = LOGLEVEL > 0;
public static int LOGLEVEL = 2;
public static boolean DEBUG = LOGLEVEL > 1;
public static boolean WARN = LOGLEVEL > 0;
// Intent category
public final static String INTENT_CATEGORY_GCS = "org.openpilot.intent.category.GCS";
@ -81,6 +81,7 @@ public class OPTelemetryService extends Service {
private boolean terminate = false;
private Thread activeTelem;
private TelemetryTask telemTask;
private final IBinder mBinder = new LocalBinder();
@ -130,7 +131,13 @@ public class OPTelemetryService extends Service {
break;
case 3:
Toast.makeText(getApplicationContext(), "Attempting TCP connection", Toast.LENGTH_SHORT).show();
activeTelem = new TcpTelemetryThread();
telemTask = new TcpUAVTalk(this);
activeTelem = new Thread(telemTask);
break;
case 4:
Toast.makeText(getApplicationContext(), "Attempting USB HID connection", Toast.LENGTH_SHORT).show();
telemTask = new HidUAVTalk(this);
activeTelem = new Thread(telemTask);
break;
default:
throw new Error("Unsupported");
@ -139,8 +146,19 @@ public class OPTelemetryService extends Service {
break;
case MSG_DISCONNECT:
Toast.makeText(getApplicationContext(), "Disconnect requested", Toast.LENGTH_SHORT).show();
if (DEBUG) Log.d(TAG, "Calling disconnect");
terminate = true;
if (activeTelem != null) {
if (telemTask != null) {
telemTask.disconnect();
telemTask = null;
try {
activeTelem.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else if (activeTelem != null) {
activeTelem.interrupt();
try {
activeTelem.join();
@ -149,6 +167,7 @@ public class OPTelemetryService extends Service {
}
activeTelem = null;
}
if (DEBUG) Log.d(TAG, "Telemetry thread terminated");
Intent intent = new Intent();
intent.setAction(INTENT_ACTION_DISCONNECTED);
sendBroadcast(intent,null);
@ -211,12 +230,25 @@ public class OPTelemetryService extends Service {
@Override
public void onDestroy() {
if (telemTask != null) {
Log.d(TAG, "onDestory() shutting down telemetry task");
telemTask.disconnect();
telemTask = null;
try {
activeTelem.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.d(TAG, "onDestory() shut down telemetry task");
Toast.makeText(this, "Telemetry service done", Toast.LENGTH_SHORT).show();
}
public class LocalBinder extends Binder {
public TelemTask getTelemTask(int id) {
return (TelemTask) activeTelem;
return telemTask.getTelemTaskIface();
}
public void openConnection() {
Toast.makeText(getApplicationContext(), "Requested open connection", Toast.LENGTH_SHORT).show();
@ -391,95 +423,4 @@ public class OPTelemetryService extends Service {
}
};
private class TcpTelemetryThread extends Thread implements TelemTask {
private final UAVObjectManager objMngr;
private UAVTalk uavTalk;
private Telemetry tel;
private TelemetryMonitor mon;
@Override
public UAVObjectManager getObjectManager() { return objMngr; };
TcpTelemetryThread() {
objMngr = new UAVObjectManager();
UAVObjectsInitialize.register(objMngr);
}
@Override
public void run() {
if (DEBUG) Log.d(TAG, "Telemetry Thread started");
Looper.prepare();
TcpUAVTalk tcp = new TcpUAVTalk(OPTelemetryService.this);
for( int i = 0; i < 10; i++ ) {
if (DEBUG) Log.d(TAG, "Attempting network Connection");
tcp.connect(objMngr);
if( tcp.getConnected() )
break;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.e(TAG, "Thread interrupted while trying to connect");
e.printStackTrace();
return;
}
}
if( ! tcp.getConnected() || terminate ) {
toastMessage("TCP connection failed");
return;
}
toastMessage("TCP Connected");
if (DEBUG) Log.d(TAG, "Connected via network");
uavTalk = tcp.getUavtalk();
tel = new Telemetry(uavTalk, objMngr);
mon = new TelemetryMonitor(objMngr,tel);
mon.addObserver(new Observer() {
@Override
public void update(Observable arg0, Object arg1) {
if (DEBUG) Log.d(TAG, "Mon updated. Connected: " + mon.getConnected() + " objects updated: " + mon.getObjectsUpdated());
if(mon.getConnected()) {
Intent intent = new Intent();
intent.setAction(INTENT_ACTION_CONNECTED);
sendBroadcast(intent,null);
}
}
});
if (DEBUG) Log.d(TAG, "Entering UAVTalk processing loop");
while( !terminate ) {
try {
if( !uavTalk.processInputStream() )
break;
} catch (IOException e) {
e.printStackTrace();
toastMessage("TCP Stream interrupted");
break;
}
}
Looper.myLooper().quit();
// Shut down all the attached
mon.stopMonitor();
mon = null;
tel.stopTelemetry();
tel = null;
// Finally close the stream if it is still open
tcp.disconnect();
if (DEBUG) Log.d(TAG, "UAVTalk stream disconnected");
toastMessage("TCP Thread finished");
}
};
}

View File

@ -28,15 +28,11 @@ import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import org.openpilot.uavtalk.UAVObjectManager;
import org.openpilot.uavtalk.UAVTalk;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
public class TcpUAVTalk {
public class TcpUAVTalk extends TelemetryTask {
private final String TAG = "TcpUAVTalk";
public static int LOGLEVEL = 2;
public static boolean WARN = LOGLEVEL > 1;
@ -46,16 +42,23 @@ public class TcpUAVTalk {
private String ip_address = "1";
private int port = 9001;
private UAVTalk uavTalk;
private boolean connected;
private Socket socket;
/**
* Construct a TcpUAVTalk object attached to the OPTelemetryService. Gets the
* connection settings from the preferences.
*/
public TcpUAVTalk(Context caller) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(caller);
public TcpUAVTalk(OPTelemetryService caller) {
super(caller);
}
@Override
boolean attemptConnection() {
if( getConnected() )
return true;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(telemService);
ip_address = prefs.getString("ip_address","127.0.0.1");
try {
port = Integer.decode(prefs.getString("port", ""));
@ -65,44 +68,6 @@ public class TcpUAVTalk {
if (DEBUG) Log.d(TAG, "Trying to open UAVTalk with " + ip_address);
connected = false;
}
/**
* Connect a TCP object to an object manager. Returns true if already
* connected, otherwise returns true if managed a successful socket.
*/
public boolean connect(UAVObjectManager objMngr) {
if( getConnected() )
return true;
if( !openTelemetryTcp(objMngr) )
return false;
return true;
}
public void disconnect() {
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
socket = null;
}
public boolean getConnected() {
return connected;
}
public UAVTalk getUavtalk() {
return uavTalk;
}
/**
* Opens a TCP socket to the address determined on construction. If successful
* creates a UAVTalk stream connection this socket to the passed in object manager
*/
private boolean openTelemetryTcp(UAVObjectManager objMngr) {
Log.d(TAG, "Opening connection to " + ip_address + " at address " + port);
InetAddress serverAddr = null;
@ -121,18 +86,43 @@ public class TcpUAVTalk {
return false;
}
connected = true;
try {
uavTalk = new UAVTalk(socket.getInputStream(), socket.getOutputStream(), objMngr);
inStream = socket.getInputStream();
outStream = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG,"Error starting UAVTalk");
// TODO Auto-generated catch block
//e.printStackTrace();
try {
socket.close();
} catch (IOException e2) {
}
return false;
}
// Post message to call attempt succeeded on the parent class
handler.post(new Runnable() {
@Override
public void run() {
TcpUAVTalk.this.attemptSucceeded();
}
});
return true;
}
@Override
public void disconnect() {
super.disconnect();
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
socket = null;
}
}
}

View File

@ -0,0 +1,235 @@
package org.openpilot.androidgcs.telemetry;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Observable;
import java.util.Observer;
import org.openpilot.uavtalk.Telemetry;
import org.openpilot.uavtalk.TelemetryMonitor;
import org.openpilot.uavtalk.UAVObjectManager;
import org.openpilot.uavtalk.UAVTalk;
import org.openpilot.uavtalk.uavobjects.UAVObjectsInitialize;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
public abstract class TelemetryTask implements Runnable {
// Logging settings
private final String TAG = TelemetryTask.class.getSimpleName();
public static final int LOGLEVEL = 2;
public static final boolean WARN = LOGLEVEL > 1;
public static final boolean DEBUG = LOGLEVEL > 0;
/*
* This is a self contained runnable that will establish (if possible)
* a telemetry connection and provides a listener interface to be
* notified of a set of events
*
* 1. attempt to establish connection
* 2. callback when it succeeds (or fails) which notifies listener
* 3. once physical connection is established instantiate uavtalk / objectmanager
* 4. notify listener they are connected
* 5. detect physical link failure and notify listener about that
* 6. provide "close link" method
*
* There are essentially four tasks that need to occur here
* 1. Transfer data from the outputStream to the physical link (some protocols do this automatically)
* 2. Transfer data from the physical link to the inputStream (again some protocols do this automatically)
* 3. Transfer data from the inputStream to UAVTalk (uavTalk.processInputByte)
* 4. Transfer data from objects via UAVTalk to output stream (occurs from Telemetry object)
*/
//! Private variables
protected Handler handler;
//! Handle to the parent service
protected final OPTelemetryService telemService;
//! The object manager that will be used for this telemetry task
protected UAVObjectManager objMngr;
//! The UAVTalk connected to the below streams
private UAVTalk uavTalk;
//! The input stream for the telemetry channel
protected InputStream inStream;
//! The output stream for the telemetry channel
protected OutputStream outStream;
//! The telemetry object which takes care of higher level transactions
private Telemetry tel;
//! The telemetry monitor which takes care of high level connects / disconnects
private TelemetryMonitor mon;
//! Flag to indicate a shut down was requested. Derived classes should take care to respect this.
boolean shutdown;
//! Indicate a physical connection is established
private boolean connected;
TelemetryTask(OPTelemetryService s) {
telemService = s;
shutdown = false;
connected = false;
}
/**
* Attempt a connection. This method may return before the results are
* known.
* @return False if the attempt failed and no connection will be established
* @return True if the attempt succeeded but does not guarantee success
*/
abstract boolean attemptConnection();
/**
* Called when a physical channel is opened
*
* When this method is called the derived class must have
* created a valid inStream and outStream
*/
boolean attemptSucceeded() {
// Create a new object manager and register all objects
// in the future the particular register method should
// be dependent on what is connected (e.g. board and
// version number).
objMngr = new UAVObjectManager();
UAVObjectsInitialize.register(objMngr);
// Create the required telemetry objects attached to this
// data stream
uavTalk = new UAVTalk(inStream, outStream, objMngr);
tel = new Telemetry(uavTalk, objMngr);
mon = new TelemetryMonitor(objMngr,tel);
// Create an observer to notify system of connection
mon.addObserver(connectionObserver);
// Create a new thread that processes the input bytes
startInputProcessing();
connected = true;
return connected;
}
boolean attemptedFailed() {
connected = false;
return connected;
}
void disconnect() {
// Make the default input procesing loop stop
shutdown = true;
// Shut down all the attached
if (mon != null) {
mon.stopMonitor();
mon.deleteObserver(connectionObserver);
mon = null;
}
if (tel != null) {
tel.stopTelemetry();
tel = null;
}
// Stop the master telemetry thread
handler.post(new Runnable() {
@Override
public void run() {
Looper.myLooper().quit();
}
});
// TODO: Make sure the input and output stream is closed
// TODO: Make sure any threads for input and output are closed
}
/**
* Default implementation for processing input stream
* which creates a new thread that keeps attempting
* to read from the input stream.
*/
private void startInputProcessing() {
new Thread(new processUavTalk()).start();
}
//! Runnable to process input stream
class processUavTalk implements Runnable {
@Override
public void run() {
if (DEBUG) Log.d(TAG, "Entering UAVTalk processing loop");
while (!shutdown) {
try {
if( !uavTalk.processInputStream() )
break;
} catch (IOException e) {
e.printStackTrace();
telemService.toastMessage("Telemetry input stream interrupted");
break;
}
}
if (DEBUG) Log.d(TAG, "UAVTalk processing loop finished");
}
};
@Override
public void run() {
try {
Looper.prepare();
handler = new Handler();
if (DEBUG) Log.d(TAG, "Attempting connection");
if( attemptConnection() == false )
return; // Attempt failed
Looper.loop();
if (DEBUG) Log.d(TAG, "TelemetryTask runnable finished");
} catch (Throwable t) {
Log.e(TAG, "halted due to an error", t);
}
telemService.toastMessage("Telemetry Thread finished");
}
private final Observer connectionObserver = new Observer() {
@Override
public void update(Observable arg0, Object arg1) {
if (DEBUG) Log.d(TAG, "Mon updated. Connected: " + mon.getConnected() + " objects updated: " + mon.getObjectsUpdated());
if(mon.getConnected()) {
Intent intent = new Intent();
intent.setAction(OPTelemetryService.INTENT_ACTION_CONNECTED);
telemService.sendBroadcast(intent,null);
}
}
};
/**** General accessors ****/
public boolean getConnected() {
return connected;
}
public UAVTalk getUavtalk() {
return uavTalk;
}
public OPTelemetryService.TelemTask getTelemTaskIface() {
return new OPTelemetryService.TelemTask() {
@Override
public UAVObjectManager getObjectManager() {
return objMngr;
}
};
}
}

View File

@ -40,10 +40,10 @@ import android.util.Log;
public class Telemetry {
private final String TAG = "Telemetry";
public static int LOGLEVEL = 0;
public static int LOGLEVEL = 1;
public static boolean WARN = LOGLEVEL > 2;
public static boolean ERROR = LOGLEVEL > 1;
public static boolean DEBUG = LOGLEVEL > 0;
public static boolean DEBUG = LOGLEVEL > 1;
public static boolean ERROR = LOGLEVEL > 0;
public class TelemetryStats {
public int txBytes;
public int rxBytes;
@ -66,6 +66,17 @@ public class Telemetry {
UAVObject obj;
int event;
boolean allInstances;
@Override
public boolean equals(Object e) {
try {
ObjectQueueInfo o = (ObjectQueueInfo) e;
return o.obj.getObjID() == obj.getObjID() && o.event == event && o.allInstances == allInstances;
} catch (Exception err) {
};
return false;
}
};
class ObjectTransactionInfo {
@ -112,13 +123,13 @@ public class Telemetry {
}
});
// Listen to transaction completions
this.utalk.setOnTransactionCompletedListener(
this.utalk.new OnTransactionCompletedListener() {
// Listen to transaction completions from uavtalk
utalk.setOnTransactionCompletedListener(
utalk.new OnTransactionCompletedListener() {
@Override
void TransactionSucceeded(UAVObject data) {
try {
transactionCompleted(data);
transactionCompleted(data, true);
} catch (IOException e) {
// Disconnect when stream fails
utalk.setOnTransactionCompletedListener(null);
@ -127,8 +138,9 @@ public class Telemetry {
@Override
void TransactionFailed(UAVObject data) {
try {
Log.d(TAG, "TransactionFailed(" + data.getName() + ")");
transactionCompleted(data);
if (DEBUG) Log.d(TAG, "TransactionFailed(" + data.getName() + ")");
transactionCompleted(data, false);
} catch (IOException e) {
// Disconnect when stream fails
utalk.setOnTransactionCompletedListener(null);
@ -250,6 +262,54 @@ public class Telemetry {
}
}
final Observer unpackedObserver = new Observer() {
@Override
public void update(Observable observable, Object data) {
try {
enqueueObjectUpdates((UAVObject) data, EV_UNPACKED, false, true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
final Observer updatedAutoObserver = new Observer() {
@Override
public void update(Observable observable, Object data) {
try {
enqueueObjectUpdates((UAVObject) data, EV_UPDATED, false, true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
final Observer updatedManualObserver = new Observer() {
@Override
public void update(Observable observable, Object data) {
try {
enqueueObjectUpdates((UAVObject) data, EV_UPDATED_MANUAL, false, true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
final Observer updatedRequestedObserver = new Observer() {
@Override
public void update(Observable observable, Object data) {
try {
enqueueObjectUpdates((UAVObject) data, EV_UPDATE_REQ, false, true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
/**
* Connect to all instances of an object depending on the event mask specified
*/
@ -260,66 +320,23 @@ public class Telemetry {
while(li.hasNext())
{
obj = li.next();
//TODO: Disconnect all
// obj.disconnect(this);
// Disconnect all previous observers from telemetry. This is imortant as this can
// be called multiple times
obj.removeUnpackedObserver(unpackedObserver);
obj.removeUpdatedAutoObserver(updatedAutoObserver);
obj.removeUpdatedManualObserver(updatedManualObserver);
obj.removeUpdateRequestedObserver(updatedRequestedObserver);
// Connect only the selected events
if ( (eventMask&EV_UNPACKED) != 0)
{
obj.addUnpackedObserver(new Observer() {
@Override
public void update(Observable observable, Object data) {
try {
objectUnpacked( (UAVObject) data);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
obj.addUnpackedObserver(unpackedObserver);
if ( (eventMask&EV_UPDATED) != 0)
{
obj.addUpdatedAutoObserver(new Observer() {
@Override
public void update(Observable observable, Object data) {
try {
objectUpdatedAuto( (UAVObject) data);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
obj.addUpdatedAutoObserver(updatedAutoObserver);
if ( (eventMask&EV_UPDATED_MANUAL) != 0)
{
obj.addUpdatedManualObserver(new Observer() {
@Override
public void update(Observable observable, Object data) {
try {
objectUpdatedManual( (UAVObject) data);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
obj.addUpdatedManualObserver(updatedManualObserver);
if ( (eventMask&EV_UPDATE_REQ) != 0)
{
obj.addUpdateRequestedObserver(new Observer() {
@Override
public void update(Observable observable, Object data) {
try {
updateRequested( (UAVObject) data);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
obj.addUpdateRequestedObserver(updatedRequestedObserver);
}
}
@ -376,7 +393,7 @@ public class Telemetry {
* Called when a transaction is successfully completed (uavtalk event)
* @throws IOException
*/
private synchronized void transactionCompleted(UAVObject obj) throws IOException
private synchronized void transactionCompleted(UAVObject obj, boolean result) throws IOException
{
if (DEBUG) Log.d(TAG,"UAVTalk transactionCompleted");
// Check if there is a pending transaction and the objects match
@ -386,13 +403,15 @@ public class Telemetry {
// Complete transaction
transTimer.cancel();
transPending = false;
// Send signal
obj.transactionCompleted(true);
//Send signal
obj.transactionCompleted(result);
// Process new object updates from queue
processObjectQueue();
} else
{
if (ERROR) Log.e(TAG,"Error: received a transaction completed when did not expect it.");
transPending = false;
}
}
@ -416,13 +435,13 @@ public class Telemetry {
}
else
{
// Terminate transaction
utalk.cancelTransaction();
transPending = false;
// Send signal
transInfo.obj.transactionCompleted(false);
// Process new object updates from queue
processObjectQueue();
if (ERROR) Log.e(TAG, "Transaction failed for: " + transInfo.obj.getName());
// Terminate transaction. This triggers UAVTalk to send a transaction
// failed signal which will make the next queue entry be processed
// Note this is UAVTalk listener TransactionFailed function and not the
// object specific transaction failed.
utalk.cancelPendingTransaction(transInfo.obj);
++txErrors;
}
}
@ -463,10 +482,10 @@ public class Telemetry {
}
/**
* Process the event received from an object
* @throws IOException
* Enqueue the event received from an object. This is the main method that handles all the callbacks
* from UAVObjects (due to updates, or update requests)
*/
private synchronized void processObjectUpdates(UAVObject obj, int event, boolean allInstances, boolean priority) throws IOException
private synchronized void enqueueObjectUpdates(UAVObject obj, int event, boolean allInstances, boolean priority) throws IOException
{
// Push event into queue
if (DEBUG) Log.d(TAG, "Push event into queue for obj " + obj.getName() + " event " + event);
@ -478,28 +497,34 @@ public class Telemetry {
objInfo.allInstances = allInstances;
if (priority)
{
if ( objPriorityQueue.size() < MAX_QUEUE_SIZE )
{
objPriorityQueue.add(objInfo);
}
else
{
++txErrors;
obj.transactionCompleted(false);
Log.w(TAG,"Telemetry: priority event queue is full, event lost " + obj.getName());
}
// Only enqueue if an identical transaction does not already exist
if(!objPriorityQueue.contains(objInfo)) {
if ( objPriorityQueue.size() < MAX_QUEUE_SIZE )
{
objPriorityQueue.add(objInfo);
}
else
{
++txErrors;
obj.transactionCompleted(false);
Log.w(TAG,"Telemetry: priority event queue is full, event lost " + obj.getName());
}
}
}
else
{
if ( objQueue.size() < MAX_QUEUE_SIZE )
{
objQueue.add(objInfo);
}
else
{
++txErrors;
obj.transactionCompleted(false);
}
// Only enqueue if an identical transaction does not already exist
if(!objQueue.contains(objInfo)) {
if ( objQueue.size() < MAX_QUEUE_SIZE )
{
objQueue.add(objInfo);
}
else
{
++txErrors;
obj.transactionCompleted(false);
}
}
}
// If there is no transaction in progress then process event
@ -520,7 +545,7 @@ public class Telemetry {
// Don nothing if a transaction is already in progress (should not happen)
if (transPending)
{
if (ERROR) Log.e(TAG,"Dequeue while a transaction pending");
if (WARN) Log.e(TAG,"Dequeue while a transaction pending");
return;
}
@ -588,7 +613,7 @@ public class Telemetry {
// we do not have additional objects still in the queue,
// so we have to reschedule queue processing to make sure they are not
// stuck:
if ( objInfo.event == EV_UNPACKED )
if ( objInfo.event == EV_UNPACKED && !transPending)
processObjectQueue();
}
@ -628,7 +653,7 @@ public class Telemetry {
objinfo.timeToNextUpdateMs = objinfo.updatePeriodMs - offset;
// Send object
startTime = System.currentTimeMillis();
processObjectUpdates(objinfo.obj, EV_UPDATED_MANUAL, true, false);
enqueueObjectUpdates(objinfo.obj, EV_UPDATED_MANUAL, true, false);
elapsedMs = (int) (System.currentTimeMillis() - startTime);
// Update timeToNextUpdateMs with the elapsed delay of sending the object;
timeToNextUpdateMs += elapsedMs;
@ -682,25 +707,6 @@ public class Telemetry {
txRetries = 0;
}
private synchronized void objectUpdatedAuto(UAVObject obj) throws IOException
{
processObjectUpdates(obj, EV_UPDATED, false, true);
}
private synchronized void objectUpdatedManual(UAVObject obj) throws IOException
{
processObjectUpdates(obj, EV_UPDATED_MANUAL, false, true);
}
private synchronized void objectUnpacked(UAVObject obj) throws IOException
{
processObjectUpdates(obj, EV_UNPACKED, false, true);
}
public synchronized void updateRequested(UAVObject obj) throws IOException
{
processObjectUpdates(obj, EV_UPDATE_REQ, false, true);
}
private void newObject(UAVObject obj)
{

View File

@ -39,14 +39,17 @@ import android.util.Log;
public class TelemetryMonitor extends Observable {
private static final String TAG = "TelemetryMonitor";
public static int LOGLEVEL = 0;
public static boolean WARN = LOGLEVEL > 1;
public static boolean DEBUG = LOGLEVEL > 0;
public static final int LOGLEVEL = 0;
public static boolean DEBUG = LOGLEVEL > 2;
public static final boolean WARN = LOGLEVEL > 1;
public static final boolean ERROR = LOGLEVEL > 0;
static final int STATS_UPDATE_PERIOD_MS = 4000;
static final int STATS_CONNECT_PERIOD_MS = 1000;
static final int CONNECTION_TIMEOUT_MS = 8000;
private final boolean HANDSHAKE_IS_CONNECTED = false;
private final UAVObjectManager objMngr;
private final Telemetry tel;
// private UAVObject objPending;
@ -146,6 +149,20 @@ public class TelemetryMonitor extends Observable {
queue.clear();
}
final Observer transactionObserver = new Observer() {
@Override
public void update(Observable observable, Object data) {
try {
UAVObject.TransactionResult result = (UAVObject.TransactionResult) data;
transactionCompleted(result.obj, result.success);
} catch (IOException e) {
// When the telemetry stream is broken disconnect these
// updates
observable.deleteObserver(this);
}
}
};
/**
* Retrieve the next object in the queue
*
@ -154,11 +171,12 @@ public class TelemetryMonitor extends Observable {
public synchronized void retrieveNextObject() throws IOException {
// If queue is empty return
if (queue.isEmpty()) {
if (DEBUG)
Log.d(TAG, "All objects retrieved: Connected Successfully");
if (DEBUG || true) Log.d(TAG, "All objects retrieved: Connected Successfully");
objects_updated = true;
setChanged();
notifyObservers();
if (!HANDSHAKE_IS_CONNECTED) {
setChanged();
notifyObservers();
}
return;
}
// Get next object from the queue
@ -173,26 +191,10 @@ public class TelemetryMonitor extends Observable {
// TODO: Does this need to stay here permanently? This appears to be
// used for setup mainly
obj.addTransactionCompleted(new Observer() {
@Override
public void update(Observable observable, Object data) {
UAVObject.TransactionResult result = (UAVObject.TransactionResult) data;
if (DEBUG)
Log.d(TAG, "Got transaction completed event from "
+ result.obj.getName() + " status: "
+ result.success);
try {
transactionCompleted(result.obj, result.success);
} catch (IOException e) {
// When the telemetry stream is broken disconnect these
// updates
observable.deleteObserver(this);
}
}
});
obj.addTransactionCompleted(transactionObserver);
// Request update
tel.updateRequested(obj);
obj.updateRequested();
}
/**
@ -205,15 +207,17 @@ public class TelemetryMonitor extends Observable {
if (DEBUG)
Log.d(TAG, "transactionCompleted. Status: " + success);
// Remove the listener for the event that just finished
obj.removeTransactionCompleted(transactionObserver);
if (!success) {
// Right now success = false means received a NAK so don't
// re-attempt
Log.e(TAG, "Transaction failed.");
if (ERROR) Log.e(TAG, "Transaction failed.");
}
// Process next object if telemetry is still available
if (((String) gcsStatsObj.getField("Status").getValue())
.compareTo("Connected") == 0) {
if (((String) gcsStatsObj.getField("Status").getValue()).compareTo("Connected") == 0) {
retrieveNextObject();
} else {
stopRetrievingObjects();
@ -347,7 +351,7 @@ public class TelemetryMonitor extends Observable {
connected = true;
objects_updated = false;
startRetrievingObjects();
setChanged();
if (HANDSHAKE_IS_CONNECTED) setChanged(); // Enabling this line makes the opConnected signal occur whenever we get a handshake
}
if (gcsDisconnected && gcsStatusChanged) {
if (DEBUG)

View File

@ -105,6 +105,11 @@ public abstract class UAVObject {
unpackedListeners.addObserver(o);
}
}
public void removeUnpackedObserver(Observer o) {
synchronized(unpackedListeners) {
unpackedListeners.deleteObserver(o);
}
}
void unpacked() {
synchronized(unpackedListeners) {
unpackedListeners.event();
@ -117,6 +122,11 @@ public abstract class UAVObject {
updatedAutoListeners.addObserver(o);
}
}
public void removeUpdatedAutoObserver(Observer o) {
synchronized(updatedAutoListeners) {
updatedAutoListeners.deleteObserver(o);
}
}
void updatedAuto() {
synchronized(updatedAutoListeners) {
updatedAutoListeners.event();
@ -129,6 +139,11 @@ public abstract class UAVObject {
updatedManualListeners.addObserver(o);
}
}
public void removeUpdatedManualObserver(Observer o) {
synchronized(updatedManualListeners) {
updatedManualListeners.deleteObserver(o);
}
}
void updatedManual() {
synchronized(updatedManualListeners) {
updatedManualListeners.event();
@ -141,6 +156,11 @@ public abstract class UAVObject {
updateRequestedListeners.addObserver(o);
}
}
public void removeUpdateRequestedObserver(Observer o) {
synchronized(updateRequestedListeners) {
updateRequestedListeners.deleteObserver(o);
}
}
public void updateRequested() {
synchronized(updateRequestedListeners) {
updateRequestedListeners.event();

View File

@ -106,9 +106,13 @@ public class UAVTalk {
static final int TYPE_MASK = 0xF8;
static final int TYPE_VER = 0x20;
//! Packet contains an object
static final int TYPE_OBJ = (TYPE_VER | 0x00);
//! Packet is a request for an object
static final int TYPE_OBJ_REQ = (TYPE_VER | 0x01);
//! Packet is an object with a request for an ack
static final int TYPE_OBJ_ACK = (TYPE_VER | 0x02);
//! Packet is an ack for an object
static final int TYPE_ACK = (TYPE_VER | 0x03);
static final int TYPE_NACK = (TYPE_VER | 0x04);
@ -135,8 +139,14 @@ public class UAVTalk {
OutputStream outStream;
UAVObjectManager objMngr;
//! Currently only one UAVTalk transaction is permitted at a time. If this is null none are in process
//! otherwise points to the pending object
UAVObject respObj;
//! If the pending transaction is for all the instances
boolean respAllInstances;
//! The type of response we are expecting
int respType;
// Variables used by the receive state machine
ByteBuffer rxTmpBuffer /* 4 */;
ByteBuffer rxBuffer;
@ -217,6 +227,8 @@ public class UAVTalk {
//inStream.wait();
val = inStream.read();
if (VERBOSE) Log.v(TAG, "Read: " + val);
if (val == -1) {
return false;
}
@ -234,7 +246,6 @@ public class UAVTalk {
* @throws IOException
*/
public boolean sendObjectRequest(UAVObject obj, boolean allInstances) throws IOException {
// QMutexLocker locker(mutex);
return objectTransaction(obj, TYPE_OBJ_REQ, allInstances);
}
@ -245,8 +256,7 @@ public class UAVTalk {
* Success (true), Failure (false)
* @throws IOException
*/
public synchronized boolean sendObject(UAVObject obj, boolean acked,
boolean allInstances) throws IOException {
public boolean sendObject(UAVObject obj, boolean acked, boolean allInstances) throws IOException {
if (acked) {
return objectTransaction(obj, TYPE_OBJ_ACK, allInstances);
} else {
@ -255,10 +265,46 @@ public class UAVTalk {
}
/**
* Cancel a pending transaction
* UAVTalk takes care of it's own transactions but if the caller knows
* it wants to give up on one (after a timeout) then it can cancel it
* @return True if that object was pending, False otherwise
*/
public synchronized void cancelTransaction() {
public synchronized boolean cancelPendingTransaction(UAVObject obj) {
if(respObj != null && respObj.getObjID() == obj.getObjID()) {
if(transactionListener != null) {
Log.d(TAG,"Canceling transaction: " + respObj.getName());
transactionListener.TransactionFailed(respObj);
}
respObj = null;
return true;
} else
return false;
}
/**
* Cancel a pending transaction. If there is a pending transaction and
* a listener then notify them that the transaction failed.
*/
/*private synchronized void cancelPendingTransaction() {
if(respObj != null && transactionListener != null) {
Log.d(TAG,"Canceling transaction: " + respObj.getName());
transactionListener.TransactionFailed(respObj);
}
respObj = null;
}*/
/**
* This is the code that sets up a new UAVTalk packet that expects a response.
*/
private synchronized void setupTransaction(UAVObject obj, boolean allInstances, int type) {
// Only cancel if it is for a different object
if(respObj != null && respObj.getObjID() != obj.getObjID())
cancelPendingTransaction(obj);
respObj = obj;
respAllInstances = allInstances;
respType = type;
}
/**
@ -269,21 +315,9 @@ public class UAVTalk {
* Success (true), Failure (false)
* @throws IOException
*/
public boolean objectTransaction(UAVObject obj, int type,
boolean allInstances) throws IOException {
// Send object depending on if a response is needed
if (type == TYPE_OBJ_ACK || type == TYPE_OBJ_REQ) {
if (transmitObject(obj, type, allInstances)) {
if(type == TYPE_OBJ_REQ)
if (ERROR) Log.e(TAG, "Sending obj req");
respObj = obj;
respAllInstances = allInstances;
return true;
} else {
return false;
}
} else if (type == TYPE_OBJ) {
return transmitObject(obj, TYPE_OBJ, allInstances);
private synchronized boolean objectTransaction(UAVObject obj, int type, boolean allInstances) throws IOException {
if (type == TYPE_OBJ_ACK || type == TYPE_OBJ_REQ || type == TYPE_OBJ) {
return transmitObject(obj, type, allInstances);
} else {
return false;
}
@ -294,207 +328,210 @@ public class UAVTalk {
* byte \return Success (true), Failure (false)
* @throws IOException
*/
public synchronized boolean processInputByte(int rxbyte) throws IOException {
public boolean processInputByte(int rxbyte) throws IOException {
Assert.assertNotNull(objMngr);
// Update stats
stats.rxBytes++;
// Only need to synchronize this method on the state machine state
synchronized(rxState) {
// Update stats
stats.rxBytes++;
rxPacketLength++; // update packet byte count
rxPacketLength++; // update packet byte count
// Receive state machine
switch (rxState) {
case STATE_SYNC:
// Receive state machine
switch (rxState) {
case STATE_SYNC:
if (rxbyte != SYNC_VAL)
if (rxbyte != SYNC_VAL)
break;
// Initialize and update CRC
rxCS = updateCRC(0, rxbyte);
rxPacketLength = 1;
rxState = RxStateType.STATE_TYPE;
break;
// Initialize and update CRC
rxCS = updateCRC(0, rxbyte);
case STATE_TYPE:
rxPacketLength = 1;
// Update CRC
rxCS = updateCRC(rxCS, rxbyte);
rxState = RxStateType.STATE_TYPE;
break;
case STATE_TYPE:
// Update CRC
rxCS = updateCRC(rxCS, rxbyte);
if ((rxbyte & TYPE_MASK) != TYPE_VER) {
Log.e(TAG, "Unknown UAVTalk type:" + rxbyte);
rxState = RxStateType.STATE_SYNC;
break;
}
rxType = rxbyte;
if (VERBOSE) Log.v(TAG, "Received packet type: " + rxType);
packetSize = 0;
rxState = RxStateType.STATE_SIZE;
rxCount = 0;
break;
case STATE_SIZE:
// Update CRC
rxCS = updateCRC(rxCS, rxbyte);
if (rxCount == 0) {
packetSize += rxbyte;
rxCount++;
break;
}
packetSize += (rxbyte << 8) & 0xff00;
if (packetSize < MIN_HEADER_LENGTH
|| packetSize > MAX_HEADER_LENGTH + MAX_PAYLOAD_LENGTH) { // incorrect
// packet
// size
rxState = RxStateType.STATE_SYNC;
break;
}
rxCount = 0;
rxState = RxStateType.STATE_OBJID;
rxTmpBuffer.position(0);
break;
case STATE_OBJID:
// Update CRC
rxCS = updateCRC(rxCS, rxbyte);
rxTmpBuffer.put(rxCount++, (byte) (rxbyte & 0xff));
if (rxCount < 4)
break;
// Search for object, if not found reset state machine
rxObjId = rxTmpBuffer.getInt(0);
// Because java treats ints as only signed we need to do this manually
if (rxObjId < 0)
rxObjId = 0x100000000l + rxObjId;
{
UAVObject rxObj = objMngr.getObject(rxObjId);
if (rxObj == null) {
if (DEBUG) Log.d(TAG, "Unknown ID: " + rxObjId);
stats.rxErrors++;
if ((rxbyte & TYPE_MASK) != TYPE_VER) {
if (ERROR) Log.e(TAG, "Unknown UAVTalk type:" + rxbyte);
rxState = RxStateType.STATE_SYNC;
break;
}
// Determine data length
if (rxType == TYPE_OBJ_REQ || rxType == TYPE_ACK || rxType == TYPE_NACK)
rxLength = 0;
else
rxLength = rxObj.getNumBytes();
rxType = rxbyte;
if (VERBOSE) Log.v(TAG, "Received packet type: " + rxType);
packetSize = 0;
// Check length and determine next state
if (rxLength >= MAX_PAYLOAD_LENGTH) {
stats.rxErrors++;
rxState = RxStateType.STATE_SIZE;
rxCount = 0;
break;
case STATE_SIZE:
// Update CRC
rxCS = updateCRC(rxCS, rxbyte);
if (rxCount == 0) {
packetSize += rxbyte;
rxCount++;
break;
}
packetSize += (rxbyte << 8) & 0xff00;
if (packetSize < MIN_HEADER_LENGTH
|| packetSize > MAX_HEADER_LENGTH + MAX_PAYLOAD_LENGTH) { // incorrect
// packet
// size
rxState = RxStateType.STATE_SYNC;
break;
}
// Check the lengths match
if ((rxPacketLength + rxLength) != packetSize) { // packet error
// -
// mismatched
// packet
// size
stats.rxErrors++;
rxState = RxStateType.STATE_SYNC;
break;
}
rxCount = 0;
rxState = RxStateType.STATE_OBJID;
rxTmpBuffer.position(0);
break;
// Check if this is a single instance object (i.e. if the
// instance ID field is coming next)
if (rxObj.isSingleInstance()) {
// If there is a payload get it, otherwise receive checksum
if (rxLength > 0)
rxState = RxStateType.STATE_DATA;
case STATE_OBJID:
// Update CRC
rxCS = updateCRC(rxCS, rxbyte);
rxTmpBuffer.put(rxCount++, (byte) (rxbyte & 0xff));
if (rxCount < 4)
break;
// Search for object, if not found reset state machine
rxObjId = rxTmpBuffer.getInt(0);
// Because java treats ints as only signed we need to do this manually
if (rxObjId < 0)
rxObjId = 0x100000000l + rxObjId;
{
UAVObject rxObj = objMngr.getObject(rxObjId);
if (rxObj == null) {
if (DEBUG) Log.d(TAG, "Unknown ID: " + rxObjId);
stats.rxErrors++;
rxState = RxStateType.STATE_SYNC;
break;
}
// Determine data length
if (rxType == TYPE_OBJ_REQ || rxType == TYPE_ACK || rxType == TYPE_NACK)
rxLength = 0;
else
rxState = RxStateType.STATE_CS;
rxInstId = 0;
rxCount = 0;
} else {
rxState = RxStateType.STATE_INSTID;
rxCount = 0;
rxLength = rxObj.getNumBytes();
// Check length and determine next state
if (rxLength >= MAX_PAYLOAD_LENGTH) {
stats.rxErrors++;
rxState = RxStateType.STATE_SYNC;
break;
}
// Check the lengths match
if ((rxPacketLength + rxLength) != packetSize) { // packet error
// -
// mismatched
// packet
// size
stats.rxErrors++;
rxState = RxStateType.STATE_SYNC;
break;
}
// Check if this is a single instance object (i.e. if the
// instance ID field is coming next)
if (rxObj.isSingleInstance()) {
// If there is a payload get it, otherwise receive checksum
if (rxLength > 0)
rxState = RxStateType.STATE_DATA;
else
rxState = RxStateType.STATE_CS;
rxInstId = 0;
rxCount = 0;
} else {
rxState = RxStateType.STATE_INSTID;
rxCount = 0;
}
}
}
break;
case STATE_INSTID:
// Update CRC
rxCS = updateCRC(rxCS, rxbyte);
rxTmpBuffer.put(rxCount++, (byte) (rxbyte & 0xff));
if (rxCount < 2)
break;
rxInstId = rxTmpBuffer.getShort(0);
case STATE_INSTID:
rxCount = 0;
// Update CRC
rxCS = updateCRC(rxCS, rxbyte);
rxTmpBuffer.put(rxCount++, (byte) (rxbyte & 0xff));
if (rxCount < 2)
break;
rxInstId = rxTmpBuffer.getShort(0);
rxCount = 0;
// If there is a payload get it, otherwise receive checksum
if (rxLength > 0)
rxState = RxStateType.STATE_DATA;
else
rxState = RxStateType.STATE_CS;
break;
case STATE_DATA:
// Update CRC
rxCS = updateCRC(rxCS, rxbyte);
rxBuffer.put(rxCount++, (byte) (rxbyte & 0xff));
if (rxCount < rxLength)
break;
// If there is a payload get it, otherwise receive checksum
if (rxLength > 0)
rxState = RxStateType.STATE_DATA;
else
rxState = RxStateType.STATE_CS;
break;
case STATE_DATA:
// Update CRC
rxCS = updateCRC(rxCS, rxbyte);
rxBuffer.put(rxCount++, (byte) (rxbyte & 0xff));
if (rxCount < rxLength)
rxCount = 0;
break;
rxState = RxStateType.STATE_CS;
rxCount = 0;
break;
case STATE_CS:
case STATE_CS:
// The CRC byte
rxCSPacket = rxbyte;
// The CRC byte
rxCSPacket = rxbyte;
if (rxCS != rxCSPacket) { // packet error - faulty CRC
if (DEBUG) Log.d(TAG,"Bad crc");
stats.rxErrors++;
rxState = RxStateType.STATE_SYNC;
break;
}
if (rxPacketLength != (packetSize + 1)) { // packet error -
// mismatched packet
// size
if (DEBUG) Log.d(TAG,"Bad size");
stats.rxErrors++;
rxState = RxStateType.STATE_SYNC;
break;
}
if (DEBUG) Log.d(TAG,"Received");
rxBuffer.position(0);
receiveObject(rxType, rxObjId, rxInstId, rxBuffer);
stats.rxObjectBytes += rxLength;
stats.rxObjects++;
if (rxCS != rxCSPacket) { // packet error - faulty CRC
if (DEBUG) Log.d(TAG,"Bad crc");
stats.rxErrors++;
rxState = RxStateType.STATE_SYNC;
break;
}
if (rxPacketLength != (packetSize + 1)) { // packet error -
// mismatched packet
// size
if (DEBUG) Log.d(TAG,"Bad size");
stats.rxErrors++;
default:
rxState = RxStateType.STATE_SYNC;
break;
stats.rxErrors++;
}
if (DEBUG) Log.d(TAG,"Received");
rxBuffer.position(0);
receiveObject(rxType, rxObjId, rxInstId, rxBuffer);
stats.rxObjectBytes += rxLength;
stats.rxObjects++;
rxState = RxStateType.STATE_SYNC;
break;
default:
rxState = RxStateType.STATE_SYNC;
stats.rxErrors++;
}
// Done
@ -510,8 +547,7 @@ public class UAVTalk {
* length \return Success (true), Failure (false)
* @throws IOException
*/
public boolean receiveObject(int type, long objId, long instId,
ByteBuffer data) throws IOException {
public boolean receiveObject(int type, long objId, long instId, ByteBuffer data) throws IOException {
if (DEBUG) Log.d(TAG, "Received object ID: " + objId);
assert (objMngr != null);
@ -526,11 +562,13 @@ public class UAVTalk {
// All instances, not allowed for OBJ messages
if (!allInstances) {
if (DEBUG) Log.d(TAG,"Received object: " + objMngr.getObject(objId).getName());
// Get object and update its data
obj = updateObject(objId, instId, data);
// Check if an ack is pending
if (obj != null) {
updateAck(obj);
// Check if this is a response to a UAVTalk transaction
updateObjReq(obj);
} else {
error = true;
}
@ -581,7 +619,7 @@ public class UAVTalk {
// Check if object exists:
if (obj != null)
{
updateNack(obj);
receivedNack(obj);
}
else
{
@ -620,11 +658,13 @@ public class UAVTalk {
// Get object
UAVObject obj = objMngr.getObject(objId, instId);
// If the instance does not exist create it
if (obj == null) {
// Get the object type
UAVObject tobj = objMngr.getObject(objId);
if (tobj == null) {
// TODO: Return a NAK since we don't know this object
return null;
}
// Make sure this is a data object
@ -661,32 +701,62 @@ public class UAVTalk {
}
/**
* Check if a transaction is pending and if yes complete it.
* Called when an object is received to check if this completes
* a UAVTalk transaction
*/
void updateNack(UAVObject obj)
{
if (DEBUG) Log.d(TAG, "NACK received: " + obj.getName());
private synchronized void updateObjReq(UAVObject obj) {
// Check if this is not a possible candidate
Assert.assertNotNull(obj);
//obj.transactionCompleted(false);
if (respObj != null && respObj.getObjID() == obj.getObjID() &&
(respObj.getInstID() == obj.getInstID() || respAllInstances)) {
if (transactionListener != null)
transactionListener.TransactionFailed(obj);
if(respObj != null && respType == TYPE_OBJ_REQ && respObj.getObjID() == obj.getObjID() &&
((respObj.getInstID() == obj.getInstID() || !respAllInstances))) {
// Indicate complete
respObj = null;
// Notify listener
if (transactionListener != null)
transactionListener.TransactionSucceeded(obj);
}
}
/**
* Check if a transaction is pending and if yes complete it.
*/
synchronized void updateAck(UAVObject obj) {
if (DEBUG) Log.d(TAG, "ACK received: " + obj.getName());
private synchronized void receivedNack(UAVObject obj)
{
Assert.assertNotNull(obj);
if(respObj != null && (respType == TYPE_OBJ_REQ || respType == TYPE_OBJ_ACK ) &&
respObj.getObjID() == obj.getObjID()) {
if (DEBUG) Log.d(TAG, "NAK: " + obj.getName());
// Indicate complete
respObj = null;
// Notify listener
if (transactionListener != null)
transactionListener.TransactionFailed(obj);
}
}
/**
* Check if a transaction is pending that this acked object corresponds to
* and if yes complete it.
*/
private synchronized void updateAck(UAVObject obj) {
if (DEBUG) Log.d(TAG, "Received ack: " + obj.getName());
Assert.assertNotNull(obj);
if (respObj != null && respObj.getObjID() == obj.getObjID()
&& (respObj.getInstID() == obj.getInstID() || respAllInstances)) {
// Indicate complete
respObj = null;
// Notify listener
if (transactionListener != null)
transactionListener.TransactionSucceeded(obj);
respObj = null;
}
}
@ -698,7 +768,7 @@ public class UAVTalk {
* @return Success (true), Failure (false)
* @throws IOException
*/
public synchronized boolean transmitObject(UAVObject obj, int type, boolean allInstances) throws IOException {
private boolean transmitObject(UAVObject obj, int type, boolean allInstances) throws IOException {
// If all instances are requested on a single instance object it is an
// error
if (allInstances && obj.isSingleInstance()) {
@ -712,6 +782,10 @@ public class UAVTalk {
int numInst = objMngr.getNumInstances(obj.getObjID());
// Send all instances
for (int instId = 0; instId < numInst; ++instId) {
// TODO: This code is buggy probably. We should send each request
// and wait for an ack in the case of an TYPE_OBJ_ACK
Assert.assertNotSame(type, TYPE_OBJ_ACK); // catch any buggy calls
UAVObject inst = objMngr.getObject(obj.getObjID(), instId);
transmitSingleObject(inst, type, false);
}
@ -738,8 +812,7 @@ public class UAVTalk {
* @param[in] obj Object handle to send
* @param[in] type Transaction type \return Success (true), Failure (false)
*/
public synchronized boolean transmitSingleObject(UAVObject obj, int type,
boolean allInstances) throws IOException {
private boolean transmitSingleObject(UAVObject obj, int type, boolean allInstances) throws IOException {
int length;
int allInstId = ALL_INSTANCES;
@ -793,6 +866,13 @@ public class UAVTalk {
bbuf.position(0);
byte[] dst = new byte[packlen];
bbuf.get(dst, 0, packlen);
if (type == TYPE_OBJ_ACK || type == TYPE_OBJ_REQ) {
// Once we send a UAVTalk packet that requires an ack or object let's set up
// the transaction here
setupTransaction(obj, allInstances, type);
}
outStream.write(dst);