summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/osx
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@qt.io>2018-07-25 14:42:06 +0200
committerAlex Blasche <alexander.blasche@qt.io>2018-07-25 14:42:18 +0200
commit25638b02766ae3109bfc83b3249e6d0dc9e21bdb (patch)
tree8ac8f0cd544dd39da674657940d6c509ea94721c /src/bluetooth/osx
parentfc2a206322f6190226ebc1f04062f2c9170f0bac (diff)
parentb19148f9a0f820630bd83432d96117e9598c315d (diff)
Merge remote-tracking branch 'gerrit/dev' into btlebtle
Diffstat (limited to 'src/bluetooth/osx')
-rw-r--r--src/bluetooth/osx/osxbt.pri7
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager.mm226
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager_p.h23
-rw-r--r--src/bluetooth/osx/osxbtgcdtimer.mm111
-rw-r--r--src/bluetooth/osx/osxbtgcdtimer_p.h94
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry.mm119
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry_p.h18
-rw-r--r--src/bluetooth/osx/osxbtperipheralmanager.mm5
8 files changed, 466 insertions, 137 deletions
diff --git a/src/bluetooth/osx/osxbt.pri b/src/bluetooth/osx/osxbt.pri
index 13187e4f..b7ac0535 100644
--- a/src/bluetooth/osx/osxbt.pri
+++ b/src/bluetooth/osx/osxbt.pri
@@ -1,6 +1,9 @@
SOURCES += osx/uistrings.cpp osx/osxbtnotifier.cpp
-PRIVATE_HEADERS += osx/uistrings_p.h
-//QMAKE_CXXFLAGS_WARN_ON += -Wno-nullability-completeness
+PRIVATE_HEADERS += osx/uistrings_p.h \
+ osx/osxbtgcdtimer_p.h
+
+OBJECTIVE_SOURCES += osx/osxbtgcdtimer.mm
+#QMAKE_CXXFLAGS_WARN_ON += -Wno-nullability-completeness
CONFIG(osx) {
PRIVATE_HEADERS += osx/osxbtutility_p.h \
diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm
index 70473f1f..9254bd98 100644
--- a/src/bluetooth/osx/osxbtcentralmanager.mm
+++ b/src/bluetooth/osx/osxbtcentralmanager.mm
@@ -79,8 +79,21 @@ NSUInteger qt_countGATTEntries(CBService *service)
return n;
}
+ObjCStrongReference<NSError> qt_timeoutNSError(OperationTimeout type)
+{
+ // For now we do not provide details, since nobody is using this NSError
+ // after all (except callbacks checking if an operation was successful
+ // or not).
+ Q_ASSERT(type != OperationTimeout::none);
+ Q_UNUSED(type)
+ NSError *nsError = [[NSError alloc] initWithDomain:CBErrorDomain
+ code:CBErrorOperationCancelled
+ userInfo:nil];
+ return ObjCStrongReference<NSError>(nsError, false /*do not retain, done already*/);
}
+} // namespace OSXBluetooth
+
QT_END_NAMESPACE
@@ -115,9 +128,11 @@ QT_END_NAMESPACE
- (id)initWith:(OSXBluetooth::LECBManagerNotifier *)aNotifier
{
+ using namespace OSXBluetooth;
+
if (self = [super init]) {
manager = nil;
- managerState = OSXBluetooth::CentralManagerIdle;
+ managerState = CentralManagerIdle;
disconnectPending = false;
peripheral = nil;
notifier = aNotifier;
@@ -125,6 +140,17 @@ QT_END_NAMESPACE
lastValidHandle = 0;
requestPending = false;
currentReadHandle = 0;
+ timeoutType = OperationTimeout::none;
+
+ if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("BLUETOOTH_GATT_TIMEOUT"))) {
+ bool ok = false;
+ const int value = qEnvironmentVariableIntValue("BLUETOOTH_GATT_TIMEOUT", &ok);
+ if (ok && value >= 0)
+ timeoutMS = value;
+ }
+
+ if (!timeoutMS)
+ timeoutMS = 20000;
}
return self;
@@ -153,6 +179,63 @@ QT_END_NAMESPACE
[super dealloc];
}
+- (void)watchAfter:(id)object timeout:(OSXBluetooth::OperationTimeout)type
+{
+ using namespace OSXBluetooth;
+
+ objectUnderWatch = object;
+ timeoutType = type;
+ [timeoutWatchdog cancelTimer];
+ timeoutWatchdog.reset([[GCDTimerObjC alloc] initWithDelegate:self]);
+ [timeoutWatchdog startWithTimeout:timeoutMS step:200];
+}
+
+- (void)stopWatchdog
+{
+ [timeoutWatchdog cancelTimer];
+ objectUnderWatch = nil;
+ timeoutType = OSXBluetooth::OperationTimeout::none;
+}
+
+- (void)timeout
+{
+ using namespace OSXBluetooth;
+
+ Q_ASSERT(objectUnderWatch);
+ NSLog(@"Timeout caused by: %@", objectUnderWatch);
+ const ObjCStrongReference<NSError> nsError(qt_timeoutNSError(timeoutType));
+ switch (timeoutType) {
+ case OperationTimeout::serviceDiscovery:
+ qCWarning(QT_BT_OSX, "Timeout in services discovery");
+ [self peripheral:peripheral didDiscoverServices:nsError];
+ break;
+ case OperationTimeout::includedServicesDiscovery:
+ qCWarning(QT_BT_OSX, "Timeout in included services discovery");
+ [self peripheral:peripheral didDiscoverIncludedServicesForService:objectUnderWatch error:nsError];
+ break;
+ case OperationTimeout::characteristicsDiscovery:
+ qCWarning(QT_BT_OSX, "Timeout in characteristics discovery");
+ [self peripheral:peripheral didDiscoverCharacteristicsForService:objectUnderWatch error:nsError];
+ break;
+ case OperationTimeout::characteristicRead:
+ qCWarning(QT_BT_OSX, "Timeout while reading a characteristic");
+ [self peripheral:peripheral didUpdateValueForCharacteristic:objectUnderWatch error:nsError];
+ break;
+ case OperationTimeout::descriptorsDiscovery:
+ qCWarning(QT_BT_OSX, "Timeout in descriptors discovery");
+ [self peripheral:peripheral didDiscoverDescriptorsForCharacteristic:objectUnderWatch error:nsError];
+ break;
+ case OperationTimeout::descriptorRead:
+ qCWarning(QT_BT_OSX, "Timeout while reading a descriptor");
+ [self peripheral:peripheral didUpdateValueForDescriptor:objectUnderWatch error:nsError];
+ break;
+ case OperationTimeout::characteristicWrite:
+ qCWarning(QT_BT_OSX, "Timeout while writing a characteristic with response");
+ [self peripheral:peripheral didWriteValueForCharacteristic:objectUnderWatch error:nsError];
+ default:;
+ }
+}
+
- (void)connectToDevice:(const QBluetoothUuid &)aDeviceUuid
{
disconnectPending = false; // Cancel the previous disconnect if any.
@@ -235,10 +318,11 @@ QT_END_NAMESPACE
- (void)connectToPeripheral
{
+ using namespace OSXBluetooth;
+
Q_ASSERT_X(manager, Q_FUNC_INFO, "invalid central manager (nil)");
Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
- Q_ASSERT_X(managerState == OSXBluetooth::CentralManagerIdle,
- Q_FUNC_INFO, "invalid state");
+ Q_ASSERT_X(managerState == CentralManagerIdle, Q_FUNC_INFO, "invalid state");
// The state is still the same - connecting.
if ([self isConnected]) {
@@ -247,7 +331,7 @@ QT_END_NAMESPACE
emit notifier->connected();
} else {
qCDebug(QT_BT_OSX) << "trying to connect";
- managerState = OSXBluetooth::CentralManagerConnecting;
+ managerState = CentralManagerConnecting;
[manager connectPeripheral:peripheral options:nil];
}
}
@@ -292,9 +376,10 @@ QT_END_NAMESPACE
- (void)discoverServices
{
+ using namespace OSXBluetooth;
+
Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
- Q_ASSERT_X(managerState == OSXBluetooth::CentralManagerIdle,
- Q_FUNC_INFO, "invalid state");
+ Q_ASSERT_X(managerState == CentralManagerIdle, Q_FUNC_INFO, "invalid state");
// From Apple's docs:
//
@@ -304,7 +389,8 @@ QT_END_NAMESPACE
//
// ... but we'd like to have them all:
[peripheral setDelegate:self];
- managerState = OSXBluetooth::CentralManagerDiscovering;
+ managerState = CentralManagerDiscovering;
+ [self watchAfter:peripheral timeout:OperationTimeout::serviceDiscovery];
[peripheral discoverServices:nil];
}
@@ -333,6 +419,7 @@ QT_END_NAMESPACE
CBService *const s = [services objectAtIndex:currentService];
[visitedServices addObject:s];
managerState = CentralManagerDiscovering;
+ [self watchAfter:s timeout:OperationTimeout::includedServicesDiscovery];
[peripheral discoverIncludedServices:nil forService:s];
}
}
@@ -359,6 +446,7 @@ QT_END_NAMESPACE
if (CBService *const service = [self serviceForUUID:serviceUuid]) {
servicesToDiscoverDetails.append(serviceUuid);
+ [self watchAfter:service timeout:OperationTimeout::characteristicsDiscovery];
[peripheral discoverCharacteristics:nil forService:service];
return;
}
@@ -366,10 +454,8 @@ QT_END_NAMESPACE
qCWarning(QT_BT_OSX) << "unknown service uuid"
<< serviceUuid;
- if (notifier) {
- emit notifier->CBManagerError(serviceUuid,
- QLowEnergyService::UnknownError);
- }
+ if (notifier)
+ emit notifier->CBManagerError(serviceUuid, QLowEnergyService::UnknownError);
}
- (void)readCharacteristics:(CBService *)service
@@ -391,8 +477,10 @@ QT_END_NAMESPACE
NSArray *const cs = service.characteristics;
for (CBCharacteristic *c in cs) {
- if (c.properties & CBCharacteristicPropertyRead)
+ if (c.properties & CBCharacteristicPropertyRead) {
+ [self watchAfter:c timeout:OperationTimeout::characteristicRead];
return [peripheral readValueForCharacteristic:c];
+ }
}
// No readable properties? Discover descriptors then:
@@ -418,15 +506,18 @@ QT_END_NAMESPACE
[self serviceDetailsDiscoveryFinished:service];
} else {
// Start from 0 and continue in the callback.
- [peripheral discoverDescriptorsForCharacteristic:[service.characteristics objectAtIndex:0]];
+ CBCharacteristic *ch = [service.characteristics objectAtIndex:0];
+ [self watchAfter:ch timeout:OperationTimeout::descriptorsDiscovery];
+ [peripheral discoverDescriptorsForCharacteristic:ch];
}
}
- (void)readDescriptors:(CBService *)service
{
+ using namespace OSXBluetooth;
+
Q_ASSERT_X(service, Q_FUNC_INFO, "invalid service (nil)");
- Q_ASSERT_X(managerState != OSXBluetooth::CentralManagerUpdating,
- Q_FUNC_INFO, "invalid state");
+ Q_ASSERT_X(managerState != CentralManagerUpdating, Q_FUNC_INFO, "invalid state");
Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
QT_BT_MAC_AUTORELEASEPOOL;
@@ -435,8 +526,11 @@ QT_END_NAMESPACE
// We can never be here if we have no characteristics.
Q_ASSERT_X(cs && cs.count, Q_FUNC_INFO, "invalid service");
for (CBCharacteristic *c in cs) {
- if (c.descriptors && c.descriptors.count)
- return [peripheral readValueForDescriptor:[c.descriptors objectAtIndex:0]];
+ if (c.descriptors && c.descriptors.count) {
+ CBDescriptor *desc = [c.descriptors objectAtIndex:0];
+ [self watchAfter:desc timeout:OperationTimeout::descriptorRead];
+ return [peripheral readValueForDescriptor:desc];
+ }
}
// No descriptors to read, done.
@@ -571,6 +665,8 @@ QT_END_NAMESPACE
requestPending = true;
currentReadHandle = request.handle;
+ // Timeouts: for now, we do not alert timeoutWatchdog - never had such
+ // bug reports and after all a read timeout can be handled externally.
[peripheral readValueForCharacteristic:charMap[request.handle]];
} else {
if (!descMap.contains(request.handle)) {
@@ -581,6 +677,7 @@ QT_END_NAMESPACE
requestPending = true;
currentReadHandle = request.handle;
+ // Timeouts: see the comment above (CharRead).
[peripheral readValueForDescriptor:descMap[request.handle]];
}
}
@@ -657,6 +754,7 @@ QT_END_NAMESPACE
return [self performNextRequest];
requestPending = true;
+ [self watchAfter:characteristic timeout:OperationTimeout::characteristicWrite];
[peripheral writeValue:data.data() forCharacteristic:characteristic
type:CBCharacteristicWriteWithResponse];
} else {
@@ -1021,6 +1119,7 @@ QT_END_NAMESPACE
charMap.clear();
descMap.clear();
currentReadHandle = 0;
+ [self stopWatchdog];
// TODO: also serviceToVisit/VisitNext and visitedServices ?
}
@@ -1030,6 +1129,9 @@ QT_END_NAMESPACE
{
using namespace OSXBluetooth;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability-new"
+
const auto state = central.state;
#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
if (state == CBManagerStateUnknown
@@ -1072,12 +1174,14 @@ QT_END_NAMESPACE
#else
if (state == CBCentralManagerStatePoweredOff) {
#endif
- managerState = CentralManagerIdle;
+
if (managerState == CentralManagerUpdating) {
+ managerState = CentralManagerIdle;
// I've seen this instead of Unsupported on OS X.
if (notifier)
emit notifier->LEnotSupported();
} else {
+ managerState = CentralManagerIdle;
// TODO: we need a better error +
// what will happen if later the state changes to PoweredOn???
if (notifier)
@@ -1099,6 +1203,8 @@ QT_END_NAMESPACE
// We actually handled all known states, but .. Core Bluetooth can change?
Q_ASSERT_X(0, Q_FUNC_INFO, "invalid centra's state");
}
+
+#pragma clang diagnostic pop
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)aPeripheral
@@ -1162,10 +1268,16 @@ QT_END_NAMESPACE
Q_UNUSED(aPeripheral)
if (managerState != OSXBluetooth::CentralManagerDiscovering) {
- // Canceled by -disconnectFromDevice.
+ // Canceled by -disconnectFromDevice, or as a result of a timeout.
+ return;
+ }
+
+ if (objectUnderWatch != aPeripheral) {
+ // Timed out.
return;
}
+ [self stopWatchdog];
managerState = OSXBluetooth::CentralManagerIdle;
if (error) {
@@ -1173,9 +1285,9 @@ QT_END_NAMESPACE
// TODO: better error mapping required.
if (notifier)
emit notifier->CBManagerError(QLowEnergyController::UnknownError);
- } else {
- [self discoverIncludedServices];
}
+
+ [self discoverIncludedServices];
}
@@ -1191,10 +1303,16 @@ QT_END_NAMESPACE
return;
}
+ if (service != objectUnderWatch) {
+ // Timed out.
+ return;
+ }
+
QT_BT_MAC_AUTORELEASEPOOL;
Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+ [self stopWatchdog];
managerState = CentralManagerIdle;
if (error) {
@@ -1217,6 +1335,7 @@ QT_END_NAMESPACE
// Continue with discovery ...
[visitedServices addObject:s];
managerState = CentralManagerDiscovering;
+ [self watchAfter:s timeout:OperationTimeout::includedServicesDiscovery];
return [peripheral discoverIncludedServices:nil forService:s];
}
}
@@ -1232,6 +1351,7 @@ QT_END_NAMESPACE
if (![visitedServices containsObject:s]) {
[visitedServices addObject:s];
managerState = CentralManagerDiscovering;
+ [self watchAfter:s timeout:OperationTimeout::includedServicesDiscovery];
return [peripheral discoverIncludedServices:nil forService:s];
}
}
@@ -1260,6 +1380,13 @@ QT_END_NAMESPACE
return;
}
+ if (service != objectUnderWatch) {
+ // Timed out already?
+ return;
+ }
+
+ [self stopWatchdog];
+
using namespace OSXBluetooth;
Q_ASSERT_X(managerState != CentralManagerUpdating, Q_FUNC_INFO, "invalid state");
@@ -1269,9 +1396,9 @@ QT_END_NAMESPACE
// We did not discover any characteristics and can not discover descriptors,
// inform our delegate (it will set a service state also).
emit notifier->CBManagerError(qt_uuid(service.UUID), QLowEnergyController::UnknownError);
- } else {
- [self readCharacteristics:service];
}
+
+ [self readCharacteristics:service];
}
- (void)peripheral:(CBPeripheral *)aPeripheral
@@ -1285,6 +1412,10 @@ QT_END_NAMESPACE
return;
}
+ const bool readMatch = characteristic == objectUnderWatch;
+ if (readMatch)
+ [self stopWatchdog];
+
using namespace OSXBluetooth;
Q_ASSERT_X(managerState != CentralManagerUpdating, Q_FUNC_INFO, "invalid state");
@@ -1311,13 +1442,17 @@ QT_END_NAMESPACE
}
if (isDetailsDiscovery) {
- // Test if we have any other characteristic to read yet.
- CBCharacteristic *const next = [self nextCharacteristicForService:characteristic.service
- startingFrom:characteristic properties:CBCharacteristicPropertyRead];
- if (next)
- [peripheral readValueForCharacteristic:next];
- else
- [self discoverDescriptors:characteristic.service];
+ if (readMatch) {
+ // Test if we have any other characteristic to read yet.
+ CBCharacteristic *const next = [self nextCharacteristicForService:characteristic.service
+ startingFrom:characteristic properties:CBCharacteristicPropertyRead];
+ if (next) {
+ [self watchAfter:next timeout:OperationTimeout::characteristicRead];
+ [peripheral readValueForCharacteristic:next];
+ } else {
+ [self discoverDescriptors:characteristic.service];
+ }
+ }
} else {
// This is (probably) the result of update notification.
// It's very possible we can have an invalid handle here (0) -
@@ -1360,6 +1495,11 @@ QT_END_NAMESPACE
QT_BT_MAC_AUTORELEASEPOOL;
+ if (characteristic != objectUnderWatch)
+ return;
+
+ [self stopWatchdog];
+
using namespace OSXBluetooth;
if (error) {
@@ -1370,10 +1510,12 @@ QT_END_NAMESPACE
// Do we have more characteristics on this service to discover descriptors?
CBCharacteristic *const next = [self nextCharacteristicForService:characteristic.service
startingFrom:characteristic];
- if (next)
+ if (next) {
+ [self watchAfter:next timeout:OperationTimeout::descriptorsDiscovery];
[peripheral discoverDescriptorsForCharacteristic:next];
- else
+ } else {
[self readDescriptors:characteristic.service];
+ }
}
- (void)peripheral:(CBPeripheral *)aPeripheral
@@ -1391,6 +1533,11 @@ QT_END_NAMESPACE
QT_BT_MAC_AUTORELEASEPOOL;
+ if (descriptor != objectUnderWatch)
+ return;
+
+ [self stopWatchdog];
+
using namespace OSXBluetooth;
CBService *const service = descriptor.characteristic.service;
@@ -1417,6 +1564,7 @@ QT_END_NAMESPACE
CBDescriptor *const next = [self nextDescriptorForCharacteristic:descriptor.characteristic
startingFrom:descriptor];
if (next) {
+ [self watchAfter:next timeout:OperationTimeout::descriptorRead];
[peripheral readValueForDescriptor:next];
} else {
// We either have to read a value for a next descriptor
@@ -1426,8 +1574,11 @@ QT_END_NAMESPACE
CBCharacteristic *nextCh = [self nextCharacteristicForService:ch.service
startingFrom:ch];
while (nextCh) {
- if (nextCh.descriptors && nextCh.descriptors.count)
- return [peripheral readValueForDescriptor:[nextCh.descriptors objectAtIndex:0]];
+ if (nextCh.descriptors && nextCh.descriptors.count) {
+ CBDescriptor *desc = [nextCh.descriptors objectAtIndex:0];
+ [self watchAfter:desc timeout:OperationTimeout::descriptorRead];
+ return [peripheral readValueForDescriptor:desc];
+ }
nextCh = [self nextCharacteristicForService:ch.service
startingFrom:nextCh];
@@ -1474,6 +1625,10 @@ QT_END_NAMESPACE
QT_BT_MAC_AUTORELEASEPOOL;
+ if (characteristic != objectUnderWatch)
+ return;
+
+ [self stopWatchdog];
requestPending = false;
@@ -1569,9 +1724,10 @@ QT_END_NAMESPACE
if (notifier) {
notifier->disconnect();
notifier->deleteLater();
- notifier = 0;
+ notifier = nullptr;
}
+ [self stopWatchdog];
[self disconnectFromDevice];
}
diff --git a/src/bluetooth/osx/osxbtcentralmanager_p.h b/src/bluetooth/osx/osxbtcentralmanager_p.h
index 697d922c..e172d874 100644
--- a/src/bluetooth/osx/osxbtcentralmanager_p.h
+++ b/src/bluetooth/osx/osxbtcentralmanager_p.h
@@ -53,6 +53,7 @@
#include "qlowenergycontroller.h"
#include "qlowenergyservice.h"
+#include "osxbtgcdtimer_p.h"
#include "qbluetoothuuid.h"
#include "osxbtutility_p.h"
#include "osxbluetooth_p.h"
@@ -86,6 +87,18 @@ enum CentralManagerState
CentralManagerDisconnecting
};
+enum class OperationTimeout
+{
+ none,
+ serviceDiscovery,
+ includedServicesDiscovery,
+ characteristicsDiscovery,
+ characteristicRead,
+ descriptorsDiscovery,
+ descriptorRead,
+ characteristicWrite
+};
+
// In Qt we work with handles and UUIDs. Core Bluetooth
// has NSArrays (and nested NSArrays inside servces/characteristics).
// To simplify a navigation, I need a simple way to map from a handle
@@ -132,7 +145,9 @@ typedef QHash<NSObject *, QByteArray> ValueHash;
QT_END_NAMESPACE
-@interface QT_MANGLE_NAMESPACE(OSXBTCentralManager) : NSObject<CBCentralManagerDelegate, CBPeripheralDelegate>
+@interface QT_MANGLE_NAMESPACE(OSXBTCentralManager) : NSObject<CBCentralManagerDelegate,
+ CBPeripheralDelegate,
+ QT_MANGLE_NAMESPACE(GCDTimerDelegate)>
{
@private
CBCentralManager *manager;
@@ -166,6 +181,12 @@ QT_END_NAMESPACE
QT_PREPEND_NAMESPACE(QLowEnergyHandle) currentReadHandle;
QT_PREPEND_NAMESPACE(OSXBluetooth)::ValueHash valuesToWrite;
+
+ qint64 timeoutMS;
+ id objectUnderWatch;
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::OperationTimeout timeoutType;
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::GCDTimer timeoutWatchdog;
+
@public
CBPeripheral *peripheral;
}
diff --git a/src/bluetooth/osx/osxbtgcdtimer.mm b/src/bluetooth/osx/osxbtgcdtimer.mm
new file mode 100644
index 00000000..095f8680
--- /dev/null
+++ b/src/bluetooth/osx/osxbtgcdtimer.mm
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "osxbtgcdtimer_p.h"
+#include "osxbtutility_p.h"
+
+#include <QtCore/qdebug.h>
+
+#include <algorithm>
+
+@implementation QT_MANGLE_NAMESPACE(OSXBTGCDTimer)
+
+- (instancetype)initWithDelegate:(id<QT_MANGLE_NAMESPACE(GCDTimerDelegate)>)delegate
+{
+ if (self = [super init]) {
+ timeoutHandler = delegate;
+ timeoutMS = 0;
+ timeoutStepMS = 0;
+ cancelled = false;
+ }
+ return self;
+}
+
+- (void)startWithTimeout:(qint64)ms step:(qint64)stepMS
+{
+ Q_ASSERT(!timeoutMS && !timeoutStepMS);
+ Q_ASSERT(!cancelled);
+
+ if (!timeoutHandler) {
+ // Nobody to report timeout to, no need to start any task then.
+ return;
+ }
+
+ if (ms <= 0 || stepMS <= 0) {
+ qCWarning(QT_BT_OSX, "Invalid timeout/step parameters");
+ return;
+ }
+
+ timeoutMS = ms;
+ timeoutStepMS = stepMS;
+ timer.start();
+
+ [self handleTimeout];
+}
+
+- (void)handleTimeout
+{
+ if (cancelled)
+ return;
+
+ const qint64 elapsed = timer.elapsed();
+ if (elapsed >= timeoutMS) {
+ [timeoutHandler timeout];
+ } else {
+ using namespace QT_PREPEND_NAMESPACE(OSXBluetooth);
+ // Re-schedule:
+ dispatch_queue_t leQueue(qt_LE_queue());
+ Q_ASSERT(leQueue);
+ const qint64 timeChunkMS = std::min(timeoutMS - elapsed, timeoutStepMS);
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
+ int64_t(timeChunkMS / 1000. * NSEC_PER_SEC)),
+ leQueue,
+ ^{
+ [self handleTimeout];
+ });
+ }
+}
+
+- (void)cancelTimer
+{
+ cancelled = true;
+ timeoutHandler = nil;
+}
+
+@end
diff --git a/src/bluetooth/osx/osxbtgcdtimer_p.h b/src/bluetooth/osx/osxbtgcdtimer_p.h
new file mode 100644
index 00000000..007a004b
--- /dev/null
+++ b/src/bluetooth/osx/osxbtgcdtimer_p.h
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef OSXBTGCDTIMER_P_H
+#define OSXBTGCDTIMER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "osxbtutility_p.h"
+
+#include <QtCore/qelapsedtimer.h>
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+
+@protocol QT_MANGLE_NAMESPACE(GCDTimerDelegate)
+@required
+- (void)timeout;
+@end
+
+@interface QT_MANGLE_NAMESPACE(OSXBTGCDTimer) : NSObject {
+@private
+ qint64 timeoutMS;
+ qint64 timeoutStepMS;
+ QT_PREPEND_NAMESPACE(QElapsedTimer) timer;
+ id<QT_MANGLE_NAMESPACE(GCDTimerDelegate)> timeoutHandler;
+ bool cancelled;
+}
+
+- (instancetype)initWithDelegate:(id<QT_MANGLE_NAMESPACE(GCDTimerDelegate)>)delegate;
+- (void)startWithTimeout:(qint64)ms step:(qint64)stepMS;
+- (void)handleTimeout;
+- (void)cancelTimer;
+
+@end
+
+QT_BEGIN_NAMESPACE
+
+namespace OSXBluetooth {
+
+using GCDTimerObjC = QT_MANGLE_NAMESPACE(OSXBTGCDTimer);
+using GCDTimer = ObjCScopedPointer<GCDTimerObjC>;
+
+}
+
+QT_END_NAMESPACE
+
+#endif // OSXBTGCDTIMER_P_H
+
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry.mm b/src/bluetooth/osx/osxbtledeviceinquiry.mm
index 7a516dd4..2cece15b 100644
--- a/src/bluetooth/osx/osxbtledeviceinquiry.mm
+++ b/src/bluetooth/osx/osxbtledeviceinquiry.mm
@@ -65,9 +65,7 @@ QBluetoothUuid qt_uuid(NSUUID *nsUuid)
}
const int timeStepMS = 100;
-
const int powerOffTimeoutMS = 30000;
-const qreal powerOffTimeStepS = 30. / 100.;
struct AdvertisementData {
// That's what CoreBluetooth has:
@@ -115,14 +113,6 @@ 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)initWithNotifier:(LECBManagerNotifier *)aNotifier
@@ -153,60 +143,22 @@ QT_USE_NAMESPACE
[super dealloc];
}
-- (void)stopScan
+- (void)timeout
{
- using namespace OSXBluetooth;
-
- // We never schedule stopScan if there is no timeout:
- Q_ASSERT(inquiryTimeoutMS > 0);
-
if (internalState == InquiryActive) {
- const int elapsed = scanTimer.elapsed();
- if (elapsed >= inquiryTimeoutMS) {
- [manager stopScan];
- [manager setDelegate:nil];
- internalState = InquiryFinished;
- 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(timeChunkMS / 1000. * NSEC_PER_SEC)),
- leQueue,
- ^{
- [self stopScan];
- });
- }
- }
-}
-
-- (void)handlePoweredOff
-{
- // This is interesting on iOS only, where
- // 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() >= powerOffTimeoutMS) {
- [manager setDelegate:nil];
- internalState = ErrorPoweredOff;
- Q_ASSERT(notifier);
- emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
- } else {
- dispatch_queue_t leQueue(qt_LE_queue());
- Q_ASSERT(leQueue);
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- (int64_t)(powerOffTimeStepS * NSEC_PER_SEC)),
- leQueue,
- ^{
- [self handlePoweredOff];
- });
- }
+ [manager stopScan];
+ [manager setDelegate:nil];
+ internalState = InquiryFinished;
+ Q_ASSERT(notifier);
+ emit notifier->discoveryFinished();
+ } else if (internalState == InquiryStarting) {
+ // This is interesting on iOS only, where the system shows an alert
+ // asking to enable Bluetooth in the 'Settings' app. If not done yet
+ // (after 30 seconds) - we consider this as an error.
+ [manager setDelegate:nil];
+ internalState = ErrorPoweredOff;
+ Q_ASSERT(notifier);
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
}
}
@@ -220,6 +172,9 @@ QT_USE_NAMESPACE
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability-new"
+
if (central != manager)
return;
@@ -230,9 +185,6 @@ QT_USE_NAMESPACE
using namespace OSXBluetooth;
- dispatch_queue_t leQueue(qt_LE_queue());
- Q_ASSERT(leQueue);
-
const auto state = central.state;
#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
if (state == CBManagerStatePoweredOn) {
@@ -243,18 +195,9 @@ QT_USE_NAMESPACE
internalState = InquiryActive;
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];
- });
+ [elapsedTimer cancelTimer];
+ elapsedTimer.reset([[GCDTimerObjC alloc] initWithDelegate:self]);
+ [elapsedTimer startWithTimeout:inquiryTimeoutMS step:timeStepMS];
}
[manager scanForPeripheralsWithServices:nil options:nil];
@@ -284,19 +227,15 @@ QT_USE_NAMESPACE
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,
+ // enable Bluetooth in the "Settings" app. If a user does so,
// we'll receive 'PoweredOn' state update later.
- // 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)(powerOffTimeStepS * NSEC_PER_SEC)),
- leQueue,
- ^{
- [self handlePoweredOff];
- });
+ // No change in internalState. Wait for 30 seconds.
+ [elapsedTimer cancelTimer];
+ elapsedTimer.reset([[GCDTimerObjC alloc] initWithDelegate:self]);
+ [elapsedTimer startWithTimeout:powerOffTimeoutMS step:300];
return;
+#else
+ Q_UNUSED(powerOffTimeoutMS)
#endif
internalState = ErrorPoweredOff;
emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
@@ -318,6 +257,8 @@ QT_USE_NAMESPACE
// lost; an update is imminent. "
// Wait for this imminent update.
}
+
+#pragma clang diagnostic pop
}
- (void)stop
@@ -325,6 +266,8 @@ QT_USE_NAMESPACE
if (internalState == InquiryActive)
[manager stopScan];
+ [elapsedTimer cancelTimer];
+
[manager setDelegate:nil];
internalState = InquiryCancelled;
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry_p.h b/src/bluetooth/osx/osxbtledeviceinquiry_p.h
index fc787a6d..a19055ab 100644
--- a/src/bluetooth/osx/osxbtledeviceinquiry_p.h
+++ b/src/bluetooth/osx/osxbtledeviceinquiry_p.h
@@ -53,10 +53,10 @@
#include "qbluetoothdevicediscoveryagent.h"
#include "qbluetoothdeviceinfo.h"
+#include "osxbtgcdtimer_p.h"
#include "osxbtutility_p.h"
#include "osxbluetooth_p.h"
-#include <QtCore/qelapsedtimer.h>
#include <QtCore/qglobal.h>
#include <QtCore/qlist.h>
@@ -75,10 +75,8 @@ class LECBManagerNotifier;
QT_END_NAMESPACE
-// Ugly but all these QT_PREPEND_NAMESPACE etc. are even worse ...
-using OSXBluetooth::LECBManagerNotifier;
-using OSXBluetooth::ObjCScopedPointer;
-using QT_PREPEND_NAMESPACE(QElapsedTimer);
+using QT_PREPEND_NAMESPACE(OSXBluetooth)::LECBManagerNotifier;
+using QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCScopedPointer;
enum LEInquiryState
{
@@ -90,7 +88,7 @@ enum LEInquiryState
ErrorLENotSupported
};
-@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) : NSObject
+@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) : NSObject<CBCentralManagerDelegate, QT_MANGLE_NAMESPACE(GCDTimerDelegate)>
{
LECBManagerNotifier *notifier;
ObjCScopedPointer<CBCentralManager> manager;
@@ -99,16 +97,14 @@ enum LEInquiryState
LEInquiryState internalState;
int inquiryTimeoutMS;
- // Timers to check if we can execute delayed callbacks:
- QT_PREPEND_NAMESPACE(QElapsedTimer) errorTimer;
- QT_PREPEND_NAMESPACE(QElapsedTimer) scanTimer;
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::GCDTimer elapsedTimer;
}
- (id)initWithNotifier:(LECBManagerNotifier *)aNotifier;
- (void)dealloc;
-// IMPORTANT: both 'startWithTimeout' and 'stop'
-// can be executed only on the "Qt's LE queue".
+// IMPORTANT: both 'startWithTimeout' and 'stop' MUST be executed on the "Qt's
+// LE queue".
- (void)startWithTimeout:(int)timeout;
- (void)stop;
diff --git a/src/bluetooth/osx/osxbtperipheralmanager.mm b/src/bluetooth/osx/osxbtperipheralmanager.mm
index 64c8cd90..d3d92f41 100644
--- a/src/bluetooth/osx/osxbtperipheralmanager.mm
+++ b/src/bluetooth/osx/osxbtperipheralmanager.mm
@@ -388,6 +388,9 @@ bool qt_validate_value_range(const QLowEnergyCharacteristicData &data)
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
{
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability-new"
+
if (peripheral != manager || !notifier)
return;
@@ -436,6 +439,8 @@ bool qt_validate_value_range(const QLowEnergyCharacteristicData &data)
emit notifier->LEnotSupported();
state = PeripheralState::idle;
}
+
+#pragma clang diagnostic pop
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral