summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-11-28 13:42:18 +0100
committerTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-12-01 11:08:23 +0100
commit68332a47fbcc124efedf73b106b7e06bfacd65fd (patch)
treeaa245330d234ae5539a0479388116e6c773eeef6
parent0e3046f29b0e86affeb33c8f6687618cfd1dfae2 (diff)
Bluetooth LE - handles/chars/descriptors/services (OS X and iOS)
In Qt we work with handles (real for Bluez, emulated for Android/iOS/OS X). Core Bluetooth has its own (quite primitive and inconvenient) data structures (arrays of objects, containing nested arrays). To make things not so ugly I'm adding several maps to be able to find a characteristic/descriptor/service using a handle from Qt's layer. Also modify writeCharacteristic to use this new 'addressing scheme'. Change-Id: Ic822c9aa82a4df1e9c4cf4c673451cac8006b9ba Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager.mm176
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager_p.h25
-rw-r--r--src/bluetooth/qlowenergycontroller_osx.mm67
-rw-r--r--src/bluetooth/qlowenergycontroller_osx_p.h5
4 files changed, 165 insertions, 108 deletions
diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm
index 025313d0..da4e6c34 100644
--- a/src/bluetooth/osx/osxbtcentralmanager.mm
+++ b/src/bluetooth/osx/osxbtcentralmanager.mm
@@ -39,12 +39,16 @@
**
****************************************************************************/
+#include "qlowenergyserviceprivate_p.h"
+#include "qlowenergycharacteristic.h"
#include "osxbtcentralmanager_p.h"
+
#include <QtCore/qloggingcategory.h>
#include <QtCore/qdebug.h>
#include <algorithm>
+#include <limits>
QT_BEGIN_NAMESPACE
@@ -54,6 +58,31 @@ CentralManagerDelegate::~CentralManagerDelegate()
{
}
+NSUInteger qt_countGATTEntries(CBService *service)
+{
+ // Identify, how many characteristics/descriptors we have on a given service,
+ // +1 for the service itself.
+ // No checks if NSUInteger is big enough :)
+ // Let's assume such number of entries is not possible :)
+
+ Q_ASSERT_X(service, "qt_countGATTEntries", "invalid service (nil)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ NSArray *const cs = service.characteristics;
+ if (!cs || !cs.count)
+ return 1;
+
+ NSUInteger n = 1 + cs.count;
+ for (CBCharacteristic *c in cs) {
+ NSArray *const ds = c.descriptors;
+ if (ds)
+ n += ds.count;
+ }
+
+ return n;
+}
+
}
QT_END_NAMESPACE
@@ -78,8 +107,6 @@ using namespace QT_NAMESPACE;
- (CBCharacteristic *)nextCharacteristicForService:(CBService*)service
startingFrom:(CBCharacteristic *)from
withProperties:(CBCharacteristicProperties)properties;
-- (CBCharacteristic *)characteristicForService:(CBService *)service
- withIndex:(NSUInteger)index;
- (CBDescriptor *)nextDescriptorForCharacteristic:(CBCharacteristic *)characteristic
startingFrom:(CBDescriptor *)descriptor;
@@ -98,6 +125,7 @@ using namespace QT_NAMESPACE;
peripheral = nil;
delegate = aDelegate;
currentService = 0;
+ lastValidHandle = 0;
}
return self;
@@ -277,6 +305,12 @@ using namespace QT_NAMESPACE;
//parameter to nil is considerably slower and is not recommended."
//
// ... but we'd like to have them all:
+
+ lastValidHandle = 0;
+ serviceMap.clear();
+ charMap.clear();
+ descMap.clear();
+
[peripheral setDelegate:self];
managerState = OSXBluetooth::CentralManagerDiscovering;
[peripheral discoverServices:nil];
@@ -435,53 +469,107 @@ using namespace QT_NAMESPACE;
using namespace OSXBluetooth;
- servicesToDiscoverDetails.removeAll(qt_uuid(service.UUID));
- delegate->serviceDetailsDiscoveryFinished(ObjCStrongReference<CBService>(service, true));
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ const QBluetoothUuid serviceUuid(qt_uuid(service.UUID));
+ servicesToDiscoverDetails.removeAll(serviceUuid);
+
+ const NSUInteger nHandles = qt_countGATTEntries(service);
+ Q_ASSERT_X(nHandles, "-serviceDetailsDiscoveryFinished:",
+ "unexpected number of GATT entires");
+
+ const QLowEnergyHandle maxHandle = std::numeric_limits<QLowEnergyHandle>::max();
+ if (nHandles >= maxHandle || lastValidHandle > maxHandle - nHandles) {
+ // Well, that's unlikely :) But we must be sure.
+ qCWarning(QT_BT_OSX) << "-serviceDetailsDiscoveryFinished:",
+ "can not allocate more handles";
+ // TODO: add more 'error' functions not to use fake handles (0)?
+ delegate->error(serviceUuid, 0, QLowEnergyService::OperationError);
+ return;
+ }
+
+ // A temporary service object to pass the details.
+ // Set only uuid, characteristics and descriptors (and probably values),
+ // nothing else is needed.
+ QSharedPointer<QLowEnergyServicePrivate> qtService(new QLowEnergyServicePrivate);
+ qtService->uuid = serviceUuid;
+ // We 'register' handles/'CBentities' even if qlowenergycontroller (delegate)
+ // later fails to do this with some error. Otherwise, if we try to implement
+ // rollback/transaction logic interface is getting too ugly/complicated.
+ ++lastValidHandle;
+ serviceMap[lastValidHandle] = service;
+ qtService->startHandle = lastValidHandle;
+
+ NSArray *const cs = service.characteristics;
+ // Now map chars/descriptors and handles.
+ if (cs && cs.count) {
+ QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList;
+
+ for (CBCharacteristic *c in cs) {
+ ++lastValidHandle;
+ // Register this characteristic:
+ charMap[lastValidHandle] = c;
+ // Create a Qt's internal characteristic:
+ QLowEnergyServicePrivate::CharData newChar = {};
+ newChar.uuid = qt_uuid(c.UUID);
+ const int cbProps = c.properties & 0xff;
+ newChar.properties = static_cast<QLowEnergyCharacteristic::PropertyTypes>(cbProps);
+ newChar.value = 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) {
+ // Register this descriptor:
+ ++lastValidHandle;
+ descMap[lastValidHandle] = d;
+ // Create a Qt's internal descriptor:
+ QLowEnergyServicePrivate::DescData newDesc = {};
+ newDesc.uuid = qt_uuid(d.UUID);
+ newDesc.value = qt_bytearray(d.value);
+ descList[lastValidHandle] = newDesc;
+ }
+
+ newChar.descriptorList = descList;
+ }
+
+ charList[newChar.valueHandle] = newChar;
+ }
+
+ qtService->characteristicList = charList;
+ }
+
+ qtService->endHandle = lastValidHandle;
+
+ ObjCStrongReference<CBService> leService(service, true);
+ delegate->serviceDetailsDiscoveryFinished(qtService);
}
- (bool)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value
- characteristic:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
- serviceUuid:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid
- serviceHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))serviceHandle
+ charHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
withResponse:(bool)withResponse
{
using namespace OSXBluetooth;
- Q_ASSERT_X(!serviceUuid.isNull(), "-write:characteristic:serviceUuid:serviceHandle:withResponse:",
- "invalid service uuid");
- Q_ASSERT_X(serviceHandle, "-write:characteristic:serviceUuid:serviceHandle:withResponse:",
- "invalid service handle (0)");
- Q_ASSERT_X(serviceHandle < charHandle, "-write:characteristic:serviceUuid:serviceHandle:withResponse:",
- "invalid characteristic handle (<= serviceHandle)");
-
+ Q_ASSERT_X(charHandle, "-write:charHandle:withResponse:", "invalid characteristic handle (0)");
QT_BT_MAC_AUTORELEASEPOOL;
- CBService *const service = [self serviceForUUID:serviceUuid];
- if (!service) {
- qCWarning(QT_BT_OSX) << "-write:characteristic:serviceUuid:serviceHandle:withResponse:, "
- "service with uuid: " << serviceUuid << " not found";
+ if (!charMap.contains(charHandle)) {
+ qCWarning(QT_BT_OSX) << "-write:charHandle:withResponse:, "
+ "characteristic: " << charHandle << " not found";
return false;
}
- // 'Convert' charHandle into the 'index' and find a characteristic.
- CBCharacteristic *const ch = [self characteristicForService:service
- withIndex:charHandle - serviceHandle - 1];
-
- if (!ch) {
- qCWarning(QT_BT_OSX) << "-write:characteristic:serviceUuid:serviceHandle:withResponse:, "
- "characteristic with handle: " << charHandle << " not "
- "found on service: " << serviceUuid;
- return false;
- }
-
- Q_ASSERT_X(peripheral, "-write:characteristic:serviceUuid:serviceHandle:withResponse:",
- "invalid peripheral (nil)");
+ CBCharacteristic *const ch = charMap.value(charHandle);
+ Q_ASSERT_X(ch, "-write:charHandle:withResponse:", "invalid characteristic (nil) for a give handle");
+ Q_ASSERT_X(peripheral, "-write:charHandle:withResponse:", "invalid peripheral (nil)");
ObjCStrongReference<NSData> data(data_from_bytearray(value));
if (!data) {
// Even if qtData.size() == 0, we still need NSData object.
- qCWarning(QT_BT_OSX) << "-write:characteristic:serviceUuid:serviceHandle:withResponse:, "
+ qCWarning(QT_BT_OSX) << "-write:charHandle:withResponse:, "
"failed to allocate NSData object";
return false;
}
@@ -616,30 +704,6 @@ using namespace QT_NAMESPACE;
return nil;
}
-- (CBCharacteristic *)characteristicForService:(CBService *)service
- withIndex:(NSUInteger)index
-{
- Q_ASSERT_X(service, "-characteristicForService:withIndex:",
- "invalid service (nil)");
-
- QT_BT_MAC_AUTORELEASEPOOL;
-
- NSArray *const chars = service.characteristics;
-
- if (!chars || !chars.count)
- return nil;
-
- for (NSUInteger i = 0, j = 0, e = chars.count; i < e; ++i) {
- CBCharacteristic *const ch = [chars objectAtIndex:i];
- if (j == index)
- return ch;
- if (ch.descriptors)
- j += ch.descriptors.count + 1; // + 1 for characteristic itself.
- }
-
- return nil;
-}
-
- (CBDescriptor *)nextDescriptorForCharacteristic:(CBCharacteristic *)characteristic
startingFrom:(CBDescriptor *)descriptor
{
diff --git a/src/bluetooth/osx/osxbtcentralmanager_p.h b/src/bluetooth/osx/osxbtcentralmanager_p.h
index 1e3028d6..f5692c38 100644
--- a/src/bluetooth/osx/osxbtcentralmanager_p.h
+++ b/src/bluetooth/osx/osxbtcentralmanager_p.h
@@ -48,6 +48,7 @@
#include "osxbtutility_p.h"
#include <QtCore/qglobal.h>
+#include <QtCore/qhash.h>
// Foundation.h must be included before corebluetoothwrapper_p.h -
// a workaround for a broken 10.9 SDK.
@@ -59,6 +60,9 @@
QT_BEGIN_NAMESPACE
+class QLowEnergyServicePrivate;
+class QByteArray;
+
namespace OSXBluetooth {
class CentralManagerDelegate
@@ -66,7 +70,7 @@ class CentralManagerDelegate
public:
typedef QT_MANGLE_NAMESPACE(OSXBTCentralManager) ObjCCentralManager;
typedef ObjCStrongReference<NSArray> LEServices;
- typedef ObjCStrongReference<CBService> LEService;
+ typedef QSharedPointer<QLowEnergyServicePrivate> LEService;
typedef ObjCStrongReference<CBCharacteristic> LECharacteristic;
virtual ~CentralManagerDelegate();
@@ -101,6 +105,15 @@ enum CentralManagerState
CentralManagerDisconnecting
};
+// In Qt we work with handles and UUIDs. Core Bluetooth
+// has NSArrays (and nested NSArrays inside servces/characteristics).
+// To simplify a navigation, I need a simple way to map from a handle
+// to a Core Bluetooth object. These are weak pointers,
+// will probably require '__weak' with ARC.
+typedef QHash<QLowEnergyHandle, CBService *> ServiceHash;
+typedef QHash<QLowEnergyHandle, CBCharacteristic *> CharHash;
+typedef QHash<QLowEnergyHandle, CBDescriptor *> DescHash;
+
}
QT_END_NAMESPACE
@@ -127,6 +140,12 @@ QT_END_NAMESPACE
QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCStrongReference<NSMutableSet> visitedServices;
QT_PREPEND_NAMESPACE(QList)<QT_PREPEND_NAMESPACE(QBluetoothUuid)> servicesToDiscoverDetails;
+
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::ServiceHash serviceMap;
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::CharHash charMap;
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::DescHash descMap;
+
+ QLowEnergyHandle lastValidHandle;
}
- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth)::CentralManagerDelegate *)aDelegate;
@@ -143,9 +162,7 @@ QT_END_NAMESPACE
// Characteristic's handle here is a 'relative' == valueHandle - service->startHandle
// to simplify mapping between Qt's handles and Core Bluetooth's data structures.
- (bool)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value
- characteristic:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
- serviceUuid:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid
- serviceHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))serviceHandle
+ charHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
withResponse:(bool)writeWithResponse;
@end
diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm
index f41423d8..88ec6421 100644
--- a/src/bluetooth/qlowenergycontroller_osx.mm
+++ b/src/bluetooth/qlowenergycontroller_osx.mm
@@ -43,6 +43,7 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qsharedpointer.h>
+#include <QtCore/qbytearray.h>
#include <QtCore/qglobal.h>
#include <QtCore/qstring.h>
#include <QtCore/qlist.h>
@@ -319,59 +320,23 @@ void QLowEnergyControllerPrivateOSX::serviceDiscoveryFinished(LEServices service
void QLowEnergyControllerPrivateOSX::serviceDetailsDiscoveryFinished(LEService service)
{
- if (!service) {
- qCDebug(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::serviceDetailsDiscoveryFinished(), "
- "invalid service (nil)";
- return;
- }
+ Q_ASSERT_X(!service.isNull(), "serviceDetailsDiscoveryFinished",
+ "invalid service (null)");
QT_BT_MAC_AUTORELEASEPOOL;
- const QBluetoothUuid qtUuid(OSXBluetooth::qt_uuid(service.data().UUID));
- if (!discoveredServices.contains(qtUuid)) {
+ if (!discoveredServices.contains(service->uuid)) {
qCDebug(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::serviceDetailsDiscoveryFinished(), "
- "unknown service uuid: " << qtUuid;
+ "unknown service uuid: " << service->uuid;
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;
- }
+ ServicePrivate qtService(discoveredServices.value(service->uuid));
+ // Assert on handles?
+ qtService->startHandle = service->startHandle;
+ qtService->endHandle = service->endHandle;
+ qtService->characteristicList = service->characteristicList;
- qtService->characteristicList = charList;
- }
-
- qtService->endHandle = lastValidHandle;
qtService->stateChanged(QLowEnergyService::ServiceDiscovered);
}
@@ -557,6 +522,14 @@ void QLowEnergyControllerPrivateOSX::discoverServiceDetails(const QBluetoothUuid
}
}
+void QLowEnergyControllerPrivateOSX::setNotifyValue(QSharedPointer<QLowEnergyServicePrivate> service,
+ QLowEnergyHandle charHandle, const QByteArray &newValue)
+{
+ Q_UNUSED(service)
+ Q_UNUSED(charHandle)
+ Q_UNUSED(newValue)
+}
+
void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service,
QLowEnergyHandle charHandle, const QByteArray &newValue,
bool writeWithResponse)
@@ -585,9 +558,7 @@ void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEner
}
const bool result = [centralManager write:newValue
- characteristic:charHandle
- serviceUuid:service->uuid
- serviceHandle:service->startHandle
+ charHandle:charHandle
withResponse:writeWithResponse];
if (!result)
service->setError(QLowEnergyService::CharacteristicWriteError);
diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h
index a8f8aaa0..2aec6200 100644
--- a/src/bluetooth/qlowenergycontroller_osx_p.h
+++ b/src/bluetooth/qlowenergycontroller_osx_p.h
@@ -49,6 +49,8 @@
QT_BEGIN_NAMESPACE
+class QByteArray;
+
// The suffix OSX is not the very right, it's also iOS.
class QLowEnergyControllerPrivateOSX : public QLowEnergyControllerPrivate,
public OSXBluetooth::CentralManagerDelegate
@@ -83,6 +85,9 @@ private:
void discoverServices();
void discoverServiceDetails(const QBluetoothUuid &serviceUuid);
+ void setNotifyValue(QSharedPointer<QLowEnergyServicePrivate> service,
+ QLowEnergyHandle charHandle, const QByteArray &newValue);
+
void writeCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service,
QLowEnergyHandle charHandle, const QByteArray &newValue,
bool writeWithResponse);