diff options
-rw-r--r-- | src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java | 182 | ||||
-rw-r--r-- | src/bluetooth/android/jni_android.cpp | 4 | ||||
-rw-r--r-- | src/bluetooth/android/lowenergynotificationhub.cpp | 72 | ||||
-rw-r--r-- | src/bluetooth/android/lowenergynotificationhub_p.h | 13 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_android.cpp | 50 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_p.h | 5 |
6 files changed, 281 insertions, 45 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 93b4a50d..9295c2bb 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 @@ -45,7 +45,6 @@ import android.bluetooth.BluetoothProfile; import android.util.Log; import java.util.ArrayList; -import java.util.Collection; import java.util.Hashtable; import java.util.List; import java.util.UUID; @@ -165,18 +164,66 @@ public class QtBluetoothLE { android.bluetooth.BluetoothGattCharacteristic characteristic, int status) { + if (status != BluetoothGatt.GATT_SUCCESS) { + Log.w(TAG, "onCharacteristicRead error: " + status); + return; + } + + //runningHandle is only used during serviceDetailsDiscovery + //If it is -1 we got an update outside of the details discovery process + if (runningHandle == -1) { + List<Integer> handles = uuidToEntry.get(characteristic.getService().getUuid()); + if (handles == null || handles.isEmpty()) { + Log.w(TAG, "Received Characteristic read update for unknown characteristic"); + return; + } + int serviceHandle = handles.get(0); + GattEntry entry; + int foundHandle = -1; + try { + for (int i = 1; serviceHandle + i < entries.size() && foundHandle == -1; i++) { + entry = entries.get(serviceHandle + i); + if (entry == null) + continue; + + if (entry.type == GattEntryType.Service) { + Log.w(TAG, "Out-of-detail-discovery: found unknown characteristic for known service"); + break; //reached next service -> unknown characteristic in service + } + + if (entry.type != GattEntryType.Characteristic) + continue; + + if (entry.characteristic == characteristic) + foundHandle = serviceHandle + i; + } + } catch (IndexOutOfBoundsException ex) { + Log.w(TAG, "Out-of-detail-discovery: cannot find handle for characteristic"); + return; + } + + if (foundHandle == -1) { + Log.w(TAG, "Out-of-detail-discovery: char update failed"); + return; + } + + leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(), + foundHandle+1, characteristic.getUuid().toString(), + characteristic.getProperties(), characteristic.getValue()); + + return; + } + GattEntry entry = entries.get(runningHandle); entry.valueKnown = true; entries.set(runningHandle, entry); - String data = new String(characteristic.getValue()); - // Qt manages handles starting at 1, in Java we use a system starting with 0 - characteristic.getService().getUuid().toString(); + // 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(), + runningHandle + 1, characteristic.getUuid().toString(), characteristic.getProperties(), characteristic.getValue()); - performServiceDiscoveryForHandle(runningHandle+1, false); + performServiceDetailDiscoveryForHandle(runningHandle + 1, false); } public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt, @@ -196,10 +243,63 @@ public class QtBluetoothLE { android.bluetooth.BluetoothGattDescriptor descriptor, int status) { + if (status != BluetoothGatt.GATT_SUCCESS) { + Log.w(TAG, "onDescriptorRead error: " + status); + return; + } + + //runningHandle is only used during serviceDetailsDiscovery + //If it is -1 we got an update outside of the details discovery process + if (runningHandle == -1) { + List<Integer> handles = uuidToEntry.get(descriptor.getCharacteristic().getService().getUuid()); + if (handles == null || handles.isEmpty()) { + Log.w(TAG, "Received Descriptor read update for unknown descriptor"); + return; + } + + int serviceHandle = handles.get(0); + GattEntry entry; + int foundHandle = -1; + try { + for (int i = 1; serviceHandle + i < entries.size() && foundHandle == -1; i++) { + entry = entries.get(serviceHandle + i); + if (entry == null) + continue; + + if (entry.type == GattEntryType.Service) { + Log.w(TAG, "Out-of-detail-discovery: found unknown descriptor for known service"); + break; //reached next service -> unknown descriptor in service + } + + if (entry.type != GattEntryType.Descriptor) + continue; + + if (entry.descriptor == descriptor) + foundHandle = serviceHandle + i; + } + } catch (IndexOutOfBoundsException ex) { + Log.w(TAG, "Out-of-detail-discovery: cannot find handle for descriptor"); + return; + } + + if (foundHandle == -1) + Log.w(TAG, "Out-of-detail-discovery: char update failed"); + + leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(), + descriptor.getCharacteristic().getUuid().toString(), foundHandle+1, + descriptor.getUuid().toString(), descriptor.getValue()); + return; + } + + GattEntry entry = entries.get(runningHandle); entry.valueKnown = true; entries.set(runningHandle, entry); - performServiceDiscoveryForHandle(runningHandle+1, false); + //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()); + performServiceDetailDiscoveryForHandle(runningHandle + 1, false); } public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt, @@ -263,6 +363,7 @@ public class QtBluetoothLE { public BluetoothGattService service = null; public BluetoothGattCharacteristic characteristic = null; public BluetoothGattDescriptor descriptor = null; + public int endHandle; } Hashtable<UUID, List<Integer>> uuidToEntry = new Hashtable<UUID, List<Integer>>(100); ArrayList<GattEntry> entries = new ArrayList<GattEntry>(100); @@ -275,11 +376,14 @@ public class QtBluetoothLE { GattEntry entry = null; List<BluetoothGattService> services = mBluetoothGatt.getServices(); for (BluetoothGattService service: services) { - entry = new GattEntry(); - entry.type = GattEntryType.Service; - entry.service = service; + GattEntry serviceEntry = new GattEntry(); + serviceEntry.type = GattEntryType.Service; + serviceEntry.service = service; entries.add(entry); + // remember handle for the service for later update + int serviceHandle = entries.size() - 1; + //some devices may have more than one service with the same uuid List<Integer> old = uuidToEntry.get(service.getUuid()); if (old == null) @@ -287,29 +391,32 @@ public class QtBluetoothLE { old.add(entries.size()-1); uuidToEntry.put(service.getUuid(), old); + // add all characteristics List<BluetoothGattCharacteristic> charList = service.getCharacteristics(); for (BluetoothGattCharacteristic characteristic: charList) { entry = new GattEntry(); entry.type = GattEntryType.Characteristic; entry.characteristic = characteristic; entries.add(entry); - //uuidToEntry.put(characteristic.getUuid(), entries.size()-1); // this emulates GATT value attributes entry = new GattEntry(); entry.type = GattEntryType.CharacteristicValue; entries.add(entry); - //uuidToEntry.put(characteristic.getUuid(), entry); + // add all descriptors List<BluetoothGattDescriptor> descList = characteristic.getDescriptors(); for (BluetoothGattDescriptor desc: descList) { entry = new GattEntry(); entry.type = GattEntryType.Descriptor; entry.descriptor = desc; entries.add(entry); - //uuidToEntry.put(desc.getUuid(), entries.size()-1); } } + + // update endHandle of current service + serviceEntry.endHandle = entries.size() - 1; + entries.set(serviceHandle, serviceEntry); } entries.trimToSize(); @@ -364,7 +471,7 @@ public class QtBluetoothLE { } if (!entry.valueKnown) { - performServiceDiscoveryForHandle(serviceHandle, true); + performServiceDetailDiscoveryForHandle(serviceHandle, true); } else { Log.w(TAG, "Service already discovered"); } @@ -379,15 +486,19 @@ public class QtBluetoothLE { private void finishCurrentServiceDiscovery() { + int currentEntry = currentServiceInDiscovery; GattEntry discoveredService = entries.get(currentServiceInDiscovery); discoveredService.valueKnown = true; entries.set(currentServiceInDiscovery, discoveredService); + runningHandle = -1; currentServiceInDiscovery = -1; - leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString()); + + leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString(), + currentEntry + 1, discoveredService.endHandle + 1); } - private synchronized void performServiceDiscoveryForHandle(int nextHandle, boolean searchStarted) + private synchronized void performServiceDetailDiscoveryForHandle(int nextHandle, boolean searchStarted) { try { if (searchStarted) { @@ -411,17 +522,40 @@ public class QtBluetoothLE { switch (entry.type) { case Characteristic: result = mBluetoothGatt.readCharacteristic(entry.characteristic); - if (!result) - performServiceDiscoveryForHandle(runningHandle+1, false); + 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 - performServiceDiscoveryForHandle(runningHandle+1, false); + performServiceDetailDiscoveryForHandle(runningHandle + 1, false); break; case Descriptor: result = mBluetoothGatt.readDescriptor(entry.descriptor); - if (!result) - performServiceDiscoveryForHandle(runningHandle+1, false); + 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(); @@ -438,9 +572,13 @@ public class QtBluetoothLE { public native void leConnectionStateChange(long qtObject, int wasErrorTransition, int newState); public native void leServicesDiscovered(long qtObject, int errorCode, String uuidList); - public native void leServiceDetailDiscoveryFinished(long qtObject, final String serviceUuid); + public native void leServiceDetailDiscoveryFinished(long qtObject, final String serviceUuid, + int startHandle, int endHandle); public native void leCharacteristicRead(long qtObject, String serviceUuid, int charHandle, String charUuid, int properties, byte[] data); + public native void leDescriptorRead(long qtObject, String serviceUuid, String charUuid, + int descHandle, String descUuid, byte[] data); + } diff --git a/src/bluetooth/android/jni_android.cpp b/src/bluetooth/android/jni_android.cpp index b75ca2df..745a0c98 100644 --- a/src/bluetooth/android/jni_android.cpp +++ b/src/bluetooth/android/jni_android.cpp @@ -203,10 +203,12 @@ static JNINativeMethod methods_le[] = { (void *) LowEnergyNotificationHub::lowEnergy_connectionChange}, {"leServicesDiscovered", "(JILjava/lang/String;)V", (void *) LowEnergyNotificationHub::lowEnergy_servicesDiscovered}, - {"leServiceDetailDiscoveryFinished", "(JLjava/lang/String;)V", + {"leServiceDetailDiscoveryFinished", "(JLjava/lang/String;II)V", (void *) LowEnergyNotificationHub::lowEnergy_serviceDetailsDiscovered}, {"leCharacteristicRead", "(JLjava/lang/String;ILjava/lang/String;I[B)V", (void *) LowEnergyNotificationHub::lowEnergy_characteristicRead}, + {"leDescriptorRead", "(JLjava/lang/String;Ljava/lang/String;ILjava/lang/String;[B)V", + (void *) LowEnergyNotificationHub::lowEnergy_descriptorRead}, }; static JNINativeMethod methods_server[] = { diff --git a/src/bluetooth/android/lowenergynotificationhub.cpp b/src/bluetooth/android/lowenergynotificationhub.cpp index 71aa5a02..709bbc68 100644 --- a/src/bluetooth/android/lowenergynotificationhub.cpp +++ b/src/bluetooth/android/lowenergynotificationhub.cpp @@ -123,7 +123,8 @@ void LowEnergyNotificationHub::lowEnergy_servicesDiscovered( } void LowEnergyNotificationHub::lowEnergy_serviceDetailsDiscovered( - JNIEnv *, jobject, jlong qtObject, jobject uuid) + JNIEnv *, jobject, jlong qtObject, jobject uuid, jint startHandle, + jint endHandle) { lock.lockForRead(); LowEnergyNotificationHub *hub = hubMap()->value(qtObject); @@ -134,7 +135,9 @@ void LowEnergyNotificationHub::lowEnergy_serviceDetailsDiscovered( const QString serviceUuid = QAndroidJniObject(uuid).toString(); QMetaObject::invokeMethod(hub, "serviceDetailsDiscoveryFinished", Qt::QueuedConnection, - Q_ARG(QString, serviceUuid)); + Q_ARG(QString, serviceUuid), + Q_ARG(int, startHandle), + Q_ARG(int, endHandle)); } void LowEnergyNotificationHub::lowEnergy_characteristicRead( @@ -147,29 +150,74 @@ void LowEnergyNotificationHub::lowEnergy_characteristicRead( if (!hub) return; - const QBluetoothUuid serviceUuid(QAndroidJniObject(sUuid).toString()); if (serviceUuid.isNull()) return; const QBluetoothUuid charUuid(QAndroidJniObject(cUuid).toString()); - - jsize length = env->GetArrayLength(data); - jbyte* nativeData = (jbyte*) malloc(length * sizeof(jbyte)); - if (!nativeData) + if (charUuid.isNull()) return; - env->GetByteArrayRegion(data, 0, length, nativeData); - const QByteArray qtArray(reinterpret_cast<const char*>(nativeData), - length); //takes ownership of data + QByteArray payload; + if (data) { //empty Java byte array is 0x0 + jsize length = env->GetArrayLength(data); + jbyte* nativeData = (jbyte*) malloc(length * sizeof(jbyte)); + if (!nativeData) + return; + + env->GetByteArrayRegion(data, 0, length, nativeData); + payload = QByteArray(reinterpret_cast<const char*>(nativeData), + length); //takes deep copy of data + free(nativeData); + } QMetaObject::invokeMethod(hub, "characteristicRead", Qt::QueuedConnection, Q_ARG(QBluetoothUuid, serviceUuid), Q_ARG(int, handle), Q_ARG(QBluetoothUuid, charUuid), Q_ARG(int, properties), - Q_ARG(QByteArray, qtArray)); - free(nativeData); + Q_ARG(QByteArray, payload)); + +} + +void LowEnergyNotificationHub::lowEnergy_descriptorRead( + JNIEnv *env, jobject, jlong qtObject, jobject sUuid, jobject cUuid, + jint handle, jobject dUuid, jbyteArray data) +{ + lock.lockForRead(); + LowEnergyNotificationHub *hub = hubMap()->value(qtObject); + lock.unlock(); + if (!hub) + return; + + const QBluetoothUuid serviceUuid(QAndroidJniObject(sUuid).toString()); + if (serviceUuid.isNull()) + return; + + const QBluetoothUuid charUuid(QAndroidJniObject(cUuid).toString()); + const QBluetoothUuid descUuid(QAndroidJniObject(dUuid).toString()); + if (charUuid.isNull() || descUuid.isNull()) + return; + + QByteArray payload; + if (data) { //empty Java byte array is 0x0 + jsize length = env->GetArrayLength(data); + jbyte* nativeData = (jbyte*) malloc(length * sizeof(jbyte)); + if (!nativeData) + return; + + env->GetByteArrayRegion(data, 0, length, nativeData); + payload = QByteArray(reinterpret_cast<const char*>(nativeData), + length); //takes deep copy of data + free(nativeData); + } + + QMetaObject::invokeMethod(hub, "descriptorRead", Qt::QueuedConnection, + Q_ARG(QBluetoothUuid, serviceUuid), + Q_ARG(QBluetoothUuid, charUuid), + Q_ARG(int, handle), + Q_ARG(QBluetoothUuid, descUuid), + Q_ARG(QByteArray, payload)); } QT_END_NAMESPACE diff --git a/src/bluetooth/android/lowenergynotificationhub_p.h b/src/bluetooth/android/lowenergynotificationhub_p.h index 7a1e2d79..39008a65 100644 --- a/src/bluetooth/android/lowenergynotificationhub_p.h +++ b/src/bluetooth/android/lowenergynotificationhub_p.h @@ -59,11 +59,15 @@ public: static void lowEnergy_servicesDiscovered(JNIEnv*, jobject, jlong qtObject, jint errorCode, jobject uuidList); static void lowEnergy_serviceDetailsDiscovered(JNIEnv *, jobject, - jlong qtObject, jobject uuid); + jlong qtObject, jobject uuid, + jint startHandle, jint endHandle); static void lowEnergy_characteristicRead(JNIEnv*env, jobject, jlong qtObject, jobject serviceUuid, jint handle, jobject charUuid, jint properties, jbyteArray data); + static void lowEnergy_descriptorRead(JNIEnv *env, jobject, jlong qtObject, + jobject sUuid, jobject cUuid, + jint handle, jobject dUuid, jbyteArray data); QAndroidJniObject javaObject() { @@ -74,10 +78,13 @@ signals: void connectionUpdated(QLowEnergyController::ControllerState newState, QLowEnergyController::Error errorCode); void servicesDiscovered(QLowEnergyController::Error errorCode, const QString &uuids); - void serviceDetailsDiscoveryFinished(const QString& serviceUuid); + void serviceDetailsDiscoveryFinished(const QString& serviceUuid, + int startHandle, int endHandle); void characteristicRead(const QBluetoothUuid &serviceUuid, int handle, const QBluetoothUuid &charUuid, - int properties, const QByteArray& data); + int properties, const QByteArray &data); + void descriptorRead(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid, + int handle, const QBluetoothUuid &descUuid, const QByteArray &data); public slots: private: diff --git a/src/bluetooth/qlowenergycontroller_android.cpp b/src/bluetooth/qlowenergycontroller_android.cpp index c3cdde83..f832d868 100644 --- a/src/bluetooth/qlowenergycontroller_android.cpp +++ b/src/bluetooth/qlowenergycontroller_android.cpp @@ -72,7 +72,8 @@ void QLowEnergyControllerPrivate::connectToDevice() this, &QLowEnergyControllerPrivate::serviceDetailsDiscoveryFinished); connect(hub, &LowEnergyNotificationHub::characteristicRead, this, &QLowEnergyControllerPrivate::characteristicRead); - + connect(hub, &LowEnergyNotificationHub::descriptorRead, + this, &QLowEnergyControllerPrivate::descriptorRead); } if (!hub->javaObject().isValid()) { @@ -129,7 +130,7 @@ void QLowEnergyControllerPrivate::discoverServiceDetails(const QBluetoothUuid &s bool result = hub->javaObject().callMethod<jboolean>("discoverServiceDetails", "(Ljava/lang/String;)Z", uuid.object<jstring>()); - if (!result || true) { + if (!result) { QSharedPointer<QLowEnergyServicePrivate> servicePrivate = serviceList.value(service); if (!servicePrivate.isNull()) { @@ -219,7 +220,7 @@ void QLowEnergyControllerPrivate::servicesDiscovered( } void QLowEnergyControllerPrivate::serviceDetailsDiscoveryFinished( - const QString &serviceUuid) + const QString &serviceUuid, int startHandle, int endHandle) { const QBluetoothUuid service(serviceUuid); if (!serviceList.contains(service)) { @@ -228,13 +229,20 @@ void QLowEnergyControllerPrivate::serviceDetailsDiscoveryFinished( return; } + //update service data QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service); + pointer->startHandle = startHandle; + pointer->endHandle = endHandle; + + qCDebug(QT_BT_ANDROID) << "Service" << serviceUuid << "discovered (start:" + << startHandle << "end:" << endHandle << ")"; + pointer->setState(QLowEnergyService::ServiceDiscovered); } void QLowEnergyControllerPrivate::characteristicRead( - const QBluetoothUuid& serviceUuid, int handle, + const QBluetoothUuid &serviceUuid, int handle, const QBluetoothUuid &charUuid, int properties, const QByteArray &data) { if (!serviceList.contains(serviceUuid)) @@ -247,14 +255,44 @@ void QLowEnergyControllerPrivate::characteristicRead( QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle]; - //Android uses same properties value as Qt which is the Bluetooth LE standard + //Android uses same property value as Qt which is the Bluetooth LE standard charDetails.properties = QLowEnergyCharacteristic::PropertyType(properties); charDetails.uuid = charUuid; charDetails.value = data; //value handle always one larger than characteristics value handle charDetails.valueHandle = charHandle + 1; +} - //service->characteristicList[charHandle] = charDetails; +void QLowEnergyControllerPrivate::descriptorRead( + const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid, + int descHandle, const QBluetoothUuid &descUuid, const QByteArray &data) +{ + if (!serviceList.contains(serviceUuid)) + return; + + QSharedPointer<QLowEnergyServicePrivate> service = + serviceList.value(serviceUuid); + + bool entryUpdated = false; + foreach (QLowEnergyHandle charHandle, service->characteristicList.keys()) { + QLowEnergyServicePrivate::CharData &charDetails = + service->characteristicList[charHandle]; + if (charDetails.uuid != charUuid) + continue; + + // new entry created if it doesn't exist + QLowEnergyServicePrivate::DescData &descDetails = + charDetails.descriptorList[descHandle]; + descDetails.uuid = descUuid; + descDetails.value = data; + entryUpdated = true; + break; + } + + if (!entryUpdated) { + qCWarning(QT_BT_ANDROID) << "Cannot find/update descriptor" + << descUuid << charUuid << serviceUuid; + } } QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index 6f416b11..6a72fa6c 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -179,10 +179,13 @@ private slots: QLowEnergyController::Error errorCode); void servicesDiscovered(QLowEnergyController::Error errorCode, const QString &foundServices); - void serviceDetailsDiscoveryFinished(const QString& serviceUuid); + void serviceDetailsDiscoveryFinished(const QString& serviceUuid, + int startHandle, int endHandle); void characteristicRead(const QBluetoothUuid &serviceUuid, int handle, const QBluetoothUuid &charUuid, int properties, const QByteArray& data); + void descriptorRead(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid, + int handle, const QBluetoothUuid &descUuid, const QByteArray &data); #endif private: |