summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-11-21 17:57:36 +0100
committerTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-11-25 15:29:16 +0100
commit4c25743910622a4834d8ebb7f3acc59e025a3c41 (patch)
tree4b2a249207ff980bace12c0d9ea75ac451241f87 /src
parent216e28ef1209ee5b28f4ca4f88cfa68a46f60c5a (diff)
QLowEnergyController - service details discovery (OS X and iOS)
Implement details discovery: characteristics, their values (if any), descriptors. We have to emulate handles (QLowEnergyHandle) - while Core Bluetooth internally has the notion of handles, it never exposes them as a public API. Change-Id: I09158433ce6835dd34fe8ad47d047212dab59e8e Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
Diffstat (limited to 'src')
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager.mm449
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager_p.h6
-rw-r--r--src/bluetooth/osx/osxbtutility.mm35
-rw-r--r--src/bluetooth/osx/osxbtutility_p.h3
-rw-r--r--src/bluetooth/qlowenergycontroller_osx.mm102
-rw-r--r--src/bluetooth/qlowenergycontroller_osx_p.h8
6 files changed, 588 insertions, 15 deletions
diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm
index dd36a91f..71622b1c 100644
--- a/src/bluetooth/osx/osxbtcentralmanager.mm
+++ b/src/bluetooth/osx/osxbtcentralmanager.mm
@@ -68,9 +68,18 @@ using namespace QT_NAMESPACE;
- (QLowEnergyController::Error)connectToDevice; // "Device" is in Qt's world ...
- (void)connectToPeripheral; // "Peripheral" is in Core Bluetooth.
- (void)discoverIncludedServices;
+- (void)readCharacteristics:(CBService *)service;
+- (void)serviceDetailsDiscoveryFinished:(CBService *)service;
// Aux. functions.
-- (bool)connected;
+- (CBService *)serviceForUUID:(const QBluetoothUuid &)qtUuid;
+- (CBCharacteristic *)nextCharacteristicForService:(CBService*)service
+ startingFrom:(CBCharacteristic *)from;
+- (CBCharacteristic *)nextCharacteristicForService:(CBService*)service
+ startingFrom:(CBCharacteristic *)from
+ withProperties:(CBCharacteristicProperties)properties;
+- (CBDescriptor *)nextDescriptorForCharacteristic:(CBCharacteristic *)characteristic
+ startingFrom:(CBDescriptor *)descriptor;
@end
@@ -233,6 +242,8 @@ using namespace QT_NAMESPACE;
- (void)disconnectFromDevice
{
+ servicesToDiscoverDetails.clear();
+
if (managerState == OSXBluetooth::CentralManagerUpdating) {
disconnectPending = true;
} else {
@@ -300,6 +311,291 @@ using namespace QT_NAMESPACE;
}
}
+- (bool)discoverServiceDetails:(const QBluetoothUuid &)serviceUuid
+{
+ // This function does not change 'managerState', since it
+ // can be called concurrently (not waiting for the previous
+ // discovery to finish).
+
+ using namespace OSXBluetooth;
+
+ Q_ASSERT_X(managerState != CentralManagerUpdating, "-discoverServiceDetails:",
+ "invalid state");
+ Q_ASSERT_X(!serviceUuid.isNull(), "-discoverServiceDetails:",
+ "invalid service UUID");
+ Q_ASSERT_X(peripheral, "-discoverServiceDetailsl:",
+ "invalid peripheral (nil)");
+
+ if (servicesToDiscoverDetails.contains(serviceUuid)) {
+ qCWarning(QT_BT_OSX) << "-discoverServiceDetails: "
+ "already discovering for " << serviceUuid;
+ return true;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if (CBService *const service = [self serviceForUUID:serviceUuid]) {
+ servicesToDiscoverDetails.append(serviceUuid);
+ [peripheral discoverCharacteristics:nil forService:service];
+ return true;
+ }
+
+ qCWarning(QT_BT_OSX) << "-discoverServiceDetails:, invalid service - "
+ "unknown uuid " << serviceUuid;
+
+ return false;
+}
+
+- (void)readCharacteristics:(CBService *)service
+{
+ // This method does not change 'managerState', we can
+ // have several 'detail discoveries' active.
+ Q_ASSERT_X(service, "-readCharacteristics:", "invalid service (nil)");
+
+ using namespace OSXBluetooth;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ Q_ASSERT_X(managerState != CentralManagerUpdating, "-readCharacteristics:",
+ "invalid state");
+ Q_ASSERT_X(manager, "-readCharacteristics:", "invalid manager (nil)");
+ Q_ASSERT_X(peripheral, "-readCharacteristics:", "invalid peripheral (nil)");
+ Q_ASSERT_X(delegate, "-readCharacteristics:", "invalid delegate (null)");
+
+ if (!service.characteristics || !service.characteristics.count)
+ return [self serviceDetailsDiscoveryFinished:service];
+
+ NSArray *const cs = service.characteristics;
+ for (CBCharacteristic *c in cs) {
+ if (c.properties & CBCharacteristicPropertyRead)
+ return [peripheral readValueForCharacteristic:c];
+ }
+
+ // No readable properties? Discover descriptors then:
+ [self discoverDescriptors:service];
+}
+
+- (void)discoverDescriptors:(CBService *)service
+{
+ // This method does not change 'managerState', we can have
+ // several discoveries active.
+ Q_ASSERT_X(service, "-discoverDescriptors:", "invalid service (nil)");
+
+ using namespace OSXBluetooth;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ Q_ASSERT_X(managerState != CentralManagerUpdating, "-discoverDescriptors",
+ "invalid state");
+ Q_ASSERT_X(manager, "-discoverDescriptors:", "invalid manager (nil)");
+ Q_ASSERT_X(peripheral, "-discoverDescriptors:", "invalid peripheral (nil)");
+
+ if (!service.characteristics || !service.characteristics.count) {
+ [self serviceDetailsDiscoveryFinished:service];
+ } else {
+ // Start from 0 and continue in the callback.
+ [peripheral discoverDescriptorsForCharacteristic:[service.characteristics objectAtIndex:0]];
+ }
+}
+
+- (void)readDescriptors:(CBService *)service
+{
+ Q_ASSERT_X(service, "-readDescriptors:",
+ "invalid service (nil)");
+ Q_ASSERT_X(managerState != OSXBluetooth::CentralManagerUpdating,
+ "-readDescriptors:",
+ "invalid state");
+ Q_ASSERT_X(peripheral, "-readDescriptors:",
+ "invalid peripheral (nil)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ NSArray *const cs = service.characteristics;
+ // We can never be here if we have no characteristics.
+ Q_ASSERT_X(cs && cs.count, "-readDescriptors:",
+ "invalid service");
+ for (CBCharacteristic *c in cs) {
+ if (c.descriptors && c.descriptors.count)
+ return [peripheral readValueForDescriptor:[c.descriptors objectAtIndex:0]];
+ }
+
+ // No descriptors to read, done.
+ [self serviceDetailsDiscoveryFinished:service];
+}
+
+- (void)serviceDetailsDiscoveryFinished:(CBService *)service
+{
+ //
+ Q_ASSERT_X(service, "-serviceDetailsDiscoveryFinished:",
+ "invalid service (nil)");
+ Q_ASSERT_X(delegate, "-serviceDetailsDiscoveryFinished:",
+ "invalid delegate (null)");
+
+ using namespace OSXBluetooth;
+
+ servicesToDiscoverDetails.removeAll(qt_uuid(service.UUID));
+ delegate->serviceDetailsDiscoveryFinished(ObjCStrongReference<CBService>(service, true));
+}
+
+// Aux. methods:
+
+- (CBService *)serviceForUUID:(const QBluetoothUuid &)qtUuid
+{
+ using namespace OSXBluetooth;
+
+ Q_ASSERT_X(!qtUuid.isNull(), "-serviceForUUID:",
+ "invalid uuid");
+ Q_ASSERT_X(peripheral, "-serviceForUUID:",
+ "invalid peripherla (nil)");
+
+ ObjCStrongReference<NSMutableArray> toVisit([NSMutableArray arrayWithArray:peripheral.services], true);
+ ObjCStrongReference<NSMutableArray> toVisitNext([[NSMutableArray alloc] init], false);
+ ObjCStrongReference<NSMutableSet> visitedNodes([[NSMutableSet alloc] init], false);
+
+ while (true) {
+ for (NSUInteger i = 0, e = [toVisit count]; i < e; ++i) {
+ CBService *const s = [toVisit objectAtIndex:i];
+ if (equal_uuids(s.UUID, qtUuid))
+ return s;
+ if ([visitedNodes containsObject:s] && s.includedServices && s.includedServices.count) {
+ [visitedNodes addObject:s];
+ [toVisitNext addObjectsFromArray:s.includedServices];
+ }
+ }
+
+ if (![toVisitNext count])
+ return nil;
+
+ toVisit.resetWithoutRetain(toVisitNext.take());
+ toVisitNext.resetWithoutRetain([[NSMutableArray alloc] init]);
+ }
+
+ return nil;
+}
+
+- (CBCharacteristic *)nextCharacteristicForService:(CBService*)service
+ startingFrom:(CBCharacteristic *)characteristic
+{
+ Q_ASSERT_X(service, "-nextCharacteristicForService:startingFrom:",
+ "invalid service (nil)");
+ Q_ASSERT_X(characteristic, "-nextCharacteristicForService:startingFrom:",
+ "invalid characteristic (nil)");
+ Q_ASSERT_X(service.characteristics, "-nextCharacteristicForService:startingFrom:",
+ "invalid service");
+ Q_ASSERT_X(service.characteristics.count, "-nextCharacteristicForService:startingFrom:",
+ "invalid service");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ // TODO: test that we NEVER have the same characteristic twice in array!
+ // At the moment I just protect against this by iterating in a reverse
+ // order (at least avoiding a potential inifite loop with '-indexOfObject:').
+ NSArray *const cs = service.characteristics;
+ if (cs.count == 1)
+ return nil;
+
+ for (NSUInteger index = cs.count - 1; index != 0; --index) {
+ if ([cs objectAtIndex:index] == characteristic) {
+ if (index + 1 == cs.count)
+ return nil;
+ else
+ return [cs objectAtIndex:index + 1];
+ }
+ }
+
+ Q_ASSERT_X([cs objectAtIndex:0] == characteristic,
+ "-nextCharacteristicForService:startingFrom:",
+ "characteristic was not found in service.characteristics");
+
+ return [cs objectAtIndex:1];
+}
+
+- (CBCharacteristic *)nextCharacteristicForService:(CBService*)service
+ startingFrom:(CBCharacteristic *)characteristic
+ properties:(CBCharacteristicProperties)properties
+{
+ Q_ASSERT_X(service, "-nextCharacteristicForService:startingFrom:properties:",
+ "invalid service (nil)");
+ Q_ASSERT_X(characteristic, "-nextCharacteristicForService:startingFrom:properties:",
+ "invalid characteristic (nil)");
+ Q_ASSERT_X(service.characteristics, "-nextCharacteristicForService:startingFrom:properties:",
+ "invalid service");
+ Q_ASSERT_X(service.characteristics.count, "-nextCharacteristicForService:startingFrom:properties:",
+ "invalid service");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ // TODO: test that we NEVER have the same characteristic twice in array!
+ // At the moment I just protect against this by iterating in a reverse
+ // order (at least avoiding a potential inifite loop with '-indexOfObject:').
+ NSArray *const cs = service.characteristics;
+ if (cs.count == 1)
+ return nil;
+
+ NSUInteger index = cs.count - 1;
+ for (; index != 0; --index) {
+ if ([cs objectAtIndex:index] == characteristic) {
+ if (index + 1 == cs.count) {
+ return nil;
+ } else {
+ index += 1;
+ break;
+ }
+ }
+ }
+
+ if (!index) {
+ Q_ASSERT_X([cs objectAtIndex:0] == characteristic,
+ "-nextCharacteristicForService:startingFrom:properties:",
+ "characteristic not found in service.characteristics");
+ index = 1;
+ }
+
+ for (const NSUInteger e = cs.count; index < e; ++index) {
+ CBCharacteristic *const c = [cs objectAtIndex:index];
+ if (c.properties & properties)
+ return c;
+ }
+
+ return nil;
+}
+
+- (CBDescriptor *)nextDescriptorForCharacteristic:(CBCharacteristic *)characteristic
+ startingFrom:(CBDescriptor *)descriptor
+{
+ Q_ASSERT_X(characteristic, "-nextDescriptorForCharacteristic:startingFrom:",
+ "invalid characteristic (nil)");
+ Q_ASSERT_X(descriptor, "-nextDescriptorForCharacteristic:startingFrom:",
+ "invalid descriptor (nil)");
+ Q_ASSERT_X(characteristic.descriptors,
+ "-nextDescriptorForCharacteristic:startingFrom:",
+ "invalid characteristic");
+ Q_ASSERT_X(characteristic.descriptors.count,
+ "-nextDescriptorForCharacteristic:startingFrom:",
+ "invalid characteristic");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ NSArray *const ds = characteristic.descriptors;
+ if (ds.count == 1)
+ return nil;
+
+ for (NSUInteger index = ds.count - 1; index != 0; --index) {
+ if ([ds objectAtIndex:index] == descriptor) {
+ if (index + 1 == ds.count)
+ return nil;
+ else
+ return [ds objectAtIndex:index + 1];
+ }
+ }
+
+ Q_ASSERT_X([ds objectAtIndex:0] == descriptor,
+ "-nextDescriptorForCharacteristic:startingFrom:",
+ "descriptor was not found in characteristic.descriptors");
+
+ return [ds objectAtIndex:1];
+}
+
// CBCentralManagerDelegate (the real one).
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
@@ -555,9 +851,156 @@ using namespace QT_NAMESPACE;
- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverCharacteristicsForService:(CBService *)service
error:(NSError *)error
{
+ // This method does not change 'managerState', we can have several
+ // discoveries active.
Q_UNUSED(aPeripheral)
- Q_UNUSED(service)
- Q_UNUSED(error)
+
+ // TODO: check that this can never be called after cancelPeripheralConnection was executed.
+
+ using namespace OSXBluetooth;
+
+ Q_ASSERT_X(managerState != CentralManagerUpdating,
+ "-peripheral:didDiscoverCharacteristicsForService:",
+ "invalid state");
+ Q_ASSERT_X(delegate, "-peripheral:didDiscoverCharacteristicsForService:",
+ "invalid delegate (null)");
+
+ if (error) {
+ // NSLog to show the actual NSError (can contain something interesting).
+ NSLog(@"-peripheral:didDiscoverCharacteristicsForService:error, failed with error: %@",
+ error);
+ // We did not discover any characteristics and can not discover descriptors,
+ // inform our delegate (it will set a service state also).
+ delegate->error(qt_uuid(service.UUID), QLowEnergyController::UnknownError);
+ } else {
+ [self readCharacteristics:service];
+ }
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
+ error:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+
+ using namespace OSXBluetooth;
+
+ Q_ASSERT_X(managerState != CentralManagerUpdating,
+ "-peripheral:didUpdateValueForCharacteristic:error:",
+ "invalid state");
+ Q_ASSERT_X(peripheral, "-peripheral:didUpdateValueForCharacteristic:error:",
+ "invalid peripheral (nil)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+ // First, let's check if we're discovering a service details now.
+ CBService *const service = characteristic.service;
+ const QBluetoothUuid qtUuid(qt_uuid(service.UUID));
+ const bool isDetailsDiscovery = servicesToDiscoverDetails.contains(qtUuid);
+
+ if (error) {
+ // Use NSLog, not qCDebug/qCWarning to log the actual error.
+ NSLog(@"-peripheral:didUpdateValueForCharacteristic:error:, failed with error %@",
+ error);
+
+ if (!isDetailsDiscovery) {
+ // TODO: this can be something else in a future (if needed at all).
+ return;
+ }
+ }
+
+ if (isDetailsDiscovery) {
+ // Test if we have any other characteristic to read yet.
+ CBCharacteristic *const next = [self nextCharacteristicForService:characteristic.service
+ startingFrom:characteristic properties:CBCharacteristicPropertyRead];
+ if (next)
+ [peripheral readValueForCharacteristic:next];
+ else
+ [self discoverDescriptors:characteristic.service];
+ } else {
+ // TODO: this can be something else in a future (if needed at all).
+ }
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral
+ didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic
+ error:(NSError *)error
+{
+ // This method does not change 'managerState', we can
+ // have several discoveries active at the same time.
+ Q_UNUSED(aPeripheral)
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ using namespace OSXBluetooth;
+
+ if (error) {
+ // Log the error using NSLog:
+ NSLog(@"-peripheral:didDiscoverDescriptorsForCharacteristic:error:, failed with error %@",
+ error);
+ // Probably, we can continue though ...
+ }
+
+ // Do we have more characteristics on this service to discover descriptors?
+ CBCharacteristic *const next = [self nextCharacteristicForService:characteristic.service
+ startingFrom:characteristic];
+ if (next)
+ [peripheral discoverDescriptorsForCharacteristic:next];
+ else
+ [self readDescriptors:characteristic.service];
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral
+ didUpdateValueForDescriptor:(CBDescriptor *)descriptor
+ error:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+
+ Q_ASSERT_X(peripheral, "-peripheral:didUpdateValueForDescriptor:error:",
+ "invalid peripheral (nil)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ using namespace OSXBluetooth;
+
+ CBService *const service = descriptor.characteristic.service;
+ const QBluetoothUuid qtUuid(qt_uuid(service.UUID));
+ const bool isDetailsDiscovery = servicesToDiscoverDetails.contains(qtUuid);
+
+ if (error) {
+ // NSLog to log the actual error ...
+ NSLog(@"-peripheral:didUpdateValueForDescriptor:error:, failed with error %@",
+ error);
+ if (!isDetailsDiscovery) {
+ // TODO: probably will be required in a future.
+ return;
+ }
+ }
+
+ if (isDetailsDiscovery) {
+ // Test if we have any other characteristic to read yet.
+ CBDescriptor *const next = [self nextDescriptorForCharacteristic:descriptor.characteristic
+ startingFrom:descriptor];
+ if (next) {
+ [peripheral readValueForDescriptor:next];
+ } else {
+ // We either have to read a value for a next descriptor
+ // on a given characteristic, or continue with the
+ // next characteristic in a given service (if any).
+ CBCharacteristic *const ch = descriptor.characteristic;
+ CBCharacteristic *nextCh = [self nextCharacteristicForService:ch.service
+ startingFrom:ch];
+ while (nextCh) {
+ if (nextCh.descriptors && nextCh.descriptors.count)
+ return [peripheral readValueForDescriptor:[nextCh.descriptors objectAtIndex:0]];
+
+ nextCh = [self nextCharacteristicForService:ch.service
+ startingFrom:nextCh];
+ }
+
+ [self serviceDetailsDiscoveryFinished:service];
+ }
+ } else {
+ // TODO: this can be something else in a future (if needed at all).
+ }
}
@end
diff --git a/src/bluetooth/osx/osxbtcentralmanager_p.h b/src/bluetooth/osx/osxbtcentralmanager_p.h
index 7ad51fb6..25faab78 100644
--- a/src/bluetooth/osx/osxbtcentralmanager_p.h
+++ b/src/bluetooth/osx/osxbtcentralmanager_p.h
@@ -65,13 +65,14 @@ class CentralManagerDelegate
public:
typedef QT_MANGLE_NAMESPACE(OSXBTCentralManager) ObjCCentralManager;
typedef ObjCStrongReference<NSArray> LEServices;
- typedef LEServices LECharacteristics;
+ typedef ObjCStrongReference<CBService> LEService;
virtual ~CentralManagerDelegate();
virtual void LEnotSupported() = 0;
virtual void connectSuccess() = 0;
virtual void serviceDiscoveryFinished(LEServices services) = 0;
+ virtual void serviceDetailsDiscoveryFinished(LEService service) = 0;
virtual void disconnected() = 0;
// General errors.
@@ -117,6 +118,8 @@ QT_END_NAMESPACE
QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCStrongReference<NSMutableArray> servicesToVisitNext;
// We'd like to avoid loops in a services' topology:
QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCStrongReference<NSMutableSet> visitedServices;
+
+ QT_PREPEND_NAMESPACE(QList)<QT_PREPEND_NAMESPACE(QBluetoothUuid)> servicesToDiscoverDetails;
}
- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth)::CentralManagerDelegate *)aDelegate;
@@ -128,6 +131,7 @@ QT_END_NAMESPACE
- (void)disconnectFromDevice;
- (void)discoverServices;
+- (bool)discoverServiceDetails:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
@end
diff --git a/src/bluetooth/osx/osxbtutility.mm b/src/bluetooth/osx/osxbtutility.mm
index 69e94434..1b07e2cb 100644
--- a/src/bluetooth/osx/osxbtutility.mm
+++ b/src/bluetooth/osx/osxbtutility.mm
@@ -43,6 +43,7 @@
#include "osxbtutility_p.h"
#include "qbluetoothuuid.h"
+#include <QtCore/qendian.h>
#include <QtCore/qstring.h>
#ifndef QT_IOS_BLUETOOTH
@@ -176,12 +177,10 @@ QBluetoothUuid qt_uuid(CBUUID *uuid)
QT_BT_MAC_AUTORELEASEPOOL;
if (uuid.data.length == 2) {
- // TODO: this is .. UGLY :)
- quint16 qtUuidData = 0;
- const quint8 *const source = static_cast<const quint8 *>(uuid.data.bytes);
- std::copy(source, source + 2, &qtUuidData);
-
- return QBluetoothUuid(qtUuidData);
+ // CBUUID's docs say nothing about byte-order.
+ // Seems to be in big-endian.
+ const uchar *const src = static_cast<const uchar *>(uuid.data.bytes);
+ return QBluetoothUuid(qFromBigEndian<quint16>(src));
} else if (uuid.data.length == 16) {
quint128 qtUuidData = {};
const quint8 *const source = static_cast<const quint8 *>(uuid.data.bytes);
@@ -223,6 +222,30 @@ ObjCStrongReference<CBUUID> cb_uuid(const QBluetoothUuid &qtUuid)
return cbUuid;
}
+bool equal_uuids(const QBluetoothUuid &qtUuid, CBUUID *cbUuid)
+{
+ const QBluetoothUuid qtUuid2(qt_uuid(cbUuid));
+ return qtUuid == qtUuid2;
+}
+
+bool equal_uuids(CBUUID *cbUuid, const QBluetoothUuid &qtUuid)
+{
+ return equal_uuids(qtUuid, cbUuid);
+}
+
+QByteArray qt_bytearray(NSData *data)
+{
+ QByteArray value;
+ if (!data || !data.length)
+ return value;
+
+ value.resize(data.length);
+ const char *const src = static_cast<const char *>(data.bytes);
+ std::copy(src, src + data.length, value.data());
+
+ return value;
+}
+
}
QT_END_NAMESPACE
diff --git a/src/bluetooth/osx/osxbtutility_p.h b/src/bluetooth/osx/osxbtutility_p.h
index fb7b9363..6e226a25 100644
--- a/src/bluetooth/osx/osxbtutility_p.h
+++ b/src/bluetooth/osx/osxbtutility_p.h
@@ -285,6 +285,9 @@ QString qt_error_string(IOReturn errorCode);
QBluetoothUuid qt_uuid(CBUUID *uuid);
CFStrongReference<CFUUIDRef> cf_uuid(const QBluetoothUuid &qtUuid);
ObjCStrongReference<CBUUID> cb_uuid(const QBluetoothUuid &qtUuid);
+bool equal_uuids(const QBluetoothUuid &qtUuid, CBUUID *cbUuid);
+bool equal_uuids(CBUUID *cbUuid, const QBluetoothUuid &qtUuid);
+QByteArray qt_bytearray(NSData *data);
} // namespace OSXBluetooth
diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm
index b576e76f..9a86c392 100644
--- a/src/bluetooth/qlowenergycontroller_osx.mm
+++ b/src/bluetooth/qlowenergycontroller_osx.mm
@@ -91,6 +91,7 @@ ServicePrivate qt_createLEService(QLowEnergyControllerPrivateOSX *controller, CB
if (included)
newService->type |= QLowEnergyService::IncludedService;
+ // TODO: isPrimary is ... always 'NO' - to be investigated.
/*
#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0)
if (!cbService.isPrimary) {
@@ -128,7 +129,8 @@ QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyControl
isConnecting(false),
lastError(QLowEnergyController::NoError),
controllerState(QLowEnergyController::UnconnectedState),
- addressType(QLowEnergyController::PublicAddress)
+ addressType(QLowEnergyController::PublicAddress),
+ lastValidHandle(0) // 0 == invalid.
{
// This is the "wrong" constructor - no valid device UUID to connect later.
Q_ASSERT_X(q, "QLowEnergyControllerPrivate", "invalid q_ptr (null)");
@@ -149,7 +151,8 @@ QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyControl
isConnecting(false),
lastError(QLowEnergyController::NoError),
controllerState(QLowEnergyController::UnconnectedState),
- addressType(QLowEnergyController::PublicAddress)
+ addressType(QLowEnergyController::PublicAddress),
+ lastValidHandle(0) // 0 == invalid.
{
Q_ASSERT_X(q, "QLowEnergyControllerPrivateOSX", "invalid q_ptr (null)");
centralManager.reset([[ObjCCentralManager alloc] initWithDelegate:this]);
@@ -284,6 +287,64 @@ void QLowEnergyControllerPrivateOSX::serviceDiscoveryFinished(LEServices service
QMetaObject::invokeMethod(q_ptr, "discoveryFinished", Qt::QueuedConnection);
}
+void QLowEnergyControllerPrivateOSX::serviceDetailsDiscoveryFinished(LEService service)
+{
+ if (!service) {
+ qCDebug(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::serviceDetailsDiscoveryFinished(), "
+ "invalid service (nil)";
+ return;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ const QBluetoothUuid qtUuid(OSXBluetooth::qt_uuid(service.data().UUID));
+ if (!discoveredServices.contains(qtUuid)) {
+ qCDebug(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::serviceDetailsDiscoveryFinished(), "
+ "unknown service uuid: " << qtUuid;
+ return;
+ }
+
+ ServicePrivate qtService(discoveredServices.value(qtUuid));
+ qtService->startHandle = ++lastValidHandle;
+ // Now we iterate on characteristics and descriptors (if any).
+ NSArray *const cs = service.data().characteristics;
+ if (cs && cs.count) {
+ QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList;
+ // That's not a real handle - just a key into a hash map.
+ for (CBCharacteristic *c in cs) {
+ QLowEnergyServicePrivate::CharData newChar = {};
+ newChar.uuid = OSXBluetooth::qt_uuid(c.UUID);
+ // CBCharacteristicProperty enum has the same values as Qt +
+ // a couple of values above 'extended' we do not support yet - mask them out.
+ // All other possible enumerators are the same:
+ const int cbProps = c.properties & 0xff;
+ newChar.properties = static_cast<QLowEnergyCharacteristic::PropertyTypes>(cbProps);
+ newChar.value = OSXBluetooth::qt_bytearray(c.value);
+ newChar.valueHandle = ++lastValidHandle;
+
+ NSArray *const ds = c.descriptors;
+ if (ds && ds.count) {
+ QHash<QLowEnergyHandle, QLowEnergyServicePrivate::DescData> descList;
+ for (CBDescriptor *d in ds) {
+ QLowEnergyServicePrivate::DescData newDesc = {};
+ newDesc.uuid = OSXBluetooth::qt_uuid(d.UUID);
+ newDesc.value = OSXBluetooth::qt_bytearray(d.value);
+ descList[++lastValidHandle] = newDesc;
+ }
+
+ newChar.descriptorList = descList;
+ }
+
+ charList[newChar.valueHandle] = newChar;
+ }
+
+ qtService->characteristicList = charList;
+ }
+
+ qtService->endHandle = lastValidHandle;
+ qtService->stateChanged(QLowEnergyService::ServiceDiscovered);
+}
+
void QLowEnergyControllerPrivateOSX::disconnected()
{
controllerState = QLowEnergyController::UnconnectedState;
@@ -324,8 +385,16 @@ void QLowEnergyControllerPrivateOSX::error(const QBluetoothUuid &serviceUuid,
QLowEnergyController::Error errorCode)
{
// Errors reported while discovering service details etc.
- Q_UNUSED(serviceUuid)
- Q_UNUSED(errorCode)
+ Q_UNUSED(errorCode) // TODO: setError?
+
+ // We failed to discover any characteristics/descriptors.
+ if (discoveredServices.contains(serviceUuid)) {
+ ServicePrivate qtService(discoveredServices.value(serviceUuid));
+ qtService->stateChanged(QLowEnergyService::InvalidService);
+ } else {
+ qCDebug(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::error(), "
+ "error reported for unknown service "<<serviceUuid;
+ }
}
void QLowEnergyControllerPrivateOSX::connectToDevice()
@@ -377,7 +446,29 @@ void QLowEnergyControllerPrivateOSX::discoverServices()
void QLowEnergyControllerPrivateOSX::discoverServiceDetails(const QBluetoothUuid &serviceUuid)
{
- Q_UNUSED(serviceUuid);
+ Q_ASSERT_X(isValid(), "discoverServiceDetails", "invalid private controller");
+
+ if (controllerState != QLowEnergyController::DiscoveredState) {
+ qCWarning(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::discoverServiceDetails(), "
+ "can not discover service details in the current state, "
+ "QLowEnergyController::DiscoveredState is expected";
+ return;
+ }
+
+ if (!discoveredServices.contains(serviceUuid)) {
+ qCWarning(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::discoverServiceDetails(), "
+ "unknown service: " << serviceUuid;
+ return;
+ }
+
+ ServicePrivate qtService(discoveredServices.value(serviceUuid));
+ if ([centralManager discoverServiceDetails:serviceUuid]) {
+ qtService->stateChanged(QLowEnergyService::DiscoveringServices);
+ } else {
+ // The error is returned by CentralManager - no
+ // service with a given UUID found on a peripheral.
+ qtService->stateChanged(QLowEnergyService::InvalidService);
+ }
}
void QLowEnergyControllerPrivateOSX::setErrorDescription(QLowEnergyController::Error errorCode)
@@ -413,6 +504,7 @@ void QLowEnergyControllerPrivateOSX::invalidateServices()
service->setState(QLowEnergyService::InvalidService);
}
+ lastValidHandle = 0;
discoveredServices.clear();
}
diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h
index 36257e02..f9764c03 100644
--- a/src/bluetooth/qlowenergycontroller_osx_p.h
+++ b/src/bluetooth/qlowenergycontroller_osx_p.h
@@ -66,6 +66,7 @@ private:
void connectSuccess() Q_DECL_OVERRIDE;
void serviceDiscoveryFinished(LEServices services) Q_DECL_OVERRIDE;
+ void serviceDetailsDiscoveryFinished(LEService service) Q_DECL_OVERRIDE;
void disconnected() Q_DECL_OVERRIDE;
void error(QLowEnergyController::Error errorCode) Q_DECL_OVERRIDE;
void error(const QBluetoothUuid &serviceUuid,
@@ -103,6 +104,13 @@ private:
typedef ServiceMap::const_iterator ConstServiceIterator;
typedef ServiceMap::iterator ServiceIterator;
ServiceMap discoveredServices;
+
+ // While Core Bluetooth has _startHandle/_endHandle for
+ // CBServices, this information is not a part of a public
+ // API and can not be used. Instead we have to 'emulate'
+ // these handles using something that looks like/works like
+ // handles:
+ QLowEnergyHandle lastValidHandle;
};
QT_END_NAMESPACE