diff options
author | Timur Pocheptsov <Timur.Pocheptsov@digia.com> | 2014-11-28 10:51:45 +0100 |
---|---|---|
committer | Timur Pocheptsov <Timur.Pocheptsov@digia.com> | 2014-12-03 10:18:39 +0100 |
commit | 493013ab2b8b89a6f1b7a0b46bbf4cf5465c4502 (patch) | |
tree | 8168c733021137ee31e5203f90b271275b65b7ce /src/bluetooth/osx | |
parent | 828603893f24db24975a327ad8141cb66826b0b0 (diff) |
QLowEnergyService - ClientCharacteristicConfiguration
Core Bluetooth has a special method to enable/disable char update notifications and
even more - Core Bluetooth does not allow to use writeValue:forDescriptor:
with ClientCharacteristicConfiguration - that's why need this 'workaround'.
Change-Id: I4a01690a76aabf62397321ca6ba22c4abb1c420c
Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
Diffstat (limited to 'src/bluetooth/osx')
-rw-r--r-- | src/bluetooth/osx/osxbtcentralmanager.mm | 186 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtcentralmanager_p.h | 19 |
2 files changed, 186 insertions, 19 deletions
diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm index 2b4a2dd4..73d9d5bc 100644 --- a/src/bluetooth/osx/osxbtcentralmanager.mm +++ b/src/bluetooth/osx/osxbtcentralmanager.mm @@ -110,7 +110,8 @@ using namespace QT_NAMESPACE; withProperties:(CBCharacteristicProperties)properties; - (CBDescriptor *)nextDescriptorForCharacteristic:(CBCharacteristic *)characteristic startingFrom:(CBDescriptor *)descriptor; - +- (CBDescriptor *)descriptor:(const QBluetoothUuid &)dUuid + forCharacteristic:(CBCharacteristic *)ch; // TODO: check _what_ exactly I have to reset ... - (void)reset; @@ -527,7 +528,7 @@ using namespace QT_NAMESPACE; // Create a Qt's internal descriptor: QLowEnergyServicePrivate::DescData newDesc = {}; newDesc.uuid = qt_uuid(d.UUID); - newDesc.value = qt_bytearray(d.value); + newDesc.value = qt_bytearray(static_cast<NSObject *>(d.value)); descList[lastValidHandle] = newDesc; } @@ -580,27 +581,72 @@ using namespace QT_NAMESPACE; CBCharacteristic *const ch = charMap[request.handle]; Q_ASSERT_X(ch, "-performNextWriteRequest", "invalid characteristic (nil)"); - ObjCStrongReference<NSData> data(data_from_bytearray(request.value)); - if (!data) { - // Even if qtData.size() == 0, we still need NSData object. - qCWarning(QT_BT_OSX) << "-performNextWriteRequest, " - "failed to allocate NSData object"; - [self performNextWriteRequest]; - } - - // TODO: check what happens if I'm using NSData with length 0. - if (request.withResponse) { + if (request.isClientConfiguration) { + if (valuesToWrite.remove(request.handle)) { + // It can happen if something went wrong - we + // tried to set notification value and never received + // a callback. + qCDebug(QT_BT_OSX) << "-performNextWriteRequest:, " + "valuesToWrite already contains " + "a value for a given client configuration " + "descriptor, replacing it"; + } + // We save the original value to report it later ... + valuesToWrite[request.handle] = request.value; + const bool enable = request.value[0] & 3; writePending = true; - [peripheral writeValue:data.data() forCharacteristic:ch - type:CBCharacteristicWriteWithResponse]; + [peripheral setNotifyValue:enable forCharacteristic:ch]; } else { - [peripheral writeValue:data.data() forCharacteristic:ch - type:CBCharacteristicWriteWithoutResponse]; - [self performNextWriteRequest]; + ObjCStrongReference<NSData> data(data_from_bytearray(request.value)); + if (!data) { + // Even if qtData.size() == 0, we still need NSData object. + qCWarning(QT_BT_OSX) << "-performNextWriteRequest, " + "failed to allocate NSData object"; + [self performNextWriteRequest]; + } + + // TODO: check what happens if I'm using NSData with length 0. + if (request.withResponse) { + NSLog(@"trying to write %@", data.data()); + NSLog(@"initial value: %@", ch.value); + writePending = true; + [peripheral writeValue:data.data() forCharacteristic:ch + type:CBCharacteristicWriteWithResponse]; + } else { + [peripheral writeValue:data.data() forCharacteristic:ch + type:CBCharacteristicWriteWithoutResponse]; + [self performNextWriteRequest]; + } } } } +- (bool)setNotifyValue:(const QByteArray &)value + forCharacteristic:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle +{ + Q_ASSERT_X(charHandle, "-setNotifyValue:forCharacteristic:", + "invalid characteristic handle (0)"); + + if (!charMap.contains(charHandle)) { + qCWarning(QT_BT_OSX) << "-setNotifyValue:forCharacteristic:, " + "unknown characteristic handle " << charHandle; + return false; + } + + OSXBluetooth::LEWriteRequest request; + request.isDescriptor = false; + request.isClientConfiguration = true; + request.handle = charHandle; + request.value = value; + + writeQueue.enqueue(request); + [self performNextWriteRequest]; + // TODO: check if I need a special map - to reset notify to NO + // before disconnect/dealloc later! Also, this can be needed if notify was set + // to YES from another application - to be tested. + return true; +} + - (bool)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value charHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle withResponse:(bool)withResponse @@ -837,15 +883,40 @@ using namespace QT_NAMESPACE; return [ds objectAtIndex:1]; } +- (CBDescriptor *)descriptor:(const QBluetoothUuid &)qtUuid + forCharacteristic:(CBCharacteristic *)ch +{ + if (qtUuid.isNull() || !ch) + return nil; + + QT_BT_MAC_AUTORELEASEPOOL; + + CBDescriptor *descriptor = nil; + NSArray *const ds = ch.descriptors; + if (ds && ds.count) { + for (CBDescriptor *d in ds) { + if (OSXBluetooth::equal_uuids(d.UUID, qtUuid)) { + descriptor = d; + break; + } + } + } + + return descriptor; +} + - (void)reset { writePending = false; + valuesToWrite.clear(); writeQueue.clear(); servicesToDiscoverDetails.clear(); lastValidHandle = 0; serviceMap.clear(); charMap.clear(); descMap.clear(); + + // TODO: also serviceToVisit/VisitNext and visitedServices ? } // CBCentralManagerDelegate (the real one). @@ -990,6 +1061,9 @@ using namespace QT_NAMESPACE; Q_ASSERT_X(delegate, "-centralManager:didDisconnectPeripheral:error:", "invalid delegate (null)"); + // Clear internal caches/data. + [self reset]; + if (error && managerState == OSXBluetooth::CentralManagerDisconnecting) { managerState = OSXBluetooth::CentralManagerIdle; qCWarning(QT_BT_OSX) << "-centralManager:didDisconnectPeripheral:, " @@ -1168,7 +1242,23 @@ using namespace QT_NAMESPACE; else [self discoverDescriptors:characteristic.service]; } else { - // TODO: this can be something else in a future (if needed at all). + // This is (probably) the result of update notification. + const QLowEnergyHandle chHandle = charMap.key(characteristic); + // It's very possible we can have an invalid handle here (0) - + // if something esle is wrong (we subscribed for a notification), + // disconnected (but other application is connected) and still receiveing + // updated values ... + // TODO: this must be properly tested. + if (!chHandle) { + qCCritical(QT_BT_OSX) << "-peripheral:didUpdateValueForCharacteristic:error:, " + "unexpected update notification, no characteristic handle found"; + return; + } + + Q_ASSERT_X(delegate, "-peripheral:didUpdateValueForCharacteristic:error:", + "invalid delegate (null)"); + delegate->characteristicUpdateNotification(chHandle, + qt_bytearray(characteristic.value)); } } @@ -1271,6 +1361,8 @@ using namespace QT_NAMESPACE; using namespace OSXBluetooth; + QT_BT_MAC_AUTORELEASEPOOL; + writePending = false; Q_ASSERT_X(delegate, "-peripheral:didWriteValueForCharacteristic:error", @@ -1289,6 +1381,7 @@ using namespace QT_NAMESPACE; const QLowEnergyHandle cHandle = charMap.key(characteristic); Q_ASSERT_X(cHandle, "-peripheral:didWriteValueForCharacteristic:error", "invalid handle, not found in the characteristics map"); + NSLog(@"characteristic written, the value is %@", characteristic.value); delegate->characteristicWriteNotification(cHandle, qt_bytearray(characteristic.value)); } @@ -1303,8 +1396,12 @@ using namespace QT_NAMESPACE; using namespace OSXBluetooth; + QT_BT_MAC_AUTORELEASEPOOL; + writePending = false; + using namespace OSXBluetooth; + if (error) { // NSLog to log the actual NSError: NSLog(@"-peripheral:didWriteValueForDescriptor:error:, failed with error %@", @@ -1319,7 +1416,58 @@ using namespace QT_NAMESPACE; const QLowEnergyHandle dHandle = descMap.key(descriptor); Q_ASSERT_X(dHandle, "-peripheral:didWriteValueForDescriptor:error:", "invalid descriptor, not found in the descriptors map"); - delegate->descriptorWriteNotification(dHandle, qt_bytearray(descriptor.value)); + delegate->descriptorWriteNotification(dHandle, qt_bytearray(static_cast<NSObject *>(descriptor.value))); + } + + [self performNextWriteRequest]; +} + +- (void)peripheral:(CBPeripheral *)aPeripheral + didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic + error:(NSError *)error +{ + Q_UNUSED(aPeripheral) + + using namespace OSXBluetooth; + + QT_BT_MAC_AUTORELEASEPOOL; + + writePending = false; + + Q_ASSERT_X(delegate, "-peripheral:didUpdateNotificationStateForCharacteristic:", + "invalid delegate (nil)"); + + const QBluetoothUuid qtUuid(QBluetoothUuid::ClientCharacteristicConfiguration); + CBDescriptor *const descriptor = [self descriptor:qtUuid forCharacteristic:characteristic]; + const QLowEnergyHandle dHandle = descMap.key(descriptor);// 0 if descriptor is nil or unknown. + const QByteArray valueToReport(valuesToWrite.value(dHandle, QByteArray())); + + if (!valuesToWrite.remove(dHandle)) { + // In future it can be a special case: we can, in principle, + // set notify value on a characteristic without 'writeDescriptor'. + // It can also be some error. Right now we report it as error, can change. + qCWarning(QT_BT_OSX) << "-peripheral:didUpdateNotificationStateForCharacteristic:, " + "setNotifyValue called, but no client characteristic descriptor " + "found or no writeDescriptor call"; + } + + if (error) { + // NSLog to log the actual NSError: + NSLog(@"-peripheral:didUpdateNotificationStateForCharacteristic:, failed with error %@", + error); + delegate->error(qt_uuid(characteristic.service.UUID), 0, + // In Qt's API it's a descriptor write actually. + QLowEnergyService::DescriptorWriteError); + } else { + if (!valueToReport.isNull()) { + delegate->descriptorWriteNotification(dHandle, valueToReport); + } else { + // TODO: can we in future have another way to set notify value without writeDescriptor? + qCWarning(QT_BT_OSX) << "-peripheral:didUpdateNotificationStateForCharacteristic:, " + "notification value set to " << int(characteristic.isNotifying) + << " but no client characteristic configuration descriptor found" + "or no previous writeDescriptor request found"; + } } [self performNextWriteRequest]; diff --git a/src/bluetooth/osx/osxbtcentralmanager_p.h b/src/bluetooth/osx/osxbtcentralmanager_p.h index 86c621c2..6d93816f 100644 --- a/src/bluetooth/osx/osxbtcentralmanager_p.h +++ b/src/bluetooth/osx/osxbtcentralmanager_p.h @@ -82,6 +82,8 @@ public: virtual void serviceDetailsDiscoveryFinished(LEService service) = 0; virtual void characteristicWriteNotification(QLowEnergyHandle charHandle, const QByteArray &value) = 0; + virtual void characteristicUpdateNotification(QLowEnergyHandle charHandle, + const QByteArray &value) = 0; virtual void descriptorWriteNotification(QLowEnergyHandle descHandle, const QByteArray &value) = 0; virtual void disconnected() = 0; @@ -122,11 +124,13 @@ typedef QHash<QLowEnergyHandle, CBDescriptor *> DescHash; struct LEWriteRequest { LEWriteRequest() : isDescriptor(false), + isClientConfiguration(false), withResponse(false), handle(0) {} bool isDescriptor; + bool isClientConfiguration; bool withResponse; QLowEnergyHandle handle; QByteArray value; @@ -134,6 +138,16 @@ struct LEWriteRequest typedef QQueue<LEWriteRequest> WriteQueue; +// It can happen that Qt's API wants to write something +// and expects the confirmation about this value written, +// but under the hood (Core Bluetooth) we have something like +// a special method without any values at all. +// To report our user a successful write, we have this map: +// handle -> value for a write operation. +// Since write operations are serialized, the key is guaranteed +// to be unique. +typedef QHash<QLowEnergyHandle, QByteArray> ValueHash; + } QT_END_NAMESPACE @@ -169,6 +183,8 @@ QT_END_NAMESPACE bool writePending; QT_PREPEND_NAMESPACE(OSXBluetooth)::WriteQueue writeQueue; + + QT_PREPEND_NAMESPACE(OSXBluetooth)::ValueHash valuesToWrite; } - (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth)::CentralManagerDelegate *)aDelegate; @@ -182,6 +198,9 @@ QT_END_NAMESPACE - (void)discoverServices; - (bool)discoverServiceDetails:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid; +- (bool)setNotifyValue:(const QT_PREPEND_NAMESPACE(QByteArray) &)value + forCharacteristic:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle; + - (bool)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value charHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle withResponse:(bool)writeWithResponse; |