summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-10-15 17:15:23 +0200
committerTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-11-06 16:25:43 +0100
commitf98a81839db4f3932038b4aded73782641958672 (patch)
tree886822f3216595b6f1b54cd91fbb35e1b3709cd8
parentc7a1622d1f76bc61f0d5bb524d3eb40501cab13b (diff)
downloadqtconnectivity-f98a81839db4f3932038b4aded73782641958672.tar.gz
Bluetooth LE - device discovery for OS X.
- Unfortunately IOBluetoothDeviceInquiry does not scan for LE devices, so I need a separate scan for OS X and iOS imeplemented with CoreBluetooth framework. - CoreBluetooth hides addresses - "think different" in action. So we'll have to use 'identifiers' (NSUUID actually) provided by CoreBluetooth instead and we'll have to ask CBCentralManager to retrieve peripheral with a given NSUUID (or however we encode it). - Inform a delegate about LE device discovered. - Add the second scan/pass (Bluetooth LE) in QBluetoothDeviceDiscoveryAgent. - Make it all more "protocol conformant" - Do a proper cleanup + fix auto-test failure - Make a device inquiry longer + remove totally useless (??) 'name update'. - Use a plain bool instead of IOReturn (no this type(??) on iOS). - CoreBluetooth provides enumerators to check if LE is supported or not, no need to manipulate with StatePowerOn and other flags. - Add a workaround for a broken SDK (10.9 + CoreBluetooth headers + c++11). NSUUID is quite new - added in 10.8/6.0, works only on 10.9 and 7.0 (works == there is required interface). - CBCentralManager: it looks like it's impossible to delete an object of this type before centralDidUpdateStatus was called - this is not good, since a C++ object (the owner) can be deleted at any point - some preliminary workaround is to create a temporary delegate and pass the ownership from the dealloc. To be investigated/tested. - Move the TransientCentralManagerDelegate into the separate file - it'll be reused in future by other users of CBCentralManager. Change-Id: I4434c23366618061029be4022cfa0f7647df45b9 Reviewed-by: Alex Blasche <alexander.blasche@digia.com>
-rw-r--r--src/bluetooth/osx/corebluetoothwrapper_p.h78
-rw-r--r--src/bluetooth/osx/osxbt.pri60
-rw-r--r--src/bluetooth/osx/osxbtcentralmanagerdelegate.mm68
-rw-r--r--src/bluetooth/osx/osxbtcentralmanagerdelegate_p.h64
-rw-r--r--src/bluetooth/osx/osxbtdeviceinquiry.mm22
-rw-r--r--src/bluetooth/osx/osxbtdeviceinquiry_p.h10
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry.mm352
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry_p.h113
-rw-r--r--src/bluetooth/osx/osxbtutility.mm17
-rw-r--r--src/bluetooth/osx/osxbtutility_p.h16
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm272
11 files changed, 981 insertions, 91 deletions
diff --git a/src/bluetooth/osx/corebluetoothwrapper_p.h b/src/bluetooth/osx/corebluetoothwrapper_p.h
new file mode 100644
index 00000000..60d9b454
--- /dev/null
+++ b/src/bluetooth/osx/corebluetoothwrapper_p.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** 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 COREBLUETOOTHWRAPPER_P_H
+#define COREBLUETOOTHWRAPPER_P_H
+
+#ifndef QT_OSX_BLUETOOTH
+
+#import <CoreBluetooth/CoreBluetooth.h>
+
+#else
+
+#include <QtCore/qglobal.h>
+
+// CoreBluetooth with SDK 10.9 seems to be broken: the class CBPeripheralManager is enabled on OS X 10.9,
+// but some of its declarations are using a disabled enum CBPeripheralAuthorizationStatus
+// (disabled using __attribute__ syntax and NS_ENUM_AVAILABLE macro).
+// This + -std=c++11 ends with a compilation error. For the SDK 10.9 we can:
+// either undefine NS_ENUM_AVAILABLE macro (it works somehow) and redefine it as an empty sequence of pp-tokens or
+// define __attribute__ as an empty sequence. Both solutions look quite ugly.
+
+#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) && !QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_10)
+#define CB_ERROR_WORKAROUND_REQUIRED
+#endif
+
+#ifdef CB_ERROR_WORKAROUND_REQUIRED
+#undef NS_ENUM_AVAILABLE
+#define NS_ENUM_AVAILABLE(_mac, _ios)
+#endif
+
+#import <IOBluetooth/IOBluetooth.h>
+
+#ifdef CB_ERROR_WORKAROUND_REQUIRED
+#undef __attribute__
+#undef CB_ERROR_WORKAROUND_REQUIRED
+#endif
+
+#endif // QT_OSX_BLUETOOTH
+
+#endif // COREBLUETOOTHWRAPPER_P_H
diff --git a/src/bluetooth/osx/osxbt.pri b/src/bluetooth/osx/osxbt.pri
index cdcf0659..288afa6d 100644
--- a/src/bluetooth/osx/osxbt.pri
+++ b/src/bluetooth/osx/osxbt.pri
@@ -1,23 +1,39 @@
-PRIVATE_HEADERS += osx/osxbtutility_p.h \
- osx/osxbtdevicepair_p.h \
- osx/osxbtdeviceinquiry_p.h \
- osx/osxbtconnectionmonitor_p.h \
- osx/osxbtsdpinquiry_p.h \
- osx/osxbtrfcommchannel_p.h \
- osx/osxbtl2capchannel_p.h \
- osx/osxbtchanneldelegate_p.h \
- osx/osxbtservicerecord_p.h \
- osx/osxbtsocketlistener_p.h \
- osx/osxbtobexsession_p.h
+CONFIG(osx) {
+ PRIVATE_HEADERS += osx/osxbtutility_p.h \
+ osx/osxbtdevicepair_p.h \
+ osx/osxbtdeviceinquiry_p.h \
+ osx/osxbtconnectionmonitor_p.h \
+ osx/osxbtsdpinquiry_p.h \
+ osx/osxbtrfcommchannel_p.h \
+ osx/osxbtl2capchannel_p.h \
+ osx/osxbtchanneldelegate_p.h \
+ osx/osxbtservicerecord_p.h \
+ osx/osxbtsocketlistener_p.h \
+ osx/osxbtobexsession_p.h \
+ osx/osxbtledeviceinquiry_p.h \
+ osx/corebluetoothwrapper_p.h \
+ osx/osxbtcentralmanagerdelegate_p.h
-OBJECTIVE_SOURCES += osx/osxbtutility.mm \
- osx/osxbtdevicepair.mm \
- osx/osxbtdeviceinquiry.mm \
- osx/osxbtconnectionmonitor.mm \
- osx/osxbtsdpinquiry.mm \
- osx/osxbtrfcommchannel.mm \
- osx/osxbtl2capchannel.mm \
- osx/osxbtchanneldelegate.mm \
- osx/osxbtservicerecord.mm \
- osx/osxbtsocketlistener.mm \
- osx/osxbtobexsession.mm
+ OBJECTIVE_SOURCES += osx/osxbtutility.mm \
+ osx/osxbtdevicepair.mm \
+ osx/osxbtdeviceinquiry.mm \
+ osx/osxbtconnectionmonitor.mm \
+ osx/osxbtsdpinquiry.mm \
+ osx/osxbtrfcommchannel.mm \
+ osx/osxbtl2capchannel.mm \
+ osx/osxbtchanneldelegate.mm \
+ osx/osxbtservicerecord.mm \
+ osx/osxbtsocketlistener.mm \
+ osx/osxbtobexsession.mm \
+ osx/osxbtledeviceinquiry.mm \
+ osx/osxbtcentralmanagerdelegate.mm
+} else {
+ PRIVATE_HEADERS += osx/osxbtutility_p.h \
+ osx/osxbtledeviceinquiry_p.h \
+ osx/corebluetoothwrapper_p.h \
+ osx/osxbtcentralmanagerdelegate_p.h
+
+ OBJECTIVE_SOURCES += osx/osxbtutility.mm \
+ osx/osxbtledeviceinquiry.mm \
+ osx/osxbtcentralmanagerdelegate.mm
+}
diff --git a/src/bluetooth/osx/osxbtcentralmanagerdelegate.mm b/src/bluetooth/osx/osxbtcentralmanagerdelegate.mm
new file mode 100644
index 00000000..c0735d53
--- /dev/null
+++ b/src/bluetooth/osx/osxbtcentralmanagerdelegate.mm
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** 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"
+
+@implementation QT_MANGLE_NAMESPACE(OSXBTCentralManagerTransientDelegate)
+
+- (id)initWithManager:(CBCentralManager *)aManager
+{
+ if (self = [super init])
+ manager = aManager;
+
+ return self;
+}
+
+- (void)centralManagerDidUpdateState:(CBCentralManager *)central
+{
+ Q_UNUSED(central)
+
+ [self performSelectorOnMainThread:@selector(cleanup) withObject:nil waitUntilDone:NO];
+}
+
+- (void)cleanup
+{
+ [manager setDelegate:nil];
+ [manager release];
+ [self release];
+}
+
+@end
diff --git a/src/bluetooth/osx/osxbtcentralmanagerdelegate_p.h b/src/bluetooth/osx/osxbtcentralmanagerdelegate_p.h
new file mode 100644
index 00000000..cb8a70d3
--- /dev/null
+++ b/src/bluetooth/osx/osxbtcentralmanagerdelegate_p.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** 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 <QtCore/qglobal.h>
+
+#import <Foundation/Foundation.h>
+
+#include "corebluetoothwrapper_p.h"
+
+// CBCentralManager is quite special: before -centralManagerDidUpdateState: call
+// (which is a callback) - we can not delete it.
+// We usually release a manager in the -dealloc method. If the state was not updated yet,
+// we create a temporary delegate (which also is becoming the owner of this manger),
+// and later in the delegate's -centralManagerDidUpdateState: we are trying to finally release
+// a manager. Otherwise, this thing dies even with ARC.
+
+@interface QT_MANGLE_NAMESPACE(OSXBTCentralManagerTransientDelegate) : NSObject<CBCentralManagerDelegate>
+{
+ CBCentralManager *manager;
+}
+
+- (id)initWithManager:(CBCentralManager *)aManager;
+- (void)centralManagerDidUpdateState:(CBCentralManager *)central;
+- (void)cleanup;
+
+@end
diff --git a/src/bluetooth/osx/osxbtdeviceinquiry.mm b/src/bluetooth/osx/osxbtdeviceinquiry.mm
index 27dbe802..61ab116c 100644
--- a/src/bluetooth/osx/osxbtdeviceinquiry.mm
+++ b/src/bluetooth/osx/osxbtdeviceinquiry.mm
@@ -77,7 +77,8 @@ using namespace QT_NAMESPACE;
if (m_inquiry) {
// TODO: something more reasonable required!
- m_inquiry.inquiryLength = 20;
+ [m_inquiry setInquiryLength:20];
+ [m_inquiry setUpdateNewDeviceNames:NO];//Useless, disable!
m_delegate = delegate;
} else {
qCCritical(QT_BT_OSX) << "-initWithDelegate:, failed to create "
@@ -189,28 +190,9 @@ using namespace QT_NAMESPACE;
m_delegate->deviceFound(sender, device);
}
-- (void)deviceInquiryDeviceNameUpdated:(IOBluetoothDeviceInquiry *)sender
- device:(IOBluetoothDevice*)device devicesRemaining:(uint32_t)devicesRemaining
-{
- Q_UNUSED(sender)
- Q_UNUSED(device)
- Q_UNUSED(devicesRemaining)
- // TODO: add names update into DeviceInquiryDelegate.
-}
-
- (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry *)sender
{
Q_UNUSED(sender)
}
-- (void)deviceInquiryUpdatingDeviceNamesStarted:(IOBluetoothDeviceInquiry *)sender
- devicesRemaining:(uint32_t)devicesRemaining
-{
- Q_UNUSED(sender)
- Q_UNUSED(devicesRemaining)
-
- // TODO: add names update into DeviceInquiryDelegate.
-}
-
-
@end
diff --git a/src/bluetooth/osx/osxbtdeviceinquiry_p.h b/src/bluetooth/osx/osxbtdeviceinquiry_p.h
index 2eda032c..3c4f83fd 100644
--- a/src/bluetooth/osx/osxbtdeviceinquiry_p.h
+++ b/src/bluetooth/osx/osxbtdeviceinquiry_p.h
@@ -50,12 +50,16 @@
#include <Foundation/Foundation.h>
#include <IOKit/IOReturn.h>
+@class QT_MANGLE_NAMESPACE(OSXBTDeviceInquiry);
+
QT_BEGIN_NAMESPACE
namespace OSXBluetooth {
class DeviceInquiryDelegate {
public:
+ typedef QT_MANGLE_NAMESPACE(OSXBTDeviceInquiry) DeviceInquiryObjC;
+
virtual ~DeviceInquiryDelegate();
virtual void inquiryFinished(IOBluetoothDeviceInquiry *inq) = 0;
@@ -88,14 +92,8 @@ QT_END_NAMESPACE
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry *)sender
device:(IOBluetoothDevice *)device;
-- (void)deviceInquiryDeviceNameUpdated:(IOBluetoothDeviceInquiry *)sender
- device:(IOBluetoothDevice*)device devicesRemaining:(uint32_t)devicesRemaining;
-
- (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry *)sender;
-- (void)deviceInquiryUpdatingDeviceNamesStarted:(IOBluetoothDeviceInquiry *)sender
- devicesRemaining:(uint32_t)devicesRemaining;
-
@end
#endif
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry.mm b/src/bluetooth/osx/osxbtledeviceinquiry.mm
new file mode 100644
index 00000000..60e3224d
--- /dev/null
+++ b/src/bluetooth/osx/osxbtledeviceinquiry.mm
@@ -0,0 +1,352 @@
+/****************************************************************************
+**
+** 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 "osxbtledeviceinquiry_p.h"
+#include "qbluetoothdeviceinfo.h"
+#include "qbluetoothuuid.h"
+#include "osxbtutility_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qdebug.h>
+
+#include "corebluetoothwrapper_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace OSXBluetooth {
+
+LEDeviceInquiryDelegate::~LEDeviceInquiryDelegate()
+{
+}
+
+// TODO: check versions!
+#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0)
+
+QBluetoothUuid qt_uuid(NSUUID *nsUuid)
+{
+ if (!nsUuid)
+ return QBluetoothUuid();
+
+ uuid_t uuidData = {};
+ [nsUuid getUUIDBytes:uuidData];
+ quint128 qtUuidData = {};
+ std::copy(uuidData, uuidData + 16, qtUuidData.data);
+ return QBluetoothUuid(qtUuidData);
+}
+
+#else
+
+QBluetoothUuid qt_uuid(CFUUIDRef uuid)
+{
+ if (!uuid)
+ return QBluetoothUuid();
+
+ const CFUUIDBytes data = CFUUIDGetUUIDBytes(uuid);
+ quint128 qtUuidData = {{data.byte0, data.byte1, data.byte2, data.byte3,
+ data.byte4, data.byte5, data.byte6, data.byte7,
+ data.byte8, data.byte9, data.byte10, data.byte11,
+ data.byte12, data.byte13, data.byte14, data.byte15}};
+
+ return QBluetoothUuid(qtUuidData);
+}
+
+typedef ObjCStrongReference<NSString> StringStrongReference;
+
+StringStrongReference uuid_as_nsstring(CFUUIDRef uuid)
+{
+ // We use the UUDI's string representation as a key in a dictionary.
+ if (!uuid)
+ return StringStrongReference();
+
+ CFStringRef cfStr = CFUUIDCreateString(kCFAllocatorDefault, uuid);
+ if (!cfStr)
+ return StringStrongReference();
+
+ // Imporant: with ARC this will require a different cast/ownership!
+ return StringStrongReference((NSString *)cfStr, false);
+}
+
+#endif
+
+}
+
+
+QT_END_NAMESPACE
+
+#ifdef QT_NAMESPACE
+
+using namespace QT_NAMESPACE;
+
+#endif
+
+@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) (PrivateAPI) <CBCentralManagerDelegate, CBPeripheralDelegate>
++ (NSTimeInterval)inquiryLength;
+// "Timeout" callback to stop a scan.
+- (void)stopScan;
+@end
+
+@implementation QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry)
+
++ (NSTimeInterval)inquiryLength
+{
+ // 10 seconds at the moment. There is no default 'time-out',
+ // CBCentralManager startScan does not stop if not asked.
+ return 10;
+}
+
+- (id)initWithDelegate:(OSXBluetooth::LEDeviceInquiryDelegate *)aDelegate
+{
+ Q_ASSERT_X(aDelegate, "-initWithWithDelegate:", "invalid delegate (null)");
+
+ if (self = [super init]) {
+ delegate = aDelegate;
+ peripherals = [[NSMutableDictionary alloc] init];
+ manager = nil;
+ pendingStart = false;
+ cancelled = false;
+ isActive = false;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ typedef QT_MANGLE_NAMESPACE(OSXBTCentralManagerTransientDelegate) TransientDelegate;
+
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+
+ if (manager) {
+ // -start was called.
+ if (pendingStart) {
+ // 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];
+ if (isActive)
+ [manager stopScan];
+ [manager release];
+ }
+ }
+
+ [peripherals release];
+ [super dealloc];
+}
+
+- (void)stopScan
+{
+ // Scan's timeout.
+ Q_ASSERT_X(delegate, "-stopScan", "invalid delegate (null)");
+ Q_ASSERT_X(manager, "-stopScan", "invalid central (nil)");
+ Q_ASSERT_X(!pendingStart, "-stopScan", "invalid state");
+ Q_ASSERT_X(!cancelled, "-stopScan", "invalid state");
+ Q_ASSERT_X(isActive, "-stopScan", "invalid state");
+
+ [manager setDelegate:nil];
+ [manager stopScan];
+ isActive = false;
+
+ delegate->LEdeviceInquiryFinished();
+}
+
+- (bool)start
+{
+ Q_ASSERT_X(![self isActive], "-start", "LE device scan is already active");
+ Q_ASSERT_X(delegate, "-start", "invalid delegate (null)");
+
+ if (!peripherals) {
+ qCCritical(QT_BT_OSX) << "-start, internal error (allocation problem)";
+ return false;
+ }
+
+ cancelled = false;
+ [peripherals removeAllObjects];
+
+ if (manager) {
+ // We can never be here, if status was not updated yet.
+ [manager setDelegate:nil];
+ [manager release];
+ }
+
+ pendingStart = true;
+ manager = [CBCentralManager alloc];
+ manager = [manager initWithDelegate:self queue:nil];
+ if (!manager) {
+ qCCritical(QT_BT_OSX) << "-start, failed to create a central manager";
+ return false;
+ }
+
+ return true;
+}
+
+- (void)centralManagerDidUpdateState:(CBCentralManager *)central
+{
+ Q_ASSERT_X(delegate, "-centralManagerDidUpdateState:", "invalid delegate (null)");
+
+ if (cancelled) {
+ Q_ASSERT_X(!isActive, "-centralManagerDidUpdateState:", "isActive is true");
+ pendingStart = false;
+ delegate->LEdeviceInquiryFinished();
+ return;
+ }
+
+ const CBCentralManagerState state = central.state;
+ if (state == CBCentralManagerStatePoweredOn) {
+ if (pendingStart) {
+ pendingStart = false;
+ isActive = true;
+ [self performSelector:@selector(stopScan) withObject:nil
+ afterDelay:[QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) inquiryLength]];
+ [manager scanForPeripheralsWithServices:nil options:nil];
+ } // Else we ignore.
+ } else if (state == CBCentralManagerStateUnsupported || state == CBCentralManagerStateUnauthorized) {
+ if (pendingStart) {
+ pendingStart = false;
+ delegate->LEnotSupported();
+ } else if (isActive) {
+ // It's not clear if this thing can happen at all.
+ // We had LE supported and now .. not anymore?
+ // Report as an error.
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ isActive = false;
+ [manager stopScan];
+ delegate->LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
+ }
+ } else if (state == CBCentralManagerStatePoweredOff) {
+ if (pendingStart) {
+ pendingStart = false;
+ delegate->LEnotSupported();
+ } else if (isActive) {
+ // We were able to start (isActive == true), so we had
+ // powered ON and now the adapter is OFF.
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ isActive = false;
+ [manager stopScan];
+ delegate->LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
+ } // Else we ignore.
+ } else {
+ // The following two states we ignore (from Apple's docs):
+ //"
+ // -CBCentralManagerStateUnknown
+ // The current state of the central manager is unknown;
+ // an update is imminent.
+ //
+ // -CBCentralManagerStateResetting
+ // The connection with the system service was momentarily
+ // lost; an update is imminent. "
+ //
+ // TODO: check if "is imminent" means UpdateState will
+ // be called again with something more reasonable.
+ }
+}
+
+- (void)stop
+{
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+
+ if (pendingStart || cancelled) {
+ // We have to wait for a status update.
+ cancelled = true;
+ return;
+ }
+
+ if (isActive) {
+ [manager stopScan];
+ isActive = false;
+ delegate->LEdeviceInquiryFinished();
+ }
+}
+
+- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
+ advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
+{
+ Q_UNUSED(central)
+ Q_UNUSED(advertisementData)
+
+ using namespace OSXBluetooth;
+
+ Q_ASSERT_X(delegate, "-centralManager:didDiscoverPeripheral:advertisementData:RSSI:",
+ "invalid delegate (null)");
+ Q_ASSERT_X(isActive, "-centralManager:didDiscoverPeripheral:advertisementData:RSSI:",
+ "called while there is no active scan");
+ Q_ASSERT_X(!pendingStart, "-centralManager:didDiscoverPeripheral:advertisementData:RSSI:",
+ "both pendingStart and isActive are true");
+
+
+#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0)
+ if (!peripheral.identifier) {
+ qCWarning(QT_BT_OSX) << "-centramManager:didDiscoverPeripheral:advertisementData:RSSI:, "
+ "peripheral without NSUUID";
+ return;
+ }
+
+ if (![peripherals objectForKey:peripheral.identifier]) {
+ [peripherals setObject:peripheral forKey:peripheral.identifier];
+ const QBluetoothUuid deviceUuid(OSXBluetooth::qt_uuid(peripheral.identifier));
+ delegate->LEdeviceFound(peripheral, deviceUuid, advertisementData, RSSI);
+ }
+#else
+ if (!peripheral.UUID) {
+ qCWarning(QT_BT_OSX) << "-centramManager:didDiscoverPeripheral:advertisementData:RSSI:, "
+ "peripheral without UUID";
+ return;
+ }
+
+ StringStrongReference key(uuid_as_nsstring(peripheral.UUID));
+ if (![peripherals objectForKey:key.data()]) {
+ [peripherals setObject:peripheral forKey:key.data()];
+ const QBluetoothUuid deviceUuid(OSXBluetooth::qt_uuid(peripheral.UUID));
+ delegate->LEdeviceFound(peripheral, deviceUuid, advertisementData, RSSI);
+ }
+#endif
+
+}
+
+- (bool)isActive
+{
+ return pendingStart || isActive;
+}
+
+@end
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry_p.h b/src/bluetooth/osx/osxbtledeviceinquiry_p.h
new file mode 100644
index 00000000..16a757a6
--- /dev/null
+++ b/src/bluetooth/osx/osxbtledeviceinquiry_p.h
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** 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 OSXBTLEDEVICEINQUIRY_P_H
+#define OSXBTLEDEVICEINQUIRY_P_H
+
+#include "qbluetoothdevicediscoveryagent.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qlist.h>
+
+#import <Foundation/Foundation.h>
+
+@class QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry);
+
+@class CBCentralManager;
+@class CBPeripheral;
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothDeviceInfo;
+class QBluetoothUuid;
+
+namespace OSXBluetooth {
+
+class LEDeviceInquiryDelegate
+{
+public:
+ typedef QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) LEDeviceInquiryObjC;
+
+ virtual ~LEDeviceInquiryDelegate();
+
+ // At the moment the only error we're reporting is PoweredOffError!
+ virtual void LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::Error error) = 0;
+
+ virtual void LEnotSupported() = 0;
+ virtual void LEdeviceFound(CBPeripheral *peripheral, const QBluetoothUuid &uuid,
+ NSDictionary *advertisementData, NSNumber *RSSI) = 0;
+ virtual void LEdeviceInquiryFinished() = 0;
+};
+
+}
+
+QT_END_NAMESPACE
+
+// Bluetooth Low Energy scan for iOS and OS X.
+
+@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) : NSObject
+{// Protocols are adopted in the mm file.
+ QT_PREPEND_NAMESPACE(OSXBluetooth::LEDeviceInquiryDelegate) *delegate;
+
+ // TODO: scoped pointers/shared pointers?
+ NSMutableDictionary *peripherals; // Found devices.
+ CBCentralManager *manager;
+
+ // pending - waiting for a status update first.
+ bool pendingStart;
+ bool cancelled;
+ // scan actually started.
+ bool isActive;
+}
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth::LEDeviceInquiryDelegate) *)aDelegate;
+- (void)dealloc;
+
+// Actual scan can be delayed - we have to wait for a status update first.
+- (bool)start;
+// Stop can be delayed - if we're waiting for a status update.
+- (void)stop;
+
+- (bool)isActive;
+
+@end
+
+#endif
diff --git a/src/bluetooth/osx/osxbtutility.mm b/src/bluetooth/osx/osxbtutility.mm
index 561de0bc..b6818ef6 100644
--- a/src/bluetooth/osx/osxbtutility.mm
+++ b/src/bluetooth/osx/osxbtutility.mm
@@ -45,15 +45,27 @@
#include <QtCore/qstring.h>
+#ifndef QT_IOS_BLUETOOTH
+
#import <IOBluetooth/objc/IOBluetoothSDPUUID.h>
+#endif
+
#include <algorithm>
#include <limits>
QT_BEGIN_NAMESPACE
+#ifndef QT_IOS_BLUETOOTH
+
Q_LOGGING_CATEGORY(QT_BT_OSX, "qt.bluetooth.osx")
+#else
+
+Q_LOGGING_CATEGORY(QT_BT_OSX, "qt.bluetooth.ios")
+
+#endif
+
namespace OSXBluetooth {
QString qt_address(NSString *address)
@@ -66,6 +78,9 @@ QString qt_address(NSString *address)
return QString();
}
+#ifndef QT_IOS_BLUETOOTH
+
+
QBluetoothAddress qt_address(const BluetoothDeviceAddress *a)
{
if (a) {
@@ -142,6 +157,8 @@ QString qt_error_string(IOReturn errorCode)
}
}
+#endif
+
}
QT_END_NAMESPACE
diff --git a/src/bluetooth/osx/osxbtutility_p.h b/src/bluetooth/osx/osxbtutility_p.h
index cad84b7e..c2a48e5f 100644
--- a/src/bluetooth/osx/osxbtutility_p.h
+++ b/src/bluetooth/osx/osxbtutility_p.h
@@ -47,11 +47,20 @@
#include <QtCore/qglobal.h>
#include <Foundation/Foundation.h>
+
+#ifndef QT_IOS_BLUETOOTH
+
#include <IOBluetooth/Bluetooth.h>
#include <IOKit/IOReturn.h>
@class IOBluetoothSDPUUID;
+#else
+
+#include <CoreBluetooth/CoreBluetooth.h>
+
+#endif
+
QT_BEGIN_NAMESPACE
class QBluetoothUuid;
@@ -171,16 +180,21 @@ private:
};
QString qt_address(NSString *address);
+
+#ifndef QT_IOS_BLUETOOTH
+
class QBluetoothAddress qt_address(const BluetoothDeviceAddress *address);
BluetoothDeviceAddress iobluetooth_address(const QBluetoothAddress &address);
ObjCStrongReference<IOBluetoothSDPUUID> iobluetooth_uuid(const QBluetoothUuid &uuid);
QBluetoothUuid qt_uuid(IOBluetoothSDPUUID *uuid);
-
QString qt_error_string(IOReturn errorCode);
+#endif
+
} // namespace OSXBluetooth
+// Logging category for both OS X and iOS.
Q_DECLARE_LOGGING_CATEGORY(QT_BT_OSX)
QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
index 7c4cd1f1..6ccdd0ff 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
@@ -40,6 +40,7 @@
****************************************************************************/
#include "qbluetoothdevicediscoveryagent.h"
+#include "osx/osxbtledeviceinquiry_p.h"
#include "osx/osxbtdeviceinquiry_p.h"
#include "qbluetoothlocaldevice.h"
#include "osx/osxbtsdpinquiry_p.h"
@@ -53,18 +54,14 @@
#include <QtCore/qdebug.h>
#include <QtCore/qlist.h>
-// We have to import, otherwise Apple's header is not protected
-// from multiple inclusion.
-#import <IOBluetooth/objc/IOBluetoothHostController.h>
-#import <IOBluetooth/objc/IOBluetoothDevice.h>
-
-// TODO: check how all these things work with threads.
+#include "osx/corebluetoothwrapper_p.h"
QT_BEGIN_NAMESPACE
using OSXBluetooth::ObjCScopedPointer;
-class QBluetoothDeviceDiscoveryAgentPrivate : public OSXBluetooth::DeviceInquiryDelegate
+class QBluetoothDeviceDiscoveryAgentPrivate : public OSXBluetooth::DeviceInquiryDelegate,
+ public OSXBluetooth::LEDeviceInquiryDelegate
{
friend class QBluetoothDeviceDiscoveryAgent;
public:
@@ -76,18 +73,36 @@ public:
bool isActive() const;
void start();
+ void startLE();
void stop();
private:
+ enum AgentState {
+ NonActive,
+ ClassicScan,
+ LEScan
+ };
+
// DeviceInquiryDelegate:
void inquiryFinished(IOBluetoothDeviceInquiry *inq) Q_DECL_OVERRIDE;
void error(IOBluetoothDeviceInquiry *inq, IOReturn error) Q_DECL_OVERRIDE;
void deviceFound(IOBluetoothDeviceInquiry *inq, IOBluetoothDevice *device) Q_DECL_OVERRIDE;
+ // LEDeviceInquiryDelegate:
+ void LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::Error error) Q_DECL_OVERRIDE;
+ void LEnotSupported() Q_DECL_OVERRIDE;
+ void LEdeviceFound(CBPeripheral *peripheral, const QBluetoothUuid &deviceUuid,
+ NSDictionary *advertisementData, NSNumber *RSSI) Q_DECL_OVERRIDE;
+ void LEdeviceInquiryFinished() Q_DECL_OVERRIDE;
+
+ // Check if it's a really new device/updated info and emit
+ // q_ptr->deviceDiscovered.
+ void deviceFound(const QBluetoothDeviceInfo &newDeviceInfo);
void setError(IOReturn error, const QString &text = QString());
void setError(QBluetoothDeviceDiscoveryAgent::Error, const QString &text = QString());
QBluetoothDeviceDiscoveryAgent *q_ptr;
+ AgentState agentState;
QBluetoothAddress adapterAddress;
@@ -99,12 +114,14 @@ private:
QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType;
- typedef QT_MANGLE_NAMESPACE(OSXBTDeviceInquiry) DeviceInquiryObjC;
typedef ObjCScopedPointer<DeviceInquiryObjC> DeviceInquiry;
DeviceInquiry inquiry;
+ typedef ObjCScopedPointer<LEDeviceInquiryObjC> LEDeviceInquiry;
+ LEDeviceInquiry inquiryLE;
+
typedef ObjCScopedPointer<IOBluetoothHostController> HostController;
- HostController hostController; // Not sure I need it at all.
+ HostController hostController;
typedef QList<QBluetoothDeviceInfo> DevicesList;
DevicesList discoveredDevices;
@@ -113,6 +130,7 @@ private:
QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &adapter,
QBluetoothDeviceDiscoveryAgent *q) :
q_ptr(q),
+ agentState(NonActive),
adapterAddress(adapter),
startPending(false),
stopPending(false),
@@ -136,8 +154,22 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(con
return;
}
+ // OSXBTLEDeviceInquiry can be constructed even if LE is not supported -
+ // at this stage it's only a memory allocation of the object itself,
+ // if it fails - we have some memory-related problems.
+ LEDeviceInquiry newInquiryLE([[LEDeviceInquiryObjC alloc] initWithDelegate:this]);
+ if (!newInquiryLE) {
+ qCWarning(QT_BT_OSX) << "QBluetoothDeviceDiscoveryAgentPrivate() "
+ "failed to initialize a LE inquiry";
+ return;
+ }
+
+ qCDebug(QT_BT_OSX) << "host controller is in 'on' state, "
+ "discovery agent created successfully";
+
hostController.reset(controller.take());
inquiry.reset(newInquiry.take());
+ inquiryLE.reset(newInquiryLE.take());
}
QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
@@ -146,11 +178,14 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
bool QBluetoothDeviceDiscoveryAgentPrivate::isValid() const
{
- // isValid() - Qt in general does not use exceptions, but the ctor
+ // isValid() - Qt does not use exceptions, but the ctor
// can fail to initialize some important data-members
// (and the error is probably not even related to Bluetooth at all)
// - say, allocation error - this is what meant here by valid/invalid.
+ const bool valid = hostController && [hostController powerState] == kBluetoothHCIPowerStateON && inquiry;
+ qCDebug(QT_BT_OSX) << "private agent is valid state? "<<valid;
+
if (hostController && [hostController powerState] != kBluetoothHCIPowerStateON)
qCWarning(QT_BT_OSX) << "adapter is powered off (was on)";
@@ -159,12 +194,18 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isValid() const
bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
{
- if (startPending)
+ if (startPending) {
+ qCDebug(QT_BT_OSX) << "start is pending, isActive == true";
return true;
- if (stopPending)
+ }
+ if (stopPending) {
+ qCDebug(QT_BT_OSX) << "stop is pending, isActive == false";
return false;
+ }
+
+ qCDebug(QT_BT_OSX)<<"isActive? "<< (agentState != NonActive);
- return [inquiry isActive];
+ return agentState != NonActive;
}
void QBluetoothDeviceDiscoveryAgentPrivate::start()
@@ -172,21 +213,50 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start()
Q_ASSERT_X(isValid(), "start()", "called on invalid device discovery agent");
Q_ASSERT_X(!isActive(), "start()", "called on active device discovery agent");
Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
- "start()", "called with invalid bluetooth adapter");
+ "start()", "called with an invalid Bluetooth adapter");
if (stopPending) {
+ qCDebug(QT_BT_OSX) << "START: stop is pending, set start pending and return";
startPending = true;
return;
}
+ agentState = ClassicScan;
+
discoveredDevices.clear();
setError(QBluetoothDeviceDiscoveryAgent::NoError);
const IOReturn res = [inquiry start];
if (res != kIOReturnSuccess) {
- qCDebug(QT_BT_OSX) << "private agent, failed to start";
+ qCDebug(QT_BT_OSX) << "START: private agent, failed to start";
setError(res, QObject::tr("device discovery agent: failed to start"));
+ agentState = NonActive;
+ emit q_ptr->error(lastError);
+ } else {
+ qCDebug(QT_BT_OSX) << "START: device inquiry started ...";
+ }
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::startLE()
+{
+ Q_ASSERT_X(isValid(), "startLE()", "called on invalid device discovery agent");
+ Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
+ "startLE()", "called with an invalid Bluetooth adapter");
+
+ agentState = LEScan;
+
+ if (![inquiryLE start]) {
+ // We can be here only if we have some kind of resource allocation error, so we
+ // do not emit finished, we emit error.
+ qCDebug(QT_BT_OSX) << "STARTLE: failed to start LE scan ...";
+
+ setError(QBluetoothDeviceDiscoveryAgent::UnknownError,
+ QObject::tr("device discovery agent, LE mode: "
+ "resource allocation error"));
+ agentState = NonActive;
emit q_ptr->error(lastError);
+ } else {
+ qCDebug(QT_BT_OSX) << "STARTLE: scan started.";
}
}
@@ -199,11 +269,11 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
const bool prevStart = startPending;
startPending = false;
+ stopPending = true;
setError(QBluetoothDeviceDiscoveryAgent::NoError);
- if ([inquiry isActive]) {
- stopPending = true;
+ if (agentState == ClassicScan) {
const IOReturn res = [inquiry stop];
if (res != kIOReturnSuccess) {
qCWarning(QT_BT_OSX) << "QBluetoothDeviceDiscoveryAgentPrivate::stop(), "
@@ -212,9 +282,15 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
stopPending = false;
setError(res, QObject::tr("device discovery agent: failed to stop"));
emit q_ptr->error(lastError);
+ } else {
+ qCDebug(QT_BT_OSX) << "stop success on a classic device inquiry";
}
- } else
- qCDebug(QT_BT_OSX) << "private agent, failed to stop, not active";
+ } else {
+ // Can be asynchronous (depending on a status update of CBCentralManager).
+ // The call itself is always 'success'.
+ qCDebug(QT_BT_OSX) << "trying to stop LE scan ...";
+ [inquiryLE stop];
+ }
}
void QBluetoothDeviceDiscoveryAgentPrivate::inquiryFinished(IOBluetoothDeviceInquiry *inq)
@@ -222,18 +298,28 @@ void QBluetoothDeviceDiscoveryAgentPrivate::inquiryFinished(IOBluetoothDeviceInq
Q_UNUSED(inq)
Q_ASSERT_X(isValid(), "inquiryFinished", "invalid device discovery agent"); //We can never be here.
- Q_ASSERT_X(inq, "inquiryFinished", "invalid device inquiry (nil)");
Q_ASSERT_X(q_ptr, "inquiryFinished", "invalid q_ptr (null)");
+ // The subsequent start(LE) function (if any)
+ // will (re)set the correct state.
+ agentState = NonActive;
+
if (stopPending && !startPending) {
+ qCDebug(QT_BT_OSX) << "inquiryFinished, stop pending, no pending start, emit canceled";
stopPending = false;
emit q_ptr->canceled();
} else if (startPending) {
+ qCDebug(QT_BT_OSX) << "inquiryFinished, NO stop pending, pending start, re-starting";
startPending = false;
stopPending = false;
start();
} else {
- emit q_ptr->finished();
+ // We can be here _only_ if a classic scan
+ // finished in a normal way (not cancelled).
+ // startLE() will take care of old devices
+ // not supporting Bluetooth 4.0.
+ qCDebug(QT_BT_OSX)<<"CLASSIC inquiryFinished, NO stop pending, starting LE";
+ startLE();
}
}
@@ -243,6 +329,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::error(IOBluetoothDeviceInquiry *inq,
Q_ASSERT_X(isValid(), "error", "invalid device discovery agent");
+ qCDebug(QT_BT_OSX)<<"ERROR: got a native error code: "<<int(error);
+
startPending = false;
stopPending = false;
@@ -259,6 +347,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(IOBluetoothDeviceInquiry
Q_ASSERT_X(isValid(), "deviceFound()",
"invalid device discovery agent");
Q_ASSERT_X(device, "deviceFound()", "invalid IOBluetoothDevice (nil)");
+ Q_ASSERT_X(agentState == ClassicScan, "deviceFound",
+ "invalid agent state (expected classic scan)");
QT_BT_MAC_AUTORELEASEPOOL;
@@ -277,25 +367,13 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(IOBluetoothDeviceInquiry
const qint32 classOfDevice(device.classOfDevice);
QBluetoothDeviceInfo deviceInfo(deviceAddress, deviceName, classOfDevice);
- deviceInfo.setCoreConfigurations(classOfDevice ? QBluetoothDeviceInfo::BaseRateCoreConfiguration:
- QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
+ deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
deviceInfo.setRssi(device.RSSI);
- const QList<QBluetoothUuid> uuids =OSXBluetooth::extract_services_uuids(device);
+ const QList<QBluetoothUuid> uuids(OSXBluetooth::extract_services_uuids(device));
deviceInfo.setServiceUuids(uuids, QBluetoothDeviceInfo::DataIncomplete);
- for (int i = 0, e = discoveredDevices.size(); i < e; ++i) {
- if (discoveredDevices[i].address() == deviceInfo.address()) {
- if (discoveredDevices[i] == deviceInfo)
- return;
- discoveredDevices.replace(i, deviceInfo);
- emit q_ptr->deviceDiscovered(deviceInfo);
- return;
- }
- }
-
- discoveredDevices.append(deviceInfo);
- emit q_ptr->deviceDiscovered(deviceInfo);
+ deviceFound(deviceInfo);
}
void QBluetoothDeviceDiscoveryAgentPrivate::setError(IOReturn error, const QString &text)
@@ -334,6 +412,102 @@ void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAg
errorString = QObject::tr("device discovery agent: unknown error");
}
}
+
+ qCDebug(QT_BT_OSX) << "error set: "<<errorString;
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::Error error)
+{
+ // At the moment the only error reported can be 'powered off' error, it happens
+ // after the LE scan started (so we have LE support and this is a real PoweredOffError).
+ Q_ASSERT_X(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError,
+ "LEdeviceInquiryError", "unexpected error code");
+
+ qCDebug(QT_BT_OSX) << "LEDeviceInquiryError: powered off";
+
+ agentState = NonActive;
+ setError(error);
+ emit q_ptr->error(lastError);
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported()
+{
+ // Not supported is not an error.
+ qCDebug(QT_BT_OSX) << "no Bluetooth LE support";
+ // After we call startLE and before receive NotSupported,
+ // the user can call stop (setting a pending stop).
+ // So the same rule apply:
+
+ qCDebug(QT_BT_OSX) << "LE not supported.";
+
+ LEdeviceInquiryFinished();
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound(CBPeripheral *peripheral, const QBluetoothUuid &deviceUuid,
+ NSDictionary *advertisementData,
+ NSNumber *RSSI)
+{
+ Q_ASSERT_X(peripheral, "LEdeviceFound()", "invalid peripheral (nil)");
+ Q_ASSERT_X(agentState == LEScan, "LEdeviceFound",
+ "invalid agent state, expected LE scan");
+
+ Q_UNUSED(advertisementData)
+
+ QString name;
+ if (peripheral.name)
+ name = QString::fromNSString(peripheral.name);
+
+ // TODO: fix 'classOfDevice' (0 for now).
+ QBluetoothDeviceInfo newDeviceInfo(deviceUuid, name, 0);
+ if (RSSI)
+ newDeviceInfo.setRssi([RSSI shortValue]);
+ // CoreBluetooth scans only for LE devices.
+ newDeviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
+
+ deviceFound(newDeviceInfo);
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceInquiryFinished()
+{
+ // The same logic as in inquiryFinished, but does not start LE scan.
+ agentState = NonActive;
+
+ if (stopPending && !startPending) {
+ qCDebug(QT_BT_OSX) << "LE scan finished, stop pending, NO start pending, emit canceled";
+ stopPending = false;
+ emit q_ptr->canceled();
+ } else if (startPending) {
+ qCDebug(QT_BT_OSX) << "LE scan finished, start pending, NO stop pending, re-start";
+ startPending = false;
+ stopPending = false;
+ start(); //Start from a classic scan again.
+ } else {
+ qCDebug(QT_BT_OSX) << "LE scan finished, emit finished";
+ emit q_ptr->finished();
+ }
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(const QBluetoothDeviceInfo &newDeviceInfo)
+{
+ // Core Bluetooth does not allow us to access addresses, we have to use uuid instead.
+ // This uuid has nothing to do with uuids in Bluetooth in general (it's generated by
+ // Apple's framework using some algorithm), but it's a 128-bit uuid after all.
+ const bool isLE = newDeviceInfo.coreConfigurations() == QBluetoothDeviceInfo::LowEnergyCoreConfiguration;
+
+ for (int i = 0, e = discoveredDevices.size(); i < e; ++i) {
+ if (isLE ? discoveredDevices[i].deviceUuid() == newDeviceInfo.deviceUuid():
+ discoveredDevices[i].address() == newDeviceInfo.address()) {
+ if (discoveredDevices[i] == newDeviceInfo)
+ return;
+
+ discoveredDevices.replace(i, newDeviceInfo);
+ emit q_ptr->deviceDiscovered(newDeviceInfo);
+ return;
+ }
+ }
+
+ discoveredDevices.append(newDeviceInfo);
+ emit q_ptr->deviceDiscovered(newDeviceInfo);
}
QBluetoothDeviceDiscoveryAgent::QBluetoothDeviceDiscoveryAgent(QObject *parent) :
@@ -383,8 +557,11 @@ void QBluetoothDeviceDiscoveryAgent::start()
{
if (d_ptr->lastError != InvalidBluetoothAdapterError) {
if (d_ptr->isValid()) {
- if (!isActive())
+ qCDebug(QT_BT_OSX) << "DDA::start?";
+ if (!isActive()) {
+ qCDebug(QT_BT_OSX) << "DDA::start!";
d_ptr->start();
+ }
} else {
// We previously failed to initialize d_ptr correctly:
// either some memory allocation problem or
@@ -399,15 +576,26 @@ void QBluetoothDeviceDiscoveryAgent::start()
void QBluetoothDeviceDiscoveryAgent::stop()
{
- if (isActive() && d_ptr->lastError != InvalidBluetoothAdapterError)
- d_ptr->stop();
- else
- qCDebug(QT_BT_OSX) << "stop failed, not active or invalid adapter";
+ if (d_ptr->isValid()) {
+ qCDebug(QT_BT_OSX) << "DDA::stop, is valid";
+ if (isActive() && d_ptr->lastError != InvalidBluetoothAdapterError) {
+ qCDebug(QT_BT_OSX) << "DDA::stop, is active and no error...";
+ d_ptr->stop();
+ }
+ } else {
+ qCDebug(QT_BT_OSX) << "DDA::stop, d_ptr is not in valid state, can not stop";
+ }
}
bool QBluetoothDeviceDiscoveryAgent::isActive() const
{
- return d_ptr->isActive();
+ qCDebug(QT_BT_OSX) << "DDA::isActive";
+ if (d_ptr->isValid()) {
+ return d_ptr->isActive();
+ } else {
+ qCDebug(QT_BT_OSX) << "DDA::isActive, d_ptr is invalid";
+ }
+ return false;
}
QBluetoothDeviceDiscoveryAgent::Error QBluetoothDeviceDiscoveryAgent::error() const