diff options
Diffstat (limited to 'src/bluetooth/osx/osxbtcentralmanager.mm')
-rw-r--r-- | src/bluetooth/osx/osxbtcentralmanager.mm | 188 |
1 files changed, 133 insertions, 55 deletions
diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm index 9254bd98..cadabbaf 100644 --- a/src/bluetooth/osx/osxbtcentralmanager.mm +++ b/src/bluetooth/osx/osxbtcentralmanager.mm @@ -46,6 +46,7 @@ #include <QtCore/qdebug.h> #include <algorithm> +#include <vector> #include <limits> Q_DECLARE_METATYPE(QLowEnergyHandle) @@ -92,13 +93,24 @@ ObjCStrongReference<NSError> qt_timeoutNSError(OperationTimeout type) return ObjCStrongReference<NSError>(nsError, false /*do not retain, done already*/); } +auto qt_find_watchdog(const std::vector<GCDTimer> &watchdogs, id object, OperationTimeout type) +{ + return std::find_if(watchdogs.begin(), watchdogs.end(), [object, type](const GCDTimer &other){ + return [other objectUnderWatch] == object && [other timeoutType] == type;}); +} + } // namespace OSXBluetooth QT_END_NAMESPACE +QT_USE_NAMESPACE @interface QT_MANGLE_NAMESPACE(OSXBTCentralManager) (PrivateAPI) +- (void)watchAfter:(id)object timeout:(OSXBluetooth::OperationTimeout)type; +- (bool)objectIsUnderWatch:(id)object operation:(OSXBluetooth::OperationTimeout)type; +- (void)stopWatchingAfter:(id)object operation:(OSXBluetooth::OperationTimeout)type; +- (void)stopWatchers; - (void)retrievePeripheralAndConnect; - (void)connectToPeripheral; - (void)discoverIncludedServices; @@ -125,6 +137,45 @@ QT_END_NAMESPACE @end @implementation QT_MANGLE_NAMESPACE(OSXBTCentralManager) +{ +@private + CBCentralManager *manager; + OSXBluetooth::CentralManagerState managerState; + bool disconnectPending; + + QBluetoothUuid deviceUuid; + + OSXBluetooth::LECBManagerNotifier *notifier; + + // Quite a verbose service discovery machinery + // (a "graph traversal"). + OSXBluetooth::ObjCStrongReference<NSMutableArray> servicesToVisit; + // The service we're discovering now (included services discovery): + NSUInteger currentService; + // Included services, we'll iterate through at the end of 'servicesToVisit': + OSXBluetooth::ObjCStrongReference<NSMutableArray> servicesToVisitNext; + // We'd like to avoid loops in a services' topology: + OSXBluetooth::ObjCStrongReference<NSMutableSet> visitedServices; + + QList<QBluetoothUuid> servicesToDiscoverDetails; + + OSXBluetooth::ServiceHash serviceMap; + OSXBluetooth::CharHash charMap; + OSXBluetooth::DescHash descMap; + + QLowEnergyHandle lastValidHandle; + + bool requestPending; + OSXBluetooth::RequestQueue requests; + QLowEnergyHandle currentReadHandle; + + OSXBluetooth::ValueHash valuesToWrite; + + qint64 timeoutMS; + std::vector<OSXBluetooth::GCDTimer> timeoutWatchdogs; + + CBPeripheral *peripheral; +} - (id)initWith:(OSXBluetooth::LECBManagerNotifier *)aNotifier { @@ -140,7 +191,6 @@ QT_END_NAMESPACE lastValidHandle = 0; requestPending = false; currentReadHandle = 0; - timeoutType = OperationTimeout::none; if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("BLUETOOTH_GATT_TIMEOUT"))) { bool ok = false; @@ -176,62 +226,91 @@ QT_END_NAMESPACE if (notifier) notifier->deleteLater(); + [self stopWatchers]; [super dealloc]; } +- (CBPeripheral *)peripheral +{ + return peripheral; +} + - (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]; + GCDTimer newWatcher([[GCDTimerObjC alloc] initWithDelegate:self], false /*do not retain*/); + [newWatcher watchAfter:object withTimeoutType:type]; + timeoutWatchdogs.push_back(newWatcher); + [newWatcher startWithTimeout:timeoutMS step:200]; +} + +- (bool)objectIsUnderWatch:(id)object operation:(OSXBluetooth::OperationTimeout)type +{ + return OSXBluetooth::qt_find_watchdog(timeoutWatchdogs, object, type) != timeoutWatchdogs.end(); } -- (void)stopWatchdog +- (void)stopWatchingAfter:(id)object operation:(OSXBluetooth::OperationTimeout)type { - [timeoutWatchdog cancelTimer]; - objectUnderWatch = nil; - timeoutType = OSXBluetooth::OperationTimeout::none; + auto pos = OSXBluetooth::qt_find_watchdog(timeoutWatchdogs, object, type); + if (pos != timeoutWatchdogs.end()) { + [*pos cancelTimer]; + timeoutWatchdogs.erase(pos); + } } -- (void)timeout +- (void)stopWatchers { + for (auto &watchdog : timeoutWatchdogs) + [watchdog cancelTimer]; + timeoutWatchdogs.clear(); +} + +- (void)timeout:(id)sender +{ + Q_UNUSED(sender) + using namespace OSXBluetooth; - Q_ASSERT(objectUnderWatch); - NSLog(@"Timeout caused by: %@", objectUnderWatch); - const ObjCStrongReference<NSError> nsError(qt_timeoutNSError(timeoutType)); - switch (timeoutType) { + GCDTimerObjC *watcher = static_cast<GCDTimerObjC *>(sender); + id cbObject = [watcher objectUnderWatch]; + const OperationTimeout type = [watcher timeoutType]; + + Q_ASSERT([self objectIsUnderWatch:cbObject operation:type]); + + NSLog(@"Timeout caused by: %@", cbObject); + + // Note that after this switch the 'watcher' is released (we don't + // own it anymore), though GCD is probably still holding a reference. + const ObjCStrongReference<NSError> nsError(qt_timeoutNSError(type)); + switch (type) { 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]; + [self peripheral:peripheral didDiscoverIncludedServicesForService:cbObject error:nsError]; break; case OperationTimeout::characteristicsDiscovery: qCWarning(QT_BT_OSX, "Timeout in characteristics discovery"); - [self peripheral:peripheral didDiscoverCharacteristicsForService:objectUnderWatch error:nsError]; + [self peripheral:peripheral didDiscoverCharacteristicsForService:cbObject error:nsError]; break; case OperationTimeout::characteristicRead: qCWarning(QT_BT_OSX, "Timeout while reading a characteristic"); - [self peripheral:peripheral didUpdateValueForCharacteristic:objectUnderWatch error:nsError]; + [self peripheral:peripheral didUpdateValueForCharacteristic:cbObject error:nsError]; break; case OperationTimeout::descriptorsDiscovery: qCWarning(QT_BT_OSX, "Timeout in descriptors discovery"); - [self peripheral:peripheral didDiscoverDescriptorsForCharacteristic:objectUnderWatch error:nsError]; + [self peripheral:peripheral didDiscoverDescriptorsForCharacteristic:cbObject error:nsError]; break; case OperationTimeout::descriptorRead: qCWarning(QT_BT_OSX, "Timeout while reading a descriptor"); - [self peripheral:peripheral didUpdateValueForDescriptor:objectUnderWatch error:nsError]; + [self peripheral:peripheral didUpdateValueForDescriptor:cbObject error:nsError]; break; case OperationTimeout::characteristicWrite: qCWarning(QT_BT_OSX, "Timeout while writing a characteristic with response"); - [self peripheral:peripheral didWriteValueForCharacteristic:objectUnderWatch error:nsError]; + [self peripheral:peripheral didWriteValueForCharacteristic:cbObject error:nsError]; default:; } } @@ -1119,7 +1198,7 @@ QT_END_NAMESPACE charMap.clear(); descMap.clear(); currentReadHandle = 0; - [self stopWatchdog]; + [self stopWatchers]; // TODO: also serviceToVisit/VisitNext and visitedServices ? } @@ -1272,12 +1351,14 @@ QT_END_NAMESPACE return; } - if (objectUnderWatch != aPeripheral) { - // Timed out. + using namespace OSXBluetooth; + if (![self objectIsUnderWatch:aPeripheral operation:OperationTimeout::serviceDiscovery]) // Timed out already return; - } - [self stopWatchdog]; + QT_BT_MAC_AUTORELEASEPOOL; + + [self stopWatchingAfter:aPeripheral operation:OperationTimeout::serviceDiscovery]; + managerState = OSXBluetooth::CentralManagerIdle; if (error) { @@ -1303,16 +1384,14 @@ QT_END_NAMESPACE return; } - if (service != objectUnderWatch) { - // Timed out. + if (![self objectIsUnderWatch:service operation:OperationTimeout::includedServicesDiscovery]) return; - } QT_BT_MAC_AUTORELEASEPOOL; Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)"); - [self stopWatchdog]; + [self stopWatchingAfter:service operation:OperationTimeout::includedServicesDiscovery]; managerState = CentralManagerIdle; if (error) { @@ -1380,14 +1459,14 @@ QT_END_NAMESPACE return; } - if (service != objectUnderWatch) { - // Timed out already? + using namespace OSXBluetooth; + + if (![self objectIsUnderWatch:service operation:OperationTimeout::characteristicsDiscovery]) return; - } - [self stopWatchdog]; + QT_BT_MAC_AUTORELEASEPOOL; - using namespace OSXBluetooth; + [self stopWatchingAfter:service operation:OperationTimeout::characteristicsDiscovery]; Q_ASSERT_X(managerState != CentralManagerUpdating, Q_FUNC_INFO, "invalid state"); @@ -1407,21 +1486,21 @@ QT_END_NAMESPACE { Q_UNUSED(aPeripheral) - if (!notifier) { - // Detached. + if (!notifier) // Detached. return; - } - - const bool readMatch = characteristic == objectUnderWatch; - if (readMatch) - [self stopWatchdog]; using namespace OSXBluetooth; + QT_BT_MAC_AUTORELEASEPOOL; + + const bool readMatch = [self objectIsUnderWatch:characteristic operation:OperationTimeout::characteristicRead]; + if (readMatch) + [self stopWatchingAfter:characteristic operation:OperationTimeout::characteristicRead]; + Q_ASSERT_X(managerState != CentralManagerUpdating, Q_FUNC_INFO, "invalid state"); Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)"); - QT_BT_MAC_AUTORELEASEPOOL; + // First, let's check if we're discovering a service details now. CBService *const service = characteristic.service; const QBluetoothUuid qtUuid(qt_uuid(service.UUID)); @@ -1495,12 +1574,12 @@ QT_END_NAMESPACE QT_BT_MAC_AUTORELEASEPOOL; - if (characteristic != objectUnderWatch) - return; + using namespace OSXBluetooth; - [self stopWatchdog]; + if (![self objectIsUnderWatch:characteristic operation:OperationTimeout::descriptorsDiscovery]) + return; - using namespace OSXBluetooth; + [self stopWatchingAfter:characteristic operation:OperationTimeout::descriptorsDiscovery]; if (error) { NSLog(@"%s failed with error %@", Q_FUNC_INFO, error); @@ -1533,12 +1612,12 @@ QT_END_NAMESPACE QT_BT_MAC_AUTORELEASEPOOL; - if (descriptor != objectUnderWatch) - return; + using namespace OSXBluetooth; - [self stopWatchdog]; + if (![self objectIsUnderWatch:descriptor operation:OperationTimeout::descriptorRead]) + return; - using namespace OSXBluetooth; + [self stopWatchingAfter:descriptor operation:OperationTimeout::descriptorRead]; CBService *const service = descriptor.characteristic.service; const QBluetoothUuid qtUuid(qt_uuid(service.UUID)); @@ -1625,13 +1704,12 @@ QT_END_NAMESPACE QT_BT_MAC_AUTORELEASEPOOL; - if (characteristic != objectUnderWatch) + if (![self objectIsUnderWatch:characteristic operation:OperationTimeout::characteristicWrite]) return; - [self stopWatchdog]; + [self stopWatchingAfter:characteristic operation:OperationTimeout::characteristicWrite]; requestPending = false; - // Error or not, but the cached value has to be deleted ... const QByteArray valueToReport(valuesToWrite.value(characteristic, QByteArray())); if (!valuesToWrite.remove(characteristic)) { @@ -1727,7 +1805,7 @@ QT_END_NAMESPACE notifier = nullptr; } - [self stopWatchdog]; + [self stopWatchers]; [self disconnectFromDevice]; } |