From a0ca56a071c8980ae82494fd5907bddcf105cae5 Mon Sep 17 00:00:00 2001 From: James Cotton Date: Sun, 12 Aug 2012 13:28:38 -0500 Subject: [PATCH 01/16] HID: move all the variables to the top --- .../androidgcs/telemetry/HidUAVTalk.java | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java index 991e83871..6ec0a950a 100644 --- a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java +++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java @@ -28,7 +28,7 @@ import android.util.Log; public class HidUAVTalk extends TelemetryTask { private static final String TAG = HidUAVTalk.class.getSimpleName(); - public static int LOGLEVEL = 0; + public static int LOGLEVEL = 2; public static boolean WARN = LOGLEVEL > 1; public static boolean DEBUG = LOGLEVEL > 0; @@ -46,7 +46,24 @@ public class HidUAVTalk extends TelemetryTask { private static final String ACTION_USB_PERMISSION = "com.access.device.USB_PERMISSION"; - UsbDevice currentDevice; + //! Define whether to use a single interface for reading and writing + private final boolean UsingSingleInterface = true; + + private UsbDevice currentDevice; + private UsbEndpoint usbEndpointRead; + private UsbEndpoint usbEndpointWrite; + private UsbManager usbManager; + private PendingIntent permissionIntent; + private UsbDeviceConnection connectionRead; + private UsbDeviceConnection connectionWrite; + private IntentFilter permissionFilter; + private UsbInterface usbInterfaceRead = null; + private UsbInterface usbInterfaceWrite = null; + private TalkInputStream inTalkStream; + private TalkOutputStream outTalkStream; + private UsbRequest writeRequest = null; + private UsbRequest readRequest = null; + private Thread readWriteThread; public HidUAVTalk(OPTelemetryService service) { super(service); @@ -199,19 +216,6 @@ public class HidUAVTalk extends TelemetryTask { } }; */ - private UsbEndpoint usbEndpointRead; - - private UsbEndpoint usbEndpointWrite; - - private UsbManager usbManager; - - private PendingIntent permissionIntent; - - private UsbDeviceConnection connectionRead; - - private UsbDeviceConnection connectionWrite; - - private IntentFilter permissionFilter; protected void CleanUpAndClose() { if (UsingSingleInterface) { @@ -244,14 +248,6 @@ public class HidUAVTalk extends TelemetryTask { return false; } - private UsbInterface usbInterfaceRead = null; - private UsbInterface usbInterfaceWrite = null; - private final boolean UsingSingleInterface = true; - - private TalkInputStream inTalkStream; - private TalkOutputStream outTalkStream; - UsbRequest writeRequest = null; - 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 @@ -370,7 +366,6 @@ public class HidUAVTalk extends TelemetryTask { return true; } - Thread readWriteThread; void displayBuffer(String msg, byte[] buf) { msg += " ("; @@ -384,7 +379,6 @@ public class HidUAVTalk extends TelemetryTask { * 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); From badbadef2ff971dcc812a34431a517b917347e89 Mon Sep 17 00:00:00 2001 From: James Cotton Date: Sun, 12 Aug 2012 13:41:15 -0500 Subject: [PATCH 02/16] Remove legacy code for supporting two USB interfaces --- .../androidgcs/telemetry/HidUAVTalk.java | 93 +++++-------------- 1 file changed, 23 insertions(+), 70 deletions(-) diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java index 6ec0a950a..4f3c64a5a 100644 --- a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java +++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java @@ -28,9 +28,10 @@ import android.util.Log; public class HidUAVTalk extends TelemetryTask { private static final String TAG = HidUAVTalk.class.getSimpleName(); - public static int LOGLEVEL = 2; - public static boolean WARN = LOGLEVEL > 1; - public static boolean DEBUG = LOGLEVEL > 0; + public static final int LOGLEVEL = 2; + public static final boolean DEBUG = LOGLEVEL > 2; + public static final boolean WARN = LOGLEVEL > 1; + public static final boolean ERROR = LOGLEVEL > 0; //! USB constants private static final int MAX_HID_PACKET_SIZE = 64; @@ -46,19 +47,14 @@ public class HidUAVTalk extends TelemetryTask { private static final String ACTION_USB_PERMISSION = "com.access.device.USB_PERMISSION"; - //! Define whether to use a single interface for reading and writing - private final boolean UsingSingleInterface = true; - private UsbDevice currentDevice; private UsbEndpoint usbEndpointRead; private UsbEndpoint usbEndpointWrite; private UsbManager usbManager; private PendingIntent permissionIntent; - private UsbDeviceConnection connectionRead; - private UsbDeviceConnection connectionWrite; + private UsbDeviceConnection usbDeviceConnection; private IntentFilter permissionFilter; - private UsbInterface usbInterfaceRead = null; - private UsbInterface usbInterfaceWrite = null; + private UsbInterface usbInterface = null; private TalkInputStream inTalkStream; private TalkOutputStream outTalkStream; private UsbRequest writeRequest = null; @@ -218,19 +214,9 @@ public class HidUAVTalk extends TelemetryTask { 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; - } + if(usbDeviceConnection != null && usbInterface != null) + usbDeviceConnection.releaseInterface(usbInterface); + usbInterface = 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. @@ -258,87 +244,54 @@ public class HidUAVTalk extends TelemetryTask { UsbEndpoint ep1 = null; UsbEndpoint ep2 = null; - - if (UsingSingleInterface) + // Using the same interface for reading and writing + usbInterface = connectDevice.getInterface(0x2); + if (usbInterface.getEndpointCount() == 2) { - // 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); - } + ep1 = usbInterface.getEndpoint(0); + ep2 = usbInterface.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"); + if (ERROR) Log.e(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"); + if (ERROR) Log.e(TAG, "Could not find write and read endpoint"); return false; } - connectionRead = usbManager.openDevice(connectDevice); - connectionRead.claimInterface(usbInterfaceRead, true); + // Claim the interface + usbDeviceConnection = usbManager.openDevice(connectDevice); + usbDeviceConnection.claimInterface(usbInterface, true); - if (UsingSingleInterface) - { - connectionWrite = connectionRead; - } - else // if (!UsingSingleInterface) - { - connectionWrite = usbManager.openDevice(connectDevice); - connectionWrite.claimInterface(usbInterfaceWrite, true); - } if (DEBUG) Log.d(TAG, "Opened endpoints"); // Create the USB requests readRequest = new UsbRequest(); - readRequest.initialize(connectionRead, usbEndpointRead); + readRequest.initialize(usbDeviceConnection, usbEndpointRead); writeRequest = new UsbRequest(); - writeRequest.initialize(connectionWrite, usbEndpointWrite); + writeRequest.initialize(usbDeviceConnection, usbEndpointWrite); inTalkStream = new TalkInputStream(); outTalkStream = new TalkOutputStream(); @@ -393,7 +346,7 @@ public class HidUAVTalk extends TelemetryTask { int dataSize; // wait for status event - if (connectionRead.requestWait() == readRequest) { + if (usbDeviceConnection.requestWait() == readRequest) { // Packet format: // 0: Report ID (1) // 1: Number of valid bytes @@ -435,7 +388,7 @@ public class HidUAVTalk extends TelemetryTask { writeRequest.queue(packet, bufferDataLength); try { - if (!writeRequest.equals(connectionWrite.requestWait())) + if (!writeRequest.equals(usbDeviceConnection.requestWait())) Log.e(TAG, "writeRequest failed"); } catch (Exception ex) From 02a1c9454502eb56cf7a37f25f3c05bc661f29fa Mon Sep 17 00:00:00 2001 From: James Cotton Date: Sun, 12 Aug 2012 14:00:50 -0500 Subject: [PATCH 03/16] HID: Since there is only one usb device connection queue read and write events onto that single connection and wait for either. --- .../androidgcs/telemetry/HidUAVTalk.java | 99 +++++++++++-------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java index 4f3c64a5a..5abb4dd09 100644 --- a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java +++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java @@ -28,7 +28,7 @@ import android.util.Log; public class HidUAVTalk extends TelemetryTask { private static final String TAG = HidUAVTalk.class.getSimpleName(); - public static final int LOGLEVEL = 2; + public static final int LOGLEVEL = 0; public static final boolean DEBUG = LOGLEVEL > 2; public static final boolean WARN = LOGLEVEL > 1; public static final boolean ERROR = LOGLEVEL > 0; @@ -61,6 +61,9 @@ public class HidUAVTalk extends TelemetryTask { private UsbRequest readRequest = null; private Thread readWriteThread; + private boolean readPending = false; + private boolean writePending = false; + public HidUAVTalk(OPTelemetryService service) { super(service); } @@ -307,8 +310,23 @@ public class HidUAVTalk extends TelemetryTask { readWriteThread = new Thread(new Runnable() { @Override public void run() { + + // Enqueue the first read + readData(); + while (!shutdown) { - readData(); + // If there are no request + + // Enqueue requests appropriately first + UsbRequest returned = usbDeviceConnection.requestWait(); + if (returned == readRequest) { + if (DEBUG) Log.d(TAG, "Received read request"); + readData(); + } else if (returned == writeRequest) { + if (DEBUG) Log.d(TAG, "Received write completed request"); + writePending = false; + } + sendData(); } } @@ -332,70 +350,65 @@ public class HidUAVTalk extends TelemetryTask { * Gets a report from HID, extract the meaningful data and push * it to the input stream */ - public int readData() { - int bufferDataLength = usbEndpointRead.getMaxPacketSize(); - ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1); + ByteBuffer readBuffer = ByteBuffer.allocate(MAX_HID_PACKET_SIZE); + private void queueRead() { + if(!readRequest.queue(readBuffer, MAX_HID_PACKET_SIZE)) { + if (ERROR) Log.e(TAG, "Failed to queue request"); + } else + readPending = true; + } + public void readData() { - // 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 (usbDeviceConnection.requestWait() == readRequest) { - // Packet format: + if (!readPending) { + queueRead(); + } else { // We just received a read + readPending = false; + // Packet format: // 0: Report ID (1) // 1: Number of valid bytes // 2:63: Data - dataSize = buffer.get(1); // Data size + int dataSize = readBuffer.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(1) > (buffer.capacity() - 2)) { - if (DEBUG) Log.d(TAG, "Badly formatted HID packet"); + if (readBuffer.get(0) != 1 || readBuffer.get(1) < 0 || readBuffer.get(1) > (readBuffer.capacity() - 2)) { + if (ERROR) Log.e(TAG, "Badly formatted HID packet"); } else { byte[] dst = new byte[dataSize]; - buffer.position(2); - buffer.get(dst, 0, dataSize); + readBuffer.position(2); + readBuffer.get(dst, 0, dataSize); if (DEBUG) Log.d(TAG, "Entered read"); inTalkStream.write(dst); if (DEBUG) Log.d(TAG, "Got read: " + dataSize + " bytes"); } - } else - return 0; - return dataSize; + // Queue another read + queueRead(); + } } /** * Send a packet if data is available */ public void sendData() { - ByteBuffer packet = null; - do { // Send all the data available to prevent sending backlog - packet = outTalkStream.getHIDpacket(); - if (packet != null) { - if (DEBUG) Log.d(TAG, "Writing to device()"); - int bufferDataLength = usbEndpointWrite.getMaxPacketSize(); - Assert.assertTrue(packet.capacity() <= bufferDataLength); + // Don't try and send data till previous request completes + if (writePending) + return; - writeRequest.queue(packet, bufferDataLength); - try - { - if (!writeRequest.equals(usbDeviceConnection.requestWait())) - Log.e(TAG, "writeRequest failed"); - } - catch (Exception ex) - { - } - } - } while (packet != null); + ByteBuffer packet = outTalkStream.getHIDpacket(); + if (packet != null) { + if (DEBUG) Log.d(TAG, "Writing to device()"); + + int bufferDataLength = usbEndpointWrite.getMaxPacketSize(); + Assert.assertTrue(packet.capacity() <= bufferDataLength); + + if(writeRequest.queue(packet, bufferDataLength)) + writePending = true; + else if (ERROR) + Log.e(TAG, "Write queuing failed"); + } } /*********** Helper classes for telemetry streams ************/ From ae56dc74c22fbe8df3ed5e64d1e78c4f80be110c Mon Sep 17 00:00:00 2001 From: James Cotton Date: Sun, 12 Aug 2012 14:12:35 -0500 Subject: [PATCH 04/16] Make sure the USB scheduling is thread safe. --- .../androidgcs/telemetry/HidUAVTalk.java | 110 +++++++++++------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java index 5abb4dd09..a074e79ca 100644 --- a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java +++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java @@ -312,7 +312,7 @@ public class HidUAVTalk extends TelemetryTask { public void run() { // Enqueue the first read - readData(); + queueRead(); while (!shutdown) { // If there are no request @@ -325,9 +325,8 @@ public class HidUAVTalk extends TelemetryTask { } else if (returned == writeRequest) { if (DEBUG) Log.d(TAG, "Received write completed request"); writePending = false; + sendData(); } - - sendData(); } } @@ -351,63 +350,79 @@ public class HidUAVTalk extends TelemetryTask { * it to the input stream */ ByteBuffer readBuffer = ByteBuffer.allocate(MAX_HID_PACKET_SIZE); - private void queueRead() { - if(!readRequest.queue(readBuffer, MAX_HID_PACKET_SIZE)) { - if (ERROR) Log.e(TAG, "Failed to queue request"); - } else - readPending = true; - } - public void readData() { - if (!readPending) { - queueRead(); - } else { // We just received a read + /** + * Schedules a USB read + */ + private void queueRead() { + synchronized(readRequest) { + if(!readRequest.queue(readBuffer, MAX_HID_PACKET_SIZE)) { + if (ERROR) Log.e(TAG, "Failed to queue request"); + } else + readPending = true; + } + } + + /** + * Reads data from the last USB transaction and schedules another read + */ + public void readData() { + synchronized(readRequest) { + + if (!readPending) { + if (ERROR) Log.e(TAG, "Tried to read read while a transaction was not pending"); + return; + } + + // We just received a read readPending = false; // Packet format: - // 0: Report ID (1) - // 1: Number of valid bytes - // 2:63: Data + // 0: Report ID (1) + // 1: Number of valid bytes + // 2:63: Data - int dataSize = readBuffer.get(1); // Data size - //Assert.assertEquals(1, buffer.get()); // Report ID - //Assert.assertTrue(dataSize < buffer.capacity()); + int dataSize = readBuffer.get(1); // Data size + //Assert.assertEquals(1, buffer.get()); // Report ID + //Assert.assertTrue(dataSize < buffer.capacity()); - if (readBuffer.get(0) != 1 || readBuffer.get(1) < 0 || readBuffer.get(1) > (readBuffer.capacity() - 2)) { - if (ERROR) Log.e(TAG, "Badly formatted HID packet"); - } else { - byte[] dst = new byte[dataSize]; - readBuffer.position(2); - readBuffer.get(dst, 0, dataSize); - if (DEBUG) Log.d(TAG, "Entered read"); - inTalkStream.write(dst); - if (DEBUG) Log.d(TAG, "Got read: " + dataSize + " bytes"); - } + if (readBuffer.get(0) != 1 || readBuffer.get(1) < 0 || readBuffer.get(1) > (readBuffer.capacity() - 2)) { + if (ERROR) Log.e(TAG, "Badly formatted HID packet"); + } else { + byte[] dst = new byte[dataSize]; + readBuffer.position(2); + readBuffer.get(dst, 0, dataSize); + if (DEBUG) Log.d(TAG, "Entered read"); + inTalkStream.write(dst); + if (DEBUG) Log.d(TAG, "Got read: " + dataSize + " bytes"); + } - // Queue another read - queueRead(); - } + // Queue another read + queueRead(); + + } } /** * Send a packet if data is available */ public void sendData() { + synchronized(writeRequest) { + // Don't try and send data till previous request completes + if (writePending) + return; - // Don't try and send data till previous request completes - if (writePending) - return; + ByteBuffer packet = outTalkStream.getHIDpacket(); + if (packet != null) { + if (DEBUG) Log.d(TAG, "Writing to device()"); - ByteBuffer packet = outTalkStream.getHIDpacket(); - if (packet != null) { - if (DEBUG) Log.d(TAG, "Writing to device()"); + int bufferDataLength = usbEndpointWrite.getMaxPacketSize(); + Assert.assertTrue(packet.capacity() <= bufferDataLength); - int bufferDataLength = usbEndpointWrite.getMaxPacketSize(); - Assert.assertTrue(packet.capacity() <= bufferDataLength); - - if(writeRequest.queue(packet, bufferDataLength)) - writePending = true; - else if (ERROR) - Log.e(TAG, "Write queuing failed"); + if(writeRequest.queue(packet, bufferDataLength)) + writePending = true; + else if (ERROR) + Log.e(TAG, "Write queuing failed"); + } } } @@ -446,6 +461,9 @@ public class HidUAVTalk extends TelemetryTask { data.put((byte) oneByte); data.notify(); } + + // If there is not a write request queued, add one + sendData(); } @Override @@ -457,6 +475,8 @@ public class HidUAVTalk extends TelemetryTask { data.put(b); data.notify(); } + + sendData(); } }; From d77912e4b080f1e7b9ad2e78246b18c107b6863f Mon Sep 17 00:00:00 2001 From: James Cotton Date: Sun, 12 Aug 2012 17:21:13 -0500 Subject: [PATCH 05/16] AndroidGCS Controller: Make sure to remove the callback on manual control settings before updating it. --- androidgcs/src/org/openpilot/androidgcs/Controller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/androidgcs/src/org/openpilot/androidgcs/Controller.java b/androidgcs/src/org/openpilot/androidgcs/Controller.java index 3ef291d4d..0d3a3c911 100644 --- a/androidgcs/src/org/openpilot/androidgcs/Controller.java +++ b/androidgcs/src/org/openpilot/androidgcs/Controller.java @@ -78,11 +78,11 @@ public class Controller extends ObjectManagerActivity { public void update(Observable observable, Object data) { // Once we have updated settings we can active the GCS receiver mode Log.d(TAG,"Got update from settings"); - activateGcsReceiver(); UAVDataObject manualControlSettings = (UAVDataObject) objMngr.getObject("ManualControlSettings"); if(manualControlSettings != null) { manualControlSettings.removeUpdatedObserver(this); } + activateGcsReceiver(); } }; From ef581342accbc46d75246fd74ffd1f60d80de86c Mon Sep 17 00:00:00 2001 From: James Cotton Date: Sun, 12 Aug 2012 17:21:40 -0500 Subject: [PATCH 06/16] AndroidGCS: Add back a few synchronize blocks in telemetry to avoid removing elements twice. --- .../src/org/openpilot/uavtalk/Telemetry.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java index 21525ea14..f130c33eb 100644 --- a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java +++ b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java @@ -551,17 +551,22 @@ public class Telemetry { // Get object information from queue (first the priority and then the regular queue) ObjectQueueInfo objInfo; - if ( !objPriorityQueue.isEmpty() ) - { - objInfo = objPriorityQueue.remove(); - } - else if ( !objQueue.isEmpty() ) - { - objInfo = objQueue.remove(); - } - else - { - return; + synchronized (objPriorityQueue) { + if ( !objPriorityQueue.isEmpty() ) + { + objInfo = objPriorityQueue.remove(); + } else { + synchronized (objQueue) { + if ( !objQueue.isEmpty() ) + { + objInfo = objQueue.remove(); + } + else + { + return; + } + } + } } // Check if a connection has been established, only process GCSTelemetryStats updates From f012248fd11141170b361616a4377f9b81adfd13 Mon Sep 17 00:00:00 2001 From: James Cotton Date: Sun, 12 Aug 2012 14:44:39 -0500 Subject: [PATCH 07/16] Updated android objects from merge --- .../uavtalk/uavobjects/FirmwareIAPObj.java | 2 +- .../uavobjects/ManualControlSettings.java | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/androidgcs/src/org/openpilot/uavtalk/uavobjects/FirmwareIAPObj.java b/androidgcs/src/org/openpilot/uavtalk/uavobjects/FirmwareIAPObj.java index 48d211ae9..90a9e8893 100644 --- a/androidgcs/src/org/openpilot/uavtalk/uavobjects/FirmwareIAPObj.java +++ b/androidgcs/src/org/openpilot/uavtalk/uavobjects/FirmwareIAPObj.java @@ -157,7 +157,7 @@ public class FirmwareIAPObj extends UAVDataObject { UAVObject.Metadata.AccessModeNum(UAVObject.AccessMode.ACCESS_READWRITE) << UAVOBJ_GCS_ACCESS_SHIFT | 1 << UAVOBJ_TELEMETRY_ACKED_SHIFT | 1 << UAVOBJ_GCS_TELEMETRY_ACKED_SHIFT | - UAVObject.Metadata.UpdateModeNum(UAVObject.UpdateMode.UPDATEMODE_MANUAL) << UAVOBJ_TELEMETRY_UPDATE_MODE_SHIFT | + UAVObject.Metadata.UpdateModeNum(UAVObject.UpdateMode.UPDATEMODE_ONCHANGE) << UAVOBJ_TELEMETRY_UPDATE_MODE_SHIFT | UAVObject.Metadata.UpdateModeNum(UAVObject.UpdateMode.UPDATEMODE_MANUAL) << UAVOBJ_GCS_TELEMETRY_UPDATE_MODE_SHIFT; metadata.flightTelemetryUpdatePeriod = 0; metadata.gcsTelemetryUpdatePeriod = 0; diff --git a/androidgcs/src/org/openpilot/uavtalk/uavobjects/ManualControlSettings.java b/androidgcs/src/org/openpilot/uavtalk/uavobjects/ManualControlSettings.java index 0b47b3e10..e42662409 100644 --- a/androidgcs/src/org/openpilot/uavtalk/uavobjects/ManualControlSettings.java +++ b/androidgcs/src/org/openpilot/uavtalk/uavobjects/ManualControlSettings.java @@ -179,10 +179,17 @@ public class ManualControlSettings extends UAVDataObject { Stabilization3SettingsEnumOptions.add("VirtualBar"); fields.add( new UAVObjectField("Stabilization3Settings", "", UAVObjectField.FieldType.ENUM, Stabilization3SettingsElemNames, Stabilization3SettingsEnumOptions) ); + List FlightModeNumberElemNames = new ArrayList(); + FlightModeNumberElemNames.add("0"); + fields.add( new UAVObjectField("FlightModeNumber", "", UAVObjectField.FieldType.UINT8, FlightModeNumberElemNames, null) ); + List FlightModePositionElemNames = new ArrayList(); FlightModePositionElemNames.add("0"); FlightModePositionElemNames.add("1"); FlightModePositionElemNames.add("2"); + FlightModePositionElemNames.add("3"); + FlightModePositionElemNames.add("4"); + FlightModePositionElemNames.add("5"); List FlightModePositionEnumOptions = new ArrayList(); FlightModePositionEnumOptions.add("Manual"); FlightModePositionEnumOptions.add("Stabilized1"); @@ -196,10 +203,6 @@ public class ManualControlSettings extends UAVDataObject { FlightModePositionEnumOptions.add("Land"); fields.add( new UAVObjectField("FlightModePosition", "", UAVObjectField.FieldType.ENUM, FlightModePositionElemNames, FlightModePositionEnumOptions) ); - List FlightModeNumberElemNames = new ArrayList(); - FlightModeNumberElemNames.add("0"); - fields.add( new UAVObjectField("FlightModeNumber", "", UAVObjectField.FieldType.UINT8, FlightModeNumberElemNames, null) ); - List FailsafeBehaviorElemNames = new ArrayList(); FailsafeBehaviorElemNames.add("0"); List FailsafeBehaviorEnumOptions = new ArrayList(); @@ -308,10 +311,13 @@ public class ManualControlSettings extends UAVDataObject { getField("Stabilization3Settings").setValue("Attitude",0); getField("Stabilization3Settings").setValue("Attitude",1); getField("Stabilization3Settings").setValue("Rate",2); + getField("FlightModeNumber").setValue(3); getField("FlightModePosition").setValue("Manual",0); getField("FlightModePosition").setValue("Stabilized1",1); getField("FlightModePosition").setValue("Stabilized2",2); - getField("FlightModeNumber").setValue(3); + getField("FlightModePosition").setValue("Stabilized3",3); + getField("FlightModePosition").setValue("Stabilized1",4); + getField("FlightModePosition").setValue("Stabilized2",5); getField("FailsafeBehavior").setValue("None"); } @@ -341,7 +347,7 @@ public class ManualControlSettings extends UAVDataObject { } // Constants - protected static final long OBJID = 0x6C188320l; + protected static final long OBJID = 0xBA39E41Al; protected static final String NAME = "ManualControlSettings"; protected static String DESCRIPTION = "Settings to indicate how to decode receiver input by @ref ManualControlModule."; protected static final boolean ISSINGLEINST = 1 > 0; From daab45d14d39f5bea82d3b06b4c48eb4a85b680a Mon Sep 17 00:00:00 2001 From: James Cotton Date: Mon, 13 Aug 2012 01:07:09 -0500 Subject: [PATCH 08/16] AndroidGCS HID: Go back to a read and write thread but now use synchronous bultTransfer for write which gets rid of the segfaults with running two asynchronous transfers. --- .../androidgcs/telemetry/HidUAVTalk.java | 98 ++++++++++++------- 1 file changed, 64 insertions(+), 34 deletions(-) diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java index a074e79ca..9cb2d8be7 100644 --- a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java +++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java @@ -28,7 +28,7 @@ import android.util.Log; public class HidUAVTalk extends TelemetryTask { private static final String TAG = HidUAVTalk.class.getSimpleName(); - public static final int LOGLEVEL = 0; + public static final int LOGLEVEL = 1; public static final boolean DEBUG = LOGLEVEL > 2; public static final boolean WARN = LOGLEVEL > 1; public static final boolean ERROR = LOGLEVEL > 0; @@ -57,9 +57,10 @@ public class HidUAVTalk extends TelemetryTask { private UsbInterface usbInterface = null; private TalkInputStream inTalkStream; private TalkOutputStream outTalkStream; - private UsbRequest writeRequest = null; + private final UsbRequest writeRequest = null; private UsbRequest readRequest = null; - private Thread readWriteThread; + private Thread readThread; + private Thread writeThread; private boolean readPending = false; private boolean writePending = false; @@ -78,9 +79,15 @@ public class HidUAVTalk extends TelemetryTask { super.disconnect(); try { - if(readWriteThread != null) { - readWriteThread.join(); - readWriteThread = null; + if(readThread != null) { + readThread.interrupt(); // Make sure not blocking for data + readThread.join(); + readThread = null; + } + if(writeThread != null) { + writeThread.interrupt(); + writeThread.join(); + writeThread = null; } } catch (InterruptedException e) { e.printStackTrace(); @@ -91,13 +98,6 @@ public class HidUAVTalk extends TelemetryTask { readRequest.close(); readRequest = null; } - - if (writeRequest != null) { - writeRequest.cancel(); - writeRequest.close(); - writeRequest = null; - } - } @Override @@ -293,9 +293,6 @@ public class HidUAVTalk extends TelemetryTask { readRequest = new UsbRequest(); readRequest.initialize(usbDeviceConnection, usbEndpointRead); - writeRequest = new UsbRequest(); - writeRequest.initialize(usbDeviceConnection, usbEndpointWrite); - inTalkStream = new TalkInputStream(); outTalkStream = new TalkOutputStream(); inStream = inTalkStream; @@ -307,31 +304,39 @@ public class HidUAVTalk extends TelemetryTask { } }); - readWriteThread = new Thread(new Runnable() { + readThread = new Thread(new Runnable() { @Override public void run() { - // Enqueue the first read queueRead(); - while (!shutdown) { - // If there are no request - - // Enqueue requests appropriately first UsbRequest returned = usbDeviceConnection.requestWait(); if (returned == readRequest) { if (DEBUG) Log.d(TAG, "Received read request"); readData(); - } else if (returned == writeRequest) { - if (DEBUG) Log.d(TAG, "Received write completed request"); - writePending = false; - sendData(); - } + } else + Log.e(TAG, "Received unknown USB response"); } } - }, "HID Read Write"); - readWriteThread.start(); + }, "HID Read"); + readThread.start(); + + writeThread = new Thread(new Runnable() { + @Override + public void run() { + if (DEBUG) Log.d(TAG, "Starting HID write thread"); + while(!shutdown) { + try { + sendDataSynchronous(); + } catch (InterruptedException e) { + break; + } + } + if (DEBUG) Log.d(TAG, "Ending HID write thread"); + } + }, "HID Write"); + writeThread.start(); return true; } @@ -402,6 +407,7 @@ public class HidUAVTalk extends TelemetryTask { } } + /** * Send a packet if data is available */ @@ -426,6 +432,21 @@ public class HidUAVTalk extends TelemetryTask { } } + /** + * Send a packet if data is available + * @throws InterruptedException + */ + public void sendDataSynchronous() throws InterruptedException { + + ByteBuffer packet = outTalkStream.getHIDpacketBlocking(); + if (packet != null) { + if (DEBUG) Log.d(TAG, "sendDataSynchronous() Writing to device()"); + + if (usbDeviceConnection.bulkTransfer(usbEndpointWrite, packet.array(), MAX_HID_PACKET_SIZE, 1000) < 0) + Log.e(TAG, "Failed to perform bult write"); + } + } + /*********** Helper classes for telemetry streams ************/ class TalkOutputStream extends OutputStream { @@ -433,6 +454,20 @@ public class HidUAVTalk extends TelemetryTask { // and ByteFifo.put(byte []) ByteFifo data = new ByteFifo(); + /** + * Blocks until data is available and then returns a properly formatted HID packet + */ + public ByteBuffer getHIDpacketBlocking() throws InterruptedException { + synchronized(data) { + if (data.remaining() == 0) + data.wait(); + return getHIDpacket(); + } + } + + /** + * Gets data from the ByteFifo in a properly formatted HID packet + */ public ByteBuffer getHIDpacket() { ByteBuffer packet = null; synchronized(data) { @@ -461,9 +496,6 @@ public class HidUAVTalk extends TelemetryTask { data.put((byte) oneByte); data.notify(); } - - // If there is not a write request queued, add one - sendData(); } @Override @@ -475,8 +507,6 @@ public class HidUAVTalk extends TelemetryTask { data.put(b); data.notify(); } - - sendData(); } }; From 3ea9ecd53b3864c25949079c52242e6daf918a58 Mon Sep 17 00:00:00 2001 From: James Cotton Date: Mon, 13 Aug 2012 01:25:08 -0500 Subject: [PATCH 09/16] AndroidGCS HID: Use the dettached message to shut down HID telemetry properly --- .../androidgcs/telemetry/HidUAVTalk.java | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java index 9cb2d8be7..c1c848a0a 100644 --- a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java +++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java @@ -64,6 +64,7 @@ public class HidUAVTalk extends TelemetryTask { private boolean readPending = false; private boolean writePending = false; + private IntentFilter deviceAttachedFilter; public HidUAVTalk(OPTelemetryService service) { super(service); @@ -73,7 +74,7 @@ public class HidUAVTalk extends TelemetryTask { public void disconnect() { CleanUpAndClose(); - //hostDisplayActivity.unregisterReceiver(usbReceiver); + telemService.unregisterReceiver(usbReceiver); telemService.unregisterReceiver(usbPermissionReceiver); super.disconnect(); @@ -110,6 +111,11 @@ public class HidUAVTalk extends TelemetryTask { permissionFilter = new IntentFilter(ACTION_USB_PERMISSION); telemService.registerReceiver(usbPermissionReceiver, permissionFilter); + deviceAttachedFilter = new IntentFilter(); + //deviceAttachedFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + deviceAttachedFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + telemService.registerReceiver(usbReceiver, deviceAttachedFilter); + // Go through all the devices plugged in HashMap deviceList = usbManager.getDeviceList(); if (DEBUG) Log.d(TAG, "Found " + deviceList.size() + " devices"); @@ -176,7 +182,6 @@ public class HidUAVTalk extends TelemetryTask { } }; - /* TODO: Detect dettached events and close the connection private final BroadcastReceiver usbReceiver = new BroadcastReceiver() { @Override @@ -195,8 +200,10 @@ public class HidUAVTalk extends TelemetryTask { { if (device.equals(currentDevice)) { + if (DEBUG) Log.d(TAG, "Matching device disconnected"); + // call your method that cleans up and closes communication with the device - CleanUpAndClose(); + disconnect(); } } } @@ -213,7 +220,7 @@ public class HidUAVTalk extends TelemetryTask { } } } - }; */ + }; protected void CleanUpAndClose() { @@ -314,8 +321,10 @@ public class HidUAVTalk extends TelemetryTask { if (returned == readRequest) { if (DEBUG) Log.d(TAG, "Received read request"); readData(); - } else + } else { Log.e(TAG, "Received unknown USB response"); + break; + } } } @@ -328,7 +337,8 @@ public class HidUAVTalk extends TelemetryTask { if (DEBUG) Log.d(TAG, "Starting HID write thread"); while(!shutdown) { try { - sendDataSynchronous(); + if (sendDataSynchronous() == false) + break; } catch (InterruptedException e) { break; } @@ -436,15 +446,18 @@ public class HidUAVTalk extends TelemetryTask { * Send a packet if data is available * @throws InterruptedException */ - public void sendDataSynchronous() throws InterruptedException { + public boolean sendDataSynchronous() throws InterruptedException { ByteBuffer packet = outTalkStream.getHIDpacketBlocking(); if (packet != null) { if (DEBUG) Log.d(TAG, "sendDataSynchronous() Writing to device()"); - if (usbDeviceConnection.bulkTransfer(usbEndpointWrite, packet.array(), MAX_HID_PACKET_SIZE, 1000) < 0) - Log.e(TAG, "Failed to perform bult write"); + if (usbDeviceConnection.bulkTransfer(usbEndpointWrite, packet.array(), MAX_HID_PACKET_SIZE, 1000) < 0) { + Log.e(TAG, "Failed to perform bulk write"); + return false; + } } + return true; } /*********** Helper classes for telemetry streams ************/ From aa9894c4817f64423ea243668c3f3d224055b6c4 Mon Sep 17 00:00:00 2001 From: James Cotton Date: Mon, 13 Aug 2012 01:37:15 -0500 Subject: [PATCH 10/16] AndroidGCS: Add a toast message when connected (all objects downloaded) --- .../openpilot/androidgcs/telemetry/HidUAVTalk.java | 6 ++++-- .../androidgcs/telemetry/TelemetryTask.java | 2 +- .../src/org/openpilot/uavtalk/TelemetryMonitor.java | 12 +++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java index c1c848a0a..ab170ff48 100644 --- a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java +++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java @@ -112,7 +112,7 @@ public class HidUAVTalk extends TelemetryTask { telemService.registerReceiver(usbPermissionReceiver, permissionFilter); deviceAttachedFilter = new IntentFilter(); - //deviceAttachedFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + deviceAttachedFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); deviceAttachedFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); telemService.registerReceiver(usbReceiver, deviceAttachedFilter); @@ -200,8 +200,8 @@ public class HidUAVTalk extends TelemetryTask { { if (device.equals(currentDevice)) { + telemService.toastMessage("Device unplugged while in use"); if (DEBUG) Log.d(TAG, "Matching device disconnected"); - // call your method that cleans up and closes communication with the device disconnect(); } @@ -348,6 +348,8 @@ public class HidUAVTalk extends TelemetryTask { }, "HID Write"); writeThread.start(); + telemService.toastMessage("HID Device Opened"); + return true; } diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java index 87196bc1e..f09cc7236 100644 --- a/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java +++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java @@ -106,7 +106,7 @@ public abstract class TelemetryTask implements Runnable { // data stream uavTalk = new UAVTalk(inStream, outStream, objMngr); tel = new Telemetry(uavTalk, objMngr); - mon = new TelemetryMonitor(objMngr,tel); + mon = new TelemetryMonitor(objMngr,tel, telemService); // Create an observer to notify system of connection mon.addObserver(connectionObserver); diff --git a/androidgcs/src/org/openpilot/uavtalk/TelemetryMonitor.java b/androidgcs/src/org/openpilot/uavtalk/TelemetryMonitor.java index a1e341197..121a5ebcc 100644 --- a/androidgcs/src/org/openpilot/uavtalk/TelemetryMonitor.java +++ b/androidgcs/src/org/openpilot/uavtalk/TelemetryMonitor.java @@ -34,6 +34,8 @@ import java.util.Observer; import java.util.Timer; import java.util.TimerTask; +import org.openpilot.androidgcs.telemetry.OPTelemetryService; + import android.util.Log; public class TelemetryMonitor extends Observable { @@ -60,6 +62,7 @@ public class TelemetryMonitor extends Observable { private long lastUpdateTime; private final List queue; + private OPTelemetryService telemService; private boolean connected = false; private boolean objects_updated = false; @@ -71,6 +74,11 @@ public class TelemetryMonitor extends Observable { return objects_updated; }; + public TelemetryMonitor(UAVObjectManager objMngr, Telemetry tel, OPTelemetryService s) { + this(objMngr, tel); + telemService = s; + } + public TelemetryMonitor(UAVObjectManager objMngr, Telemetry tel) { this.objMngr = objMngr; this.tel = tel; @@ -171,7 +179,9 @@ public class TelemetryMonitor extends Observable { public synchronized void retrieveNextObject() throws IOException { // If queue is empty return if (queue.isEmpty()) { - if (DEBUG || true) Log.d(TAG, "All objects retrieved: Connected Successfully"); + if (telemService != null) + telemService.toastMessage("Connected"); + if (DEBUG) Log.d(TAG, "All objects retrieved: Connected Successfully"); objects_updated = true; if (!HANDSHAKE_IS_CONNECTED) { setChanged(); From 2f6595295231c4b432315e068669420924de10b2 Mon Sep 17 00:00:00 2001 From: James Cotton Date: Mon, 13 Aug 2012 02:00:54 -0500 Subject: [PATCH 11/16] AndroidGCS HID: Remove more locks to try and prevent HID deadlocking --- .../src/org/openpilot/uavtalk/Telemetry.java | 72 ++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java index f130c33eb..8e6161074 100644 --- a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java +++ b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java @@ -343,7 +343,7 @@ public class Telemetry { /** * Update an object based on its metadata properties */ - private synchronized void updateObject(UAVObject obj) + private void updateObject(UAVObject obj) { // Get metadata UAVObject.Metadata metadata = obj.getMetadata(); @@ -393,7 +393,7 @@ public class Telemetry { * Called when a transaction is successfully completed (uavtalk event) * @throws IOException */ - private synchronized void transactionCompleted(UAVObject obj, boolean result) throws IOException + private 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 @@ -401,8 +401,11 @@ public class Telemetry { { if (DEBUG) Log.d(TAG,"Telemetry: transaction completed for " + obj.getName()); // Complete transaction - transTimer.cancel(); - transPending = false; + + synchronized(transTimer) { + transTimer.cancel(); + transPending = false; + } //Send signal obj.transactionCompleted(result); @@ -419,39 +422,41 @@ public class Telemetry { * Called when a transaction is not completed within the timeout period (timer event) * @throws IOException */ - private synchronized void transactionTimeout() throws IOException + private void transactionTimeout() throws IOException { if (DEBUG) Log.d(TAG,"Telemetry: transaction timeout."); - transTimer.cancel(); - // Proceed only if there is a pending transaction - if ( transPending ) - { - // Check if more retries are pending - if (transInfo.retriesRemaining > 0) - { - --transInfo.retriesRemaining; - processObjectTransaction(); - ++txRetries; - } - else - { - if (ERROR) Log.e(TAG, "Transaction failed for: " + transInfo.obj.getName()); + synchronized(transTimer) { + transTimer.cancel(); + // Proceed only if there is a pending transaction + if ( transPending ) + { + // Check if more retries are pending + if (transInfo.retriesRemaining > 0) + { + --transInfo.retriesRemaining; + processObjectTransaction(); + ++txRetries; + } + else + { + 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; - } - } + // 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; + } + } + } } /** * Start an object transaction with UAVTalk, all information is stored in transInfo * @throws IOException */ - private synchronized void processObjectTransaction() throws IOException + private void processObjectTransaction() throws IOException { if (transPending) { @@ -472,8 +477,10 @@ public class Telemetry { } else { - transTimer.cancel(); - transPending = false; + synchronized(transTimer) { + transTimer.cancel(); + transPending = false; + } } } else { @@ -628,11 +635,12 @@ public class Telemetry { * TODO: Clean-up * @throws IOException */ - private synchronized void processPeriodicUpdates() throws IOException + private void processPeriodicUpdates() throws IOException { if (DEBUG) Log.d(TAG, "processPeriodicUpdates()"); // Stop timer + updateTimer.cancel(); // Iterate through each object and update its timer, if zero then transmit object. @@ -705,7 +713,7 @@ public class Telemetry { return stats; } - public synchronized void resetStats() + public void resetStats() { utalk.resetStats(); txErrors = 0; From 2d7bb4d3bbbdcb1b8b0ee2aa5dc9aef6d2ba0e73 Mon Sep 17 00:00:00 2001 From: James Cotton Date: Mon, 13 Aug 2012 15:02:15 -0500 Subject: [PATCH 12/16] AndroidGCS: Start moving the telemetry object queue to a handler and a looper --- .../telemetry/OPTelemetryService.java | 15 +- .../androidgcs/telemetry/TelemetryTask.java | 2 +- .../src/org/openpilot/uavtalk/Telemetry.java | 238 ++++++++++++------ .../uavtalk/TelemetryMonitorTest.java | 20 +- 4 files changed, 176 insertions(+), 99 deletions(-) diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java index 4eeb57abe..4698900ef 100644 --- a/androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java +++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java @@ -26,16 +26,10 @@ */ package org.openpilot.androidgcs.telemetry; -import java.io.IOException; import java.lang.ref.WeakReference; -import java.util.Observable; -import java.util.Observer; -import org.openpilot.uavtalk.Telemetry; -import org.openpilot.uavtalk.TelemetryMonitor; import org.openpilot.uavtalk.UAVDataObject; import org.openpilot.uavtalk.UAVObjectManager; -import org.openpilot.uavtalk.UAVTalk; import org.openpilot.uavtalk.uavobjects.UAVObjectsInitialize; import android.app.Service; @@ -127,7 +121,7 @@ public class OPTelemetryService extends Service { break; case 2: Toast.makeText(getApplicationContext(), "Attempting BT connection", Toast.LENGTH_SHORT).show(); - activeTelem = new BTTelemetryThread(); + //activeTelem = new BTTelemetryThread(); break; case 3: Toast.makeText(getApplicationContext(), "Attempting TCP connection", Toast.LENGTH_SHORT).show(); @@ -347,6 +341,8 @@ public class OPTelemetryService extends Service { } } } + + /* private class BTTelemetryThread extends Thread implements TelemTask { private final UAVObjectManager objMngr; @@ -401,7 +397,7 @@ public class OPTelemetryService extends Service { @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() /*&& mon.getObjectsUpdated()*/) { + if(mon.getConnected() ) { Intent intent = new Intent(); intent.setAction(INTENT_ACTION_CONNECTED); sendBroadcast(intent,null); @@ -423,6 +419,5 @@ public class OPTelemetryService extends Service { } if (DEBUG) Log.d(TAG, "UAVTalk stream disconnected"); } - - }; + };*/ } diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java index f09cc7236..3d16d94f9 100644 --- a/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java +++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java @@ -105,7 +105,7 @@ public abstract class TelemetryTask implements Runnable { // Create the required telemetry objects attached to this // data stream uavTalk = new UAVTalk(inStream, outStream, objMngr); - tel = new Telemetry(uavTalk, objMngr); + tel = new Telemetry(uavTalk, objMngr, Looper.myLooper()); mon = new TelemetryMonitor(objMngr,tel, telemService); // Create an observer to notify system of connection diff --git a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java index 8e6161074..ac814e76b 100644 --- a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java +++ b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java @@ -35,9 +35,17 @@ import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentLinkedQueue; +import junit.framework.Assert; +import android.os.Handler; +import android.os.Looper; import android.util.Log; public class Telemetry { + /** + * Telemetry provides a messaging handler to handle all the object updates and transfer + * requests. This handler can either be attached to a new loop attached to the thread + * started by the telemetry service. + */ private final String TAG = "Telemetry"; public static int LOGLEVEL = 1; @@ -98,11 +106,14 @@ public class Telemetry { /** * Constructor */ - public Telemetry(UAVTalk utalkIn, UAVObjectManager objMngr) + public Telemetry(UAVTalk utalkIn, UAVObjectManager objMngr, Looper l) { this.utalk = utalkIn; this.objMngr = objMngr; + // Create a handler for object messages + handler = new ObjectUpdateHandler(l); + // Process all objects in the list List< List > objs = objMngr.getObjects(); ListIterator> li = objs.listIterator(); @@ -265,48 +276,28 @@ 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(); - } + handler.unpacked((UAVObject) data); } }; 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(); - } + handler.updatedAuto((UAVObject) data); } }; 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(); - } + handler.updatedManual((UAVObject) data); } }; 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(); - } + handler.updateRequested((UAVObject) data); } }; @@ -488,59 +479,6 @@ public class Telemetry { } } - /** - * 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 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); - if(event == 8 && obj.getName().compareTo("GCSTelemetryStats") == 0) - Thread.dumpStack(); - ObjectQueueInfo objInfo = new ObjectQueueInfo(); - objInfo.obj = obj; - objInfo.event = event; - objInfo.allInstances = allInstances; - if (priority) - { - // 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 - { - // 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 - if (!transPending) - { - processObjectQueue(); - } - } - /** * Process events from the object queue * @throws IOException @@ -666,7 +604,8 @@ public class Telemetry { objinfo.timeToNextUpdateMs = objinfo.updatePeriodMs - offset; // Send object startTime = System.currentTimeMillis(); - enqueueObjectUpdates(objinfo.obj, EV_UPDATED_MANUAL, true, false); + handler.updatedManual(objinfo.obj); + //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; @@ -780,6 +719,147 @@ public class Telemetry { private static final int MIN_UPDATE_PERIOD_MS = 1; private static final int MAX_QUEUE_SIZE = 20; + private final ObjectUpdateHandler handler; + public class ObjectUpdateHandler extends Handler { + //! This can only be created while attaching to a particular looper + ObjectUpdateHandler(Looper l) { + super(l); + } + + //! Generic enqueue + void enqueueObjectUpdates(UAVObject obj, int event, boolean allInstances, boolean priority) { + + if (DEBUG) Log.d(TAG, "Enqueing update " + obj.getName() + " event " + event); + + ObjectQueueInfo objInfo = new ObjectQueueInfo(); + objInfo.obj = obj; + objInfo.event = event; + objInfo.allInstances = allInstances; + + post(new ObjectRunnable(objInfo)); + } + + //! Enqueue an unpacked event + void unpacked(UAVObject obj) { + enqueueObjectUpdates(obj, EV_UNPACKED, false, true); + } + + //! Enqueue an updated auto event + void updatedAuto(UAVObject obj) { + enqueueObjectUpdates(obj,EV_UPDATED, false, true); + } + + //! Enqueue an updated manual event + void updatedManual(UAVObject obj) { + enqueueObjectUpdates(obj, EV_UPDATE_REQ, false, true); + } + + //! Enqueue an update requested event + void updateRequested(UAVObject obj) { + enqueueObjectUpdates(obj, EV_UPDATE_REQ, false, true); + } + + } + + class ObjectRunnable implements Runnable { + + //! Transaction information to perform + private final ObjectQueueInfo objInfo; +// private final ObjectTransactionInfo transInfo = new ObjectTransactionInfo(); + + ObjectRunnable(ObjectQueueInfo info) { + Assert.assertNotNull(info); + objInfo = info; + } + + //! Perform the transaction on the looper thread + @Override + public void run () { + Log.d(TAG,"object transaction running"); + // 1. Check GCS is connected, throw this out if not + // 2. Set up a transaction which includes multiple retries, whether to wait for ack etc + // 3. Send UAVTalk message + // 4. Based on transaction type either wait for update or end + + // 1. Check if a connection has been established, only process GCSTelemetryStats updates + // (used to establish the connection) + gcsStatsObj = objMngr.getObject("GCSTelemetryStats"); + if ( ((String) gcsStatsObj.getField("Status").getValue()).compareTo("Connected") != 0 ) + { + if ( objInfo.obj.getObjID() != objMngr.getObject("GCSTelemetryStats").getObjID() ) + { + if (DEBUG) Log.d(TAG,"transactionCompleted(false) due to receiving object not GCSTelemetryStats while not connected."); + objInfo.obj.transactionCompleted(false); + return; + } + } + + Log.e(TAG, "A"); + // 2. Setup transaction (skip if unpack event) + if ( objInfo.event != EV_UNPACKED ) + { + Log.e(TAG, "A1"); + UAVObject.Metadata metadata = objInfo.obj.getMetadata(); + transInfo.obj = objInfo.obj; + transInfo.allInstances = objInfo.allInstances; + transInfo.retriesRemaining = MAX_RETRIES; + transInfo.acked = metadata.GetGcsTelemetryAcked(); + if ( objInfo.event == EV_UPDATED || objInfo.event == EV_UPDATED_MANUAL ) + { + transInfo.objRequest = false; + } + else if ( objInfo.event == EV_UPDATE_REQ ) + { + transInfo.objRequest = true; + } + // Start transaction + transPending = true; + } + Log.e(TAG, "B"); + // If this is a metaobject then make necessary telemetry updates (this is why we catch unpack) + if (objInfo.obj.isMetadata()) + { + UAVMetaObject metaobj = (UAVMetaObject) objInfo.obj; + updateObject( metaobj.getParentObject() ); + } + Log.e(TAG, "C"); + // 3. Execute transaction + if (transPending) + { + Log.e(TAG, "D"); + try { + if (DEBUG || true) Log.d(TAG, "Process Object transaction for " + transInfo.obj.getName()); + // Initiate transaction + if (transInfo.objRequest) + { + utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); + } + else + { + Log.d(TAG, "Sending object"); + utalk.sendObject(transInfo.obj, transInfo.acked, transInfo.allInstances); + } + + // TODO: Block if request expected (??) + if ( transInfo.objRequest || transInfo.acked ) + { + transTimerSetPeriod(REQ_TIMEOUT_MS); + } + else + { + synchronized(transTimer) { + transTimer.cancel(); + transPending = false; + } + } + } catch (IOException e) { + // TODO Auto-generated catch block + Log.e(TAG, "E"); + e.printStackTrace(); + } + } + } + } } diff --git a/androidgcs/tests/org/openpilot/uavtalk/TelemetryMonitorTest.java b/androidgcs/tests/org/openpilot/uavtalk/TelemetryMonitorTest.java index 0b0cc1d73..6ddcfe03e 100644 --- a/androidgcs/tests/org/openpilot/uavtalk/TelemetryMonitorTest.java +++ b/androidgcs/tests/org/openpilot/uavtalk/TelemetryMonitorTest.java @@ -1,6 +1,6 @@ package org.openpilot.uavtalk; -import static org.junit.Assert.*; +import static org.junit.Assert.fail; import java.io.IOException; import java.net.InetAddress; @@ -8,11 +8,12 @@ import java.net.Socket; import org.junit.Test; import org.openpilot.uavtalk.uavobjects.UAVObjectsInitialize; -import org.openpilot.uavtalk.UAVTalk; + +import android.os.Looper; public class TelemetryMonitorTest { - + static UAVObjectManager objMngr; static UAVTalk talk; static final String IP_ADDRDESS = new String("127.0.0.1"); @@ -32,7 +33,7 @@ public class TelemetryMonitorTest { e.printStackTrace(); fail("Couldn't connect to test platform"); } - + try { talk = new UAVTalk(connection.getInputStream(), connection.getOutputStream(), objMngr); } catch (IOException e) { @@ -40,16 +41,17 @@ public class TelemetryMonitorTest { e.printStackTrace(); fail("Couldn't construct UAVTalk object"); } - + Thread inputStream = talk.getInputProcessThread(); inputStream.start(); - - Telemetry tel = new Telemetry(talk, objMngr); + + Looper.prepare(); + Telemetry tel = new Telemetry(talk, objMngr, Looper.myLooper()); @SuppressWarnings("unused") TelemetryMonitor mon = new TelemetryMonitor(objMngr,tel); - + Thread.sleep(10000); - + System.out.println("Done"); } From 653702ac23ffaead2f894a43725c60f990392ece Mon Sep 17 00:00:00 2001 From: James Cotton Date: Mon, 13 Aug 2012 23:47:37 -0500 Subject: [PATCH 13/16] AndroidGCS Telemetry: Finish moving telemetry into a runnable. --- .../src/org/openpilot/uavtalk/Telemetry.java | 1357 +++++++++-------- 1 file changed, 679 insertions(+), 678 deletions(-) diff --git a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java index ac814e76b..3e4007777 100644 --- a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java +++ b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java @@ -42,147 +42,158 @@ import android.util.Log; public class Telemetry { /** - * Telemetry provides a messaging handler to handle all the object updates and transfer - * requests. This handler can either be attached to a new loop attached to the thread - * started by the telemetry service. + * Telemetry provides a messaging handler to handle all the object updates + * and transfer requests. This handler can either be attached to a new loop + * attached to the thread started by the telemetry service. */ private final String TAG = "Telemetry"; - public static int LOGLEVEL = 1; + public static int LOGLEVEL = 0; public static boolean WARN = LOGLEVEL > 2; public static boolean DEBUG = LOGLEVEL > 1; public static boolean ERROR = LOGLEVEL > 0; - public class TelemetryStats { - public int txBytes; - public int rxBytes; - public int txObjectBytes; - public int rxObjectBytes; - public int rxObjects; - public int txObjects; - public int txErrors; - public int rxErrors; - public int txRetries; - } ; - class ObjectTimeInfo { - UAVObject obj; - int updatePeriodMs; /** Update period in ms or 0 if no periodic updates are needed */ - int timeToNextUpdateMs; /** Time delay to the next update */ - }; + public class TelemetryStats { + public int txBytes; + public int rxBytes; + public int txObjectBytes; + public int rxObjectBytes; + public int rxObjects; + public int txObjects; + public int txErrors; + public int rxErrors; + public int txRetries; + }; - class ObjectQueueInfo { - UAVObject obj; - int event; - boolean allInstances; + class ObjectTimeInfo { + UAVObject obj; + int updatePeriodMs; + /** Update period in ms or 0 if no periodic updates are needed */ + int timeToNextUpdateMs; + /** Time delay to the next update */ + }; - @Override + class ObjectQueueInfo { + 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) { + try { + ObjectQueueInfo o = (ObjectQueueInfo) e; + return o.obj.getObjID() == obj.getObjID() && o.event == event + && o.allInstances == allInstances; + } catch (Exception err) { - }; - return false; - } - }; - - class ObjectTransactionInfo { - UAVObject obj; - boolean allInstances; - boolean objRequest; - int retriesRemaining; - boolean acked; - } ; - - /** - * Events generated by objects. Not enum because used in mask. - */ - private static final int EV_UNPACKED = 0x01; /** Object data updated by unpacking */ - private static final int EV_UPDATED = 0x02; /** Object data updated by changing the data structure */ - private static final int EV_UPDATED_MANUAL = 0x04; /** Object update event manually generated */ - private static final int EV_UPDATE_REQ = 0x08; /** Request to update object data */ - - /** - * Constructor - */ - public Telemetry(UAVTalk utalkIn, UAVObjectManager objMngr, Looper l) - { - this.utalk = utalkIn; - this.objMngr = objMngr; - - // Create a handler for object messages - handler = new ObjectUpdateHandler(l); - - // Process all objects in the list - List< List > objs = objMngr.getObjects(); - ListIterator> li = objs.listIterator(); - while(li.hasNext()) - registerObject(li.next().get(0)); // we only need to register one instance per object type - - // Listen to new object creations - objMngr.addNewInstanceObserver(new Observer() { - @Override - public void update(Observable observable, Object data) { - newInstance((UAVObject) data); } - }); - objMngr.addNewObjectObserver(new Observer() { + ; + return false; + } + }; + + class ObjectTransactionInfo { + UAVObject obj; + boolean allInstances; + boolean objRequest; + int retriesRemaining; + boolean acked; + }; + + /** + * Events generated by objects. Not enum because used in mask. + */ + private static final int EV_UNPACKED = 0x01; + /** Object data updated by unpacking */ + private static final int EV_UPDATED = 0x02; + /** Object data updated by changing the data structure */ + private static final int EV_UPDATED_MANUAL = 0x04; + /** Object update event manually generated */ + private static final int EV_UPDATE_REQ = 0x08; + + /** Request to update object data */ + + /** + * Constructor + */ + public Telemetry(UAVTalk utalkIn, UAVObjectManager objMngr, Looper l) { + this.utalk = utalkIn; + this.objMngr = objMngr; + + // Create a handler for object messages + handler = new ObjectUpdateHandler(l); + + // Process all objects in the list + List> objs = objMngr.getObjects(); + ListIterator> li = objs.listIterator(); + while (li.hasNext()) + registerObject(li.next().get(0)); // we only need to register one + // instance per object type + + // Listen to new object creations + objMngr.addNewInstanceObserver(new Observer() { @Override public void update(Observable observable, Object data) { - newObject((UAVObject) data); - } - }); + newInstance((UAVObject) data); + } + }); + objMngr.addNewObjectObserver(new Observer() { + @Override + public void update(Observable observable, Object data) { + newObject((UAVObject) data); + } + }); - // Listen to transaction completions from uavtalk - utalk.setOnTransactionCompletedListener( - utalk.new OnTransactionCompletedListener() { + // Listen to transaction completions from uavtalk + utalk.setOnTransactionCompletedListener(utalk.new OnTransactionCompletedListener() { @Override void TransactionSucceeded(UAVObject data) { - try { + try { transactionCompleted(data, true); } catch (IOException e) { // Disconnect when stream fails utalk.setOnTransactionCompletedListener(null); } - } + } + @Override void TransactionFailed(UAVObject data) { - try { - if (DEBUG) Log.d(TAG, "TransactionFailed(" + data.getName() + ")"); + try { + if (DEBUG) + Log.d(TAG, "TransactionFailed(" + data.getName() + ")"); transactionCompleted(data, false); } catch (IOException e) { // Disconnect when stream fails utalk.setOnTransactionCompletedListener(null); } - } + } - }); + }); - // Get GCS stats object - gcsStatsObj = objMngr.getObject("GCSTelemetryStats"); + // Get GCS stats object + gcsStatsObj = objMngr.getObject("GCSTelemetryStats"); - // Setup transaction timer - transPending = false; - // Setup and start the periodic timer - timeToNextUpdateMs = 0; - updateTimerSetPeriod(1000); - // Setup and start the stats timer - txErrors = 0; - txRetries = 0; - } + // Setup transaction timer + transPending = false; + // Setup and start the periodic timer + timeToNextUpdateMs = 0; + updateTimerSetPeriod(1000); + // Setup and start the stats timer + txErrors = 0; + txRetries = 0; + } - synchronized void transTimerSetPeriod(int periodMs) { - if(transTimerTask != null) - transTimerTask.cancel(); + synchronized void transTimerSetPeriod(int periodMs) { + if (transTimerTask != null) + transTimerTask.cancel(); - if(transTimer != null) - transTimer.purge(); + if (transTimer != null) + transTimer.purge(); - transTimer = new Timer(); + transTimer = new Timer(); - transTimerTask = new TimerTask() { + transTimerTask = new TimerTask() { @Override public void run() { try { @@ -191,21 +202,21 @@ public class Telemetry { cancel(); } } - }; - transTimer.schedule(transTimerTask, periodMs, periodMs); - } + }; + transTimer.schedule(transTimerTask, periodMs, periodMs); + } - synchronized void updateTimerSetPeriod(int periodMs) { - if (updateTimer != null) { - updateTimer.cancel(); - updateTimer = null; - } - if (updateTimerTask != null) { - updateTimerTask.cancel(); - updateTimerTask = null; - } - updateTimer = new Timer(); - updateTimerTask = new TimerTask() { + synchronized void updateTimerSetPeriod(int periodMs) { + if (updateTimer != null) { + updateTimer.cancel(); + updateTimer = null; + } + if (updateTimerTask != null) { + updateTimerTask.cancel(); + updateTimerTask = null; + } + updateTimer = new Timer(); + updateTimerTask = new TimerTask() { @Override public void run() { try { @@ -215,651 +226,641 @@ public class Telemetry { updateTimer.cancel(); } } - }; - updateTimer.schedule(updateTimerTask, periodMs, periodMs); + }; + updateTimer.schedule(updateTimerTask, periodMs, periodMs); - } + } - /** - * Register a new object for periodic updates (if enabled) - */ - private synchronized void registerObject(UAVObject obj) - { - // Setup object for periodic updates - addObject(obj); + /** + * Register a new object for periodic updates (if enabled) + */ + private synchronized void registerObject(UAVObject obj) { + // Setup object for periodic updates + addObject(obj); - // Setup object for telemetry updates - updateObject(obj); - } + // Setup object for telemetry updates + updateObject(obj); + } - /** - * Add an object in the list used for periodic updates - */ - private synchronized void addObject(UAVObject obj) - { - // Check if object type is already in the list - ListIterator li = objList.listIterator(); - while(li.hasNext()) { - ObjectTimeInfo n = li.next(); - if( n.obj.getObjID() == obj.getObjID() ) - { - // Object type (not instance!) is already in the list, do nothing - return; - } - } + /** + * Add an object in the list used for periodic updates + */ + private synchronized void addObject(UAVObject obj) { + // Check if object type is already in the list + ListIterator li = objList.listIterator(); + while (li.hasNext()) { + ObjectTimeInfo n = li.next(); + if (n.obj.getObjID() == obj.getObjID()) { + // Object type (not instance!) is already in the list, do + // nothing + return; + } + } - // If this point is reached, then the object type is new, let's add it - ObjectTimeInfo timeInfo = new ObjectTimeInfo(); - timeInfo.obj = obj; - timeInfo.timeToNextUpdateMs = 0; - timeInfo.updatePeriodMs = 0; - objList.add(timeInfo); - } + // If this point is reached, then the object type is new, let's add it + ObjectTimeInfo timeInfo = new ObjectTimeInfo(); + timeInfo.obj = obj; + timeInfo.timeToNextUpdateMs = 0; + timeInfo.updatePeriodMs = 0; + objList.add(timeInfo); + } - /** - * Update the object's timers - */ - private synchronized void setUpdatePeriod(UAVObject obj, int periodMs) - { - // Find object type (not instance!) and update its period - ListIterator li = objList.listIterator(); - while(li.hasNext()) { - ObjectTimeInfo n = li.next(); - if ( n.obj.getObjID() == obj.getObjID() ) - { - n.updatePeriodMs = periodMs; - n.timeToNextUpdateMs = (int) (periodMs * (new java.util.Random()).nextDouble()); // avoid bunching of updates - } - } - } + /** + * Update the object's timers + */ + private synchronized void setUpdatePeriod(UAVObject obj, int periodMs) { + // Find object type (not instance!) and update its period + ListIterator li = objList.listIterator(); + while (li.hasNext()) { + ObjectTimeInfo n = li.next(); + if (n.obj.getObjID() == obj.getObjID()) { + n.updatePeriodMs = periodMs; + n.timeToNextUpdateMs = (int) (periodMs * (new java.util.Random()) + .nextDouble()); // avoid bunching of updates + } + } + } - final Observer unpackedObserver = new Observer() { + final Observer unpackedObserver = new Observer() { @Override public void update(Observable observable, Object data) { handler.unpacked((UAVObject) data); - } + } }; final Observer updatedAutoObserver = new Observer() { @Override public void update(Observable observable, Object data) { handler.updatedAuto((UAVObject) data); - } + } }; final Observer updatedManualObserver = new Observer() { @Override public void update(Observable observable, Object data) { handler.updatedManual((UAVObject) data); - } + } }; final Observer updatedRequestedObserver = new Observer() { @Override public void update(Observable observable, Object data) { handler.updateRequested((UAVObject) data); - } + } }; - /** - * Connect to all instances of an object depending on the event mask specified - */ - private synchronized void connectToObjectInstances(UAVObject obj, int eventMask) - { - List objs = objMngr.getObjectInstances(obj.getObjID()); - ListIterator li = objs.listIterator(); - while(li.hasNext()) - { - obj = li.next(); + /** + * Connect to all instances of an object depending on the event mask + * specified + */ + private synchronized void connectToObjectInstances(UAVObject obj, + int eventMask) { + List objs = objMngr.getObjectInstances(obj.getObjID()); + ListIterator li = objs.listIterator(); + while (li.hasNext()) { + obj = li.next(); - // 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); + // 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(unpackedObserver); - if ( (eventMask&EV_UPDATED) != 0) - obj.addUpdatedAutoObserver(updatedAutoObserver); - if ( (eventMask&EV_UPDATED_MANUAL) != 0) - obj.addUpdatedManualObserver(updatedManualObserver); - if ( (eventMask&EV_UPDATE_REQ) != 0) - obj.addUpdateRequestedObserver(updatedRequestedObserver); - } - } + // Connect only the selected events + if ((eventMask & EV_UNPACKED) != 0) + obj.addUnpackedObserver(unpackedObserver); + if ((eventMask & EV_UPDATED) != 0) + obj.addUpdatedAutoObserver(updatedAutoObserver); + if ((eventMask & EV_UPDATED_MANUAL) != 0) + obj.addUpdatedManualObserver(updatedManualObserver); + if ((eventMask & EV_UPDATE_REQ) != 0) + obj.addUpdateRequestedObserver(updatedRequestedObserver); + } + } - /** - * Update an object based on its metadata properties - */ - private void updateObject(UAVObject obj) - { - // Get metadata - UAVObject.Metadata metadata = obj.getMetadata(); + /** + * Update an object based on its metadata properties + */ + private void updateObject(UAVObject obj) { + // Get metadata + UAVObject.Metadata metadata = obj.getMetadata(); - // Setup object depending on update mode - int eventMask; - if ( metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_PERIODIC ) - { - // Set update period - setUpdatePeriod(obj, metadata.gcsTelemetryUpdatePeriod); - // Connect signals for all instances - eventMask = EV_UPDATED_MANUAL | EV_UPDATE_REQ; - if(obj.isMetadata()) - eventMask |= EV_UNPACKED; // we also need to act on remote updates (unpack events) + // Setup object depending on update mode + int eventMask; + if (metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_PERIODIC) { + // Set update period + setUpdatePeriod(obj, metadata.gcsTelemetryUpdatePeriod); + // Connect signals for all instances + eventMask = EV_UPDATED_MANUAL | EV_UPDATE_REQ; + if (obj.isMetadata()) + eventMask |= EV_UNPACKED; // we also need to act on remote + // updates (unpack events) - connectToObjectInstances(obj, eventMask); - } - else if ( metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_ONCHANGE ) - { - // Set update period - setUpdatePeriod(obj, 0); - // Connect signals for all instances - eventMask = EV_UPDATED | EV_UPDATED_MANUAL | EV_UPDATE_REQ; - if(obj.isMetadata()) - eventMask |= EV_UNPACKED; // we also need to act on remote updates (unpack events) + connectToObjectInstances(obj, eventMask); + } else if (metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_ONCHANGE) { + // Set update period + setUpdatePeriod(obj, 0); + // Connect signals for all instances + eventMask = EV_UPDATED | EV_UPDATED_MANUAL | EV_UPDATE_REQ; + if (obj.isMetadata()) + eventMask |= EV_UNPACKED; // we also need to act on remote + // updates (unpack events) - connectToObjectInstances(obj, eventMask); - } - else if ( metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_THROTTLED ) - { - // TODO - } - else if ( metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_MANUAL ) - { - // Set update period - setUpdatePeriod(obj, 0); - // Connect signals for all instances - eventMask = EV_UPDATED_MANUAL | EV_UPDATE_REQ; - if(obj.isMetadata()) - eventMask |= EV_UNPACKED; // we also need to act on remote updates (unpack events) + connectToObjectInstances(obj, eventMask); + } else if (metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_THROTTLED) { + // TODO + } else if (metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_MANUAL) { + // Set update period + setUpdatePeriod(obj, 0); + // Connect signals for all instances + eventMask = EV_UPDATED_MANUAL | EV_UPDATE_REQ; + if (obj.isMetadata()) + eventMask |= EV_UNPACKED; // we also need to act on remote + // updates (unpack events) - connectToObjectInstances(obj, eventMask); - } - } + connectToObjectInstances(obj, eventMask); + } + } - /** - * Called when a transaction is successfully completed (uavtalk event) - * @throws IOException - */ - private 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 - if ( transPending && transInfo.obj.getObjID() == obj.getObjID() ) - { - if (DEBUG) Log.d(TAG,"Telemetry: transaction completed for " + obj.getName()); - // Complete transaction + /** + * Called when a transaction is successfully completed (uavtalk event) + * + * @throws IOException + */ + private 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 + if (transPending && transInfo.obj.getObjID() == obj.getObjID()) { + if (DEBUG) Log.d(TAG, "Telemetry: transaction completed for " + obj.getName()); - synchronized(transTimer) { - transTimer.cancel(); - transPending = false; - } + // Complete transaction + synchronized(transTimer) { + transTimer.cancel(); + transPending = false; + } - //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; - } - } + //Send signal + obj.transactionCompleted(result); + } else { + if (ERROR) + Log.e(TAG, + "Error: received a transaction completed when did not expect it."); + transPending = false; + } + } - /** - * Called when a transaction is not completed within the timeout period (timer event) - * @throws IOException - */ - private void transactionTimeout() throws IOException - { - if (DEBUG) Log.d(TAG,"Telemetry: transaction timeout."); - synchronized(transTimer) { - transTimer.cancel(); - // Proceed only if there is a pending transaction - if ( transPending ) - { - // Check if more retries are pending - if (transInfo.retriesRemaining > 0) - { - --transInfo.retriesRemaining; - processObjectTransaction(); - ++txRetries; - } - else - { - if (ERROR) Log.e(TAG, "Transaction failed for: " + transInfo.obj.getName()); + /** + * Called when a transaction is not completed within the timeout period + * (timer event) + * + * @throws IOException + */ + private void transactionTimeout() throws IOException { + if (DEBUG) + Log.d(TAG, "Telemetry: transaction timeout."); + synchronized (transTimer) { + transTimer.cancel(); + // Proceed only if there is a pending transaction + if (transPending) { + // Check if more retries are pending + if (transInfo.retriesRemaining > 0) { + --transInfo.retriesRemaining; + processObjectTransaction(); + ++txRetries; + } else { + 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; - } - } - } - } + // 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; + } + } + } + } - /** - * Start an object transaction with UAVTalk, all information is stored in transInfo - * @throws IOException - */ - private void processObjectTransaction() throws IOException - { - if (transPending) - { - if (DEBUG) Log.d(TAG, "Process Object transaction for " + transInfo.obj.getName()); - // Initiate transaction - if (transInfo.objRequest) - { - utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); - } - else - { - utalk.sendObject(transInfo.obj, transInfo.acked, transInfo.allInstances); - } - // Start timer if a response is expected - if ( transInfo.objRequest || transInfo.acked ) - { - transTimerSetPeriod(REQ_TIMEOUT_MS); - } - else - { - synchronized(transTimer) { - transTimer.cancel(); - transPending = false; - } - } - } else - { - if (ERROR) Log.e(TAG,"Error: inside of processObjectTransaction with no transPending"); - } - } + /** + * Start an object transaction with UAVTalk, all information is stored in + * transInfo + * + * @throws IOException + */ + private void processObjectTransaction() throws IOException { + if (transPending) { + if (DEBUG) + Log.d(TAG, + "Process Object transaction for " + + transInfo.obj.getName()); + // Initiate transaction + if (transInfo.objRequest) { + utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); + } else { + utalk.sendObject(transInfo.obj, transInfo.acked, + transInfo.allInstances); + } + // Start timer if a response is expected + if (transInfo.objRequest || transInfo.acked) { + transTimerSetPeriod(REQ_TIMEOUT_MS); + } else { + synchronized (transTimer) { + transTimer.cancel(); + transPending = false; + } + } + } else { + if (ERROR) + Log.e(TAG, + "Error: inside of processObjectTransaction with no transPending"); + } + } - /** - * Process events from the object queue - * @throws IOException - */ - private void processObjectQueue() throws IOException - { - if (DEBUG) Log.d(TAG, "Process object queue - Depth " + objQueue.size() + " priority " + objPriorityQueue.size()); + /** + * Process events from the object queue + * + * @throws IOException + */ + private void processObjectQueue() throws IOException { + if (DEBUG) + Log.d(TAG, "Process object queue - Depth " + objQueue.size() + + " priority " + objPriorityQueue.size()); - // Don nothing if a transaction is already in progress (should not happen) - if (transPending) - { - if (WARN) Log.e(TAG,"Dequeue while a transaction pending"); - return; - } + // Don nothing if a transaction is already in progress (should not + // happen) + if (transPending) { + if (WARN) + Log.e(TAG, "Dequeue while a transaction pending"); + return; + } - // Get object information from queue (first the priority and then the regular queue) - ObjectQueueInfo objInfo; - synchronized (objPriorityQueue) { - if ( !objPriorityQueue.isEmpty() ) - { - objInfo = objPriorityQueue.remove(); - } else { - synchronized (objQueue) { - if ( !objQueue.isEmpty() ) - { - objInfo = objQueue.remove(); - } - else - { - return; - } - } - } - } + // Get object information from queue (first the priority and then the + // regular queue) + ObjectQueueInfo objInfo; + synchronized (objPriorityQueue) { + if (!objPriorityQueue.isEmpty()) { + objInfo = objPriorityQueue.remove(); + } else { + synchronized (objQueue) { + if (!objQueue.isEmpty()) { + objInfo = objQueue.remove(); + } else { + return; + } + } + } + } - // Check if a connection has been established, only process GCSTelemetryStats updates - // (used to establish the connection) - gcsStatsObj = objMngr.getObject("GCSTelemetryStats"); - if ( ((String) gcsStatsObj.getField("Status").getValue()).compareTo("Connected") != 0 ) - { - objQueue.clear(); - if ( objInfo.obj.getObjID() != objMngr.getObject("GCSTelemetryStats").getObjID() ) - { - if (DEBUG) Log.d(TAG,"transactionCompleted(false) due to receiving object not GCSTelemetryStats while not connected."); - objInfo.obj.transactionCompleted(false); - return; - } - } + // Check if a connection has been established, only process + // GCSTelemetryStats updates + // (used to establish the connection) + gcsStatsObj = objMngr.getObject("GCSTelemetryStats"); + if (((String) gcsStatsObj.getField("Status").getValue()) + .compareTo("Connected") != 0) { + objQueue.clear(); + if (objInfo.obj.getObjID() != objMngr + .getObject("GCSTelemetryStats").getObjID()) { + if (DEBUG) + Log.d(TAG, + "transactionCompleted(false) due to receiving object not GCSTelemetryStats while not connected."); + objInfo.obj.transactionCompleted(false); + return; + } + } - // Setup transaction (skip if unpack event) - if ( objInfo.event != EV_UNPACKED ) - { - UAVObject.Metadata metadata = objInfo.obj.getMetadata(); - transInfo.obj = objInfo.obj; - transInfo.allInstances = objInfo.allInstances; - transInfo.retriesRemaining = MAX_RETRIES; - transInfo.acked = metadata.GetGcsTelemetryAcked(); - if ( objInfo.event == EV_UPDATED || objInfo.event == EV_UPDATED_MANUAL ) - { - transInfo.objRequest = false; - } - else if ( objInfo.event == EV_UPDATE_REQ ) - { - transInfo.objRequest = true; - } - // Start transaction - transPending = true; - processObjectTransaction(); - } else - { -// qDebug() << QString("Process object queue: this is an unpack event for %1").arg(objInfo.obj->getName()); - } + // Setup transaction (skip if unpack event) + if (objInfo.event != EV_UNPACKED) { + UAVObject.Metadata metadata = objInfo.obj.getMetadata(); + transInfo.obj = objInfo.obj; + transInfo.allInstances = objInfo.allInstances; + transInfo.retriesRemaining = MAX_RETRIES; + transInfo.acked = metadata.GetGcsTelemetryAcked(); + if (objInfo.event == EV_UPDATED + || objInfo.event == EV_UPDATED_MANUAL) { + transInfo.objRequest = false; + } else if (objInfo.event == EV_UPDATE_REQ) { + transInfo.objRequest = true; + } + // Start transaction + transPending = true; + processObjectTransaction(); + } else { + // qDebug() << + // QString("Process object queue: this is an unpack event for %1").arg(objInfo.obj->getName()); + } - // If this is a metaobject then make necessary telemetry updates - if (objInfo.obj.isMetadata()) - { - UAVMetaObject metaobj = (UAVMetaObject) objInfo.obj; - updateObject( metaobj.getParentObject() ); - } + // If this is a metaobject then make necessary telemetry updates + if (objInfo.obj.isMetadata()) { + UAVMetaObject metaobj = (UAVMetaObject) objInfo.obj; + updateObject(metaobj.getParentObject()); + } - // The fact we received an unpacked event does not mean that - // 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 && !transPending) - processObjectQueue(); + // The fact we received an unpacked event does not mean that + // 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 && !transPending) + processObjectQueue(); - } + } - /** - * Check is any objects are pending for periodic updates - * TODO: Clean-up - * @throws IOException - */ - private void processPeriodicUpdates() throws IOException - { + /** + * Check is any objects are pending for periodic updates TODO: Clean-up + * + * @throws IOException + */ + private void processPeriodicUpdates() throws IOException { - if (DEBUG) Log.d(TAG, "processPeriodicUpdates()"); - // Stop timer + if (DEBUG) + Log.d(TAG, "processPeriodicUpdates()"); + // Stop timer - updateTimer.cancel(); + updateTimer.cancel(); - // Iterate through each object and update its timer, if zero then transmit object. - // Also calculate smallest delay to next update (will be used for setting timeToNextUpdateMs) - int minDelay = MAX_UPDATE_PERIOD_MS; - ObjectTimeInfo objinfo; - int elapsedMs = 0; - long startTime; - int offset; - ListIterator li = objList.listIterator(); - while(li.hasNext()) - { - objinfo = li.next(); - // If object is configured for periodic updates - if (objinfo.updatePeriodMs > 0) - { - objinfo.timeToNextUpdateMs -= timeToNextUpdateMs; - // Check if time for the next update - if (objinfo.timeToNextUpdateMs <= 0) - { - // Reset timer - offset = (-objinfo.timeToNextUpdateMs) % objinfo.updatePeriodMs; - objinfo.timeToNextUpdateMs = objinfo.updatePeriodMs - offset; - // Send object - startTime = System.currentTimeMillis(); - handler.updatedManual(objinfo.obj); - //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; - } - // Update minimum delay - if (objinfo.timeToNextUpdateMs < minDelay) - { - minDelay = objinfo.timeToNextUpdateMs; - } - } - } + // Iterate through each object and update its timer, if zero then + // transmit object. + // Also calculate smallest delay to next update (will be used for + // setting timeToNextUpdateMs) + int minDelay = MAX_UPDATE_PERIOD_MS; + ObjectTimeInfo objinfo; + int elapsedMs = 0; + long startTime; + int offset; + ListIterator li = objList.listIterator(); + while (li.hasNext()) { + objinfo = li.next(); + // If object is configured for periodic updates + if (objinfo.updatePeriodMs > 0) { + objinfo.timeToNextUpdateMs -= timeToNextUpdateMs; + // Check if time for the next update + if (objinfo.timeToNextUpdateMs <= 0) { + // Reset timer + offset = (-objinfo.timeToNextUpdateMs) + % objinfo.updatePeriodMs; + objinfo.timeToNextUpdateMs = objinfo.updatePeriodMs + - offset; + // Send object + startTime = System.currentTimeMillis(); + handler.updatedManual(objinfo.obj); + // 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; + } + // Update minimum delay + if (objinfo.timeToNextUpdateMs < minDelay) { + minDelay = objinfo.timeToNextUpdateMs; + } + } + } - // Check if delay for the next update is too short - if (minDelay < MIN_UPDATE_PERIOD_MS) - { - minDelay = MIN_UPDATE_PERIOD_MS; - } + // Check if delay for the next update is too short + if (minDelay < MIN_UPDATE_PERIOD_MS) { + minDelay = MIN_UPDATE_PERIOD_MS; + } - // Done - timeToNextUpdateMs = minDelay; + // Done + timeToNextUpdateMs = minDelay; - // Restart timer - updateTimerSetPeriod(timeToNextUpdateMs); - } + // Restart timer + updateTimerSetPeriod(timeToNextUpdateMs); + } - public TelemetryStats getStats() - { - // Get UAVTalk stats - UAVTalk.ComStats utalkStats = utalk.getStats(); + public TelemetryStats getStats() { + // Get UAVTalk stats + UAVTalk.ComStats utalkStats = utalk.getStats(); - // Update stats - TelemetryStats stats = new TelemetryStats(); - stats.txBytes = utalkStats.txBytes; - stats.rxBytes = utalkStats.rxBytes; - stats.txObjectBytes = utalkStats.txObjectBytes; - stats.rxObjectBytes = utalkStats.rxObjectBytes; - stats.rxObjects = utalkStats.rxObjects; - stats.txObjects = utalkStats.txObjects; - stats.txErrors = utalkStats.txErrors + txErrors; - stats.rxErrors = utalkStats.rxErrors; - stats.txRetries = txRetries; + // Update stats + TelemetryStats stats = new TelemetryStats(); + stats.txBytes = utalkStats.txBytes; + stats.rxBytes = utalkStats.rxBytes; + stats.txObjectBytes = utalkStats.txObjectBytes; + stats.rxObjectBytes = utalkStats.rxObjectBytes; + stats.rxObjects = utalkStats.rxObjects; + stats.txObjects = utalkStats.txObjects; + stats.txErrors = utalkStats.txErrors + txErrors; + stats.rxErrors = utalkStats.rxErrors; + stats.txRetries = txRetries; - // Done - return stats; - } + // Done + return stats; + } - public void resetStats() - { - utalk.resetStats(); - txErrors = 0; - txRetries = 0; - } + public void resetStats() { + utalk.resetStats(); + txErrors = 0; + txRetries = 0; + } + private void newObject(UAVObject obj) { + registerObject(obj); + } - private void newObject(UAVObject obj) - { - registerObject(obj); - } + private synchronized void newInstance(UAVObject obj) { + registerObject(obj); + } - private synchronized void newInstance(UAVObject obj) - { - registerObject(obj); - } - - /** - * Stop all the telemetry timers - */ - public void stopTelemetry() - { - if (updateTimerTask != null) - updateTimerTask.cancel(); - updateTimerTask = null; - if (updateTimer != null) - updateTimer.cancel(); - updateTimer = null; - if (transTimerTask != null) - transTimerTask.cancel(); - transTimerTask = null; - if (transTimer != null) - transTimer.cancel(); - transTimer = null; - } + /** + * Stop all the telemetry timers + */ + public void stopTelemetry() { + if (updateTimerTask != null) + updateTimerTask.cancel(); + updateTimerTask = null; + if (updateTimer != null) + updateTimer.cancel(); + updateTimer = null; + if (transTimerTask != null) + transTimerTask.cancel(); + transTimerTask = null; + if (transTimer != null) + transTimer.cancel(); + transTimer = null; + } /** * Private variables */ - private final UAVObjectManager objMngr; - private final UAVTalk utalk; - private UAVObject gcsStatsObj; - private final List objList = new ArrayList(); - private final Queue objQueue = new ConcurrentLinkedQueue(); - private final Queue objPriorityQueue = new ConcurrentLinkedQueue(); - private final ObjectTransactionInfo transInfo = new ObjectTransactionInfo(); - private boolean transPending; + private final UAVObjectManager objMngr; + private final UAVTalk utalk; + private UAVObject gcsStatsObj; + private final List objList = new ArrayList(); + private final Queue objQueue = new ConcurrentLinkedQueue(); + private final Queue objPriorityQueue = new ConcurrentLinkedQueue(); + private final ObjectTransactionInfo transInfo = new ObjectTransactionInfo(); + private boolean transPending; - private Timer updateTimer; - private TimerTask updateTimerTask; - private Timer transTimer; - private TimerTask transTimerTask; + private Timer updateTimer; + private TimerTask updateTimerTask; + private Timer transTimer; + private TimerTask transTimerTask; - private int timeToNextUpdateMs; - private int txErrors; - private int txRetries; + private int timeToNextUpdateMs; + private int txErrors; + private int txRetries; - /** - * Private constants - */ - private static final int REQ_TIMEOUT_MS = 250; - private static final int MAX_RETRIES = 2; - private static final int MAX_UPDATE_PERIOD_MS = 1000; - private static final int MIN_UPDATE_PERIOD_MS = 1; - private static final int MAX_QUEUE_SIZE = 20; + /** + * Private constants + */ + private static final int REQ_TIMEOUT_MS = 250; + private static final int MAX_RETRIES = 2; + private static final int MAX_UPDATE_PERIOD_MS = 1000; + private static final int MIN_UPDATE_PERIOD_MS = 1; + private static final int MAX_QUEUE_SIZE = 20; - private final ObjectUpdateHandler handler; + private final ObjectUpdateHandler handler; - public class ObjectUpdateHandler extends Handler { + public class ObjectUpdateHandler extends Handler { - //! This can only be created while attaching to a particular looper - ObjectUpdateHandler(Looper l) { - super(l); - } - - //! Generic enqueue - void enqueueObjectUpdates(UAVObject obj, int event, boolean allInstances, boolean priority) { - - if (DEBUG) Log.d(TAG, "Enqueing update " + obj.getName() + " event " + event); - - ObjectQueueInfo objInfo = new ObjectQueueInfo(); - objInfo.obj = obj; - objInfo.event = event; - objInfo.allInstances = allInstances; - - post(new ObjectRunnable(objInfo)); - } - - //! Enqueue an unpacked event - void unpacked(UAVObject obj) { - enqueueObjectUpdates(obj, EV_UNPACKED, false, true); + // ! This can only be created while attaching to a particular looper + ObjectUpdateHandler(Looper l) { + super(l); } - //! Enqueue an updated auto event - void updatedAuto(UAVObject obj) { - enqueueObjectUpdates(obj,EV_UPDATED, false, true); - } + // ! Generic enqueue + void enqueueObjectUpdates(UAVObject obj, int event, + boolean allInstances, boolean priority) { - //! Enqueue an updated manual event - void updatedManual(UAVObject obj) { - enqueueObjectUpdates(obj, EV_UPDATE_REQ, false, true); - } + if (DEBUG) + Log.d(TAG, "Enqueing update " + obj.getName() + " event " + + event); - //! Enqueue an update requested event - void updateRequested(UAVObject obj) { - enqueueObjectUpdates(obj, EV_UPDATE_REQ, false, true); - } + ObjectQueueInfo objInfo = new ObjectQueueInfo(); + objInfo.obj = obj; + objInfo.event = event; + objInfo.allInstances = allInstances; - } + post(new ObjectRunnable(objInfo)); + } - class ObjectRunnable implements Runnable { + // ! Enqueue an unpacked event + void unpacked(UAVObject obj) { + enqueueObjectUpdates(obj, EV_UNPACKED, false, true); + } - //! Transaction information to perform - private final ObjectQueueInfo objInfo; -// private final ObjectTransactionInfo transInfo = new ObjectTransactionInfo(); + // ! Enqueue an updated auto event + void updatedAuto(UAVObject obj) { + enqueueObjectUpdates(obj, EV_UPDATED, false, true); + } - ObjectRunnable(ObjectQueueInfo info) { - Assert.assertNotNull(info); - objInfo = info; - } + // ! Enqueue an updated manual event + void updatedManual(UAVObject obj) { + enqueueObjectUpdates(obj, EV_UPDATED_MANUAL, false, true); + } - //! Perform the transaction on the looper thread - @Override - public void run () { - Log.d(TAG,"object transaction running"); - // 1. Check GCS is connected, throw this out if not - // 2. Set up a transaction which includes multiple retries, whether to wait for ack etc - // 3. Send UAVTalk message - // 4. Based on transaction type either wait for update or end + // ! Enqueue an update requested event + void updateRequested(UAVObject obj) { + enqueueObjectUpdates(obj, EV_UPDATE_REQ, false, true); + } - // 1. Check if a connection has been established, only process GCSTelemetryStats updates - // (used to establish the connection) - gcsStatsObj = objMngr.getObject("GCSTelemetryStats"); - if ( ((String) gcsStatsObj.getField("Status").getValue()).compareTo("Connected") != 0 ) - { - if ( objInfo.obj.getObjID() != objMngr.getObject("GCSTelemetryStats").getObjID() ) - { - if (DEBUG) Log.d(TAG,"transactionCompleted(false) due to receiving object not GCSTelemetryStats while not connected."); - objInfo.obj.transactionCompleted(false); - return; - } - } + } - Log.e(TAG, "A"); - // 2. Setup transaction (skip if unpack event) - if ( objInfo.event != EV_UNPACKED ) - { - Log.e(TAG, "A1"); - UAVObject.Metadata metadata = objInfo.obj.getMetadata(); - transInfo.obj = objInfo.obj; - transInfo.allInstances = objInfo.allInstances; - transInfo.retriesRemaining = MAX_RETRIES; - transInfo.acked = metadata.GetGcsTelemetryAcked(); - if ( objInfo.event == EV_UPDATED || objInfo.event == EV_UPDATED_MANUAL ) - { - transInfo.objRequest = false; - } - else if ( objInfo.event == EV_UPDATE_REQ ) - { - transInfo.objRequest = true; - } - // Start transaction - transPending = true; - } - Log.e(TAG, "B"); - // If this is a metaobject then make necessary telemetry updates (this is why we catch unpack) - if (objInfo.obj.isMetadata()) - { - UAVMetaObject metaobj = (UAVMetaObject) objInfo.obj; - updateObject( metaobj.getParentObject() ); - } - Log.e(TAG, "C"); - // 3. Execute transaction - if (transPending) - { - Log.e(TAG, "D"); - try { - if (DEBUG || true) Log.d(TAG, "Process Object transaction for " + transInfo.obj.getName()); - // Initiate transaction - if (transInfo.objRequest) - { - utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); - } - else - { - Log.d(TAG, "Sending object"); - utalk.sendObject(transInfo.obj, transInfo.acked, transInfo.allInstances); - } + class ObjectRunnable implements Runnable { - // TODO: Block if request expected (??) - if ( transInfo.objRequest || transInfo.acked ) - { - transTimerSetPeriod(REQ_TIMEOUT_MS); - } - else - { - synchronized(transTimer) { - transTimer.cancel(); - transPending = false; - } - } - } catch (IOException e) { - // TODO Auto-generated catch block - Log.e(TAG, "E"); - e.printStackTrace(); - } - } - } - } + // ! Transaction information to perform + private final ObjectQueueInfo objInfo; + + // private final ObjectTransactionInfo transInfo = new + // ObjectTransactionInfo(); + + ObjectRunnable(ObjectQueueInfo info) { + Assert.assertNotNull(info); + objInfo = info; + } + + // ! Perform the transaction on the looper thread + @Override + public void run() { + if (DEBUG) Log.d(TAG, "Object transaction running. Event:" + objInfo.event); + // 1. Check GCS is connected, throw this out if not + // 2. Set up a transaction which includes multiple retries, whether + // to wait for ack etc + // 3. Send UAVTalk message + // 4. Based on transaction type either wait for update or end + + // 1. Check if a connection has been established, only process + // GCSTelemetryStats updates + // (used to establish the connection) + gcsStatsObj = objMngr.getObject("GCSTelemetryStats"); + if (((String) gcsStatsObj.getField("Status").getValue()) + .compareTo("Connected") != 0) { + if (objInfo.obj.getObjID() != objMngr.getObject( + "GCSTelemetryStats").getObjID()) { + if (DEBUG) + Log.d(TAG, + "transactionCompleted(false) due to receiving object not GCSTelemetryStats while not connected."); + objInfo.obj.transactionCompleted(false); + return; + } + } + + // 2. Setup transaction (skip if unpack event) + if (objInfo.event != EV_UNPACKED) { + UAVObject.Metadata metadata = objInfo.obj.getMetadata(); + transInfo.obj = objInfo.obj; + transInfo.allInstances = objInfo.allInstances; + transInfo.retriesRemaining = MAX_RETRIES; + transInfo.acked = metadata.GetGcsTelemetryAcked(); + if (objInfo.event == EV_UPDATED + || objInfo.event == EV_UPDATED_MANUAL) { + transInfo.objRequest = false; + } else if (objInfo.event == EV_UPDATE_REQ) { + transInfo.objRequest = true; + } + // Start transaction + transPending = true; + } + + // If this is a metaobject then make necessary telemetry updates + // (this is why we catch unpack) + if (objInfo.obj.isMetadata()) { + UAVMetaObject metaobj = (UAVMetaObject) objInfo.obj; + updateObject(metaobj.getParentObject()); + } + + // 3. Execute transaction + if (transPending) { + try { + if (DEBUG) Log.d(TAG, "Process Object transaction for " + transInfo.obj.getName()); + + // Initiate transaction + if (transInfo.objRequest) { + if (DEBUG) Log.d(TAG, "Sending object request"); + utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); + } else { + if (DEBUG) Log.d(TAG, "Sending object " + transInfo.obj.getName() + " " + transInfo.obj.toStringData()); + utalk.sendObject(transInfo.obj, transInfo.acked, transInfo.allInstances); + } + + + // TODO: Block if request expected (??) + if (transInfo.objRequest || transInfo.acked ) { + transTimerSetPeriod(REQ_TIMEOUT_MS); + } else { + synchronized(transTimer) { + transTimer.cancel(); + transPending = false; + } + } + + } catch (IOException e) { + // TODO Auto-generated catch block + Log.e(TAG, "E"); + e.printStackTrace(); + } + } + } + } } From 1bbfb354466e0e78d87412379fcac493249cfc69 Mon Sep 17 00:00:00 2001 From: James Cotton Date: Wed, 15 Aug 2012 00:01:14 -0500 Subject: [PATCH 14/16] AndroidGCS: Handler based telemetry. Now reschedule transactions if one is pending. --- .../src/org/openpilot/uavtalk/Telemetry.java | 238 ++++++------------ 1 file changed, 72 insertions(+), 166 deletions(-) diff --git a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java index 3e4007777..e514e61c4 100644 --- a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java +++ b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java @@ -30,10 +30,8 @@ import java.util.List; import java.util.ListIterator; import java.util.Observable; import java.util.Observer; -import java.util.Queue; import java.util.Timer; import java.util.TimerTask; -import java.util.concurrent.ConcurrentLinkedQueue; import junit.framework.Assert; import android.os.Handler; @@ -49,8 +47,8 @@ public class Telemetry { private final String TAG = "Telemetry"; public static int LOGLEVEL = 0; - public static boolean WARN = LOGLEVEL > 2; - public static boolean DEBUG = LOGLEVEL > 1; + public static boolean DEBUG = LOGLEVEL > 2; + public static boolean WARN = LOGLEVEL > 1; public static boolean ERROR = LOGLEVEL > 0; public class TelemetryStats { @@ -391,8 +389,8 @@ public class Telemetry { */ private void transactionCompleted(UAVObject obj, boolean result) throws IOException { - if (DEBUG) - Log.d(TAG, "UAVTalk transactionCompleted"); + if (DEBUG) Log.d(TAG, "UAVTalk transactionCompleted"); + // Check if there is a pending transaction and the objects match if (transPending && transInfo.obj.getObjID() == obj.getObjID()) { if (DEBUG) Log.d(TAG, "Telemetry: transaction completed for " + obj.getName()); @@ -406,9 +404,7 @@ public class Telemetry { //Send signal obj.transactionCompleted(result); } else { - if (ERROR) - Log.e(TAG, - "Error: received a transaction completed when did not expect it."); + if (ERROR) Log.e(TAG, "Error: received a transaction completed when did not expect it."); transPending = false; } } @@ -433,16 +429,11 @@ public class Telemetry { ++txRetries; } else { if (ERROR) - Log.e(TAG, - "Transaction failed for: " - + transInfo.obj.getName()); + 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 + // 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; @@ -460,9 +451,8 @@ public class Telemetry { private void processObjectTransaction() throws IOException { if (transPending) { if (DEBUG) - Log.d(TAG, - "Process Object transaction for " - + transInfo.obj.getName()); + Log.d(TAG, "Process Object transaction for " + transInfo.obj.getName()); + // Initiate transaction if (transInfo.objRequest) { utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); @@ -470,6 +460,7 @@ public class Telemetry { utalk.sendObject(transInfo.obj, transInfo.acked, transInfo.allInstances); } + // Start timer if a response is expected if (transInfo.objRequest || transInfo.acked) { transTimerSetPeriod(REQ_TIMEOUT_MS); @@ -481,99 +472,10 @@ public class Telemetry { } } else { if (ERROR) - Log.e(TAG, - "Error: inside of processObjectTransaction with no transPending"); + Log.e(TAG, "Error: inside of processObjectTransaction with no transPending"); } } - /** - * Process events from the object queue - * - * @throws IOException - */ - private void processObjectQueue() throws IOException { - if (DEBUG) - Log.d(TAG, "Process object queue - Depth " + objQueue.size() - + " priority " + objPriorityQueue.size()); - - // Don nothing if a transaction is already in progress (should not - // happen) - if (transPending) { - if (WARN) - Log.e(TAG, "Dequeue while a transaction pending"); - return; - } - - // Get object information from queue (first the priority and then the - // regular queue) - ObjectQueueInfo objInfo; - synchronized (objPriorityQueue) { - if (!objPriorityQueue.isEmpty()) { - objInfo = objPriorityQueue.remove(); - } else { - synchronized (objQueue) { - if (!objQueue.isEmpty()) { - objInfo = objQueue.remove(); - } else { - return; - } - } - } - } - - // Check if a connection has been established, only process - // GCSTelemetryStats updates - // (used to establish the connection) - gcsStatsObj = objMngr.getObject("GCSTelemetryStats"); - if (((String) gcsStatsObj.getField("Status").getValue()) - .compareTo("Connected") != 0) { - objQueue.clear(); - if (objInfo.obj.getObjID() != objMngr - .getObject("GCSTelemetryStats").getObjID()) { - if (DEBUG) - Log.d(TAG, - "transactionCompleted(false) due to receiving object not GCSTelemetryStats while not connected."); - objInfo.obj.transactionCompleted(false); - return; - } - } - - // Setup transaction (skip if unpack event) - if (objInfo.event != EV_UNPACKED) { - UAVObject.Metadata metadata = objInfo.obj.getMetadata(); - transInfo.obj = objInfo.obj; - transInfo.allInstances = objInfo.allInstances; - transInfo.retriesRemaining = MAX_RETRIES; - transInfo.acked = metadata.GetGcsTelemetryAcked(); - if (objInfo.event == EV_UPDATED - || objInfo.event == EV_UPDATED_MANUAL) { - transInfo.objRequest = false; - } else if (objInfo.event == EV_UPDATE_REQ) { - transInfo.objRequest = true; - } - // Start transaction - transPending = true; - processObjectTransaction(); - } else { - // qDebug() << - // QString("Process object queue: this is an unpack event for %1").arg(objInfo.obj->getName()); - } - - // If this is a metaobject then make necessary telemetry updates - if (objInfo.obj.isMetadata()) { - UAVMetaObject metaobj = (UAVMetaObject) objInfo.obj; - updateObject(metaobj.getParentObject()); - } - - // The fact we received an unpacked event does not mean that - // 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 && !transPending) - processObjectQueue(); - - } - /** * Check is any objects are pending for periodic updates TODO: Clean-up * @@ -697,9 +599,7 @@ public class Telemetry { private final UAVTalk utalk; private UAVObject gcsStatsObj; private final List objList = new ArrayList(); - private final Queue objQueue = new ConcurrentLinkedQueue(); - private final Queue objPriorityQueue = new ConcurrentLinkedQueue(); - private final ObjectTransactionInfo transInfo = new ObjectTransactionInfo(); + private ObjectTransactionInfo transInfo = new ObjectTransactionInfo(); private boolean transPending; private Timer updateTimer; @@ -718,7 +618,6 @@ public class Telemetry { private static final int MAX_RETRIES = 2; private static final int MAX_UPDATE_PERIOD_MS = 1000; private static final int MIN_UPDATE_PERIOD_MS = 1; - private static final int MAX_QUEUE_SIZE = 20; private final ObjectUpdateHandler handler; @@ -733,9 +632,7 @@ public class Telemetry { void enqueueObjectUpdates(UAVObject obj, int event, boolean allInstances, boolean priority) { - if (DEBUG) - Log.d(TAG, "Enqueing update " + obj.getName() + " event " - + event); + if (DEBUG) Log.d(TAG, "Enqueing update " + obj.getName() + " event " + event); ObjectQueueInfo objInfo = new ObjectQueueInfo(); objInfo.obj = obj; @@ -794,35 +691,15 @@ public class Telemetry { // GCSTelemetryStats updates // (used to establish the connection) gcsStatsObj = objMngr.getObject("GCSTelemetryStats"); - if (((String) gcsStatsObj.getField("Status").getValue()) - .compareTo("Connected") != 0) { - if (objInfo.obj.getObjID() != objMngr.getObject( - "GCSTelemetryStats").getObjID()) { + if (((String) gcsStatsObj.getField("Status").getValue()).compareTo("Connected") != 0) { + if (objInfo.obj.getObjID() != objMngr.getObject("GCSTelemetryStats").getObjID()) { if (DEBUG) - Log.d(TAG, - "transactionCompleted(false) due to receiving object not GCSTelemetryStats while not connected."); + Log.d(TAG, "transactionCompleted(false) due to receiving object not GCSTelemetryStats while not connected."); objInfo.obj.transactionCompleted(false); return; } } - // 2. Setup transaction (skip if unpack event) - if (objInfo.event != EV_UNPACKED) { - UAVObject.Metadata metadata = objInfo.obj.getMetadata(); - transInfo.obj = objInfo.obj; - transInfo.allInstances = objInfo.allInstances; - transInfo.retriesRemaining = MAX_RETRIES; - transInfo.acked = metadata.GetGcsTelemetryAcked(); - if (objInfo.event == EV_UPDATED - || objInfo.event == EV_UPDATED_MANUAL) { - transInfo.objRequest = false; - } else if (objInfo.event == EV_UPDATE_REQ) { - transInfo.objRequest = true; - } - // Start transaction - transPending = true; - } - // If this is a metaobject then make necessary telemetry updates // (this is why we catch unpack) if (objInfo.obj.isMetadata()) { @@ -830,35 +707,64 @@ public class Telemetry { updateObject(metaobj.getParentObject()); } - // 3. Execute transaction - if (transPending) { - try { + // 2. Setup transaction (skip if unpack event) + ObjectTransactionInfo newTrans = new ObjectTransactionInfo(); + boolean newTransactionPending = false; + if (objInfo.event != EV_UNPACKED) { + UAVObject.Metadata metadata = objInfo.obj.getMetadata(); + newTrans.obj = objInfo.obj; + newTrans.allInstances = objInfo.allInstances; + newTrans.retriesRemaining = MAX_RETRIES; + newTrans.acked = metadata.GetGcsTelemetryAcked(); + if (objInfo.event == EV_UPDATED || objInfo.event == EV_UPDATED_MANUAL) { + newTrans.objRequest = false; + } else if (objInfo.event == EV_UPDATE_REQ) { + newTrans.objRequest = true; + } + + // Determine if this will schedule a new transaction + newTransactionPending = !(newTrans.objRequest || newTrans.acked); + + // If there is a transaction pending and this would set up a new one reschedule it + if (transPending && newTransactionPending) { + if (WARN) Log.w(TAG, "Postponing transaction for" + newTrans.obj.getName()); + handler.postDelayed(this, 100); + return; + } + + synchronized (transInfo) { + transPending = newTransactionPending; + transInfo = newTrans; + if (DEBUG) Log.d(TAG, "Process Object transaction for " + transInfo.obj.getName()); - // Initiate transaction - if (transInfo.objRequest) { - if (DEBUG) Log.d(TAG, "Sending object request"); - utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); - } else { - if (DEBUG) Log.d(TAG, "Sending object " + transInfo.obj.getName() + " " + transInfo.obj.toStringData()); - utalk.sendObject(transInfo.obj, transInfo.acked, transInfo.allInstances); + // 3. Execute transaction + try { + + // Initiate transaction + if (transInfo.objRequest) { + if (DEBUG) Log.d(TAG, "Sending object request"); + utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); + } else { + if (DEBUG) Log.d(TAG, "Sending object " + transInfo.obj.getName() + " " + transInfo.obj.toStringData()); + utalk.sendObject(transInfo.obj, transInfo.acked, transInfo.allInstances); + } + + + // TODO: Block if request expected (??) + if (transPending) { + transTimerSetPeriod(REQ_TIMEOUT_MS); + } else if (transTimer != null) { + synchronized(transTimer) { + transTimer.cancel(); + transPending = false; + } + } + } catch (IOException e) { + // TODO Auto-generated catch block + Log.e(TAG, "E"); + e.printStackTrace(); } - - - // TODO: Block if request expected (??) - if (transInfo.objRequest || transInfo.acked ) { - transTimerSetPeriod(REQ_TIMEOUT_MS); - } else { - synchronized(transTimer) { - transTimer.cancel(); - transPending = false; - } - } - - } catch (IOException e) { - // TODO Auto-generated catch block - Log.e(TAG, "E"); - e.printStackTrace(); } } } From 7f028f6d42446476c63d47e02220477e81a9f25e Mon Sep 17 00:00:00 2001 From: James Cotton Date: Wed, 15 Aug 2012 01:14:57 -0500 Subject: [PATCH 15/16] AndroidGCS Telemetry: Use a runnable for the transaction timeout. Now all of telemetry is using handlers nicely, but we still can have multiple transactions queued for the same object. --- .../src/org/openpilot/uavtalk/Telemetry.java | 286 ++++++++---------- 1 file changed, 118 insertions(+), 168 deletions(-) diff --git a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java index e514e61c4..10885f746 100644 --- a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java +++ b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java @@ -126,7 +126,7 @@ public class Telemetry { ListIterator> li = objs.listIterator(); while (li.hasNext()) registerObject(li.next().get(0)); // we only need to register one - // instance per object type + // instance per object type // Listen to new object creations objMngr.addNewInstanceObserver(new Observer() { @@ -146,25 +146,15 @@ public class Telemetry { utalk.setOnTransactionCompletedListener(utalk.new OnTransactionCompletedListener() { @Override void TransactionSucceeded(UAVObject data) { - try { - transactionCompleted(data, true); - } catch (IOException e) { - // Disconnect when stream fails - utalk.setOnTransactionCompletedListener(null); - } + transactionCompleted(data, true); } @Override void TransactionFailed(UAVObject data) { - try { - if (DEBUG) - Log.d(TAG, "TransactionFailed(" + data.getName() + ")"); + if (DEBUG) + Log.d(TAG, "TransactionFailed(" + data.getName() + ")"); - transactionCompleted(data, false); - } catch (IOException e) { - // Disconnect when stream fails - utalk.setOnTransactionCompletedListener(null); - } + transactionCompleted(data, false); } }); @@ -182,28 +172,6 @@ public class Telemetry { txRetries = 0; } - synchronized void transTimerSetPeriod(int periodMs) { - if (transTimerTask != null) - transTimerTask.cancel(); - - if (transTimer != null) - transTimer.purge(); - - transTimer = new Timer(); - - transTimerTask = new TimerTask() { - @Override - public void run() { - try { - transactionTimeout(); - } catch (IOException e) { - cancel(); - } - } - }; - transTimer.schedule(transTimerTask, periodMs, periodMs); - } - synchronized void updateTimerSetPeriod(int periodMs) { if (updateTimer != null) { updateTimer.cancel(); @@ -354,7 +322,7 @@ public class Telemetry { eventMask = EV_UPDATED_MANUAL | EV_UPDATE_REQ; if (obj.isMetadata()) eventMask |= EV_UNPACKED; // we also need to act on remote - // updates (unpack events) + // updates (unpack events) connectToObjectInstances(obj, eventMask); } else if (metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_ONCHANGE) { @@ -364,7 +332,7 @@ public class Telemetry { eventMask = EV_UPDATED | EV_UPDATED_MANUAL | EV_UPDATE_REQ; if (obj.isMetadata()) eventMask |= EV_UNPACKED; // we also need to act on remote - // updates (unpack events) + // updates (unpack events) connectToObjectInstances(obj, eventMask); } else if (metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_THROTTLED) { @@ -376,106 +344,12 @@ public class Telemetry { eventMask = EV_UPDATED_MANUAL | EV_UPDATE_REQ; if (obj.isMetadata()) eventMask |= EV_UNPACKED; // we also need to act on remote - // updates (unpack events) + // updates (unpack events) connectToObjectInstances(obj, eventMask); } } - /** - * Called when a transaction is successfully completed (uavtalk event) - * - * @throws IOException - */ - private 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 - if (transPending && transInfo.obj.getObjID() == obj.getObjID()) { - if (DEBUG) Log.d(TAG, "Telemetry: transaction completed for " + obj.getName()); - - // Complete transaction - synchronized(transTimer) { - transTimer.cancel(); - transPending = false; - } - - //Send signal - obj.transactionCompleted(result); - } else { - if (ERROR) Log.e(TAG, "Error: received a transaction completed when did not expect it."); - transPending = false; - } - } - - /** - * Called when a transaction is not completed within the timeout period - * (timer event) - * - * @throws IOException - */ - private void transactionTimeout() throws IOException { - if (DEBUG) - Log.d(TAG, "Telemetry: transaction timeout."); - synchronized (transTimer) { - transTimer.cancel(); - // Proceed only if there is a pending transaction - if (transPending) { - // Check if more retries are pending - if (transInfo.retriesRemaining > 0) { - --transInfo.retriesRemaining; - processObjectTransaction(); - ++txRetries; - } else { - 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 - // object specific transaction failed. - utalk.cancelPendingTransaction(transInfo.obj); - ++txErrors; - } - } - } - } - - /** - * Start an object transaction with UAVTalk, all information is stored in - * transInfo - * - * @throws IOException - */ - private void processObjectTransaction() throws IOException { - if (transPending) { - if (DEBUG) - Log.d(TAG, "Process Object transaction for " + transInfo.obj.getName()); - - // Initiate transaction - if (transInfo.objRequest) { - utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); - } else { - utalk.sendObject(transInfo.obj, transInfo.acked, - transInfo.allInstances); - } - - // Start timer if a response is expected - if (transInfo.objRequest || transInfo.acked) { - transTimerSetPeriod(REQ_TIMEOUT_MS); - } else { - synchronized (transTimer) { - transTimer.cancel(); - transPending = false; - } - } - } else { - if (ERROR) - Log.e(TAG, "Error: inside of processObjectTransaction with no transPending"); - } - } - /** * Check is any objects are pending for periodic updates TODO: Clean-up * @@ -513,6 +387,8 @@ public class Telemetry { - offset; // Send object startTime = System.currentTimeMillis(); + + if (DEBUG) Log.d(TAG, "Manual update: " + objinfo.obj.getName()); handler.updatedManual(objinfo.obj); // enqueueObjectUpdates(objinfo.obj, EV_UPDATED_MANUAL, // true, false); @@ -584,12 +460,6 @@ public class Telemetry { if (updateTimer != null) updateTimer.cancel(); updateTimer = null; - if (transTimerTask != null) - transTimerTask.cancel(); - transTimerTask = null; - if (transTimer != null) - transTimer.cancel(); - transTimer = null; } /** @@ -604,8 +474,6 @@ public class Telemetry { private Timer updateTimer; private TimerTask updateTimerTask; - private Timer transTimer; - private TimerTask transTimerTask; private int timeToNextUpdateMs; private int txErrors; @@ -664,14 +532,15 @@ public class Telemetry { } + /** + * Perform an update on an object where on an event based on the contents provided + * to the constructors. This update will also set a timeout for transaction failure. + */ class ObjectRunnable implements Runnable { // ! Transaction information to perform private final ObjectQueueInfo objInfo; - // private final ObjectTransactionInfo transInfo = new - // ObjectTransactionInfo(); - ObjectRunnable(ObjectQueueInfo info) { Assert.assertNotNull(info); objInfo = info; @@ -725,48 +594,129 @@ public class Telemetry { // Determine if this will schedule a new transaction newTransactionPending = !(newTrans.objRequest || newTrans.acked); - // If there is a transaction pending and this would set up a new one reschedule it - if (transPending && newTransactionPending) { - if (WARN) Log.w(TAG, "Postponing transaction for" + newTrans.obj.getName()); - handler.postDelayed(this, 100); - return; - } - synchronized (transInfo) { + + // If there is a transaction pending and this would set up a new one reschedule it + if (transPending && newTransactionPending) { + if (WARN) Log.w(TAG, "Postponing transaction for" + newTrans.obj.getName()); + handler.postDelayed(this, 100); + return; + } + + // Store this as the active transaction transPending = newTransactionPending; transInfo = newTrans; if (DEBUG) Log.d(TAG, "Process Object transaction for " + transInfo.obj.getName()); - // 3. Execute transaction try { - // Initiate transaction + // 3. Execute transaction by sending the appropriate UAVTalk command if (transInfo.objRequest) { - if (DEBUG) Log.d(TAG, "Sending object request"); + if (DEBUG) Log.d(TAG, "Sending object request" + transInfo.obj.getName()); utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); } else { - if (DEBUG) Log.d(TAG, "Sending object " + transInfo.obj.getName() + " " + transInfo.obj.toStringData()); + if (DEBUG) Log.d(TAG, "Sending object " + transInfo.obj.getName()); utalk.sendObject(transInfo.obj, transInfo.acked, transInfo.allInstances); } - - // TODO: Block if request expected (??) - if (transPending) { - transTimerSetPeriod(REQ_TIMEOUT_MS); - } else if (transTimer != null) { - synchronized(transTimer) { - transTimer.cancel(); - transPending = false; - } - } } catch (IOException e) { - // TODO Auto-generated catch block - Log.e(TAG, "E"); + if (ERROR) Log.e(TAG, "Unable to send UAVTalk message"); e.printStackTrace(); } + + // Post a timeout timer if a response is epxected + if (transPending) + handler.postDelayed(transactionTimeout, REQ_TIMEOUT_MS); } } } } + + + /** + * Runnable posted to handle a timeout of a transaction. Tracks the number of retry attempts + * retries that many, and finally sends a transaction failed signal. + */ + final Runnable transactionTimeout = new Runnable() { + @Override + public void run() { + // Lock on the transaction + synchronized (transInfo) { + + // Proceed only if there is a pending transaction + if (!transPending) { + if (WARN) Log.w(TAG,"Transaction completed but timeout still called. Probable race condition"); + return; + } + + if (DEBUG) Log.d(TAG, "Telemetry: transaction timeout."); + + // Check if more retries are pending + if (transInfo.retriesRemaining > 0) { + --transInfo.retriesRemaining; + + // Repeat whatever is required for this transaction type + // (transInfo.objRequest) { + if (DEBUG) Log.d(TAG, "Sending object request"); + + try { + // Execute transaction by sending the appropriate UAVTalk command + if (transInfo.objRequest) { + if (DEBUG) Log.d(TAG, "Sending object request" + transInfo.obj.getName()); + utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); + } else { + if (DEBUG) Log.d(TAG, "Sending object " + transInfo.obj.getName()); + utalk.sendObject(transInfo.obj, transInfo.acked, transInfo.allInstances); + } + } catch (IOException e) { + if (ERROR) Log.e(TAG, "Unable to send UAVTalk message"); + e.printStackTrace(); + } + + handler.postDelayed(transactionTimeout, REQ_TIMEOUT_MS); + + ++txRetries; + } else { + 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 + // object specific transaction failed. + utalk.cancelPendingTransaction(transInfo.obj); + ++txErrors; + } + } + } + }; + + + /** + * Called when a transaction is successfully completed (UAVTalk event) and maps that to + * the appropriate object event as well as canceling the pending transaction and timeout + */ + private void transactionCompleted(UAVObject obj, boolean result) { + + if (DEBUG) Log.d(TAG, "UAVTalk transactionCompleted"); + + // Check if there is a pending transaction and the objects match + synchronized(transInfo) { + if (transPending && transInfo.obj.getObjID() == obj.getObjID()) { + if (DEBUG) Log.d(TAG, "Telemetry: transaction completed for " + obj.getName()); + + // Cancel timeout and complete transaction + handler.removeCallbacks(transactionTimeout); + transPending = false; + + //Send signal + obj.transactionCompleted(result); + } else { + if (ERROR) Log.e(TAG, "Error: received a transaction completed when did not expect it."); + transPending = false; + } + } + } + } + From 8a5819379a4fe16e0c22689c45e98b8383e522cb Mon Sep 17 00:00:00 2001 From: James Cotton Date: Wed, 15 Aug 2012 02:02:05 -0500 Subject: [PATCH 16/16] AndroidGCS Telemetry: Fix the determination of whether a transaction is pending --- .../src/org/openpilot/uavtalk/Telemetry.java | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java index 10885f746..6fcbb2491 100644 --- a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java +++ b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java @@ -30,8 +30,10 @@ import java.util.List; import java.util.ListIterator; import java.util.Observable; import java.util.Observer; +import java.util.Queue; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.ConcurrentLinkedQueue; import junit.framework.Assert; import android.os.Handler; @@ -487,8 +489,14 @@ public class Telemetry { private static final int MAX_UPDATE_PERIOD_MS = 1000; private static final int MIN_UPDATE_PERIOD_MS = 1; - private final ObjectUpdateHandler handler; + static private ObjectUpdateHandler handler; + //! Accessor for the object updated handler + ObjectUpdateHandler getHandler() { return handler; } + + /** + * Handler which posts all the messages for individual object updates + */ public class ObjectUpdateHandler extends Handler { // ! This can only be created while attaching to a particular looper @@ -496,6 +504,8 @@ public class Telemetry { super(l); } + Queue objQueue = new ConcurrentLinkedQueue(); + // ! Generic enqueue void enqueueObjectUpdates(UAVObject obj, int event, boolean allInstances, boolean priority) { @@ -507,7 +517,26 @@ public class Telemetry { objInfo.event = event; objInfo.allInstances = allInstances; - post(new ObjectRunnable(objInfo)); + // For now maintain a list of objects in the queue so we don't add duplicates + // later we should make the runnables static to each class so we can use removeCallback + synchronized(objQueue) { + if (objQueue.contains(objInfo)) { + if (WARN) Log.w(TAG, "Found previously scheduled queue element: " + objInfo.obj.getName()); + } else { + objQueue.add(objInfo); + post(new ObjectRunnable(objInfo)); + } + } + } + + public boolean removeActivatedQueue(ObjectQueueInfo objInfo) { + synchronized(objQueue) { + if (objQueue.remove(objInfo)) { + if (WARN) Log.w(TAG, "Unable to find queue element to remove"); + return false; + } + } + return true; } // ! Enqueue an unpacked event @@ -592,13 +621,13 @@ public class Telemetry { } // Determine if this will schedule a new transaction - newTransactionPending = !(newTrans.objRequest || newTrans.acked); + newTransactionPending = (newTrans.objRequest || newTrans.acked); synchronized (transInfo) { // If there is a transaction pending and this would set up a new one reschedule it if (transPending && newTransactionPending) { - if (WARN) Log.w(TAG, "Postponing transaction for" + newTrans.obj.getName()); + if (WARN) Log.w(TAG, "Postponing transaction for" + newTrans.obj.getName() + " existing transaction for " + transInfo.obj.getName()); handler.postDelayed(this, 100); return; } @@ -609,11 +638,14 @@ public class Telemetry { if (DEBUG) Log.d(TAG, "Process Object transaction for " + transInfo.obj.getName()); + // Remove this one from the list of pending transactions + handler.removeActivatedQueue(objInfo); + try { // 3. Execute transaction by sending the appropriate UAVTalk command if (transInfo.objRequest) { - if (DEBUG) Log.d(TAG, "Sending object request" + transInfo.obj.getName()); + if (DEBUG) Log.d(TAG, "Sending object request " + transInfo.obj.getName()); utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances); } else { if (DEBUG) Log.d(TAG, "Sending object " + transInfo.obj.getName());