summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qlowenergycontroller_osx.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/qlowenergycontroller_osx.mm')
-rw-r--r--src/bluetooth/qlowenergycontroller_osx.mm967
1 files changed, 967 insertions, 0 deletions
diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm
new file mode 100644
index 00000000..e3cf57b1
--- /dev/null
+++ b/src/bluetooth/qlowenergycontroller_osx.mm
@@ -0,0 +1,967 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "osx/osxbtutility_p.h"
+#include "osx/uistrings_p.h"
+
+#include "qlowenergyserviceprivate_p.h"
+#include "qlowenergycontroller_osx_p.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/qsysinfo.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qlist.h>
+
+#define OSX_D_PTR QLowEnergyControllerPrivateOSX *osx_d_ptr = static_cast<QLowEnergyControllerPrivateOSX *>(d_ptr)
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+
+class QLowEnergyControllerMetaTypes
+{
+public:
+ QLowEnergyControllerMetaTypes()
+ {
+ qRegisterMetaType<QLowEnergyController::ControllerState>();
+ qRegisterMetaType<QLowEnergyController::Error>();
+ }
+} qLowEnergyControllerMetaTypes;
+
+
+typedef QSharedPointer<QLowEnergyServicePrivate> ServicePrivate;
+
+// Convenience function, can return a smart pointer that 'isNull'.
+ServicePrivate qt_createLEService(QLowEnergyControllerPrivateOSX *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) << Q_FUNC_INFO << "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 QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0)
+ using OSXBluetooth::qt_OS_limit;
+ if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_6_0)) {
+ if (!cbService.isPrimary) {
+ // Our guess included/not was probably wrong.
+ newService->type &= ~QLowEnergyService::PrimaryService;
+ newService->type |= QLowEnergyService::IncludedService;
+ }
+ }
+ #endif
+ */
+ // No such property before 10_9/6_0.
+ 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;
+}
+
+}
+
+QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController *q)
+ : q_ptr(q),
+ isConnecting(false),
+ lastError(QLowEnergyController::NoError),
+ controllerState(QLowEnergyController::UnconnectedState),
+ addressType(QLowEnergyController::PublicAddress)
+{
+ // This is the "wrong" constructor - no valid device UUID to connect later.
+ Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)");
+ // We still create a manager, to simplify error handling later.
+ centralManager.reset([[ObjCCentralManager alloc] initWithDelegate:this]);
+ if (!centralManager) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO
+ << "failed to initialize central manager";
+ }
+}
+
+QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController *q,
+ const QBluetoothDeviceInfo &deviceInfo)
+ : q_ptr(q),
+ deviceUuid(deviceInfo.deviceUuid()),
+ isConnecting(false),
+ lastError(QLowEnergyController::NoError),
+ controllerState(QLowEnergyController::UnconnectedState),
+ addressType(QLowEnergyController::PublicAddress)
+{
+ Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)");
+ centralManager.reset([[ObjCCentralManager alloc] initWithDelegate:this]);
+ if (!centralManager) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO
+ << "failed to initialize central manager";
+ }
+}
+
+QLowEnergyControllerPrivateOSX::~QLowEnergyControllerPrivateOSX()
+{
+}
+
+bool QLowEnergyControllerPrivateOSX::isValid() const
+{
+ // isValid means only "was able to allocate all resources",
+ // nothing more.
+ return centralManager;
+}
+
+void QLowEnergyControllerPrivateOSX::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 QLowEnergyControllerPrivateOSX::connectSuccess()
+{
+ Q_ASSERT_X(controllerState == QLowEnergyController::ConnectingState,
+ Q_FUNC_INFO, "invalid state");
+
+ controllerState = QLowEnergyController::ConnectedState;
+
+ if (!isConnecting) {
+ emit q_ptr->stateChanged(QLowEnergyController::ConnectedState);
+ emit q_ptr->connected();
+ }
+}
+
+void QLowEnergyControllerPrivateOSX::serviceDiscoveryFinished(LEServices services)
+{
+ Q_ASSERT_X(controllerState == QLowEnergyController::DiscoveringState,
+ Q_FUNC_INFO, "invalid state");
+
+ using namespace OSXBluetooth;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ // 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.data()) {
+ const ServicePrivate newService(qt_createLEService(this, cbService, false));
+ if (!newService.data())
+ continue;
+ if (discoveredServices.contains(newService->uuid)) {
+ // It's a bit stupid we first created it ...
+ qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "discovered service with a duplicated UUID "
+ << newService->uuid;
+ continue;
+ }
+ discoveredServices.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 (discoveredServices.contains(uuid) && discoveredCBServices.value(uuid) == s) {
+ ServicePrivate qtService(discoveredServices.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 (discoveredServices.contains(uuid)) {
+ if (discoveredCBServices.value(uuid) == s) {
+ ServicePrivate qtService(discoveredServices.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));
+ discoveredServices.insert(newService->uuid, newService);
+ discoveredCBServices.insert(newService->uuid, s);
+ }
+ }
+
+ toVisit.resetWithoutRetain(toVisitNext.take());
+ toVisitNext.resetWithoutRetain([[NSMutableArray alloc] init]);
+ }
+ } else {
+ qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no services found";
+ }
+
+ foreach (const QBluetoothUuid &uuid, discoveredServices.keys()) {
+ QMetaObject::invokeMethod(q_ptr, "serviceDiscovered", Qt::QueuedConnection,
+ Q_ARG(QBluetoothUuid, uuid));
+ }
+
+ controllerState = QLowEnergyController::DiscoveredState;
+ QMetaObject::invokeMethod(q_ptr, "stateChanged", Qt::QueuedConnection,
+ Q_ARG(QLowEnergyController::ControllerState, controllerState));
+ QMetaObject::invokeMethod(q_ptr, "discoveryFinished", Qt::QueuedConnection);
+}
+
+void QLowEnergyControllerPrivateOSX::serviceDetailsDiscoveryFinished(LEService service)
+{
+ Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if (!discoveredServices.contains(service->uuid)) {
+ qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "unknown service uuid: "
+ << service->uuid;
+ return;
+ }
+
+ ServicePrivate qtService(discoveredServices.value(service->uuid));
+ // Assert on handles?
+ qtService->startHandle = service->startHandle;
+ qtService->endHandle = service->endHandle;
+ qtService->characteristicList = service->characteristicList;
+
+ qtService->setState(QLowEnergyService::ServiceDiscovered);
+}
+
+void QLowEnergyControllerPrivateOSX::characteristicReadNotification(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) << Q_FUNC_INFO << "unknown characteristic";
+ return;
+ }
+
+ if (characteristic.properties() & QLowEnergyCharacteristic::Read)
+ updateValueOfCharacteristic(charHandle, value, false);
+
+ emit service->characteristicRead(characteristic, value);
+}
+
+void QLowEnergyControllerPrivateOSX::characteristicWriteNotification(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) << Q_FUNC_INFO << "can not find service for characteristic handle "
+ << charHandle;
+ return;
+ }
+
+ QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
+ if (!characteristic.isValid()) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown characteristic";
+ return;
+ }
+
+ if (characteristic.properties() & QLowEnergyCharacteristic::Read)
+ updateValueOfCharacteristic(charHandle, value, false);
+
+ emit service->characteristicWritten(characteristic, value);
+}
+
+void QLowEnergyControllerPrivateOSX::characteristicUpdateNotification(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) << Q_FUNC_INFO << "unknown characteristic";
+ return;
+ }
+
+ if (characteristic.properties() & QLowEnergyCharacteristic::Read)
+ updateValueOfCharacteristic(charHandle, value, false);
+
+ emit service->characteristicChanged(characteristic, value);
+}
+
+void QLowEnergyControllerPrivateOSX::descriptorReadNotification(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) << Q_FUNC_INFO << "unknown descriptor " << dHandle;
+ return;
+ }
+
+ ServicePrivate service(serviceForHandle(qtDescriptor.characteristicHandle()));
+ updateValueOfDescriptor(qtDescriptor.characteristicHandle(), dHandle, value, false);
+ emit service->descriptorRead(qtDescriptor, value);
+}
+
+void QLowEnergyControllerPrivateOSX::descriptorWriteNotification(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) << Q_FUNC_INFO << "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;
+
+ if (!isConnecting) {
+ emit q_ptr->stateChanged(QLowEnergyController::UnconnectedState);
+ emit q_ptr->disconnected();
+ }
+}
+
+void QLowEnergyControllerPrivateOSX::error(QLowEnergyController::Error errorCode)
+{
+ // Errors reported during connect and general errors.
+
+ // We're still in connectToDevice,
+ // some error was reported synchronously.
+ // Return, the error will be correctly set later
+ // by connectToDevice.
+ if (isConnecting) {
+ lastError = errorCode;
+ return;
+ }
+
+ setErrorDescription(errorCode);
+ emit q_ptr->error(lastError);
+
+ if (controllerState == QLowEnergyController::ConnectingState) {
+ controllerState = QLowEnergyController::UnconnectedState;
+ emit q_ptr->stateChanged(controllerState);
+ } else if (controllerState == QLowEnergyController::DiscoveringState) {
+ controllerState = QLowEnergyController::ConnectedState;
+ emit q_ptr->stateChanged(controllerState);
+ } // In any other case we stay in Discovered, it's
+ // a service/characteristic - related error.
+}
+
+void QLowEnergyControllerPrivateOSX::error(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 (discoveredServices.contains(serviceUuid)) {
+ ServicePrivate qtService(discoveredServices.value(serviceUuid));
+ qtService->setState(QLowEnergyService::InvalidService);
+ } else {
+ qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "error reported for unknown service "
+ << serviceUuid;
+ }
+}
+
+void QLowEnergyControllerPrivateOSX::error(const QBluetoothUuid &serviceUuid,
+ QLowEnergyService::ServiceError errorCode)
+{
+ if (!discoveredServices.contains(serviceUuid)) {
+ qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "unknown service uuid: "
+ << serviceUuid;
+ return;
+ }
+
+ ServicePrivate service(discoveredServices.value(serviceUuid));
+ service->setError(errorCode);
+}
+
+void QLowEnergyControllerPrivateOSX::connectToDevice()
+{
+ Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller");
+ Q_ASSERT_X(controllerState == QLowEnergyController::UnconnectedState,
+ Q_FUNC_INFO, "invalid state");
+ Q_ASSERT_X(!deviceUuid.isNull(), Q_FUNC_INFO,
+ "invalid private controller (no device uuid)");
+ Q_ASSERT_X(!isConnecting, Q_FUNC_INFO,
+ "recursive connectToDevice call");
+
+ setErrorDescription(QLowEnergyController::NoError);
+
+ isConnecting = true;// Do not emit signals if some callback is executed synchronously.
+ controllerState = QLowEnergyController::ConnectingState;
+ const QLowEnergyController::Error status = [centralManager connectToDevice:deviceUuid];
+ isConnecting = false;
+
+ if (status == QLowEnergyController::NoError && lastError == QLowEnergyController::NoError) {
+ emit q_ptr->stateChanged(controllerState);
+ if (controllerState == QLowEnergyController::ConnectedState) {
+ // If a peripheral is connected already from the Core Bluetooth's
+ // POV:
+ emit q_ptr->connected();
+ } else if (controllerState == QLowEnergyController::UnconnectedState) {
+ // Ooops, tried to connect, got peripheral disconnect instead -
+ // this happens with Core Bluetooth.
+ emit q_ptr->disconnected();
+ }
+ } else if (status != QLowEnergyController::NoError) {
+ error(status);
+ } else {
+ // Re-set the error/description and emit.
+ error(lastError);
+ }
+}
+
+void QLowEnergyControllerPrivateOSX::discoverServices()
+{
+ Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller");
+ Q_ASSERT_X(controllerState != QLowEnergyController::UnconnectedState,
+ Q_FUNC_INFO, "not connected to peripheral");
+
+ controllerState = QLowEnergyController::DiscoveringState;
+ emit q_ptr->stateChanged(QLowEnergyController::DiscoveringState);
+ [centralManager discoverServices];
+}
+
+void QLowEnergyControllerPrivateOSX::discoverServiceDetails(const QBluetoothUuid &serviceUuid)
+{
+ Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller");
+
+ if (controllerState != QLowEnergyController::DiscoveredState) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO
+ << "can not discover service details in the current state, "
+ << "QLowEnergyController::DiscoveredState is expected";
+ return;
+ }
+
+ if (!discoveredServices.contains(serviceUuid)) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown service: " << serviceUuid;
+ return;
+ }
+
+ ServicePrivate qtService(discoveredServices.value(serviceUuid));
+ if ([centralManager discoverServiceDetails:serviceUuid]) {
+ qtService->setState(QLowEnergyService::DiscoveringServices);
+ } else {
+ // The error is returned by CentralManager - no
+ // service with a given UUID found on a peripheral.
+ qtService->setState(QLowEnergyService::InvalidService);
+ }
+}
+
+void QLowEnergyControllerPrivateOSX::setNotifyValue(QSharedPointer<QLowEnergyServicePrivate> service,
+ QLowEnergyHandle charHandle,
+ const QByteArray &newValue)
+{
+ Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
+ Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+
+ 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) << Q_FUNC_INFO << "client characteristic configuration descriptor "
+ "is 2 bytes, but value size is: " << newValue.size();
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return;
+ }
+
+ if (!discoveredServices.contains(service->uuid)) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid: "
+ << service->uuid << " found";
+ return;
+ }
+
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle: "
+ << charHandle << " found";
+ return;
+ }
+
+ if (![centralManager setNotifyValue:newValue forCharacteristic:charHandle])
+ service->setError(QLowEnergyService::DescriptorWriteError);
+}
+
+void QLowEnergyControllerPrivateOSX::readCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service,
+ QLowEnergyHandle charHandle)
+{
+ Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
+ Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+
+ if (!discoveredServices.contains(service->uuid)) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid:"
+ << service->uuid << "found";
+ return;
+ }
+
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle:"
+ << charHandle << "found";
+ return;
+ }
+
+ if (![centralManager readCharacteristic:charHandle])
+ service->setError(QLowEnergyService::CharacteristicReadError);
+}
+
+void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service,
+ QLowEnergyHandle charHandle, const QByteArray &newValue,
+ bool writeWithResponse)
+{
+ Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
+ Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+
+ // 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) << Q_FUNC_INFO << "no service with uuid: "
+ << service->uuid << " found";
+ return;
+ }
+
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle: "
+ << charHandle << " found";
+ return;
+ }
+
+ const bool result = [centralManager write:newValue
+ charHandle:charHandle
+ withResponse:writeWithResponse];
+ if (!result)
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+}
+
+quint16 QLowEnergyControllerPrivateOSX::updateValueOfCharacteristic(QLowEnergyHandle charHandle,
+ const QByteArray &value,
+ bool appendValue)
+{
+ QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
+ if (!service.isNull() && service->characteristicList.contains(charHandle)) {
+ if (appendValue)
+ service->characteristicList[charHandle].value += value;
+ else
+ service->characteristicList[charHandle].value = value;
+
+ return service->characteristicList[charHandle].value.size();
+ }
+
+ return 0;
+}
+
+void QLowEnergyControllerPrivateOSX::readDescriptor(QSharedPointer<QLowEnergyServicePrivate> service,
+ QLowEnergyHandle descriptorHandle)
+{
+ Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
+ Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+
+ if (!discoveredServices.contains(service->uuid)) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid:"
+ << service->uuid << "found";
+ return;
+ }
+
+ if (![centralManager readDescriptor:descriptorHandle])
+ service->setError(QLowEnergyService::DescriptorReadError);
+}
+
+void QLowEnergyControllerPrivateOSX::writeDescriptor(QSharedPointer<QLowEnergyServicePrivate> service,
+ QLowEnergyHandle descriptorHandle,
+ const QByteArray &newValue)
+{
+ Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
+ Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+
+ // 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) << Q_FUNC_INFO << "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)
+{
+ foreach (QSharedPointer<QLowEnergyServicePrivate> service, discoveredServices.values()) {
+ if (service->startHandle <= handle && handle <= service->endHandle)
+ return service;
+ }
+
+ return QSharedPointer<QLowEnergyServicePrivate>();
+}
+
+QLowEnergyCharacteristic QLowEnergyControllerPrivateOSX::characteristicForHandle(QLowEnergyHandle charHandle)
+{
+ QSharedPointer<QLowEnergyServicePrivate> service(serviceForHandle(charHandle));
+ if (service.isNull())
+ return QLowEnergyCharacteristic();
+
+ if (service->characteristicList.isEmpty())
+ return QLowEnergyCharacteristic();
+
+ // Check whether it is the handle of a characteristic header
+ if (service->characteristicList.contains(charHandle))
+ return QLowEnergyCharacteristic(service, charHandle);
+
+ // Check whether it is the handle of the characteristic value or its descriptors
+ QList<QLowEnergyHandle> charHandles(service->characteristicList.keys());
+ std::sort(charHandles.begin(), charHandles.end());
+
+ for (int i = charHandles.size() - 1; i >= 0; --i) {
+ if (charHandles.at(i) > charHandle)
+ continue;
+
+ return QLowEnergyCharacteristic(service, charHandles.at(i));
+ }
+
+ 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!
+
+ lastError = errorCode;
+
+ switch (lastError) {
+ case QLowEnergyController::NoError:
+ errorString.clear();
+ break;
+ case QLowEnergyController::UnknownRemoteDeviceError:
+ errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_RDEV_NO_FOUND);
+ break;
+ case QLowEnergyController::InvalidBluetoothAdapterError:
+ errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_NO_LOCAL_DEV);
+ break;
+ case QLowEnergyController::NetworkError:
+ errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_IO_ERROR);
+ break;
+ case QLowEnergyController::UnknownError:
+ default:
+ errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_UNKNOWN_ERROR);
+ break;
+ }
+}
+
+void QLowEnergyControllerPrivateOSX::invalidateServices()
+{
+ foreach (const QSharedPointer<QLowEnergyServicePrivate> service, discoveredServices.values()) {
+ service->setController(Q_NULLPTR);
+ service->setState(QLowEnergyService::InvalidService);
+ }
+
+ discoveredServices.clear();
+}
+
+QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddress,
+ QObject *parent)
+ : QObject(parent),
+ d_ptr(new QLowEnergyControllerPrivateOSX(this))
+{
+ OSX_D_PTR;
+
+ osx_d_ptr->remoteAddress = remoteAddress;
+ osx_d_ptr->localAddress = QBluetoothLocalDevice().address();
+
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "construction with remote address "
+ "is not supported!";
+}
+
+QLowEnergyController::QLowEnergyController(const QBluetoothDeviceInfo &remoteDevice,
+ QObject *parent)
+ : QObject(parent),
+ d_ptr(new QLowEnergyControllerPrivateOSX(this, remoteDevice))
+{
+ OSX_D_PTR;
+
+ osx_d_ptr->localAddress = QBluetoothLocalDevice().address();
+ // That's the only "real" ctor - with Core Bluetooth we need a _valid_ deviceUuid
+ // from 'remoteDevice'.
+}
+
+QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddress,
+ const QBluetoothAddress &localAddress,
+ QObject *parent)
+ : QObject(parent),
+ d_ptr(new QLowEnergyControllerPrivateOSX(this))
+{
+ OSX_D_PTR;
+
+ osx_d_ptr->remoteAddress = remoteAddress;
+ osx_d_ptr->localAddress = localAddress;
+
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "construction with remote/local "
+ "addresses is not supported!";
+}
+
+QLowEnergyController::~QLowEnergyController()
+{
+ // Deleting a peripheral will also disconnect.
+ delete d_ptr;
+}
+
+QBluetoothAddress QLowEnergyController::localAddress() const
+{
+ OSX_D_PTR;
+
+ return osx_d_ptr->localAddress;
+}
+
+QBluetoothAddress QLowEnergyController::remoteAddress() const
+{
+ OSX_D_PTR;
+
+ return osx_d_ptr->remoteAddress;
+}
+
+QLowEnergyController::ControllerState QLowEnergyController::state() const
+{
+ OSX_D_PTR;
+
+ return osx_d_ptr->controllerState;
+}
+
+QLowEnergyController::RemoteAddressType QLowEnergyController::remoteAddressType() const
+{
+ OSX_D_PTR;
+
+ return osx_d_ptr->addressType;
+}
+
+void QLowEnergyController::setRemoteAddressType(RemoteAddressType type)
+{
+ Q_UNUSED(type)
+
+ OSX_D_PTR;
+
+ osx_d_ptr->addressType = type;
+}
+
+void QLowEnergyController::connectToDevice()
+{
+ OSX_D_PTR;
+
+ // A memory allocation problem.
+ if (!osx_d_ptr->isValid())
+ return osx_d_ptr->error(UnknownError);
+
+ // No QBluetoothDeviceInfo provided during construction.
+ if (osx_d_ptr->deviceUuid.isNull())
+ return osx_d_ptr->error(UnknownRemoteDeviceError);
+
+ if (osx_d_ptr->controllerState != UnconnectedState)
+ return;
+
+ osx_d_ptr->connectToDevice();
+}
+
+void QLowEnergyController::disconnectFromDevice()
+{
+ if (state() == UnconnectedState || state() == ClosingState)
+ return;
+
+ OSX_D_PTR;
+
+ if (osx_d_ptr->isValid()) {
+ const ControllerState oldState = osx_d_ptr->controllerState;
+
+ osx_d_ptr->controllerState = ClosingState;
+ emit stateChanged(ClosingState);
+ osx_d_ptr->invalidateServices();
+ [osx_d_ptr->centralManager disconnectFromDevice];
+
+ if (oldState == 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.
+ osx_d_ptr->controllerState = UnconnectedState;
+ emit stateChanged(UnconnectedState);
+ }
+ }
+}
+
+void QLowEnergyController::discoverServices()
+{
+ if (state() != ConnectedState)
+ return;
+
+ OSX_D_PTR;
+
+ osx_d_ptr->discoverServices();
+}
+
+QList<QBluetoothUuid> QLowEnergyController::services() const
+{
+ OSX_D_PTR;
+
+ return osx_d_ptr->discoveredServices.keys();
+}
+
+QLowEnergyService *QLowEnergyController::createServiceObject(const QBluetoothUuid &serviceUuid,
+ QObject *parent)
+{
+ OSX_D_PTR;
+
+ if (!osx_d_ptr->discoveredServices.contains(serviceUuid))
+ return Q_NULLPTR;
+
+ return new QLowEnergyService(osx_d_ptr->discoveredServices.value(serviceUuid), parent);
+}
+
+QLowEnergyController::Error QLowEnergyController::error() const
+{
+ OSX_D_PTR;
+
+ return osx_d_ptr->lastError;
+}
+
+QString QLowEnergyController::errorString() const
+{
+ OSX_D_PTR;
+
+ return osx_d_ptr->errorString;
+}
+
+QT_END_NAMESPACE