summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qlowenergycontroller_darwin.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/qlowenergycontroller_darwin.mm')
-rw-r--r--src/bluetooth/qlowenergycontroller_darwin.mm1098
1 files changed, 1098 insertions, 0 deletions
diff --git a/src/bluetooth/qlowenergycontroller_darwin.mm b/src/bluetooth/qlowenergycontroller_darwin.mm
new file mode 100644
index 00000000..253956e2
--- /dev/null
+++ b/src/bluetooth/qlowenergycontroller_darwin.mm
@@ -0,0 +1,1098 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2016 Javier S. Pedro <maemo@javispedro.com>
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "osx/osxbtutility_p.h"
+#include "osx/uistrings_p.h"
+
+#ifndef Q_OS_TVOS
+#include "osx/osxbtperipheralmanager_p.h"
+#endif // Q_OS_TVOS
+
+#include "qlowenergycontroller_darwin_p.h"
+#include "qlowenergyserviceprivate_p.h"
+#include "osx/osxbtcentralmanager_p.h"
+
+#include "qlowenergyservicedata.h"
+#include "qbluetoothlocaldevice.h"
+#include "qbluetoothdeviceinfo.h"
+#include "qlowenergycontroller.h"
+#include "qbluetoothuuid.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qsharedpointer.h>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qlist.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+typedef QSharedPointer<QLowEnergyServicePrivate> ServicePrivate;
+
+// Convenience function, can return a smart pointer that 'isNull'.
+ServicePrivate qt_createLEService(QLowEnergyControllerPrivateDarwin *controller, CBService *cbService, bool included)
+{
+ Q_ASSERT_X(controller, Q_FUNC_INFO, "invalid controller (null)");
+ Q_ASSERT_X(cbService, Q_FUNC_INFO, "invalid service (nil)");
+
+ CBUUID *const cbUuid = cbService.UUID;
+ if (!cbUuid) {
+ qCDebug(QT_BT_OSX) << "invalid service, UUID is nil";
+ return ServicePrivate();
+ }
+
+ const QBluetoothUuid qtUuid(OSXBluetooth::qt_uuid(cbUuid));
+ if (qtUuid.isNull()) // Conversion error is reported by qt_uuid.
+ return ServicePrivate();
+
+ ServicePrivate newService(new QLowEnergyServicePrivate);
+ newService->uuid = qtUuid;
+ newService->setController(controller);
+
+ if (included)
+ newService->type |= QLowEnergyService::IncludedService;
+
+ // TODO: isPrimary is ... always 'NO' - to be investigated.
+ /*
+ if (!cbService.isPrimary) {
+ // Our guess included/not was probably wrong.
+ newService->type &= ~QLowEnergyService::PrimaryService;
+ newService->type |= QLowEnergyService::IncludedService;
+ }
+ */
+ return newService;
+}
+
+typedef QList<QBluetoothUuid> UUIDList;
+
+UUIDList qt_servicesUuids(NSArray *services)
+{
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if (!services || !services.count)
+ return UUIDList();
+
+ UUIDList uuids;
+
+ for (CBService *s in services)
+ uuids.append(OSXBluetooth::qt_uuid(s.UUID));
+
+ return uuids;
+}
+
+} // unnamed namespace
+
+#ifndef Q_OS_TVOS
+using ObjCPeripheralManager = QT_MANGLE_NAMESPACE(OSXBTPeripheralManager);
+#endif // Q_OS_TVOS
+
+using ObjCCentralManager = QT_MANGLE_NAMESPACE(OSXBTCentralManager);
+
+QLowEnergyControllerPrivateDarwin::QLowEnergyControllerPrivateDarwin()
+{
+ void registerQLowEnergyControllerMetaType();
+ registerQLowEnergyControllerMetaType();
+ qRegisterMetaType<QLowEnergyHandle>("QLowEnergyHandle");
+ qRegisterMetaType<QSharedPointer<QLowEnergyServicePrivate>>();
+}
+
+QLowEnergyControllerPrivateDarwin::~QLowEnergyControllerPrivateDarwin()
+{
+ if (const auto leQueue = OSXBluetooth::qt_LE_queue()) {
+ if (role == QLowEnergyController::CentralRole) {
+ const auto manager = centralManager.getAs<ObjCCentralManager>();
+ dispatch_sync(leQueue, ^{
+ [manager detach];
+ });
+ } else {
+#ifndef Q_OS_TVOS
+ const auto manager = peripheralManager.getAs<ObjCPeripheralManager>();
+ dispatch_sync(leQueue, ^{
+ [manager detach];
+ });
+#endif
+ }
+ }
+}
+
+bool QLowEnergyControllerPrivateDarwin::isValid() const
+{
+#ifdef Q_OS_TVOS
+ return centralManager;
+#else
+ return centralManager || peripheralManager;
+#endif
+}
+
+void QLowEnergyControllerPrivateDarwin::init()
+{
+ using OSXBluetooth::LECBManagerNotifier;
+
+ QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier);
+ if (role == QLowEnergyController::PeripheralRole) {
+#ifndef Q_OS_TVOS
+ peripheralManager.reset([[ObjCPeripheralManager alloc] initWith:notifier.data()],
+ DarwinBluetooth::RetainPolicy::noInitialRetain);
+ if (!peripheralManager) {
+ qCWarning(QT_BT_OSX) << "failed to create a peripheral manager";
+ return;
+ }
+#else
+ qCWarning(QT_BT_OSX) << "the peripheral role is not supported on your platform";
+ return;
+#endif // Q_OS_TVOS
+ } else {
+ centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()],
+ DarwinBluetooth::RetainPolicy::noInitialRetain);
+ if (!centralManager) {
+ qCWarning(QT_BT_OSX) << "failed to initialize a central manager";
+ return;
+ }
+ }
+
+ if (!connectSlots(notifier.data()))
+ qCWarning(QT_BT_OSX) << "failed to connect to notifier's signal(s)";
+
+ // Ownership was taken by central manager.
+ notifier.take();
+}
+
+void QLowEnergyControllerPrivateDarwin::connectToDevice()
+{
+ Q_ASSERT_X(state == QLowEnergyController::UnconnectedState,
+ Q_FUNC_INFO, "invalid state");
+
+ if (!isValid()) {
+ // init() had failed for was never called.
+ return _q_CBManagerError(QLowEnergyController::UnknownError);
+ }
+
+ if (deviceUuid.isNull()) {
+ // Wrong constructor was used or invalid UUID was provided.
+ return _q_CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ }
+
+ // The logic enforcing the role is in the public class.
+ Q_ASSERT_X(role != QLowEnergyController::PeripheralRole,
+ Q_FUNC_INFO, "invalid role (peripheral)");
+
+ dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ if (!leQueue) {
+ qCWarning(QT_BT_OSX) << "no LE queue found";
+ setErrorDescription(QLowEnergyController::UnknownError);
+ return;
+ }
+
+ setErrorDescription(QLowEnergyController::NoError);
+ setState(QLowEnergyController::ConnectingState);
+
+ const QBluetoothUuid deviceUuidCopy(deviceUuid);
+ ObjCCentralManager *manager = centralManager.getAs<ObjCCentralManager>();
+ dispatch_async(leQueue, ^{
+ [manager connectToDevice:deviceUuidCopy];
+ });
+}
+
+void QLowEnergyControllerPrivateDarwin::disconnectFromDevice()
+{
+ if (role == QLowEnergyController::PeripheralRole) {
+ // CoreBluetooth API intentionally does not provide any way of closing
+ // a connection. All we can do here is to stop the advertisement.
+ stopAdvertising();
+ return;
+ }
+
+ if (isValid()) {
+ const auto oldState = state;
+
+ if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
+ setState(QLowEnergyController::ClosingState);
+ invalidateServices();
+
+ auto manager = centralManager.getAs<ObjCCentralManager>();
+ dispatch_async(leQueue, ^{
+ [manager disconnectFromDevice];
+ });
+
+ if (oldState == QLowEnergyController::ConnectingState) {
+ // With a pending connect attempt there is no
+ // guarantee we'll ever have didDisconnect callback,
+ // set the state here and now to make sure we still
+ // can connect.
+ setState(QLowEnergyController::UnconnectedState);
+ }
+ } else {
+ qCCritical(QT_BT_OSX) << "qt LE queue is nil, "
+ "can not dispatch 'disconnect'";
+ }
+ }
+}
+
+void QLowEnergyControllerPrivateDarwin::discoverServices()
+{
+ Q_ASSERT_X(state != QLowEnergyController::UnconnectedState,
+ Q_FUNC_INFO, "not connected to peripheral");
+ Q_ASSERT_X(role != QLowEnergyController::PeripheralRole,
+ Q_FUNC_INFO, "invalid role (peripheral)");
+
+ dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ Q_ASSERT_X(leQueue, Q_FUNC_INFO, "LE queue not found");
+
+ setState(QLowEnergyController::DiscoveringState);
+
+ ObjCCentralManager *manager = centralManager.getAs<ObjCCentralManager>();
+ dispatch_async(leQueue, ^{
+ [manager discoverServices];
+ });
+}
+
+void QLowEnergyControllerPrivateDarwin::discoverServiceDetails(const QBluetoothUuid &serviceUuid)
+{
+ if (state != QLowEnergyController::DiscoveredState) {
+ qCWarning(QT_BT_OSX) << "can not discover service details in the current state, "
+ "QLowEnergyController::DiscoveredState is expected";
+ return;
+ }
+
+ if (!serviceList.contains(serviceUuid)) {
+ qCWarning(QT_BT_OSX) << "unknown service: " << serviceUuid;
+ return;
+ }
+
+ dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ Q_ASSERT(leQueue);
+
+ ServicePrivate qtService(serviceList.value(serviceUuid));
+ qtService->setState(QLowEnergyService::DiscoveringServices);
+ // Copy objects ...
+ ObjCCentralManager *manager = centralManager.getAs<ObjCCentralManager>();
+ const QBluetoothUuid serviceUuidCopy(serviceUuid);
+ dispatch_async(leQueue, ^{
+ [manager discoverServiceDetails:serviceUuidCopy];
+ });
+}
+
+void QLowEnergyControllerPrivateDarwin::requestConnectionUpdate(const QLowEnergyConnectionParameters &params)
+{
+ Q_UNUSED(params);
+ // TODO: implement this, if possible.
+ qCWarning(QT_BT_OSX) << "Connection update not implemented on your platform";
+}
+
+void QLowEnergyControllerPrivateDarwin::addToGenericAttributeList(const QLowEnergyServiceData &service,
+ QLowEnergyHandle startHandle)
+{
+ Q_UNUSED(service);
+ Q_UNUSED(startHandle);
+ // TODO: check why I don't need this (apparently it is used in addServiceHelper
+ // of the base class).
+}
+
+QLowEnergyService * QLowEnergyControllerPrivateDarwin::addServiceHelper(const QLowEnergyServiceData &service)
+{
+ // Three checks below should be removed, they are done in the q_ptr's class.
+#ifdef Q_OS_TVOS
+ Q_UNUSED(service);
+ qCDebug(QT_BT_OSX, "peripheral role is not supported on tvOS");
+#else
+ if (role != QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "not in peripheral role";
+ return nullptr;
+ }
+
+ if (state != QLowEnergyController::UnconnectedState) {
+ qCWarning(QT_BT_OSX) << "invalid state";
+ return nullptr;
+ }
+
+ if (!service.isValid()) {
+ qCWarning(QT_BT_OSX) << "invalid service";
+ return nullptr;
+ }
+
+ for (auto includedService : service.includedServices())
+ includedService->d_ptr->type |= QLowEnergyService::IncludedService;
+
+ const auto manager = peripheralManager.getAs<ObjCPeripheralManager>();
+ Q_ASSERT(manager);
+ if (const auto servicePrivate = [manager addService:service]) {
+ servicePrivate->setController(this);
+ servicePrivate->state = QLowEnergyService::LocalService;
+ localServices.insert(servicePrivate->uuid, servicePrivate);
+ return new QLowEnergyService(servicePrivate);
+ }
+#endif // Q_OS_TVOS
+ return nullptr;
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_connected()
+{
+ setState(QLowEnergyController::ConnectedState);
+ emit q_ptr->connected();
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_disconnected()
+{
+ if (role == QLowEnergyController::CentralRole)
+ invalidateServices();
+
+ setState(QLowEnergyController::UnconnectedState);
+ emit q_ptr->disconnected();
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_serviceDiscoveryFinished()
+{
+ Q_ASSERT_X(state == QLowEnergyController::DiscoveringState,
+ Q_FUNC_INFO, "invalid state");
+
+ using namespace OSXBluetooth;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ NSArray *const services = [centralManager.getAs<ObjCCentralManager>() peripheral].services;
+ // Now we have to traverse the discovered services tree.
+ // Essentially it's an iterative version of more complicated code from the
+ // OSXBTCentralManager's code.
+ // All Obj-C entities either auto-release, or guarded by ObjCScopedReferences.
+ if (services && [services count]) {
+ QMap<QBluetoothUuid, CBService *> discoveredCBServices;
+ //1. The first pass - none of this services is 'included' yet (we'll discover 'included'
+ // during the pass 2); we also ignore duplicates (== services with the same UUID)
+ // - since we do not have a way to distinguish them later
+ // (our API is using uuids when creating QLowEnergyServices).
+ for (CBService *cbService in services) {
+ const ServicePrivate newService(qt_createLEService(this, cbService, false));
+ if (!newService.data())
+ continue;
+ if (serviceList.contains(newService->uuid)) {
+ // It's a bit stupid we first created it ...
+ qCDebug(QT_BT_OSX) << "discovered service with a duplicated UUID"
+ << newService->uuid;
+ continue;
+ }
+ serviceList.insert(newService->uuid, newService);
+ discoveredCBServices.insert(newService->uuid, cbService);
+ }
+
+ ObjCStrongReference<NSMutableArray> toVisit([[NSMutableArray alloc] initWithArray:services], false);
+ ObjCStrongReference<NSMutableArray> toVisitNext([[NSMutableArray alloc] init], false);
+ ObjCStrongReference<NSMutableSet> visited([[NSMutableSet alloc] init], false);
+
+ while (true) {
+ for (NSUInteger i = 0, e = [toVisit count]; i < e; ++i) {
+ CBService *const s = [toVisit objectAtIndex:i];
+ if (![visited containsObject:s]) {
+ [visited addObject:s];
+ if (s.includedServices && s.includedServices.count)
+ [toVisitNext addObjectsFromArray:s.includedServices];
+ }
+
+ const QBluetoothUuid uuid(qt_uuid(s.UUID));
+ if (serviceList.contains(uuid) && discoveredCBServices.value(uuid) == s) {
+ ServicePrivate qtService(serviceList.value(uuid));
+ // Add included UUIDs:
+ qtService->includedServices.append(qt_servicesUuids(s.includedServices));
+ }// Else - we ignored this CBService object.
+ }
+
+ if (![toVisitNext count])
+ break;
+
+ for (NSUInteger i = 0, e = [toVisitNext count]; i < e; ++i) {
+ CBService *const s = [toVisitNext objectAtIndex:i];
+ const QBluetoothUuid uuid(qt_uuid(s.UUID));
+ if (serviceList.contains(uuid)) {
+ if (discoveredCBServices.value(uuid) == s) {
+ ServicePrivate qtService(serviceList.value(uuid));
+ qtService->type |= QLowEnergyService::IncludedService;
+ } // Else this is the duplicate we ignored already.
+ } else {
+ // Oh, we do not even have it yet???
+ ServicePrivate newService(qt_createLEService(this, s, true));
+ serviceList.insert(newService->uuid, newService);
+ discoveredCBServices.insert(newService->uuid, s);
+ }
+ }
+
+ toVisit.resetWithoutRetain(toVisitNext.take());
+ toVisitNext.resetWithoutRetain([[NSMutableArray alloc] init]);
+ }
+ } else {
+ qCDebug(QT_BT_OSX) << "no services found";
+ }
+
+ for (ServiceMap::const_iterator it = serviceList.constBegin(); it != serviceList.constEnd(); ++it)
+ emit q_ptr->serviceDiscovered(it.key());
+
+ setState(QLowEnergyController::DiscoveredState);
+ emit q_ptr->discoveryFinished();
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_serviceDetailsDiscoveryFinished(QSharedPointer<QLowEnergyServicePrivate> service)
+{
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ Q_ASSERT(service);
+
+ if (!serviceList.contains(service->uuid)) {
+ qCDebug(QT_BT_OSX) << "unknown service uuid:"
+ << service->uuid;
+ return;
+ }
+
+ ServicePrivate qtService(serviceList.value(service->uuid));
+ // Assert on handles?
+ qtService->startHandle = service->startHandle;
+ qtService->endHandle = service->endHandle;
+ qtService->characteristicList = service->characteristicList;
+
+ qtService->setState(QLowEnergyService::ServiceDiscovered);
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_servicesWereModified()
+{
+ if (!(state == QLowEnergyController::DiscoveringState
+ || state == QLowEnergyController::DiscoveredState)) {
+ qCWarning(QT_BT_OSX) << "services were modified while controller is not in Discovered/Discovering state";
+ return;
+ }
+
+ if (state == QLowEnergyController::DiscoveredState)
+ invalidateServices();
+
+ setState(QLowEnergyController::ConnectedState);
+ q_ptr->discoverServices();
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_characteristicRead(QLowEnergyHandle charHandle,
+ const QByteArray &value)
+{
+ Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle(0)");
+
+ ServicePrivate service(serviceForHandle(charHandle));
+ if (service.isNull())
+ return;
+
+ QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
+ if (!characteristic.isValid()) {
+ qCWarning(QT_BT_OSX) << "unknown characteristic";
+ return;
+ }
+
+ if (characteristic.properties() & QLowEnergyCharacteristic::Read)
+ updateValueOfCharacteristic(charHandle, value, false);
+
+ emit service->characteristicRead(characteristic, value);
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_characteristicWritten(QLowEnergyHandle charHandle,
+ const QByteArray &value)
+{
+ Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle(0)");
+
+ ServicePrivate service(serviceForHandle(charHandle));
+ if (service.isNull()) {
+ qCWarning(QT_BT_OSX) << "can not find service for characteristic handle"
+ << charHandle;
+ return;
+ }
+
+ QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
+ if (!characteristic.isValid()) {
+ qCWarning(QT_BT_OSX) << "unknown characteristic";
+ return;
+ }
+
+ if (characteristic.properties() & QLowEnergyCharacteristic::Read)
+ updateValueOfCharacteristic(charHandle, value, false);
+
+ emit service->characteristicWritten(characteristic, value);
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_characteristicUpdated(QLowEnergyHandle charHandle,
+ const QByteArray &value)
+{
+ // TODO: write/update notifications are quite similar (except asserts/warnings messages
+ // and different signals emitted). Merge them into one function?
+ Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle(0)");
+
+ ServicePrivate service(serviceForHandle(charHandle));
+ if (service.isNull()) {
+ // This can be an error (no characteristic found for this handle),
+ // it can also be that we set notify value before the service
+ // was reported (serviceDetailsDiscoveryFinished) - this happens,
+ // if we read a descriptor (characteristic client configuration),
+ // and it's (pre)set.
+ return;
+ }
+
+ QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
+ if (!characteristic.isValid()) {
+ qCWarning(QT_BT_OSX) << "unknown characteristic";
+ return;
+ }
+
+ if (characteristic.properties() & QLowEnergyCharacteristic::Read)
+ updateValueOfCharacteristic(charHandle, value, false);
+
+ emit service->characteristicChanged(characteristic, value);
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_descriptorRead(QLowEnergyHandle dHandle,
+ const QByteArray &value)
+{
+ Q_ASSERT_X(dHandle, Q_FUNC_INFO, "invalid descriptor handle (0)");
+
+ const QLowEnergyDescriptor qtDescriptor(descriptorForHandle(dHandle));
+ if (!qtDescriptor.isValid()) {
+ qCWarning(QT_BT_OSX) << "unknown descriptor" << dHandle;
+ return;
+ }
+
+ ServicePrivate service(serviceForHandle(qtDescriptor.characteristicHandle()));
+ updateValueOfDescriptor(qtDescriptor.characteristicHandle(), dHandle, value, false);
+ emit service->descriptorRead(qtDescriptor, value);
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_descriptorWritten(QLowEnergyHandle dHandle,
+ const QByteArray &value)
+{
+ Q_ASSERT_X(dHandle, Q_FUNC_INFO, "invalid descriptor handle (0)");
+
+ const QLowEnergyDescriptor qtDescriptor(descriptorForHandle(dHandle));
+ if (!qtDescriptor.isValid()) {
+ qCWarning(QT_BT_OSX) << "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 QLowEnergyControllerPrivateDarwin::_q_notificationEnabled(QLowEnergyHandle charHandle,
+ bool enabled)
+{
+ // CoreBluetooth in peripheral role does not allow mutable descriptors,
+ // in central we can only call setNotification:enabled/disabled.
+ // But from Qt API's point of view, a central has to write into
+ // client characteristic configuration descriptor. So here we emulate
+ // such a write (we cannot say if it's a notification or indication and
+ // report as both).
+
+ Q_ASSERT_X(role == QLowEnergyController::PeripheralRole, Q_FUNC_INFO,
+ "controller has an invalid role, 'peripheral' expected");
+ Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle (0)");
+
+ const QLowEnergyCharacteristic qtChar(characteristicForHandle(charHandle));
+ if (!qtChar.isValid()) {
+ qCWarning(QT_BT_OSX) << "unknown characteristic" << charHandle;
+ return;
+ }
+
+ const QLowEnergyDescriptor qtDescriptor =
+ qtChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
+ if (!qtDescriptor.isValid()) {
+ qCWarning(QT_BT_OSX) << "characteristic" << charHandle
+ << "does not have a client characteristic "
+ "descriptor";
+ return;
+ }
+
+ ServicePrivate service(serviceForHandle(charHandle));
+ if (service.data()) {
+ // It's a 16-bit value, the least significant bit is for notifications,
+ // the next one - for indications (thus 1 means notifications enabled,
+ // 2 - indications enabled).
+ // 3 is the maximum value and it means both enabled.
+ QByteArray value(2, 0);
+ if (enabled)
+ value[0] = 3;
+ updateValueOfDescriptor(charHandle, qtDescriptor.handle(), value, false);
+ emit service->descriptorWritten(qtDescriptor, value);
+ }
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_LEnotSupported()
+{
+ // Report as an error. But this should not be possible
+ // actually: before connecting to any device, we have
+ // to discover it, if it was discovered ... LE _must_
+ // be supported.
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_CBManagerError(QLowEnergyController::Error errorCode)
+{
+ // This function handles errors reported while connecting to a remote device
+ // and also other errors in general.
+ setError(errorCode);
+
+ if (state == QLowEnergyController::ConnectingState)
+ setState(QLowEnergyController::UnconnectedState);
+ else if (state == QLowEnergyController::DiscoveringState)
+ setState(QLowEnergyController::ConnectedState);
+
+ // In any other case we stay in Discovered, it's
+ // a service/characteristic - related error.
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_CBManagerError(const QBluetoothUuid &serviceUuid,
+ QLowEnergyController::Error errorCode)
+{
+ // Errors reported while discovering service details etc.
+ Q_UNUSED(errorCode) // TODO: setError?
+
+ // We failed to discover any characteristics/descriptors.
+ if (serviceList.contains(serviceUuid)) {
+ ServicePrivate qtService(serviceList.value(serviceUuid));
+ qtService->setState(QLowEnergyService::InvalidService);
+ } else {
+ qCDebug(QT_BT_OSX) << "error reported for unknown service"
+ << serviceUuid;
+ }
+}
+
+void QLowEnergyControllerPrivateDarwin::_q_CBManagerError(const QBluetoothUuid &serviceUuid,
+ QLowEnergyService::ServiceError errorCode)
+{
+ if (!serviceList.contains(serviceUuid)) {
+ qCDebug(QT_BT_OSX) << "unknown service uuid:"
+ << serviceUuid;
+ return;
+ }
+
+ ServicePrivate service(serviceList.value(serviceUuid));
+ service->setError(errorCode);
+}
+
+void QLowEnergyControllerPrivateDarwin::setNotifyValue(QSharedPointer<QLowEnergyServicePrivate> service,
+ QLowEnergyHandle charHandle,
+ const QByteArray &newValue)
+{
+ Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
+
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role (peripheral)";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return;
+ }
+
+ if (newValue.size() > 2) {
+ // Qt's API requires an error on such write.
+ // With Core Bluetooth we do not write any descriptor,
+ // but instead call a special method. So it's better to
+ // intercept wrong data size here:
+ qCWarning(QT_BT_OSX) << "client characteristic configuration descriptor"
+ "is 2 bytes, but value size is: " << newValue.size();
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return;
+ }
+
+ if (!serviceList.contains(service->uuid)) {
+ qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << "found";
+ return;
+ }
+
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_OSX) << "no characteristic with handle:"
+ << charHandle << "found";
+ return;
+ }
+
+ dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found");
+
+ ObjCCentralManager *manager = centralManager.getAs<ObjCCentralManager>();
+ const QBluetoothUuid serviceUuid(service->uuid);
+ const QByteArray newValueCopy(newValue);
+ dispatch_async(leQueue, ^{
+ [manager setNotifyValue:newValueCopy
+ forCharacteristic:charHandle
+ onService:serviceUuid];
+ });
+}
+
+void QLowEnergyControllerPrivateDarwin::readCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle)
+{
+ Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
+
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role (peripheral)";
+ return;
+ }
+
+ if (!serviceList.contains(service->uuid)) {
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
+ << service->uuid << "found";
+ return;
+ }
+
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_OSX) << "no characteristic with handle:"
+ << charHandle << "found";
+ return;
+ }
+
+ dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found");
+
+ // Attention! We have to copy UUID.
+ ObjCCentralManager *manager = centralManager.getAs<ObjCCentralManager>();
+ const QBluetoothUuid serviceUuid(service->uuid);
+ dispatch_async(leQueue, ^{
+ [manager readCharacteristic:charHandle onService:serviceUuid];
+ });
+}
+
+void QLowEnergyControllerPrivateDarwin::writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle, const QByteArray &newValue,
+ QLowEnergyService::WriteMode mode)
+{
+ Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
+
+ // We can work only with services found on a given peripheral
+ // (== created by the given LE controller).
+
+ if (!serviceList.contains(service->uuid) && !localServices.contains(service->uuid)) {
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
+ << service->uuid << " found";
+ return;
+ }
+
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_OSX) << "no characteristic with handle:"
+ << charHandle << " found";
+ return;
+ }
+
+ dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found");
+ // Attention! We have to copy objects!
+ const QByteArray newValueCopy(newValue);
+ if (role == QLowEnergyController::CentralRole) {
+ const QBluetoothUuid serviceUuid(service->uuid);
+ const auto manager = centralManager.getAs<ObjCCentralManager>();
+ dispatch_async(leQueue, ^{
+ [manager write:newValueCopy
+ charHandle:charHandle
+ onService:serviceUuid
+ withResponse:mode == QLowEnergyService::WriteWithResponse];
+ });
+ } else {
+#ifndef Q_OS_TVOS
+ const auto manager = peripheralManager.getAs<ObjCPeripheralManager>();
+ dispatch_async(leQueue, ^{
+ [manager write:newValueCopy charHandle:charHandle];
+ });
+#else
+ qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform";
+#endif
+ }
+}
+
+quint16 QLowEnergyControllerPrivateDarwin::updateValueOfCharacteristic(QLowEnergyHandle charHandle,
+ const QByteArray &value,
+ bool appendValue)
+{
+ QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
+ if (!service.isNull()) {
+ CharacteristicDataMap::iterator charIt = service->characteristicList.find(charHandle);
+ if (charIt != service->characteristicList.end()) {
+ QLowEnergyServicePrivate::CharData &charData = charIt.value();
+ if (appendValue)
+ charData.value += value;
+ else
+ charData.value = value;
+
+ return charData.value.size();
+ }
+ }
+
+ return 0;
+}
+
+void QLowEnergyControllerPrivateDarwin::readDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle)
+{
+ Q_UNUSED(charHandle) // Hehe, yes!
+
+ Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
+
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role (peripheral)";
+ return;
+ }
+
+ if (!serviceList.contains(service->uuid)) {
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
+ << service->uuid << "found";
+ return;
+ }
+
+ dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ if (!leQueue) {
+ qCWarning(QT_BT_OSX) << "no LE queue found";
+ return;
+ }
+ // Attention! Copy objects!
+ const QBluetoothUuid serviceUuid(service->uuid);
+ ObjCCentralManager * const manager = centralManager.getAs<ObjCCentralManager>();
+ dispatch_async(leQueue, ^{
+ [manager readDescriptor:descriptorHandle
+ onService:serviceUuid];
+ });
+}
+
+void QLowEnergyControllerPrivateDarwin::writeDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle,
+ const QByteArray &newValue)
+{
+ Q_UNUSED(charHandle)
+
+ Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
+
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role (peripheral)";
+ 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 (!serviceList.contains(service->uuid)) {
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
+ << service->uuid << " found";
+ return;
+ }
+
+ dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found");
+ // Attention! Copy objects!
+ const QBluetoothUuid serviceUuid(service->uuid);
+ ObjCCentralManager * const manager = centralManager.getAs<ObjCCentralManager>();
+ const QByteArray newValueCopy(newValue);
+ dispatch_async(leQueue, ^{
+ [manager write:newValueCopy
+ descHandle:descriptorHandle
+ onService:serviceUuid];
+ });
+}
+
+quint16 QLowEnergyControllerPrivateDarwin::updateValueOfDescriptor(QLowEnergyHandle charHandle, QLowEnergyHandle descHandle,
+ const QByteArray &value, bool appendValue)
+{
+ QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
+ if (!service.isNull()) {
+ CharacteristicDataMap::iterator charIt = service->characteristicList.find(charHandle);
+ if (charIt != service->characteristicList.end()) {
+ QLowEnergyServicePrivate::CharData &charData = charIt.value();
+
+ DescriptorDataMap::iterator descIt = charData.descriptorList.find(descHandle);
+ if (descIt != charData.descriptorList.end()) {
+ QLowEnergyServicePrivate::DescData &descDetails = descIt.value();
+
+ if (appendValue)
+ descDetails.value += value;
+ else
+ descDetails.value = value;
+
+ return descDetails.value.size();
+ }
+ }
+ }
+
+ return 0;
+}
+
+void QLowEnergyControllerPrivateDarwin::setErrorDescription(QLowEnergyController::Error errorCode)
+{
+ // This function does not emit!
+ // TODO: well, it is not a reason to duplicate a significant part of
+ // setError though!
+
+ error = errorCode;
+
+ switch (error) {
+ case QLowEnergyController::NoError:
+ errorString.clear();
+ break;
+ case QLowEnergyController::UnknownRemoteDeviceError:
+ errorString = QLowEnergyController::tr("Remote device cannot be found");
+ break;
+ case QLowEnergyController::InvalidBluetoothAdapterError:
+ errorString = QLowEnergyController::tr("Cannot find local adapter");
+ break;
+ case QLowEnergyController::NetworkError:
+ errorString = QLowEnergyController::tr("Error occurred during connection I/O");
+ break;
+ case QLowEnergyController::ConnectionError:
+ errorString = QLowEnergyController::tr("Error occurred trying to connect to remote device.");
+ break;
+ case QLowEnergyController::AdvertisingError:
+ errorString = QLowEnergyController::tr("Error occurred trying to start advertising");
+ break;
+ case QLowEnergyController::UnknownError:
+ default:
+ errorString = QLowEnergyController::tr("Unknown Error");
+ break;
+ }
+}
+
+bool QLowEnergyControllerPrivateDarwin::connectSlots(OSXBluetooth::LECBManagerNotifier *notifier)
+{
+ using OSXBluetooth::LECBManagerNotifier;
+
+ Q_ASSERT_X(notifier, Q_FUNC_INFO, "invalid notifier object (null)");
+
+ bool ok = connect(notifier, &LECBManagerNotifier::connected,
+ this, &QLowEnergyControllerPrivateDarwin::_q_connected);
+ ok = ok && connect(notifier, &LECBManagerNotifier::disconnected,
+ this, &QLowEnergyControllerPrivateDarwin::_q_disconnected);
+ ok = ok && connect(notifier, &LECBManagerNotifier::serviceDiscoveryFinished,
+ this, &QLowEnergyControllerPrivateDarwin::_q_serviceDiscoveryFinished);
+ ok = ok && connect(notifier, &LECBManagerNotifier::servicesWereModified,
+ this, &QLowEnergyControllerPrivateDarwin::_q_servicesWereModified);
+ ok = ok && connect(notifier, &LECBManagerNotifier::serviceDetailsDiscoveryFinished,
+ this, &QLowEnergyControllerPrivateDarwin::_q_serviceDetailsDiscoveryFinished);
+ ok = ok && connect(notifier, &LECBManagerNotifier::characteristicRead,
+ this, &QLowEnergyControllerPrivateDarwin::_q_characteristicRead);
+ ok = ok && connect(notifier, &LECBManagerNotifier::characteristicWritten,
+ this, &QLowEnergyControllerPrivateDarwin::_q_characteristicWritten);
+ ok = ok && connect(notifier, &LECBManagerNotifier::characteristicUpdated,
+ this, &QLowEnergyControllerPrivateDarwin::_q_characteristicUpdated);
+ ok = ok && connect(notifier, &LECBManagerNotifier::descriptorRead,
+ this, &QLowEnergyControllerPrivateDarwin::_q_descriptorRead);
+ ok = ok && connect(notifier, &LECBManagerNotifier::descriptorWritten,
+ this, &QLowEnergyControllerPrivateDarwin::_q_descriptorWritten);
+ ok = ok && connect(notifier, &LECBManagerNotifier::notificationEnabled,
+ this, &QLowEnergyControllerPrivateDarwin::_q_notificationEnabled);
+ ok = ok && connect(notifier, &LECBManagerNotifier::LEnotSupported,
+ this, &QLowEnergyControllerPrivateDarwin::_q_LEnotSupported);
+ ok = ok && connect(notifier, SIGNAL(CBManagerError(QLowEnergyController::Error)),
+ this, SLOT(_q_CBManagerError(QLowEnergyController::Error)));
+ ok = ok && connect(notifier, SIGNAL(CBManagerError(const QBluetoothUuid &, QLowEnergyController::Error)),
+ this, SLOT(_q_CBManagerError(const QBluetoothUuid &, QLowEnergyController::Error)));
+ ok = ok && connect(notifier, SIGNAL(CBManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)),
+ this, SLOT(_q_CBManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)));
+
+ if (!ok)
+ notifier->disconnect();
+
+ return ok;
+}
+
+void QLowEnergyControllerPrivateDarwin::startAdvertising(const QLowEnergyAdvertisingParameters &params,
+ const QLowEnergyAdvertisingData &advertisingData,
+ const QLowEnergyAdvertisingData &scanResponseData)
+{
+#ifdef Q_OS_TVOS
+ Q_UNUSED(params)
+ Q_UNUSED(advertisingData)
+ Q_UNUSED(scanResponseData)
+ qCWarning(QT_BT_OSX) << "advertising is not supported on your platform";
+#else
+
+ if (!isValid())
+ return _q_CBManagerError(QLowEnergyController::UnknownError);
+
+ if (role != QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "controller is not a peripheral, cannot start advertising";
+ return;
+ }
+
+ if (state != QLowEnergyController::UnconnectedState) {
+ qCWarning(QT_BT_OSX) << "invalid state" << state;
+ return;
+ }
+
+ auto leQueue(OSXBluetooth::qt_LE_queue());
+ if (!leQueue) {
+ qCWarning(QT_BT_OSX) << "no LE queue found";
+ setErrorDescription(QLowEnergyController::UnknownError);
+ return;
+ }
+
+ const auto manager = peripheralManager.getAs<ObjCPeripheralManager>();
+ [manager setParameters:params data:advertisingData scanResponse:scanResponseData];
+
+ setState(QLowEnergyController::AdvertisingState);
+
+ dispatch_async(leQueue, ^{
+ [manager startAdvertising];
+ });
+#endif
+}
+
+void QLowEnergyControllerPrivateDarwin::stopAdvertising()
+{
+#ifdef Q_OS_TVOS
+ qCWarning(QT_BT_OSX) << "advertising is not supported on your platform";
+#else
+ if (!isValid())
+ return _q_CBManagerError(QLowEnergyController::UnknownError);
+
+ if (state != QLowEnergyController::AdvertisingState) {
+ qCDebug(QT_BT_OSX) << "cannot stop advertising, called in state" << state;
+ return;
+ }
+
+ if (const auto leQueue = OSXBluetooth::qt_LE_queue()) {
+ const auto manager = peripheralManager.getAs<ObjCPeripheralManager>();
+ dispatch_sync(leQueue, ^{
+ [manager stopAdvertising];
+ });
+
+ setState(QLowEnergyController::UnconnectedState);
+ } else {
+ qCWarning(QT_BT_OSX) << "no LE queue found";
+ setErrorDescription(QLowEnergyController::UnknownError);
+ return;
+ }
+#endif
+}
+
+QT_END_NAMESPACE
+