diff options
author | Timur Pocheptsov <Timur.Pocheptsov@digia.com> | 2014-11-25 16:11:48 +0100 |
---|---|---|
committer | Timur Pocheptsov <Timur.Pocheptsov@digia.com> | 2014-12-01 12:10:36 +0100 |
commit | 63a158b2d53a4f18f118a95796c0386911e1e5fa (patch) | |
tree | 0c39dafd71c760fd4fdc6a1fbfc4063aeb000553 /src | |
parent | ae3d26661aeaa2917ed35e5892648dea4caced04 (diff) |
QLowEnergyController - writeDescriptor (OS X/iOS)
Core Bluetooth - based implementation.
Change-Id: Ie642a13ae9a4d75401dd10648fac6eeee4123a3b
Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/bluetooth/osx/osxbtcentralmanager.mm | 117 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtcentralmanager_p.h | 27 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_osx.mm | 89 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_osx_p.h | 10 | ||||
-rw-r--r-- | src/bluetooth/qlowenergydescriptor.h | 1 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyservice_osx.mm | 26 |
6 files changed, 241 insertions, 29 deletions
diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm index da4e6c34..35a284c2 100644 --- a/src/bluetooth/osx/osxbtcentralmanager.mm +++ b/src/bluetooth/osx/osxbtcentralmanager.mm @@ -99,6 +99,7 @@ using namespace QT_NAMESPACE; - (void)discoverIncludedServices; - (void)readCharacteristics:(CBService *)service; - (void)serviceDetailsDiscoveryFinished:(CBService *)service; +- (void)performNextWriteRequest; // Aux. functions. - (CBService *)serviceForUUID:(const QBluetoothUuid &)qtUuid; @@ -110,6 +111,9 @@ using namespace QT_NAMESPACE; - (CBDescriptor *)nextDescriptorForCharacteristic:(CBCharacteristic *)characteristic startingFrom:(CBDescriptor *)descriptor; +// TODO: check _what_ exactly I have to reset ... +- (void)reset; + @end @implementation QT_MANGLE_NAMESPACE(OSXBTCentralManager) @@ -126,6 +130,7 @@ using namespace QT_NAMESPACE; delegate = aDelegate; currentService = 0; lastValidHandle = 0; + writePending = false; } return self; @@ -272,7 +277,7 @@ using namespace QT_NAMESPACE; - (void)disconnectFromDevice { - servicesToDiscoverDetails.clear(); + [self reset]; if (managerState == OSXBluetooth::CentralManagerUpdating) { disconnectPending = true; @@ -306,11 +311,6 @@ using namespace QT_NAMESPACE; // // ... 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]; @@ -546,6 +546,41 @@ using namespace QT_NAMESPACE; delegate->serviceDetailsDiscoveryFinished(qtService); } +- (void)performNextWriteRequest +{ + using namespace OSXBluetooth; + + Q_ASSERT_X(peripheral, "-performNextWriteRequest", + "invalid peripheral (nil)"); + + if (writePending || !writeQueue.size()) + return; + + LEWriteRequest request(writeQueue.dequeue()); + + if (request.isDescriptor) { + if (!descMap.contains(request.handle)) { + qCWarning(QT_BT_OSX) << "-performNextWriteRequest, descriptor with " + "handle: " << request.handle << " not found"; + [self performNextWriteRequest]; + } + + CBDescriptor *const d = descMap[request.handle]; + ObjCStrongReference<NSData> data(data_from_bytearray(request.value)); + if (!data) { + // Even if qtData.size() == 0, we still need NSData object. + qCWarning(QT_BT_OSX) << "-write:descHandle:, failed " + "to allocate an NSData object"; + [self performNextWriteRequest]; + } + + writePending = true; + return [peripheral writeValue:data.data() forDescriptor:d]; + } else { + // TODO: characteristics write requests. + } +} + - (bool)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value charHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle withResponse:(bool)withResponse @@ -562,7 +597,7 @@ using namespace QT_NAMESPACE; return false; } - CBCharacteristic *const ch = charMap.value(charHandle); + CBCharacteristic *const ch = charMap[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)"); @@ -581,6 +616,32 @@ using namespace QT_NAMESPACE; return true; } +- (bool)write:(const QByteArray &)value descHandle:(QLowEnergyHandle)descHandle +{ + using namespace OSXBluetooth; + + Q_ASSERT_X(descHandle, "-write:descHandle:", + "invalid descriptor handle (0)"); + + if (!descMap.contains(descHandle)) { + qCWarning(QT_BT_OSX) << "-write:descHandle:, descriptor with " + "handle: " << descHandle << " not found"; + return false; + } + + LEWriteRequest request; + request.isDescriptor = true; + request.handle = descHandle; + request.value = value; + + writeQueue.enqueue(request); + [self performNextWriteRequest]; + // TODO: this is quite ugly: true value can be returned after + // write actually. If I have any problems with the order later, + // I'll use performSelector afterDelay with some delay. + return true; +} + // Aux. methods: - (CBService *)serviceForUUID:(const QBluetoothUuid &)qtUuid @@ -740,6 +801,17 @@ using namespace QT_NAMESPACE; return [ds objectAtIndex:1]; } +- (void)reset +{ + writePending = false; + writeQueue.clear(); + servicesToDiscoverDetails.clear(); + lastValidHandle = 0; + serviceMap.clear(); + charMap.clear(); + descMap.clear(); +} + // CBCentralManagerDelegate (the real one). - (void)centralManagerDidUpdateState:(CBCentralManager *)central @@ -1162,7 +1234,6 @@ using namespace QT_NAMESPACE; Q_UNUSED(aPeripheral) Q_UNUSED(characteristic) - Q_UNUSED(error) Q_ASSERT_X(delegate, "-peripheral:didWriteValueForCharacteristic:error", "invalid delegate (null)"); @@ -1181,4 +1252,34 @@ using namespace QT_NAMESPACE; } } +- (void)peripheral:(CBPeripheral *)aPeripheral + didWriteValueForDescriptor:(CBDescriptor *)descriptor + error:(NSError *)error +{ + Q_UNUSED(aPeripheral) + + using namespace OSXBluetooth; + + writePending = false; + + if (error) { + // NSLog to log the actual NSError: + NSLog(@"-peripheral:didWriteValueForDescriptor:error:, failed with error %@", + error); + // TODO: this error function is not good at all - it takes charHandle, + // which is noop at the moment and ... we actually work with a descriptor handle + // here. + delegate->error(qt_uuid(descriptor.characteristic.service.UUID), 0, + QLowEnergyService::DescriptorWriteError); + } else { + // We know that keys are unique, so can find a key for a given descriptor. + const QLowEnergyHandle dHandle = descMap.key(descriptor); + Q_ASSERT_X(dHandle, "-peripheral:didWriteValueForDescriptor:error:", + "invalid descriptor, not found in a descMap"); + delegate->descriptorWriteNotification(dHandle, qt_bytearray(descriptor.value)); + } + + [self performNextWriteRequest]; +} + @end diff --git a/src/bluetooth/osx/osxbtcentralmanager_p.h b/src/bluetooth/osx/osxbtcentralmanager_p.h index f5692c38..2bdea3c0 100644 --- a/src/bluetooth/osx/osxbtcentralmanager_p.h +++ b/src/bluetooth/osx/osxbtcentralmanager_p.h @@ -47,7 +47,9 @@ #include "qbluetoothuuid.h" #include "osxbtutility_p.h" +#include <QtCore/qbytearray.h> #include <QtCore/qglobal.h> +#include <QtCore/qqueue.h> #include <QtCore/qhash.h> // Foundation.h must be included before corebluetoothwrapper_p.h - @@ -61,7 +63,6 @@ QT_BEGIN_NAMESPACE class QLowEnergyServicePrivate; -class QByteArray; namespace OSXBluetooth { @@ -80,6 +81,8 @@ public: virtual void serviceDiscoveryFinished(LEServices services) = 0; virtual void serviceDetailsDiscoveryFinished(LEService service) = 0; virtual void characteristicWriteNotification(LECharacteristic ch) = 0; + virtual void descriptorWriteNotification(QLowEnergyHandle descHandle, + const QByteArray &value) = 0; virtual void disconnected() = 0; // General errors. @@ -114,6 +117,19 @@ typedef QHash<QLowEnergyHandle, CBService *> ServiceHash; typedef QHash<QLowEnergyHandle, CBCharacteristic *> CharHash; typedef QHash<QLowEnergyHandle, CBDescriptor *> DescHash; +// Descriptor write request - we have to serialize 'concurrent' write requests. +struct LEWriteRequest +{ + LEWriteRequest() : isDescriptor(false), handle(0) + {} + + bool isDescriptor; + QLowEnergyHandle handle; + QByteArray value; +}; + +typedef QQueue<LEWriteRequest> WriteQueue; + } QT_END_NAMESPACE @@ -145,7 +161,10 @@ QT_END_NAMESPACE QT_PREPEND_NAMESPACE(OSXBluetooth)::CharHash charMap; QT_PREPEND_NAMESPACE(OSXBluetooth)::DescHash descMap; - QLowEnergyHandle lastValidHandle; + QT_PREPEND_NAMESPACE(QLowEnergyHandle) lastValidHandle; + + bool writePending; + QT_PREPEND_NAMESPACE(OSXBluetooth)::WriteQueue writeQueue; } - (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth)::CentralManagerDelegate *)aDelegate; @@ -159,12 +178,12 @@ QT_END_NAMESPACE - (void)discoverServices; - (bool)discoverServiceDetails:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid; -// 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 charHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle withResponse:(bool)writeWithResponse; +- (bool)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value + descHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))descHandle; @end #endif diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm index 88ec6421..7a938ae9 100644 --- a/src/bluetooth/qlowenergycontroller_osx.mm +++ b/src/bluetooth/qlowenergycontroller_osx.mm @@ -123,6 +123,7 @@ UUIDList qt_servicesUuids(NSArray *services) return uuids; } +// TODO: get rid of this. QLowEnergyHandle qt_findCharacteristicHandle(QLowEnergyHandle serviceHandle, CBService *service, CBCharacteristic *ch) { @@ -130,12 +131,9 @@ QLowEnergyHandle qt_findCharacteristicHandle(QLowEnergyHandle serviceHandle, // but duplicating data structures (CB char-tree, Qt char-tree, etc.) // is even more annoying. - Q_ASSERT_X(serviceHandle, "qt_findCharacteristicHandle", - "invalid service handle (0)"); - Q_ASSERT_X(service, "qt_findCharacteristicHandle", - "invalid service (nil)"); - Q_ASSERT_X(ch, "qt_findCharacteristicHandle", - "invalid characteristic (nil)"); + Q_ASSERT_X(serviceHandle, "qt_findCharacteristicHandle", "invalid service handle (0)"); + Q_ASSERT_X(service, "qt_findCharacteristicHandle", "invalid service (nil)"); + Q_ASSERT_X(ch, "qt_findCharacteristicHandle", "invalid characteristic (nil)"); NSArray *const chars = service.characteristics; if (!chars || !chars.count) @@ -374,12 +372,28 @@ void QLowEnergyControllerPrivateOSX::characteristicWriteNotification(LECharacter return; } - // TODO: check that this 'value' is what we need! const QByteArray data(OSXBluetooth::qt_bytearray([ch value])); updateValueOfCharacteristic(charHandle, data, false); emit service->characteristicWritten(characteristic, data); } +void QLowEnergyControllerPrivateOSX::descriptorWriteNotification(QLowEnergyHandle dHandle, const QByteArray &value) +{ + Q_ASSERT_X(dHandle, "descriptorWriteNotification", "invalid descriptor handle (0)"); + + const QLowEnergyDescriptor qtDescriptor(descriptorForHandle(dHandle)); + if (!qtDescriptor.isValid()) { + qCWarning(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::descriptorWriteNotification(), " + "unknown descriptor " << dHandle; + return; + } + + ServicePrivate service(serviceForHandle(qtDescriptor.characteristicHandle())); + // TODO: test if this data is what we expected. + updateValueOfDescriptor(qtDescriptor.characteristicHandle(), dHandle, value, false); + emit service->descriptorWritten(qtDescriptor, value); +} + void QLowEnergyControllerPrivateOSX::disconnected() { controllerState = QLowEnergyController::UnconnectedState; @@ -547,13 +561,13 @@ void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEner // otherwise we can not write anything at all. if (!discoveredServices.contains(service->uuid)) { qCWarning(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::writeCharacteristic(), " - "no service with uuid: " << service << "found"; + "no service with uuid: " << service->uuid << " found"; return; } if (!service->characteristicList.contains(charHandle)) { qCDebug(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::writeCharacteristic(), " - "no characteristic with handle: " << charHandle << "found"; + "no characteristic with handle: " << charHandle << " found"; return; } @@ -582,13 +596,46 @@ quint16 QLowEnergyControllerPrivateOSX::updateValueOfCharacteristic(QLowEnergyHa } void QLowEnergyControllerPrivateOSX::writeDescriptor(QSharedPointer<QLowEnergyServicePrivate> service, - QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle, + QLowEnergyHandle descriptorHandle, const QByteArray &newValue) { - Q_UNUSED(service) - Q_UNUSED(charHandle) - Q_UNUSED(descriptorHandle) - Q_UNUSED(newValue) + Q_ASSERT_X(!service.isNull(), "writeDescriptor", "invalid service (null)"); + + if (!isValid()) { + qCWarning(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::writeDescriptor(), " + "invalid controller"; + return; + } + + // We can work only with services found on a given peripheral + // (== created by the given LE controller), + // otherwise we can not write anything at all. + if (!discoveredServices.contains(service->uuid)) { + qCWarning(QT_BT_OSX) << "QLowEnergyControllerPrivateOSX::writeDescriptor(), " + "no service with uuid: " << service->uuid << " found"; + return; + } + + if (![centralManager write:newValue descHandle:descriptorHandle]) + service->setError(QLowEnergyService::DescriptorWriteError); +} + +quint16 QLowEnergyControllerPrivateOSX::updateValueOfDescriptor(QLowEnergyHandle charHandle, QLowEnergyHandle descHandle, + const QByteArray &value, bool appendValue) +{ + ServicePrivate service(serviceForHandle(charHandle)); + if (service.isNull() || !service->characteristicList.contains(charHandle)) + return 0; + + if (!service->characteristicList[charHandle].descriptorList.contains(descHandle)) + return 0; + + if (appendValue) + service->characteristicList[charHandle].descriptorList[descHandle].value += value; + else + service->characteristicList[charHandle].descriptorList[descHandle].value = value; + + return service->characteristicList[charHandle].descriptorList[descHandle].value.size(); } QSharedPointer<QLowEnergyServicePrivate> QLowEnergyControllerPrivateOSX::serviceForHandle(QLowEnergyHandle handle) @@ -628,6 +675,20 @@ QLowEnergyCharacteristic QLowEnergyControllerPrivateOSX::characteristicForHandle return QLowEnergyCharacteristic(); } +QLowEnergyDescriptor QLowEnergyControllerPrivateOSX::descriptorForHandle(QLowEnergyHandle descriptorHandle) +{ + const QLowEnergyCharacteristic ch(characteristicForHandle(descriptorHandle)); + if (!ch.isValid()) + return QLowEnergyDescriptor(); + + const QLowEnergyServicePrivate::CharData charData = ch.d_ptr->characteristicList[ch.attributeHandle()]; + + if (charData.descriptorList.contains(descriptorHandle)) + return QLowEnergyDescriptor(ch.d_ptr, ch.attributeHandle(), descriptorHandle); + + return QLowEnergyDescriptor(); +} + void QLowEnergyControllerPrivateOSX::setErrorDescription(QLowEnergyController::Error errorCode) { // This function does not emit! diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h index 2aec6200..df25e88a 100644 --- a/src/bluetooth/qlowenergycontroller_osx_p.h +++ b/src/bluetooth/qlowenergycontroller_osx_p.h @@ -73,6 +73,8 @@ private: void serviceDiscoveryFinished(LEServices services) Q_DECL_OVERRIDE; void serviceDetailsDiscoveryFinished(LEService service) Q_DECL_OVERRIDE; void characteristicWriteNotification(LECharacteristic ch) Q_DECL_OVERRIDE; + void descriptorWriteNotification(QLowEnergyHandle descHandle, + const QByteArray &newValue) Q_DECL_OVERRIDE; void disconnected() Q_DECL_OVERRIDE; void error(QLowEnergyController::Error errorCode) Q_DECL_OVERRIDE; void error(const QBluetoothUuid &serviceUuid, @@ -97,13 +99,19 @@ private: bool appendValue); void writeDescriptor(QSharedPointer<QLowEnergyServicePrivate> service, - QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle, + QLowEnergyHandle descriptorHandle, const QByteArray &newValue); + quint16 updateValueOfDescriptor(QLowEnergyHandle charHandle, + QLowEnergyHandle descHandle, + const QByteArray &value, + bool appendValue); + // 'Lookup' functions: QSharedPointer<QLowEnergyServicePrivate> serviceForHandle(QLowEnergyHandle serviceHandle); QLowEnergyCharacteristic characteristicForHandle(QLowEnergyHandle charHandle); + QLowEnergyDescriptor descriptorForHandle(QLowEnergyHandle descriptorHandle); void setErrorDescription(QLowEnergyController::Error errorCode); void invalidateServices(); diff --git a/src/bluetooth/qlowenergydescriptor.h b/src/bluetooth/qlowenergydescriptor.h index edbac253..eb0ae54f 100644 --- a/src/bluetooth/qlowenergydescriptor.h +++ b/src/bluetooth/qlowenergydescriptor.h @@ -73,6 +73,7 @@ protected: friend class QLowEnergyCharacteristic; friend class QLowEnergyService; friend class QLowEnergyControllerPrivate; + friend class QLowEnergyControllerPrivateOSX; QLowEnergyDescriptorPrivate *data; QLowEnergyDescriptor(QSharedPointer<QLowEnergyServicePrivate> p, diff --git a/src/bluetooth/qlowenergyservice_osx.mm b/src/bluetooth/qlowenergyservice_osx.mm index f9c124e4..996de64b 100644 --- a/src/bluetooth/qlowenergyservice_osx.mm +++ b/src/bluetooth/qlowenergyservice_osx.mm @@ -221,8 +221,30 @@ bool QLowEnergyService::contains(const QLowEnergyDescriptor &descriptor) const void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) { - Q_UNUSED(descriptor) - Q_UNUSED(newValue) + if (!contains(descriptor)) + return; + + QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr); + if (!controller) + return; + + if (state() != ServiceDiscovered) { + d_ptr->setError(OperationError); + return; + } + + if (descriptor.uuid() == QBluetoothUuid::ClientCharacteristicConfiguration) { + // Core Bluetooth: + // + // "You cannot use this method to write the value of a client configuration descriptor + // (represented by the CBUUIDClientCharacteristicConfigurationString constant), + // which describes how notification or indications are configured for a + // characteristic’s value with respect to a client. If you want to manage + // notifications or indications for a characteristic’s value, you must + // use the setNotifyValue:forCharacteristic: method instead." + } else { + controller->writeDescriptor(descriptor.d_ptr, descriptor.handle(), newValue); + } } QT_END_NAMESPACE |