summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/osx/osxbtledeviceinquiry.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/osx/osxbtledeviceinquiry.mm')
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry.mm221
1 files changed, 97 insertions, 124 deletions
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry.mm b/src/bluetooth/osx/osxbtledeviceinquiry.mm
index 7b9e7431..8b924d82 100644
--- a/src/bluetooth/osx/osxbtledeviceinquiry.mm
+++ b/src/bluetooth/osx/osxbtledeviceinquiry.mm
@@ -39,19 +39,19 @@
#include "osxbtledeviceinquiry_p.h"
#include "qbluetoothdeviceinfo.h"
+#include "osxbtnotifier_p.h"
#include "qbluetoothuuid.h"
#include "osxbtutility_p.h"
#include <QtCore/qloggingcategory.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qdebug.h>
+#include <algorithm>
+
QT_BEGIN_NAMESPACE
namespace OSXBluetooth {
-#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0)
-
QBluetoothUuid qt_uuid(NSUUID *nsUuid)
{
if (!nsUuid)
@@ -64,58 +64,35 @@ QBluetoothUuid qt_uuid(NSUUID *nsUuid)
return QBluetoothUuid(qtUuidData);
}
-#endif
-
-QBluetoothUuid qt_uuid(CFUUIDRef uuid)
-{
- if (!uuid)
- return QBluetoothUuid();
+const int timeStepMS = 100;
- 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);
-}
+const int powerOffTimeoutMS = 30000;
+const qreal powerOffTimeStepS = 30. / 100.;
}
-
QT_END_NAMESPACE
QT_USE_NAMESPACE
@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) (PrivateAPI) <CBCentralManagerDelegate>
+// These two methods are scheduled with a small time step
+// within a given timeout, they either re-schedule
+// themselves or emit a signal/stop some operation.
- (void)stopScan;
- (void)handlePoweredOff;
@end
@implementation QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry)
-- (id)init
+-(id)initWithNotifier:(LECBManagerNotifier *)aNotifier
{
if (self = [super init]) {
+ Q_ASSERT(aNotifier);
+ notifier = aNotifier;
uuids.reset([[NSMutableSet alloc] init]);
internalState = InquiryStarting;
- state.store(int(internalState));
+ inquiryTimeoutMS = OSXBluetooth::defaultLEScanTimeoutMS;
}
return self;
@@ -129,27 +106,36 @@ QT_USE_NAMESPACE
[manager stopScan];
}
+ if (notifier) {
+ notifier->disconnect();
+ notifier->deleteLater();
+ }
+
[super dealloc];
}
- (void)stopScan
{
- // Scan's "timeout" - we consider LE device
- // discovery finished.
using namespace OSXBluetooth;
+ // We never schedule stopScan if there is no timeout:
+ Q_ASSERT(inquiryTimeoutMS > 0);
+
if (internalState == InquiryActive) {
- if (scanTimer.elapsed() >= qt_LE_deviceInquiryLength() * 1000) {
- // We indeed stop now:
+ const int elapsed = scanTimer.elapsed();
+ if (elapsed >= inquiryTimeoutMS) {
[manager stopScan];
[manager setDelegate:nil];
internalState = InquiryFinished;
- state.store(int(internalState));
+ Q_ASSERT(notifier);
+ emit notifier->discoveryFinished();
} else {
+ // Re-schedule 'stopScan':
dispatch_queue_t leQueue(qt_LE_queue());
Q_ASSERT(leQueue);
+ const int timeChunkMS = std::min(inquiryTimeoutMS - elapsed, timeStepMS);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- int64_t(qt_LE_deviceInquiryLength() / 100. * NSEC_PER_SEC)),
+ int64_t(timeChunkMS / 1000. * NSEC_PER_SEC)),
leQueue,
^{
[self stopScan];
@@ -164,30 +150,32 @@ QT_USE_NAMESPACE
// the system shows an alert asking to enable
// Bluetooth in the 'Settings' app. If not done yet (after 30
// seconds) - we consider it an error.
+ using namespace OSXBluetooth;
+
if (internalState == InquiryStarting) {
- if (errorTimer.elapsed() >= 30000) {
+ if (errorTimer.elapsed() >= powerOffTimeoutMS) {
[manager setDelegate:nil];
internalState = ErrorPoweredOff;
- state.store(int(internalState));
+ Q_ASSERT(notifier);
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
} else {
- dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ dispatch_queue_t leQueue(qt_LE_queue());
Q_ASSERT(leQueue);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- (int64_t)(30 / 100. * NSEC_PER_SEC)),
+ (int64_t)(powerOffTimeStepS * NSEC_PER_SEC)),
leQueue,
^{
[self handlePoweredOff];
});
-
}
}
}
-- (void)start
+- (void)startWithTimeout:(int)timeout
{
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
-
Q_ASSERT(leQueue);
+ inquiryTimeoutMS = timeout;
manager.reset([[CBCentralManager alloc] initWithDelegate:self queue:leQueue]);
}
@@ -199,30 +187,38 @@ QT_USE_NAMESPACE
if (internalState != InquiryActive && internalState != InquiryStarting)
return;
+ Q_ASSERT(notifier);
+
using namespace OSXBluetooth;
dispatch_queue_t leQueue(qt_LE_queue());
Q_ASSERT(leQueue);
#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0)
- const CBManagerState cbState(central.state);
- if (cbState == CBManagerStatePoweredOn) {
+ const CBManagerState state(central.state);
+ if (state == CBManagerStatePoweredOn) {
#else
- const CBCentralManagerState cbState(central.state);
- if (cbState == CBCentralManagerStatePoweredOn) {
+ const CBCentralManagerState state(central.state);
+ if (state == CBCentralManagerStatePoweredOn) {
#endif
if (internalState == InquiryStarting) {
internalState = InquiryActive;
- // Scan time is actually 10 seconds. Having a block with such delay can prevent
- // 'self' from being deleted in time, which is not good. So we split this
- // 10 s. timeout into smaller 'chunks'.
- scanTimer.start();
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- int64_t(qt_LE_deviceInquiryLength() / 100. * NSEC_PER_SEC)),
- leQueue,
- ^{
- [self stopScan];
- });
+
+ if (inquiryTimeoutMS > 0) {
+ // We have a finite-length discovery, schedule stopScan,
+ // with a smaller time step, otherwise it can prevent
+ // 'self' from being deleted in time, which is not good
+ // (the block will retain 'self', waiting for timeout).
+ scanTimer.start();
+ const int timeChunkMS = std::min(timeStepMS, inquiryTimeoutMS);
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
+ int64_t(timeChunkMS / 1000. * NSEC_PER_SEC)),
+ leQueue,
+ ^{
+ [self stopScan];
+ });
+ }
+
[manager scanForPeripheralsWithServices:nil options:nil];
} // Else we ignore.
#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0)
@@ -232,28 +228,32 @@ QT_USE_NAMESPACE
#endif
if (internalState == InquiryActive) {
[manager stopScan];
- // Not sure how this is possible at all, probably, can never happen.
+ // Not sure how this is possible at all,
+ // probably, can never happen.
internalState = ErrorPoweredOff;
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
} else {
internalState = ErrorLENotSupported;
+ emit notifier->LEnotSupported();
}
[manager setDelegate:nil];
#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0)
- } else if (cbState == CBManagerStatePoweredOff) {
+ } else if (state == CBManagerStatePoweredOff) {
#else
- } else if (cbState == CBCentralManagerStatePoweredOff) {
+ } else if (state == CBCentralManagerStatePoweredOff) {
#endif
if (internalState == InquiryStarting) {
#ifndef Q_OS_OSX
- // On iOS a user can see at this point an alert asking to enable
- // Bluetooth in the "Settings" app. If a user does,
+ // On iOS a user can see at this point an alert asking to
+ // enable Bluetooth in the "Settings" app. If a user does,
// we'll receive 'PoweredOn' state update later.
- // No change in state. Wait for 30 seconds (we split it into 'chunks' not
- // to retain 'self' for too long ) ...
+ // No change in internalState. Wait for 30 seconds
+ // (we split it into smaller steps not to retain 'self' for
+ // too long ) ...
errorTimer.start();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- (int64_t)(30 / 100. * NSEC_PER_SEC)),
+ (int64_t)(powerOffTimeStepS * NSEC_PER_SEC)),
leQueue,
^{
[self handlePoweredOff];
@@ -261,9 +261,10 @@ QT_USE_NAMESPACE
return;
#endif
internalState = ErrorPoweredOff;
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
} else {
- internalState = ErrorPoweredOff;
[manager stopScan];
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
}
[manager setDelegate:nil];
@@ -279,8 +280,6 @@ QT_USE_NAMESPACE
// lost; an update is imminent. "
// Wait for this imminent update.
}
-
- state.store(int(internalState));
}
- (void)stop
@@ -290,11 +289,16 @@ QT_USE_NAMESPACE
[manager setDelegate:nil];
internalState = InquiryCancelled;
- state.store(int(internalState));
+
+ notifier->disconnect();
+ notifier->deleteLater();
+ notifier = nullptr;
}
-- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
- advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
+- (void)centralManager:(CBCentralManager *)central
+ didDiscoverPeripheral:(CBPeripheral *)peripheral
+ advertisementData:(NSDictionary *)advertisementData
+ RSSI:(NSNumber *)RSSI
{
Q_UNUSED(advertisementData);
@@ -306,49 +310,28 @@ QT_USE_NAMESPACE
if (internalState != InquiryActive)
return;
- QBluetoothUuid deviceUuid;
-
-#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_7_0)
- if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0)) {
- if (!peripheral.identifier) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "peripheral without NSUUID";
- return;
- }
+ if (!notifier)
+ return;
- if ([uuids containsObject:peripheral.identifier]) {
- // We already know this peripheral ...
- return;
- }
+ QBluetoothUuid deviceUuid;
- [uuids addObject:peripheral.identifier];
- deviceUuid = OSXBluetooth::qt_uuid(peripheral.identifier);
+ if (!peripheral.identifier) {
+ qCWarning(QT_BT_OSX) << "peripheral without NSUUID";
+ return;
}
-#endif
- // Either SDK or the target is below 10.9/7.0:
- // The property UUID was finally removed in iOS 9, we have
- // to avoid compilation errors ...
- if (deviceUuid.isNull()) {
- CFUUIDRef cfUUID = Q_NULLPTR;
-
- if ([peripheral respondsToSelector:@selector(UUID)]) {
- // This will require a bridged cast if we switch to ARC ...
- cfUUID = reinterpret_cast<CFUUIDRef>([peripheral performSelector:@selector(UUID)]);
- }
- if (!cfUUID) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "peripheral without CFUUID";
- return;
- }
-
- StringStrongReference key(uuid_as_nsstring(cfUUID));
- if ([uuids containsObject:key.data()])
- return; // We've seen this peripheral before ...
- [uuids addObject:key.data()];
- deviceUuid = OSXBluetooth::qt_uuid(cfUUID);
+ if ([uuids containsObject:peripheral.identifier]) {
+ // TODO: my understanding of the same peripheral reported many times seems
+ // to be outdated or even wrong - nowadays it's reported twice and the
+ // second time (AFAIK) more info can be extracted ...
+ return;
}
+ [uuids addObject:peripheral.identifier];
+ deviceUuid = OSXBluetooth::qt_uuid(peripheral.identifier);
+
if (deviceUuid.isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no way to address peripheral, QBluetoothUuid is null";
+ qCWarning(QT_BT_OSX) << "no way to address peripheral, QBluetoothUuid is null";
return;
}
@@ -362,17 +345,7 @@ QT_USE_NAMESPACE
newDeviceInfo.setRssi([RSSI shortValue]);
// CoreBluetooth scans only for LE devices.
newDeviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
- devices.append(newDeviceInfo);
-}
-
-- (LEInquiryState) inquiryState
-{
- return LEInquiryState(state.load());
-}
-
-- (const QList<QBluetoothDeviceInfo> &)discoveredDevices
-{
- return devices;
+ emit notifier->deviceDiscovered(newDeviceInfo);
}
@end