diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java | 462 | ||||
-rw-r--r-- | src/src.pro | 4 | ||||
-rw-r--r-- | src/tools/sdpscanner/qt_attribution.json | 2 |
3 files changed, 236 insertions, 232 deletions
diff --git a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java index 9fe88e9c..481f2917 100644 --- a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java +++ b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java @@ -54,6 +54,7 @@ import java.util.ArrayList; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; +import java.util.NoSuchElementException; import java.util.UUID; public class QtBluetoothLE { @@ -182,64 +183,58 @@ public class QtBluetoothLE { android.bluetooth.BluetoothGattCharacteristic characteristic, int status) { - //runningHandle is only used during serviceDetailsDiscovery - //If it is -1 we got an update outside of the details discovery process - final boolean isServiceDiscoveryRun = (runningHandle != -1); - - if (status != BluetoothGatt.GATT_SUCCESS) { - Log.w(TAG, "onCharacteristicRead error: " + status); - - // read errors during serviceDiscovery are ignored - if (isServiceDiscoveryRun) - return; - } - + int foundHandle = -1; synchronized (this) { - if (uuidToEntry.isEmpty()) // ignore data if internal setup is not ready; + foundHandle = handleForCharacteristic(characteristic); + if (foundHandle == -1 || foundHandle >= entries.size() ) { + Log.w(TAG, "Cannot find characteristic read request for read notification - handle: " + + foundHandle + " size: " + entries.size()); + + //unlock the queue for next item + synchronized (readWriteQueue) { + ioJobPending = false; + } + + performNextIO(); return; + } } - // once we have a service discovery run we report regular changes - if (!isServiceDiscoveryRun) { + GattEntry entry = entries.get(foundHandle); + final boolean isServiceDiscoveryRun = !entry.valueKnown; + entry.valueKnown = true; - int foundHandle = -1; - synchronized (this) { - foundHandle = handleForCharacteristic(characteristic); + if (status == BluetoothGatt.GATT_SUCCESS) { + // Qt manages handles starting at 1, in Java we use a system starting with 0 + //TODO avoid sending service uuid -> service handle should be sufficient + leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(), + foundHandle + 1, characteristic.getUuid().toString(), + characteristic.getProperties(), characteristic.getValue()); + } else { + if (isServiceDiscoveryRun) { + //do nothing just continue with queue + Log.w(TAG, "onCharacteristicRead during discovery error: " + status); + } else { + // This must be in sync with QLowEnergyService::CharacteristicReadError + final int characteristicReadError = 5; + leServiceError(qtObject, foundHandle + 1, characteristicReadError); } + } - synchronized (readWriteQueue) { - ioJobPending = false; - } + if (isServiceDiscoveryRun) { - if (foundHandle == -1) { - Log.w(TAG, "Out-of-detail-discovery: char update failed. " + - "Cannot find handle for characteristic"); - } else { - if (status == BluetoothGatt.GATT_SUCCESS) { - leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(), - foundHandle + 1, characteristic.getUuid().toString(), - characteristic.getProperties(), characteristic.getValue()); - } else { - // This must be in sync with QLowEnergyService::CharacteristicReadError - final int characteristicReadError = 5; - leServiceError(qtObject, foundHandle + 1, characteristicReadError); - } - } + // last entry of pending service discovery run -> send discovery finished state update + GattEntry serviceEntry = entries.get(entry.associatedServiceHandle); + if (serviceEntry.endHandle == foundHandle) + finishCurrentServiceDiscovery(entry.associatedServiceHandle); + } - performNextIO(); - return; + //unlock the queue for next item + synchronized (readWriteQueue) { + ioJobPending = false; } - GattEntry entry = entries.get(runningHandle); - entry.valueKnown = true; - entries.set(runningHandle, entry); - - // Qt manages handles starting at 1, in Java we use a system starting with 0 - //TODO avoid sending service uuid -> service handle should be sufficient - leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(), - runningHandle + 1, characteristic.getUuid().toString(), - characteristic.getProperties(), characteristic.getValue()); - performServiceDetailDiscoveryForHandle(runningHandle + 1, false); + performNextIO(); } public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt, @@ -287,81 +282,74 @@ public class QtBluetoothLE { android.bluetooth.BluetoothGattDescriptor descriptor, int status) { - //runningHandle is only used during serviceDetailsDiscovery - //If it is -1 we got an update outside of the details discovery process - final boolean isServiceDiscoveryRun = (runningHandle != -1); - - if (status != BluetoothGatt.GATT_SUCCESS) { - Log.w(TAG, "onDescriptorRead error: " + status); - - // read errors during serviceDiscovery are ignored - if (isServiceDiscoveryRun) - return; - } - + int foundHandle = -1; synchronized (this) { - if (uuidToEntry.isEmpty()) // ignore data if internal setup is not ready; + foundHandle = handleForDescriptor(descriptor); + if (foundHandle == -1 || foundHandle >= entries.size() ) { + Log.w(TAG, "Cannot find descriptor read request for read notification - handle: " + + foundHandle + " size: " + entries.size()); + + //unlock the queue for next item + synchronized (readWriteQueue) { + ioJobPending = false; + } + performNextIO(); return; - } + } + } - if (!isServiceDiscoveryRun) { + GattEntry entry = entries.get(foundHandle); + final boolean isServiceDiscoveryRun = !entry.valueKnown; + entry.valueKnown = true; - int foundHandle = -1; - synchronized (this) { - foundHandle = handleForDescriptor(descriptor); + if (status == BluetoothGatt.GATT_SUCCESS) { + //TODO avoid sending service and characteristic uuid -> handles should be sufficient + leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(), + descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1, + descriptor.getUuid().toString(), descriptor.getValue()); + } else { + if (isServiceDiscoveryRun) { + //ignore + Log.w(TAG, "onDescriptorcRead during discovery error: " + status); + } else { + // This must be in sync with QLowEnergyService::DescriptorReadError + final int descriptorReadError = 6; + leServiceError(qtObject, foundHandle + 1, descriptorReadError); } - synchronized (readWriteQueue) { - ioJobPending = false; + } + + if (isServiceDiscoveryRun) { + // last entry of pending service discovery run? ->send discovery finished state update + GattEntry serviceEntry = entries.get(entry.associatedServiceHandle); + if (serviceEntry.endHandle == foundHandle) { + finishCurrentServiceDiscovery(entry.associatedServiceHandle); } - if (foundHandle == -1) { - Log.w(TAG, "Out-of-detail-discovery: char update failed. " + - "Cannot find handle for descriptor."); - } else { - if (status == BluetoothGatt.GATT_SUCCESS) { - leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(), - descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1, - descriptor.getUuid().toString(), descriptor.getValue()); - } else { - // This must be in sync with QLowEnergyService::DescriptorReadError - final int descriptorReadError = 6; - leServiceError(qtObject, foundHandle + 1, descriptorReadError); + /* Some devices preset ClientCharacteristicConfiguration descriptors + * to enable notifications out of the box. However the additional + * BluetoothGatt.setCharacteristicNotification call prevents + * automatic notifications from coming through. Hence we manually set them + * up here. + */ + if (descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) { + final int value = descriptor.getValue()[0]; + // notification or indication bit set? + if ((value & 0x03) > 0) { + Log.d(TAG, "Found descriptor with automatic notifications."); + mBluetoothGatt.setCharacteristicNotification( + descriptor.getCharacteristic(), true); } } - - performNextIO(); - return; } - - GattEntry entry = entries.get(runningHandle); - entry.valueKnown = true; - entries.set(runningHandle, entry); - //TODO avoid sending service and characteristic uuid -> handles should be sufficient - leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(), - descriptor.getCharacteristic().getUuid().toString(), runningHandle+1, - descriptor.getUuid().toString(), descriptor.getValue()); - - /* Some devices preset ClientCharacteristicConfiguration descriptors - * to enable notifications out of the box. However the additional - * BluetoothGatt.setCharacteristicNotification call prevents - * automatic notifications from coming through. Hence we manually set them - * up here. - */ - - if (descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) { - final int value = descriptor.getValue()[0]; - // notification or indication bit set? - if ((value & 0x03) > 0) { - Log.d(TAG, "Found descriptor with automatic notifications."); - mBluetoothGatt.setCharacteristicNotification( - descriptor.getCharacteristic(), true); - } + //unlock the queue for next item + synchronized (readWriteQueue) { + ioJobPending = false; } - performServiceDetailDiscoveryForHandle(runningHandle + 1, false); + performNextIO(); } public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt, @@ -440,7 +428,15 @@ public class QtBluetoothLE { public BluetoothGattService service = null; public BluetoothGattCharacteristic characteristic = null; public BluetoothGattDescriptor descriptor = null; - public int endHandle; + /* + * endHandle defined for GattEntryType.Service and GattEntryType.CharacteristicValue + * If the type is service this is the value of the last Gatt entry belonging to the very + * same service. If the type is a char value it is the entries index inside + * the "entries" list. + */ + public int endHandle = -1; + // pointer back to the handle that describes the service that this GATT entry belongs to + public int associatedServiceHandle; } private enum IoJobType @@ -456,8 +452,12 @@ public class QtBluetoothLE { public IoJobType jobType; } + // service uuid -> service handle mapping (there can be more than one service with same uuid) private final Hashtable<UUID, List<Integer>> uuidToEntry = new Hashtable<UUID, List<Integer>>(100); + // index into array is equivalent to handle id private final ArrayList<GattEntry> entries = new ArrayList<GattEntry>(100); + //backlog of to be discovered services + // TODO remove private final LinkedList<Integer> servicesToBeDiscovered = new LinkedList<Integer>(); @@ -557,10 +557,12 @@ public class QtBluetoothLE { GattEntry serviceEntry = new GattEntry(); serviceEntry.type = GattEntryType.Service; serviceEntry.service = service; - entries.add(entry); + entries.add(serviceEntry); // remember handle for the service for later update int serviceHandle = entries.size() - 1; + //point to itself -> mostly done for consistence reasons with other entries + serviceEntry.associatedServiceHandle = serviceHandle; //some devices may have more than one service with the same uuid List<Integer> old = uuidToEntry.get(service.getUuid()); @@ -575,11 +577,15 @@ public class QtBluetoothLE { entry = new GattEntry(); entry.type = GattEntryType.Characteristic; entry.characteristic = characteristic; + entry.associatedServiceHandle = serviceHandle; + //entry.endHandle = .. undefined entries.add(entry); // this emulates GATT value attributes entry = new GattEntry(); entry.type = GattEntryType.CharacteristicValue; + entry.associatedServiceHandle = serviceHandle; + entry.endHandle = entries.size(); // special case -> current index in entries list entries.add(entry); // add all descriptors @@ -588,26 +594,22 @@ public class QtBluetoothLE { entry = new GattEntry(); entry.type = GattEntryType.Descriptor; entry.descriptor = desc; + entry.associatedServiceHandle = serviceHandle; + //entry.endHandle = .. undefined entries.add(entry); } } // update endHandle of current service serviceEntry.endHandle = entries.size() - 1; - entries.set(serviceHandle, serviceEntry); } entries.trimToSize(); } - private int currentServiceInDiscovery = -1; - private int runningHandle = -1; - private void resetData() { synchronized (this) { - runningHandle = -1; - currentServiceInDiscovery = -1; uuidToEntry.clear(); entries.clear(); servicesToBeDiscovered.clear(); @@ -654,28 +656,15 @@ public class QtBluetoothLE { return false; } - // current service already under investigation - if (currentServiceInDiscovery == serviceHandle) - return true; - - if (currentServiceInDiscovery != -1) { - // we are currently discovering another service - // we queue the new one up until we finish the previous one - if (!entry.valueKnown) { - servicesToBeDiscovered.add(serviceHandle); - Log.w(TAG, "Service discovery already running on another service, " + - "queueing request for " + serviceUuid); - } else { - Log.w(TAG, "Service already known"); - } + // current service already discovered or under investigation + if (entry.valueKnown || servicesToBeDiscovered.contains(serviceHandle)) { + Log.w(TAG, "Service already known or to be discovered"); return true; } - if (!entry.valueKnown) { - performServiceDetailDiscoveryForHandle(serviceHandle, true); - } else { - Log.w(TAG, "Service already discovered"); - } + servicesToBeDiscovered.add(serviceHandle); + scheduleServiceDetailDiscovery(serviceHandle); + performNextIO(); } catch (Exception ex) { ex.printStackTrace(); @@ -716,98 +705,70 @@ public class QtBluetoothLE { return builder.toString(); } - private void finishCurrentServiceDiscovery() + //TODO function not yet used + private void finishCurrentServiceDiscovery(int handleDiscoveredService) { - int currentEntry = currentServiceInDiscovery; - GattEntry discoveredService = entries.get(currentServiceInDiscovery); + Log.w(TAG, "Finished current discovery for service handle " + handleDiscoveredService); + GattEntry discoveredService = entries.get(handleDiscoveredService); discoveredService.valueKnown = true; - entries.set(currentServiceInDiscovery, discoveredService); - - runningHandle = -1; - currentServiceInDiscovery = -1; - - leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString(), - currentEntry + 1, discoveredService.endHandle + 1); - - if (!servicesToBeDiscovered.isEmpty()) { + synchronized (this) { try { - int nextService = servicesToBeDiscovered.remove(); - performServiceDetailDiscoveryForHandle(nextService, true); - } catch (IndexOutOfBoundsException ex) { + servicesToBeDiscovered.removeFirst(); + } catch (NoSuchElementException ex) { Log.w(TAG, "Expected queued service but didn't find any"); } } + + leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString(), + handleDiscoveredService + 1, discoveredService.endHandle + 1); } - private synchronized void performServiceDetailDiscoveryForHandle(int nextHandle, boolean searchStarted) + /* + Internal Helper function for discoverServiceDetails() + + Adds all Gatt entries for the given service to the readWriteQueue to be discovered. + This function only ever adds read requests to the queue. + + //TODO function not yet used + */ + private void scheduleServiceDetailDiscovery(int serviceHandle) { - try { - if (searchStarted) { - currentServiceInDiscovery = nextHandle; - runningHandle = ++nextHandle; - } else { - runningHandle = nextHandle; - } + GattEntry serviceEntry = entries.get(serviceHandle); + final int endHandle = serviceEntry.endHandle; - GattEntry entry; - try { - entry = entries.get(nextHandle); - } catch (IndexOutOfBoundsException ex) { - //ex.printStackTrace(); - Log.w(TAG, "Last entry of last service read"); - finishCurrentServiceDiscovery(); - return; - } + synchronized (readWriteQueue) { + // entire block inside mutex to ensure all service discovery jobs go in one after the other + // ensures that serviceDiscovered() signal is sent when required - boolean result; - switch (entry.type) { - case Characteristic: - result = mBluetoothGatt.readCharacteristic(entry.characteristic); - try { - if (!result) { - // add characteristic now since we won't get a read update later one - // this is possible when the characteristic is not readable - Log.d(TAG, "Non-readable characteristic " + entry.characteristic.getUuid() + - " for service " + entry.characteristic.getService().getUuid()); - leCharacteristicRead(qtObject, entry.characteristic.getService().getUuid().toString(), - nextHandle + 1, entry.characteristic.getUuid().toString(), - entry.characteristic.getProperties(), entry.characteristic.getValue()); - performServiceDetailDiscoveryForHandle(runningHandle + 1, false); - } - } catch (Exception ex) - { - ex.printStackTrace(); - } - break; - case CharacteristicValue: - // ignore -> nothing to do for this artificial type - performServiceDetailDiscoveryForHandle(runningHandle + 1, false); - break; - case Descriptor: - result = mBluetoothGatt.readDescriptor(entry.descriptor); - if (!result) { - // atm all descriptor types are readable - Log.d(TAG, "Non-readable descriptor " + entry.descriptor.getUuid() + - " for service/char" + entry.descriptor.getCharacteristic().getService().getUuid() + - "/" + entry.descriptor.getCharacteristic().getUuid()); - leDescriptorRead(qtObject, - entry.descriptor.getCharacteristic().getService().getUuid().toString(), - entry.descriptor.getCharacteristic().getUuid().toString(), - nextHandle+1, entry.descriptor.getUuid().toString(), - entry.descriptor.getValue()); - performServiceDetailDiscoveryForHandle(runningHandle + 1, false); - } - break; - case Service: - finishCurrentServiceDiscovery(); - break; - default: - Log.w(TAG, "Invalid GATT attribute type"); - break; - } - } catch(Exception ex) { - ex.printStackTrace(); + // serviceHandle + 1 -> ignore service handle itself + for (int i = serviceHandle + 1; i <= endHandle; i++) { + GattEntry entry = entries.get(i); + + switch (entry.type) { + case Characteristic: + case Descriptor: + // we schedule CharacteristicValue for initial discovery to simplify + // detection of the end of service discovery process + // performNextIO() ignores CharacteristicValue GATT entries + case CharacteristicValue: + break; + case Service: + // should not really happen unless endHandle is wrong + Log.w(TAG, "scheduleServiceDetailDiscovery: wrong endHandle"); + return; + } + + // only descriptor and characteristic fall through to this point + ReadWriteJob newJob = new ReadWriteJob(); + newJob.entry = entry; + newJob.jobType = IoJobType.Read; + + final boolean result = readWriteQueue.add(newJob); + if (!result) + Log.w(TAG, "Cannot add service discovery job for " + serviceEntry.service.getUuid() + + " on item " + entry.type); + } } } @@ -982,7 +943,7 @@ public class QtBluetoothLE { nextJob = readWriteQueue.remove(); - Log.w(TAG, "Performing queued job " + nextJob.jobType); + Log.w(TAG, "Performing queued job " + nextJob.jobType + " " + nextJob.entry.valueKnown); if (nextJob.jobType == IoJobType.Read) skip = executeReadJob(nextJob); else @@ -999,27 +960,67 @@ public class QtBluetoothLE { BluetoothGatt.[read|write][Characteristic|Descriptor]() immediately return in cases where meta data doesn't match the intended action (e.g. trying to write to read-only char). When this happens - we have to report an error back to Qt. This is not required during + we have to report an error back to Qt. The error report is not required during the initial service discovery though. */ - final boolean isServiceDiscoveryRun = (runningHandle != -1); - if (!isServiceDiscoveryRun) { - int handle = -1; - if (nextJob.entry.type == GattEntryType.Characteristic) - handle = handleForCharacteristic(nextJob.entry.characteristic); - else - handle = handleForDescriptor(nextJob.entry.descriptor); - - if (handle != -1) { + int handle = -1; + GattEntry entry = nextJob.entry; + + if (entry.type == GattEntryType.Characteristic) + handle = handleForCharacteristic(entry.characteristic); + else if (entry.type == GattEntryType.Descriptor) + handle = handleForDescriptor(entry.descriptor); + else if (entry.type == GattEntryType.CharacteristicValue) + handle = entry.endHandle; + + if (handle != -1) { + // during service discovery we do not report error but emit characteristicRead() + // any other time a failure emits serviceError() signal + + final boolean isServiceDiscovery = !entry.valueKnown; + + if (isServiceDiscovery) { + entry.valueKnown = true; + switch (entry.type) { + case Characteristic: + Log.d(TAG, "Non-readable characteristic " + entry.characteristic.getUuid() + + " for service " + entry.characteristic.getService().getUuid()); + leCharacteristicRead(qtObject, entry.characteristic.getService().getUuid().toString(), + handle + 1, entry.characteristic.getUuid().toString(), + entry.characteristic.getProperties(), entry.characteristic.getValue()); + break; + case Descriptor: + // atm all descriptor types are readable + Log.d(TAG, "Non-readable descriptor " + entry.descriptor.getUuid() + + " for service/char" + entry.descriptor.getCharacteristic().getService().getUuid() + + "/" + entry.descriptor.getCharacteristic().getUuid()); + leDescriptorRead(qtObject, + entry.descriptor.getCharacteristic().getService().getUuid().toString(), + entry.descriptor.getCharacteristic().getUuid().toString(), + handle + 1, entry.descriptor.getUuid().toString(), + entry.descriptor.getValue()); + break; + case CharacteristicValue: + // for more details see scheduleServiceDetailDiscovery(int) + // ignore and continue unless last entry + GattEntry serviceEntry = entries.get(entry.associatedServiceHandle); + if (serviceEntry.endHandle == handle) + finishCurrentServiceDiscovery(entry.associatedServiceHandle); + break; + case Service: + Log.w(TAG, "Scheduling of Service Gatt entry for service discovery should never happen."); + break; + } + } else { int errorCode = 0; // The error codes below must be in sync with QLowEnergyService::ServiceError if (nextJob.jobType == IoJobType.Read) { - errorCode = (nextJob.entry.type == GattEntryType.Characteristic) ? - 5 : 6; // CharacteristicReadError : DescriptorReadError + errorCode = (entry.type == GattEntryType.Characteristic) ? + 5 : 6; // CharacteristicReadError : DescriptorReadError } else { - errorCode = (nextJob.entry.type == GattEntryType.Characteristic) ? - 2 : 3; // CharacteristicWriteError : DescriptorWriteError + errorCode = (entry.type == GattEntryType.Characteristic) ? + 2 : 3; // CharacteristicWriteError : DescriptorWriteError } leServiceError(qtObject, handle + 1, errorCode); @@ -1109,8 +1110,9 @@ public class QtBluetoothLE { return true; // skip break; case Service: - case CharacteristicValue: return true; + case CharacteristicValue: + return true; //skip } return false; } diff --git a/src/src.pro b/src/src.pro index 8c54e4fb..38be9a4f 100644 --- a/src/src.pro +++ b/src/src.pro @@ -24,5 +24,7 @@ qtHaveModule(quick) { include($$OUT_PWD/bluetooth/qtbluetooth-config.pri) QT_FOR_CONFIG += bluetooth-private qtConfig(bluez):qtHaveModule(dbus) { - SUBDIRS += tools/sdpscanner + sdpscanner.subdir = tools/sdpscanner + sdpscanner.depends = bluetooth + SUBDIRS += sdpscanner } diff --git a/src/tools/sdpscanner/qt_attribution.json b/src/tools/sdpscanner/qt_attribution.json index ee01349e..9f2ada92 100644 --- a/src/tools/sdpscanner/qt_attribution.json +++ b/src/tools/sdpscanner/qt_attribution.json @@ -2,7 +2,7 @@ "Id": "bluez", "Name": "BlueZ", "QDocModule": "qtbluetooth", - "QtUsage": "On Linux, Qt Bluetooth uses a separate executable, \c sdpscanner, to integrate with the official Linux bluetooth protocol stack BlueZ. The Qt Bluetooth library does not link against BlueZ directly.", + "QtUsage": "On Linux, Qt Bluetooth uses a separate executable, sdpscanner, to integrate with the official Linux bluetooth protocol stack BlueZ. The Qt Bluetooth library does not link against BlueZ directly.", "Description": "BlueZ", "Homepage": "http://www.bluez.org/", |