From f98a81839db4f3932038b4aded73782641958672 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Wed, 15 Oct 2014 17:15:23 +0200 Subject: 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 --- src/bluetooth/osx/corebluetoothwrapper_p.h | 78 +++++ src/bluetooth/osx/osxbt.pri | 60 ++-- src/bluetooth/osx/osxbtcentralmanagerdelegate.mm | 68 ++++ src/bluetooth/osx/osxbtcentralmanagerdelegate_p.h | 64 ++++ src/bluetooth/osx/osxbtdeviceinquiry.mm | 22 +- src/bluetooth/osx/osxbtdeviceinquiry_p.h | 10 +- src/bluetooth/osx/osxbtledeviceinquiry.mm | 352 +++++++++++++++++++++ src/bluetooth/osx/osxbtledeviceinquiry_p.h | 113 +++++++ src/bluetooth/osx/osxbtutility.mm | 17 + src/bluetooth/osx/osxbtutility_p.h | 16 +- .../qbluetoothdevicediscoveryagent_osx.mm | 272 +++++++++++++--- 11 files changed, 981 insertions(+), 91 deletions(-) create mode 100644 src/bluetooth/osx/corebluetoothwrapper_p.h create mode 100644 src/bluetooth/osx/osxbtcentralmanagerdelegate.mm create mode 100644 src/bluetooth/osx/osxbtcentralmanagerdelegate_p.h create mode 100644 src/bluetooth/osx/osxbtledeviceinquiry.mm create mode 100644 src/bluetooth/osx/osxbtledeviceinquiry_p.h 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 + +#else + +#include + +// 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 + +#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 + +#import + +#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 +{ + 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 #include +@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 +#include + +#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 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) ++ (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 +#include + +#import + +@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 +#ifndef QT_IOS_BLUETOOTH + #import +#endif + #include #include 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 #include + +#ifndef QT_IOS_BLUETOOTH + #include #include @class IOBluetoothSDPUUID; +#else + +#include + +#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 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 #include -// We have to import, otherwise Apple's header is not protected -// from multiple inclusion. -#import -#import - -// 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 DeviceInquiry; DeviceInquiry inquiry; + typedef ObjCScopedPointer LEDeviceInquiry; + LEDeviceInquiry inquiryLE; + typedef ObjCScopedPointer HostController; - HostController hostController; // Not sure I need it at all. + HostController hostController; typedef QList 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? "<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: "< uuids =OSXBluetooth::extract_services_uuids(device); + const QList 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: "<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 -- cgit v1.2.1