summaryrefslogtreecommitdiffstats
path: root/src/bluetooth
diff options
context:
space:
mode:
authorTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-11-07 14:00:40 +0100
committerTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-11-12 14:32:35 +0100
commit51dbe4a457c0bf81b96c898aab5d83b421e7a4f0 (patch)
treecc1c878a608c0d03ea7c67367548cc7b73322aa8 /src/bluetooth
parentbe5883540fe61123da3fb39dd39cfd34eec442b8 (diff)
QLowEnergyController - connectTo/disconnectFrom/Device (OS X, iOS)
Implement connect to device/disconnect from device methods, add CentralManager class dealing with CBCentralManager (with additional external logic). - Improve error handling while in 'connecting' state. - Use the proper error (ConnectionError) instead of UnknownError + make the error handling more uniform while isConnecting == true. - Use isNull on QBluetoothUuid, no need in ugly 'hasUuid'. Change-Id: I84a704a746b4677ccb870b9c132db5f7359030c6 Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
Diffstat (limited to 'src/bluetooth')
-rw-r--r--src/bluetooth/osx/osxbt.pri6
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager.mm448
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager_p.h128
-rw-r--r--src/bluetooth/qlowenergycontroller_osx.mm179
-rw-r--r--src/bluetooth/qlowenergycontroller_osx_p.h27
5 files changed, 780 insertions, 8 deletions
diff --git a/src/bluetooth/osx/osxbt.pri b/src/bluetooth/osx/osxbt.pri
index 288afa6d..1eddcf4c 100644
--- a/src/bluetooth/osx/osxbt.pri
+++ b/src/bluetooth/osx/osxbt.pri
@@ -12,7 +12,8 @@ CONFIG(osx) {
osx/osxbtobexsession_p.h \
osx/osxbtledeviceinquiry_p.h \
osx/corebluetoothwrapper_p.h \
- osx/osxbtcentralmanagerdelegate_p.h
+ osx/osxbtcentralmanagerdelegate_p.h \
+ osx/osxbtcentralmanager_p.h
OBJECTIVE_SOURCES += osx/osxbtutility.mm \
osx/osxbtdevicepair.mm \
@@ -26,7 +27,8 @@ CONFIG(osx) {
osx/osxbtsocketlistener.mm \
osx/osxbtobexsession.mm \
osx/osxbtledeviceinquiry.mm \
- osx/osxbtcentralmanagerdelegate.mm
+ osx/osxbtcentralmanagerdelegate.mm \
+ osx/osxbtcentralmanager.mm
} else {
PRIVATE_HEADERS += osx/osxbtutility_p.h \
osx/osxbtledeviceinquiry_p.h \
diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm
new file mode 100644
index 00000000..d2e45832
--- /dev/null
+++ b/src/bluetooth/osx/osxbtcentralmanager.mm
@@ -0,0 +1,448 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/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 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "osxbtcentralmanagerdelegate_p.h"
+#include "osxbtcentralmanager_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qdebug.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+namespace OSXBluetooth {
+
+CentralManagerDelegate::~CentralManagerDelegate()
+{
+}
+
+}
+
+QT_END_NAMESPACE
+
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+@interface QT_MANGLE_NAMESPACE(OSXBTCentralManager) (PrivateAPI)
+
+- (QLowEnergyController::Error)connectToDevice; // "Device" is in Qt's world ...
+- (void)connectToPeripheral; // "Peripheral" is in Core Bluetooth.
+- (bool)connected;
+
+@end
+
+@implementation QT_MANGLE_NAMESPACE(OSXBTCentralManager)
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth)::CentralManagerDelegate *)aDelegate
+{
+ Q_ASSERT_X(aDelegate, "-initWithDelegate:", "invalid delegate (null)");
+
+ if (self = [super init]) {
+ manager = nil;
+ managerState = OSXBluetooth::CentralManagerIdle;
+ disconnectPending = false;
+ peripheral = nil;
+ delegate = aDelegate;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ typedef QT_MANGLE_NAMESPACE(OSXBTCentralManagerTransientDelegate) TransientDelegate;
+
+ if (managerState == OSXBluetooth::CentralManagerUpdating) {
+ // Here we have to trick with a transient delegate not to
+ // delete the manager too early - or Core Bluetooth will crash.
+ // State was not updated yet, too early to release.
+ TransientDelegate *const transient = [[TransientDelegate alloc] initWithManager:manager];
+ // On ARC the lifetime of a transient delegate will become a problem, since delegate itself
+ // is a weak reference in a manager.
+ [manager setDelegate:transient];
+ } else {
+ [manager setDelegate:nil];
+ [manager release];
+ }
+
+ [peripheral setDelegate:nil];
+ [peripheral release];
+
+ [super dealloc];
+}
+
+- (QLowEnergyController::Error)connectToDevice:(const QBluetoothUuid &)aDeviceUuid
+{
+ Q_ASSERT_X(managerState == OSXBluetooth::CentralManagerIdle, "-connectToDevice",
+ "invalid state"); // QLowEnergyController connects only if in UnconnectedState.
+
+ deviceUuid = aDeviceUuid;
+
+ if (!manager) {
+ managerState = OSXBluetooth::CentralManagerUpdating; // We'll have to wait for updated state.
+ manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
+ if (!manager) {
+ managerState = OSXBluetooth::CentralManagerIdle;
+ qCWarning(QT_BT_OSX) << "-connectToDevice:, failed to allocate a "
+ "central manager";
+ return QLowEnergyController::ConnectionError;
+ }
+ return QLowEnergyController::NoError;
+ } else {
+ return [self connectToDevice];
+ }
+}
+
+- (QLowEnergyController::Error)connectToDevice
+{
+ Q_ASSERT_X(manager, "-connectToDevice", "invalid central manager (nil)");
+ Q_ASSERT_X(managerState == OSXBluetooth::CentralManagerIdle,
+ "-connectToDevice", "invalid state");
+
+ if ([self isConnected]) {
+ qCDebug(QT_BT_OSX) << "-connectToDevice, already connected";
+ delegate->connectSuccess();
+ return QLowEnergyController::NoError;
+ } else if (peripheral) {
+ // Was retrieved already, but not connected
+ // or disconnected.
+ [self connectToPeripheral];
+ return QLowEnergyController::NoError;
+ }
+
+ using namespace OSXBluetooth;
+
+ // Retrieve a peripheral first ...
+ ObjCScopedPointer<NSMutableArray> uuids([[NSMutableArray alloc] init]);
+ if (!uuids) {
+ qCWarning(QT_BT_OSX) << "-connectToDevice, failed to allocate identifiers";
+ return QLowEnergyController::ConnectionError;
+ }
+
+#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0)
+ const quint128 qtUuidData(deviceUuid.toUInt128());
+ // STATIC_ASSERT on sizes would be handy!
+ uuid_t uuidData = {};
+ std::copy(qtUuidData.data, qtUuidData.data + 16, uuidData);
+ const ObjCScopedPointer<NSUUID> nsUuid([[NSUUID alloc] initWithUUIDBytes:uuidData]);
+ if (!nsUuid) {
+ qCWarning(QT_BT_OSX) << "-connectToDevice, failed to allocate NSUUID identifier";
+ return QLowEnergyController::ConnectionError;
+ }
+
+ [uuids addObject:nsUuid];
+
+ // With the latest CoreBluetooth, we can synchronously retrive peripherals:
+ QT_BT_MAC_AUTORELEASEPOOL;
+ NSArray *const peripherals = [manager retrievePeripheralsWithIdentifiers:uuids];
+ if (!peripherals || peripherals.count != 1) {
+ qCWarning(QT_BT_OSX) << "-connectToDevice, failed to retrive a peripheral";
+ return QLowEnergyController::UnknownRemoteDeviceError;
+ }
+
+ peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain];
+ [self connectToPeripheral];
+
+ return QLowEnergyController::NoError;
+#else
+ OSXBluetooth::CFStrongReference<CFUUIDRef> cfUuid(OSXBluetooth::cf_uuid(deviceUuid));
+ if (!cfUuid) {
+ qCWarning(QT_BT_OSX) << "-connectToDevice:, failed to create CFUUID object";
+ return QLowEnergyController::ConnectionError;
+ }
+ // TODO: With ARC this cast will be illegal:
+ [uuids addObject:(id)cfUuid.data()];
+ // Unfortunately, with old Core Bluetooth this call is asynchronous ...
+ managerState = OSXBluetooth::CentralManagerConnecting;
+ [manager retrievePeripherals:uuids];
+
+ return QLowEnergyController::NoError;
+#endif
+}
+
+- (void)connectToPeripheral
+{
+ Q_ASSERT_X(manager, "-connectToPeripheral", "invalid central manager (nil)");
+ Q_ASSERT_X(peripheral, "-connectToPeripheral", "invalid peripheral (nil)");
+ Q_ASSERT_X(managerState == OSXBluetooth::CentralManagerIdle,
+ "-connectToPeripheral", "invalid state");
+
+ // The state is still the same - connecting.
+ if ([self isConnected]) {
+ qCDebug(QT_BT_OSX) << "-connectToPeripheral, already connected";
+ delegate->connectSuccess();
+ } else {
+ qCDebug(QT_BT_OSX) << "-connectToPeripheral, trying to connect";
+ managerState = OSXBluetooth::CentralManagerConnecting;
+ [manager connectPeripheral:peripheral options:nil];
+ }
+}
+
+- (bool)isConnected
+{
+ if (!peripheral)
+ return false;
+
+#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_7_0)
+ return peripheral.state == CBPeripheralStateConnected;
+#else
+ return peripheral.isConnected;
+#endif
+}
+
+- (void)disconnectFromDevice
+{
+ if (managerState == OSXBluetooth::CentralManagerUpdating) {
+ disconnectPending = true;
+ } else {
+ disconnectPending = false;
+
+ if ([self isConnected]) {
+ Q_ASSERT_X(peripheral, "-disconnectFromDevice", "invalid peripheral (nil)");
+ Q_ASSERT_X(manager, "-disconnectFromDevice", "invalid central manager (nil)");
+ managerState = OSXBluetooth::CentralManagerDisconnecting;
+ [manager cancelPeripheralConnection:peripheral];
+ } else {
+ managerState = OSXBluetooth::CentralManagerIdle;
+ delegate->disconnected();
+ }
+ }
+}
+
+- (void)discoverServices
+{
+}
+
+- (bool)discoverServiceDetails:(const QBluetoothUuid &)serviceUuid
+{
+ Q_UNUSED(serviceUuid)
+ return false;
+}
+
+- (bool)discoverCharacteristics:(const QBluetoothUuid &)serviceUuid
+{
+ Q_UNUSED(serviceUuid)
+ return false;
+}
+
+// CBCentralManagerDelegate (the real one).
+
+- (void)centralManagerDidUpdateState:(CBCentralManager *)central
+{
+ Q_ASSERT_X(delegate, "-centralManagerDidUpdateState:", "invalid delegate (null)");
+
+ using namespace OSXBluetooth;
+
+ const CBCentralManagerState state = central.state;
+
+ if (state == CBCentralManagerStateUnknown
+ || state == CBCentralManagerStateResetting) {
+ // We still have to wait, docs say:
+ // "The current state of the central manager is unknown;
+ // an update is imminent." or
+ // "The connection with the system service was momentarily
+ // lost; an update is imminent."
+ return;
+ }
+
+ if (disconnectPending) {
+ disconnectPending = false;
+ managerState = OSXBluetooth::CentralManagerIdle;
+ return [self disconnectFromDevice];
+ }
+
+ // Let's check some states we do not like first:
+ if (state == CBCentralManagerStateUnsupported || state == CBCentralManagerStateUnauthorized) {
+ if (managerState == CentralManagerUpdating) {
+ // We tried to connect just to realize, LE is not supported. Report this.
+ managerState = CentralManagerIdle;
+ delegate->LEnotSupported();
+ } else {
+ // TODO: if we are here, LE _was_ supported and we first managed to update
+ // and reset managerState from CentralManagerUpdating.
+ managerState = CentralManagerIdle;
+ delegate->error(QLowEnergyController::InvalidBluetoothAdapterError);
+ }
+ return;
+ }
+
+ if (state == CBCentralManagerStatePoweredOff) {
+ if (managerState == CentralManagerUpdating) {
+ // I've seen this instead of Unsopported on OS X.
+ managerState = CentralManagerIdle;
+ delegate->LEnotSupported();
+ } else {
+ managerState = CentralManagerIdle;
+ // TODO: we need a better error +
+ // what will happen if later the state changes to PoweredOn???
+ delegate->error(QLowEnergyController::InvalidBluetoothAdapterError);
+ }
+ return;
+ }
+
+ if (state == CBCentralManagerStatePoweredOn) {
+ if (managerState == CentralManagerUpdating) {
+ managerState = CentralManagerIdle;
+ const QLowEnergyController::Error status = [self connectToDevice];
+ if (status != QLowEnergyController::NoError)// An allocation problem?
+ delegate->error(status);
+ }
+ } else {
+ // We actually handled all known states, but .. Core Bluetooth can change?
+ Q_ASSERT_X(0, "-centralManagerDidUpdateState:", "invalid centra's state");
+ }
+}
+
+- (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals
+{
+ Q_UNUSED(central)
+
+ // This method is required for iOS before 7.0 and OS X below 10.9.
+ Q_ASSERT_X(manager, "-centralManager:didRetrivePeripherals:", "invalid central manager (nil)");
+
+ if (managerState != OSXBluetooth::CentralManagerConnecting) {
+ // Canceled by calling -disconnectFromDevice method.
+ return;
+ }
+
+ managerState = OSXBluetooth::CentralManagerIdle;
+
+ if (!peripherals || peripherals.count != 1) {
+ Q_ASSERT_X(delegate, "-centralManager:didRetrievePeripherals:",
+ "invalid delegate (null)");
+
+ qCDebug(QT_BT_OSX) << "-centralManager:didRetrievePeripherals:, "
+ "unexpected number of peripherals (!= 1)";
+
+ delegate->error(QLowEnergyController::UnknownRemoteDeviceError);
+ } else {
+ peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain];
+ [self connectToPeripheral];
+ }
+}
+
+- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)aPeripheral
+{
+ Q_UNUSED(central)
+ Q_UNUSED(aPeripheral)
+
+ Q_ASSERT_X(delegate, "-centralManager:didConnectPeripheral:",
+ "invalid delegate (null)");
+
+ if (managerState != OSXBluetooth::CentralManagerConnecting) {
+ // We called cancel but before disconnected, managed to connect?
+ return;
+ }
+
+ managerState = OSXBluetooth::CentralManagerIdle;
+ delegate->connectSuccess();
+}
+
+- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)aPeripheral
+ error:(NSError *)error
+{
+ Q_UNUSED(central)
+ Q_UNUSED(aPeripheral)
+ Q_UNUSED(error)
+
+ Q_ASSERT_X(delegate, "-centralManager:didFailToConnectPeripheral:",
+ "invalid delegate (null)");
+
+ if (managerState != OSXBluetooth::CentralManagerConnecting) {
+ // Canceled already.
+ return;
+ }
+
+ managerState = OSXBluetooth::CentralManagerIdle;
+ // TODO: better error mapping is required.
+ delegate->error(QLowEnergyController::UnknownRemoteDeviceError);
+}
+
+- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)aPeripheral
+ error:(NSError *)error
+{
+ Q_UNUSED(central)
+ Q_UNUSED(aPeripheral)
+
+ Q_ASSERT_X(delegate, "-centralManager:didDisconnectPeripheral:error:",
+ "invalid delegate (null)");
+
+ if (error && managerState == OSXBluetooth::CentralManagerDisconnecting) {
+ managerState = OSXBluetooth::CentralManagerIdle;
+ qCWarning(QT_BT_OSX) << "-centralManager:didDisconnectPeripheral:, "
+ "failed to disconnect";
+ // TODO: instead of 'unknown' - .. ?
+ delegate->error(QLowEnergyController::UnknownRemoteDeviceError);
+ } else {
+ managerState = OSXBluetooth::CentralManagerIdle;
+ delegate->disconnected();
+ }
+}
+
+// CBPeripheralDelegate.
+
+- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverServices:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+ Q_UNUSED(error)
+}
+
+
+- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverIncludedServicesForService:(CBService *)service
+ error:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+ Q_UNUSED(service)
+ Q_UNUSED(error)
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverCharacteristicsForService:(CBService *)service
+ error:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+ Q_UNUSED(service)
+ Q_UNUSED(error)
+}
+
+@end
diff --git a/src/bluetooth/osx/osxbtcentralmanager_p.h b/src/bluetooth/osx/osxbtcentralmanager_p.h
new file mode 100644
index 00000000..5f836f42
--- /dev/null
+++ b/src/bluetooth/osx/osxbtcentralmanager_p.h
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/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 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef OSXBTCENTRALMANAGER_P_H
+#define OSXBTCENTRALMANAGER_P_H
+
+#include "qlowenergycontroller.h"
+#include "qbluetoothuuid.h"
+#include "osxbtutility_p.h"
+
+#include <QtCore/qglobal.h>
+
+// Foundation.h must be included before corebluetoothwrapper_p.h -
+// a workaround for a broken 10.9 SDK.
+#include <Foundation/Foundation.h>
+
+#include "corebluetoothwrapper_p.h"
+
+@class QT_MANGLE_NAMESPACE(OSXBTCentralManager);
+
+QT_BEGIN_NAMESPACE
+
+namespace OSXBluetooth {
+
+class CentralManagerDelegate
+{
+public:
+ typedef QT_MANGLE_NAMESPACE(OSXBTCentralManager) ObjCCentralManager;
+ typedef ObjCStrongReference<NSArray> LEServices;
+ typedef LEServices LECharacteristics;
+
+ virtual ~CentralManagerDelegate();
+
+ virtual void LEnotSupported() = 0;
+ virtual void connectSuccess() = 0;
+ virtual void serviceDiscoveryFinished(LEServices services) = 0;
+ virtual void includedServicesDiscoveryFinished(const QBluetoothUuid &serviceUuid,
+ LEServices services) = 0;
+ virtual void characteristicsDiscoveryFinished(const QBluetoothUuid &serviceUuid,
+ LECharacteristics characteristics) = 0;
+ virtual void disconnected() = 0;
+
+ // General errors.
+ virtual void error(QLowEnergyController::Error error) = 0;
+ // Service related errors.
+ virtual void error(const QBluetoothUuid &serviceUuid,
+ QLowEnergyController::Error error) = 0;
+};
+
+enum CentralManagerState
+{
+ // QLowEnergyController already has some of these states,
+ // but it's not enough and we need more special states here.
+ CentralManagerIdle,
+ // Required by CBCentralManager to avoid problems with dangled pointers.
+ CentralManagerUpdating,
+ CentralManagerConnecting,
+ CentralManagerDiscovering,
+ CentralManagerDisconnecting
+};
+
+}
+
+QT_END_NAMESPACE
+
+@interface QT_MANGLE_NAMESPACE(OSXBTCentralManager) : NSObject<CBCentralManagerDelegate, CBPeripheralDelegate>
+{
+ CBCentralManager *manager;
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::CentralManagerState managerState;
+ bool disconnectPending;
+
+ QBluetoothUuid deviceUuid;
+ CBPeripheral *peripheral;
+
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::CentralManagerDelegate *delegate;
+}
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth)::CentralManagerDelegate *)aDelegate;
+- (void)dealloc;
+
+- (QLowEnergyController::Error)connectToDevice:(const QBluetoothUuid &)aDeviceUuid;
+- (void)disconnectFromDevice;
+
+- (void)discoverServices;
+- (bool)discoverServiceDetails:(const QBluetoothUuid &)serviceUuid;
+- (bool)discoverCharacteristics:(const QBluetoothUuid &)serviceUuid;
+
+@end
+
+#endif
diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm
index 08721933..6e62844c 100644
--- a/src/bluetooth/qlowenergycontroller_osx.mm
+++ b/src/bluetooth/qlowenergycontroller_osx.mm
@@ -53,23 +53,39 @@ QT_BEGIN_NAMESPACE
QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController *q)
: q_ptr(q),
+ isConnecting(false),
lastError(QLowEnergyController::NoError),
controllerState(QLowEnergyController::UnconnectedState),
addressType(QLowEnergyController::PublicAddress)
{
- Q_ASSERT_X(q, "QLowEnergyControllerPrivate", "invalid q_ptr (null)");
// This is the "wrong" constructor - no valid device UUID to connect later.
+ Q_ASSERT_X(q, "QLowEnergyControllerPrivate", "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) << "QBluetoothLowEnergyControllerPrivateOSX::"
+ "QBluetoothLowEnergyControllerPrivateOSX(), "
+ "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, "QLowEnergyControllerPrivateOSX", "invalid q_ptr (null)");
+ centralManager.reset([[ObjCCentralManager alloc] initWithDelegate:this]);
+ if (!centralManager) {
+ qCWarning(QT_BT_OSX) << "QBluetoothLowEnergyControllerPrivateOSX::"
+ "QBluetoothLowEnergyControllerPrivateOSX(), "
+ "failed to initialize central manager";
+ }
}
QLowEnergyControllerPrivateOSX::~QLowEnergyControllerPrivateOSX()
@@ -78,11 +94,136 @@ QLowEnergyControllerPrivateOSX::~QLowEnergyControllerPrivateOSX()
bool QLowEnergyControllerPrivateOSX::isValid() const
{
- return false;
+ // 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,
+ "connectSuccess", "invalid state");
+
+ controllerState = QLowEnergyController::ConnectedState;
+
+ if (!isConnecting) {
+ emit q_ptr->stateChanged(QLowEnergyController::ConnectedState);
+ emit q_ptr->connected();
+ }
+}
+
+void QLowEnergyControllerPrivateOSX::serviceDiscoveryFinished(LEServices services)
+{
+ Q_UNUSED(services)
+}
+
+void QLowEnergyControllerPrivateOSX::includedServicesDiscoveryFinished(const QBluetoothUuid &serviceUuid,
+ LEServices services)
+{
+ Q_UNUSED(serviceUuid)
+ Q_UNUSED(services)
+}
+
+void QLowEnergyControllerPrivateOSX::characteristicsDiscoveryFinished(const QBluetoothUuid &serviceUuid,
+ LECharacteristics characteristics)
+{
+ Q_UNUSED(serviceUuid)
+ Q_UNUSED(characteristics)
+}
+
+
+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.
+ lastError = errorCode;
+
+ // We're still in connectToDevice,
+ // some error was reported synchronously.
+ // Return, the error will be correctly set later
+ // by connectToDevice.
+ if (isConnecting)
+ return;
+
+ switch (lastError) {
+ 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::UnknownError:
+ default:
+ errorString = QLowEnergyController::tr("Unknown Error");
+ break;
+ }
+
+ emit q_ptr->error(lastError);
+}
+
+void QLowEnergyControllerPrivateOSX::error(const QBluetoothUuid &serviceUuid,
+ QLowEnergyController::Error errorCode)
+{
+ // Service/characteristics-related errors.
+ Q_UNUSED(serviceUuid)
+ Q_UNUSED(errorCode)
}
void QLowEnergyControllerPrivateOSX::connectToDevice()
{
+ Q_ASSERT_X(isValid(), "connectToDevice", "invalid private controller");
+ Q_ASSERT_X(controllerState == QLowEnergyController::UnconnectedState,
+ "connectToDevice", "invalid state");
+ Q_ASSERT_X(!deviceUuid.isNull(), "connectToDevice",
+ "invalid private controller (no device uuid)");
+ Q_ASSERT_X(!isConnecting, "connectToDevice",
+ "recursive connectToDevice call");
+
+ lastError = QLowEnergyController::NoError;
+ errorString.clear();
+
+ 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()
@@ -100,6 +241,9 @@ QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddres
d_ptr(new QLowEnergyControllerPrivateOSX(this))
{
Q_UNUSED(remoteAddress)
+
+ qCWarning(QT_BT_OSX) << "QLowEnergyController::QLowEnergyController(), "
+ "construction with remote address is not supported!";
}
QLowEnergyController::QLowEnergyController(const QBluetoothDeviceInfo &remoteDevice,
@@ -107,7 +251,8 @@ QLowEnergyController::QLowEnergyController(const QBluetoothDeviceInfo &remoteDev
: QObject(parent),
d_ptr(new QLowEnergyControllerPrivateOSX(this, remoteDevice))
{
- Q_UNUSED(remoteDevice)
+ // That's the only "real" ctor - with Core Bluetooth we need a _valid_ deviceUuid
+ // from 'remoteDevice'.
}
QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddress,
@@ -122,12 +267,12 @@ QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddres
osx_d_ptr->localAddress = localAddress;
qCWarning(QT_BT_OSX) << "QLowEnergyController::QLowEnergyController(), "
- "construction with remote address is not supported";
+ "construction with remote/local addresses is not supported!";
}
QLowEnergyController::~QLowEnergyController()
{
- // TODO: disconnect, but this can be quite problematic - it's async. operation!
+ // Deleting a peripheral will also disconnect.
delete d_ptr;
}
@@ -170,10 +315,34 @@ void QLowEnergyController::setRemoteAddressType(RemoteAddressType 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()) {
+ osx_d_ptr->controllerState = ClosingState;
+ emit stateChanged(ClosingState);
+ [osx_d_ptr->centralManager disconnectFromDevice];
+ }
}
void QLowEnergyController::discoverServices()
diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h
index 6d48f591..4162c764 100644
--- a/src/bluetooth/qlowenergycontroller_osx_p.h
+++ b/src/bluetooth/qlowenergycontroller_osx_p.h
@@ -34,6 +34,7 @@
#ifndef QLOWENERGYCONTROLLER_OSX_P_H
#define QLOWENERGYCONTROLLER_OSX_P_H
+#include "osx/osxbtcentralmanager_p.h"
#include "qlowenergycontroller_p.h"
#include "qlowenergycontroller.h"
#include "osx/osxbtutility_p.h"
@@ -47,7 +48,8 @@
QT_BEGIN_NAMESPACE
// The suffix OSX is not the very right, it's also iOS.
-class QLowEnergyControllerPrivateOSX : public QLowEnergyControllerPrivate
+class QLowEnergyControllerPrivateOSX : public QLowEnergyControllerPrivate,
+ public OSXBluetooth::CentralManagerDelegate
{
friend class QLowEnergyController;
public:
@@ -59,12 +61,32 @@ public:
bool isValid() const;
private:
+ // CentralManagerDelegate:
+ void LEnotSupported() Q_DECL_OVERRIDE;
+ void connectSuccess() Q_DECL_OVERRIDE;
+
+ void serviceDiscoveryFinished(LEServices services) Q_DECL_OVERRIDE;
+ void includedServicesDiscoveryFinished(const QBluetoothUuid &serviceUuid,
+ LEServices services) Q_DECL_OVERRIDE;
+ void characteristicsDiscoveryFinished(const QBluetoothUuid &serviceUuid,
+ LECharacteristics characteristics) Q_DECL_OVERRIDE;
+ void disconnected() Q_DECL_OVERRIDE;
+ void error(QLowEnergyController::Error errorCode) Q_DECL_OVERRIDE;
+ void error(const QBluetoothUuid &serviceUuid,
+ QLowEnergyController::Error errorCode) Q_DECL_OVERRIDE;
+
void connectToDevice();
void discoverServices();
void discoverServiceDetails(const QBluetoothUuid &serviceUuid);
QLowEnergyController *q_ptr;
QBluetoothUuid deviceUuid;
+ // To be sure we set controller's state correctly
+ // (Connecting or Connected) we have to know if we're
+ // still inside connectToDevice - this is important,
+ // if a peripheral is _already_ connected from Core Bluetooth's
+ // point of view.
+ bool isConnecting;
QString errorString;
QLowEnergyController::Error lastError;
@@ -75,6 +97,9 @@ private:
QLowEnergyController::ControllerState controllerState;
QLowEnergyController::RemoteAddressType addressType;
+ typedef OSXBluetooth::ObjCScopedPointer<ObjCCentralManager> CentralManager;
+ CentralManager centralManager;
+
typedef QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate> > ServiceMap;
typedef ServiceMap::const_iterator ConstServiceIterator;
typedef ServiceMap::iterator ServiceIterator;