diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java | 254 | ||||
-rw-r--r-- | src/bluetooth/android/jni_android.cpp | 2 | ||||
-rw-r--r-- | src/bluetooth/android/lowenergynotificationhub.cpp | 15 | ||||
-rw-r--r-- | src/bluetooth/android/lowenergynotificationhub_p.h | 3 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_android.cpp | 61 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_p.h | 1 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyservice.cpp | 2 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyservice.h | 3 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyserviceprivate.cpp | 3 |
9 files changed, 339 insertions, 5 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 0fd73613..50e2f293 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 @@ -38,12 +38,17 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; 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; public class QtBluetoothLE { private static final String TAG = "QtBluetoothGatt"; @@ -69,6 +74,11 @@ public class QtBluetoothLE { mRemoteGattAddress = remoteAddress; } + + /*************************************************************/ + /* Device scan */ + /*************************************************************/ + /* Returns true, if request was successfully completed */ @@ -101,6 +111,10 @@ public class QtBluetoothLE { public native void leScanResult(long qtObject, BluetoothDevice device, int rssi); + /*************************************************************/ + /* Service Discovery */ + /*************************************************************/ + private BluetoothGattCallback gattCallback = new BluetoothGattCallback() { public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { @@ -146,10 +160,59 @@ public class QtBluetoothLE { } leServicesDiscovered(qtObject, errorCode, builder.toString()); } + + public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt, + android.bluetooth.BluetoothGattCharacteristic characteristic, + int status) + { + GattEntry entry = entries.get(runningHandle); + entry.valueKnown = true; + entries.set(runningHandle, entry); + performServiceDiscoveryForHandle(runningHandle+1, false); + } + + public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt, + android.bluetooth.BluetoothGattCharacteristic characteristic, + int status) + { + System.out.println("onCharacteristicWrite"); + } + + public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt, + android.bluetooth.BluetoothGattCharacteristic characteristic) + { + System.out.println("onCharacteristicChanged"); + } + + public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt, + android.bluetooth.BluetoothGattDescriptor descriptor, + int status) + { + GattEntry entry = entries.get(runningHandle); + entry.valueKnown = true; + entries.set(runningHandle, entry); + performServiceDiscoveryForHandle(runningHandle+1, false); + } + + public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt, + android.bluetooth.BluetoothGattDescriptor descriptor, + int status) + { + System.out.println("onDescriptorWrite"); + } + //TODO Requires Android API 21 which is not available on CI yet. +// public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt gatt, +// int status) { +// System.out.println("onReliableWriteCompleted"); +// } +// +// public void onReadRemoteRssi(android.bluetooth.BluetoothGatt gatt, +// int rssi, int status) { +// System.out.println("onReadRemoteRssi"); +// } + }; - public native void leConnectionStateChange(long qtObject, int wasErrorTransition, int newState); - public native void leServicesDiscovered(long qtObject, int errorCode, String uuidList); public boolean connect() { if (mBluetoothGatt != null) @@ -181,5 +244,192 @@ public class QtBluetoothLE { return mBluetoothGatt.discoverServices(); } + private enum GattEntryType + { + Service, Characteristic, CharacteristicValue, Descriptor + } + private class GattEntry + { + public GattEntryType type; + public boolean valueKnown = false; + public BluetoothGattService service = null; + public BluetoothGattCharacteristic characteristic = null; + public BluetoothGattDescriptor descriptor = null; + } + Hashtable<UUID, List<Integer>> uuidToEntry = new Hashtable<UUID, List<Integer>>(100); + ArrayList<GattEntry> entries = new ArrayList<GattEntry>(100); + + private void populateHandles() + { + // We introduce the notion of artificial handles. While GATT handles + // are not exposed on Android they help to quickly identify GATT attributes + // on the C++ side. The Qt Api will not expose the handles + GattEntry entry = null; + List<BluetoothGattService> services = mBluetoothGatt.getServices(); + for (BluetoothGattService service: services) { + entry = new GattEntry(); + entry.type = GattEntryType.Service; + entry.service = service; + entries.add(entry); + + //some devices may have more than one service with the same uuid + List<Integer> old = uuidToEntry.get(service.getUuid()); + if (old == null) + old = new ArrayList<Integer>(); + old.add(entries.size()-1); + uuidToEntry.put(service.getUuid(), old); + + 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); + + 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); + } + } + } + + entries.trimToSize(); + } + + private int currentServiceInDiscovery = -1; + private int runningHandle = -1; + public synchronized boolean discoverServiceDetails(String serviceUuid) + { + try { + if (mBluetoothGatt == null) + return false; + + if (entries.isEmpty()) + populateHandles(); + + GattEntry entry; + int serviceHandle; + try { + UUID service = UUID.fromString(serviceUuid); + List<Integer> handles = uuidToEntry.get(service); + if (handles == null || handles.isEmpty()) { + Log.w(TAG, "Unknown service uuid for current device: " + service.toString()); + return false; + } + + //TODO for now we assume we always want the first service in case of uuid collision + serviceHandle = handles.get(0); + entry = entries.get(serviceHandle); + if (entry == null) { + Log.w(TAG, "Service with UUID " + service.toString() + " not found"); + return false; + } + } catch (IllegalArgumentException ex) { + //invalid UUID string passed + Log.w(TAG, "Cannot parse given UUID"); + return false; + } + + if (entry.type != GattEntryType.Service) { + Log.w(TAG, "Given UUID is not a service UUID: " + serviceUuid); + return false; + } + + // current service already under investigation + if (currentServiceInDiscovery == serviceHandle) + return true; + + if (currentServiceInDiscovery != -1) { + Log.w(TAG, "Service discovery already running on another service"); + return false; + } + + if (!entry.valueKnown) { + performServiceDiscoveryForHandle(serviceHandle, true); + } else { + Log.w(TAG, "Service already discovered"); + } + + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + + return true; + } + + private void finishCurrentServiceDiscovery() + { + GattEntry discoveredService = entries.get(currentServiceInDiscovery); + discoveredService.valueKnown = true; + entries.set(currentServiceInDiscovery, discoveredService); + runningHandle = -1; + currentServiceInDiscovery = -1; + leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString()); + } + + private synchronized void performServiceDiscoveryForHandle(int nextHandle, boolean searchStarted) + { + try { + if (searchStarted) { + currentServiceInDiscovery = nextHandle; + runningHandle = ++nextHandle; + } else { + runningHandle = nextHandle; + } + + GattEntry entry = null; + try { + entry = entries.get(nextHandle); + } catch (IndexOutOfBoundsException ex) { + ex.printStackTrace(); + Log.w(TAG, "Last entry of last service read"); + finishCurrentServiceDiscovery(); + return; + } + + boolean result = false; + switch (entry.type) { + case Characteristic: + result = mBluetoothGatt.readCharacteristic(entry.characteristic); + if (!result) + performServiceDiscoveryForHandle(runningHandle+1, false); + break; + case CharacteristicValue: + // ignore -> nothing to do for this artificial type + performServiceDiscoveryForHandle(runningHandle+1, false); + break; + case Descriptor: + result = mBluetoothGatt.readDescriptor(entry.descriptor); + if (!result) + performServiceDiscoveryForHandle(runningHandle+1, false); + break; + case Service: + finishCurrentServiceDiscovery(); + break; + default: + Log.w(TAG, "Invalid GATT attribute type"); + break; + } + + } catch(Exception ex) { + ex.printStackTrace(); + } + } + + 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); } diff --git a/src/bluetooth/android/jni_android.cpp b/src/bluetooth/android/jni_android.cpp index 6a293a4e..5ae18161 100644 --- a/src/bluetooth/android/jni_android.cpp +++ b/src/bluetooth/android/jni_android.cpp @@ -203,6 +203,8 @@ static JNINativeMethod methods_le[] = { (void *) LowEnergyNotificationHub::lowEnergy_connectionChange}, {"leServicesDiscovered", "(JILjava/lang/String;)V", (void *) LowEnergyNotificationHub::lowEnergy_servicesDiscovered}, + {"leServiceDetailDiscoveryFinished", "(JLjava/lang/String;)V", + (void *) LowEnergyNotificationHub::lowEnergy_serviceDetailsDiscovered}, }; static JNINativeMethod methods_server[] = { diff --git a/src/bluetooth/android/lowenergynotificationhub.cpp b/src/bluetooth/android/lowenergynotificationhub.cpp index 56c3eec7..e009e5f7 100644 --- a/src/bluetooth/android/lowenergynotificationhub.cpp +++ b/src/bluetooth/android/lowenergynotificationhub.cpp @@ -122,4 +122,19 @@ void LowEnergyNotificationHub::lowEnergy_servicesDiscovered( Q_ARG(QString, uuids)); } +void LowEnergyNotificationHub::lowEnergy_serviceDetailsDiscovered( + JNIEnv *, jobject, jlong qtObject, jobject uuid) +{ + lock.lockForRead(); + LowEnergyNotificationHub *hub = hubMap()->value(qtObject); + lock.unlock(); + if (!hub) + return; + + const QString serviceUuid = QAndroidJniObject(uuid).toString(); + QMetaObject::invokeMethod(hub, "serviceDetailsDiscoveryFinished", + Qt::QueuedConnection, + Q_ARG(QString, serviceUuid)); +} + QT_END_NAMESPACE diff --git a/src/bluetooth/android/lowenergynotificationhub_p.h b/src/bluetooth/android/lowenergynotificationhub_p.h index 0cd3b02d..06c7b3ff 100644 --- a/src/bluetooth/android/lowenergynotificationhub_p.h +++ b/src/bluetooth/android/lowenergynotificationhub_p.h @@ -57,6 +57,8 @@ public: jint errorCode, jint newState); static void lowEnergy_servicesDiscovered(JNIEnv*, jobject, jlong qtObject, jint errorCode, jobject uuidList); + static void lowEnergy_serviceDetailsDiscovered(JNIEnv *, jobject, + jlong qtObject, jobject uuid); QAndroidJniObject javaObject() { @@ -67,6 +69,7 @@ signals: void connectionUpdated(QLowEnergyController::ControllerState newState, QLowEnergyController::Error errorCode); void servicesDiscovered(QLowEnergyController::Error errorCode, const QString &uuids); + void serviceDetailsDiscoveryFinished(const QString& serviceUuid); public slots: private: diff --git a/src/bluetooth/qlowenergycontroller_android.cpp b/src/bluetooth/qlowenergycontroller_android.cpp index 134a21b7..3a7ca98d 100644 --- a/src/bluetooth/qlowenergycontroller_android.cpp +++ b/src/bluetooth/qlowenergycontroller_android.cpp @@ -68,6 +68,8 @@ void QLowEnergyControllerPrivate::connectToDevice() this, &QLowEnergyControllerPrivate::connectionUpdated); connect(hub, &LowEnergyNotificationHub::servicesDiscovered, this, &QLowEnergyControllerPrivate::servicesDiscovered); + connect(hub, &LowEnergyNotificationHub::serviceDetailsDiscoveryFinished, + this, &QLowEnergyControllerPrivate::serviceDetailsDiscoveryFinished); } if (!hub->javaObject().isValid()) { @@ -103,9 +105,39 @@ void QLowEnergyControllerPrivate::discoverServices() } } -void QLowEnergyControllerPrivate::discoverServiceDetails(const QBluetoothUuid &/*service*/) +void QLowEnergyControllerPrivate::discoverServiceDetails(const QBluetoothUuid &service) { + if (!serviceList.contains(service)) { + qCWarning(QT_BT_ANDROID) << "Discovery of unknown service" << service.toString() + << "not possible"; + return; + } + if (!hub) + return; + + //cut leading { and trailing } {xxx-xxx} + QString tempUuid = service.toString(); + tempUuid.chop(1); //remove trailing '}' + tempUuid.remove(0, 1); //remove first '{' + + QAndroidJniEnvironment env; + QAndroidJniObject uuid = QAndroidJniObject::fromString(tempUuid); + bool result = hub->javaObject().callMethod<jboolean>("discoverServiceDetails", + "(Ljava/lang/String;)Z", + uuid.object<jstring>()); + if (!result || true) { + QSharedPointer<QLowEnergyServicePrivate> servicePrivate = + serviceList.value(service); + if (!servicePrivate.isNull()) { + servicePrivate->setError(QLowEnergyService::UnknownError); + servicePrivate->setState(QLowEnergyService::DiscoveryRequired); + } + qCWarning(QT_BT_ANDROID) << "Cannot discover details for" << service.toString(); + return; + } + + qCDebug(QT_BT_ANDROID) << "Discovery of" << service << "started"; } void QLowEnergyControllerPrivate::writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> /*service*/, @@ -113,7 +145,6 @@ void QLowEnergyControllerPrivate::writeCharacteristic(const QSharedPointer<QLowE const QByteArray &/*newValue*/, bool /*writeWithResponse*/) { - } void QLowEnergyControllerPrivate::writeDescriptor( @@ -162,6 +193,17 @@ void QLowEnergyControllerPrivate::servicesDiscovered( //Android delivers all services in one go const QStringList list = foundServices.split(QStringLiteral(" "), QString::SkipEmptyParts); foreach (const QString &entry, list) { + const QBluetoothUuid service(entry); + if (service.isNull()) + return; + + QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate(); + priv->uuid = service; + priv->setController(this); + + QSharedPointer<QLowEnergyServicePrivate> pointer(priv); + serviceList.insert(service, pointer); + emit q->serviceDiscovered(QBluetoothUuid(entry)); } @@ -173,4 +215,19 @@ void QLowEnergyControllerPrivate::servicesDiscovered( } } +void QLowEnergyControllerPrivate::serviceDetailsDiscoveryFinished( + const QString &serviceUuid) +{ + const QBluetoothUuid service(serviceUuid); + if (!serviceList.contains(service)) { + qCWarning(QT_BT_ANDROID) << "Discovery done of unknown service:" + << service.toString(); + return; + } + + QSharedPointer<QLowEnergyServicePrivate> pointer = + serviceList.value(service); + pointer->setState(QLowEnergyService::ServiceDiscovered); +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index 5dea046d..7676c19e 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -179,6 +179,7 @@ private slots: QLowEnergyController::Error errorCode); void servicesDiscovered(QLowEnergyController::Error errorCode, const QString &foundServices); + void serviceDetailsDiscoveryFinished(const QString& serviceUuid); #endif private: diff --git a/src/bluetooth/qlowenergyservice.cpp b/src/bluetooth/qlowenergyservice.cpp index 0ec92d45..0c803144 100644 --- a/src/bluetooth/qlowenergyservice.cpp +++ b/src/bluetooth/qlowenergyservice.cpp @@ -169,6 +169,8 @@ QT_BEGIN_NAMESPACE \value DescriptorWriteError An attempt to write a new value to a descriptor failed. For example, it might be triggered when attempting to write to a read-only descriptor. + \value UnknownError An unknown error occurred when interacting with the service. + This value was introduced by Qt 5.5. */ /*! diff --git a/src/bluetooth/qlowenergyservice.h b/src/bluetooth/qlowenergyservice.h index 903b64fe..136f85ee 100644 --- a/src/bluetooth/qlowenergyservice.h +++ b/src/bluetooth/qlowenergyservice.h @@ -56,7 +56,8 @@ public: NoError = 0, OperationError, CharacteristicWriteError, - DescriptorWriteError + DescriptorWriteError, + UnknownError }; enum ServiceState { diff --git a/src/bluetooth/qlowenergyserviceprivate.cpp b/src/bluetooth/qlowenergyserviceprivate.cpp index 43bb3498..7c310123 100644 --- a/src/bluetooth/qlowenergyserviceprivate.cpp +++ b/src/bluetooth/qlowenergyserviceprivate.cpp @@ -65,6 +65,9 @@ void QLowEnergyServicePrivate::setError(QLowEnergyService::ServiceError newError void QLowEnergyServicePrivate::setState(QLowEnergyService::ServiceState newState) { + if (state == newState) + return; + state = newState; emit stateChanged(newState); } |