diff options
46 files changed, 3939 insertions, 1039 deletions
diff --git a/.qmake.conf b/.qmake.conf index a2a0d418..aefa1e70 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -1,3 +1,3 @@ load(qt_build_config) -MODULE_VERSION = 5.7.1 +MODULE_VERSION = 5.8.0 diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index 1857e983..63e9ec11 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -153,14 +153,10 @@ config_bluez:qtHaveModule(dbus) { qlowenergycontroller_osx.mm \ qlowenergyservice_osx.mm - SOURCES += \ - qlowenergycontroller_p.cpp - PRIVATE_HEADERS += qbluetoothsocket_osx_p.h \ qbluetoothserver_osx_p.h \ qbluetoothtransferreply_osx_p.h \ qbluetoothtransferreply_osx_p.h \ - qbluetoothdevicediscoverytimer_osx_p.h \ qlowenergycontroller_osx_p.h SOURCES -= qbluetoothdevicediscoveryagent.cpp @@ -182,8 +178,7 @@ config_bluez:qtHaveModule(dbus) { qlowenergyservice_osx.mm PRIVATE_HEADERS += \ - qlowenergycontroller_osx_p.h \ - qbluetoothdevicediscoverytimer_osx_p.h + qlowenergycontroller_osx_p.h include(osx/osxbt.pri) SOURCES += \ @@ -198,6 +193,21 @@ config_bluez:qtHaveModule(dbus) { SOURCES -= qlowenergycontroller_p.cpp SOURCES -= qlowenergyservice.cpp SOURCES -= qlowenergycontroller.cpp +} else:if(winphone|winrt-*-msvc2015) { + DEFINES += QT_WINRT_BLUETOOTH + QT += core-private + + # remove dummy warning once platform port is complete + include(dummy/dummy.pri) + + SOURCES += \ + qbluetoothdevicediscoveryagent_winrt.cpp \ + qbluetoothlocaldevice_p.cpp \ + qbluetoothserver_p.cpp \ + qbluetoothservicediscoveryagent_p.cpp \ + qbluetoothserviceinfo_p.cpp \ + qbluetoothsocket_p.cpp \ + qlowenergycontroller_winrt.cpp } else { message("Unsupported Bluetooth platform, will not build a working QtBluetooth library.") message("Either no Qt D-Bus found or no BlueZ headers available.") @@ -212,6 +222,12 @@ config_bluez:qtHaveModule(dbus) { qlowenergycontroller_p.cpp } +winrt-*-msvc2015 { + MODULE_WINRT_CAPABILITIES_DEVICE += \ + bluetooth.genericAttributeProfile \ + bluetooth.rfcomm +} + OTHER_FILES += HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS diff --git a/src/bluetooth/osx/osxbt.pri b/src/bluetooth/osx/osxbt.pri index 5ca833cc..13187e4f 100644 --- a/src/bluetooth/osx/osxbt.pri +++ b/src/bluetooth/osx/osxbt.pri @@ -1,5 +1,6 @@ SOURCES += osx/uistrings.cpp osx/osxbtnotifier.cpp PRIVATE_HEADERS += osx/uistrings_p.h +//QMAKE_CXXFLAGS_WARN_ON += -Wno-nullability-completeness CONFIG(osx) { PRIVATE_HEADERS += osx/osxbtutility_p.h \ @@ -16,7 +17,8 @@ CONFIG(osx) { osx/osxbtledeviceinquiry_p.h \ osx/osxbluetooth_p.h \ osx/osxbtcentralmanager_p.h \ - osx/osxbtnotifier_p.h + osx/osxbtnotifier_p.h \ + osx/osxbtperipheralmanager_p.h OBJECTIVE_SOURCES += osx/osxbtutility.mm \ osx/osxbtdevicepair.mm \ @@ -30,15 +32,22 @@ CONFIG(osx) { osx/osxbtsocketlistener.mm \ osx/osxbtobexsession.mm \ osx/osxbtledeviceinquiry.mm \ - osx/osxbtcentralmanager.mm + osx/osxbtcentralmanager.mm \ + osx/osxbtperipheralmanager.mm } else { PRIVATE_HEADERS += osx/osxbtutility_p.h \ osx/osxbtledeviceinquiry_p.h \ osx/osxbluetooth_p.h \ osx/osxbtcentralmanager_p.h \ osx/osxbtnotifier_p.h + ios { + PRIVATE_HEADERS += osx/osxbtperipheralmanager_p.h + } OBJECTIVE_SOURCES += osx/osxbtutility.mm \ osx/osxbtledeviceinquiry.mm \ osx/osxbtcentralmanager.mm + ios { + OBJECTIVE_SOURCES += osx/osxbtperipheralmanager.mm + } } diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm index 06425753..f7218ca6 100644 --- a/src/bluetooth/osx/osxbtcentralmanager.mm +++ b/src/bluetooth/osx/osxbtcentralmanager.mm @@ -43,7 +43,6 @@ #include "osxbtnotifier_p.h" #include <QtCore/qloggingcategory.h> -#include <QtCore/qsysinfo.h> #include <QtCore/qdebug.h> #include <algorithm> @@ -116,7 +115,7 @@ QT_END_NAMESPACE @implementation QT_MANGLE_NAMESPACE(OSXBTCentralManager) -- (id)initWith:(OSXBluetooth::LECentralNotifier *)aNotifier +- (id)initWith:(OSXBluetooth::LECBManagerNotifier *)aNotifier { if (self = [super init]) { manager = nil; @@ -171,9 +170,9 @@ QT_END_NAMESPACE if (!manager) { managerState = OSXBluetooth::CentralManagerIdle; - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate a central manager"; + qCWarning(QT_BT_OSX) << "failed to allocate a central manager"; if (notifier) - emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError); + emit notifier->CBManagerError(QLowEnergyController::ConnectionError); } } else if (managerState != OSXBluetooth::CentralManagerUpdating) { [self retrievePeripheralAndConnect]; @@ -187,7 +186,7 @@ QT_END_NAMESPACE Q_FUNC_INFO, "invalid state"); if ([self isConnected]) { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "already connected"; + qCDebug(QT_BT_OSX) << "already connected"; if (notifier) emit notifier->connected(); return; @@ -203,62 +202,37 @@ QT_END_NAMESPACE // Retrieve a peripheral first ... ObjCScopedPointer<NSMutableArray> uuids([[NSMutableArray alloc] init]); if (!uuids) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate identifiers"; + qCWarning(QT_BT_OSX) << "failed to allocate identifiers"; if (notifier) - emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError); + emit notifier->CBManagerError(QLowEnergyController::ConnectionError); return; } -#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)) { - const quint128 qtUuidData(deviceUuid.toUInt128()); - // STATIC_ASSERT on sizes would be handy! - uuid_t uuidData = {}; - std::copy(qtUuidData.data, qtUuidData.data + 16, uuidData); - const ObjCScopedPointer<NSUUID> nsUuid([[NSUUID alloc] initWithUUIDBytes:uuidData]); - if (!nsUuid) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate NSUUID identifier"; - if (notifier) - emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError); - return; - } - - [uuids addObject:nsUuid]; - // With the latest CoreBluetooth, we can synchronously retrive peripherals: - QT_BT_MAC_AUTORELEASEPOOL; - NSArray *const peripherals = [manager retrievePeripheralsWithIdentifiers:uuids]; - if (!peripherals || peripherals.count != 1) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to retrive a peripheral"; - if (notifier) - emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError); - return; - } - peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain]; - [self connectToPeripheral]; - return; - } -#endif - // Either SDK or the target is below 10.9/7.0 - if (![manager respondsToSelector:@selector(retrievePeripherals:)]) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to retrive a peripheral"; + const quint128 qtUuidData(deviceUuid.toUInt128()); + uuid_t uuidData = {}; + std::copy(qtUuidData.data, qtUuidData.data + 16, uuidData); + const ObjCScopedPointer<NSUUID> nsUuid([[NSUUID alloc] initWithUUIDBytes:uuidData]); + if (!nsUuid) { + qCWarning(QT_BT_OSX) << "failed to allocate NSUUID identifier"; if (notifier) - emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError); + emit notifier->CBManagerError(QLowEnergyController::ConnectionError); return; } - OSXBluetooth::CFStrongReference<CFUUIDRef> cfUuid(OSXBluetooth::cf_uuid(deviceUuid)); - if (!cfUuid) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create CFUUID object"; + [uuids addObject:nsUuid]; + // With the latest CoreBluetooth, we can synchronously retrive peripherals: + QT_BT_MAC_AUTORELEASEPOOL; + NSArray *const peripherals = [manager retrievePeripheralsWithIdentifiers:uuids]; + if (!peripherals || peripherals.count != 1) { + qCWarning(QT_BT_OSX) << "failed to retrive a peripheral"; if (notifier) - emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError); + emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError); return; } - // With ARC this cast will be illegal: - [uuids addObject:(id)cfUuid.data()]; - // Unfortunately, with old Core Bluetooth this call is asynchronous ... - managerState = OSXBluetooth::CentralManagerConnecting; - [manager performSelector:@selector(retrievePeripherals:) withObject:uuids.data()]; + + peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain]; + [self connectToPeripheral]; } - (void)connectToPeripheral @@ -270,11 +244,11 @@ QT_END_NAMESPACE // The state is still the same - connecting. if ([self isConnected]) { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "already connected"; + qCDebug(QT_BT_OSX) << "already connected"; if (notifier) emit notifier->connected(); } else { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "trying to connect"; + qCDebug(QT_BT_OSX) << "trying to connect"; managerState = OSXBluetooth::CentralManagerConnecting; [manager connectPeripheral:peripheral options:nil]; } @@ -285,18 +259,7 @@ QT_END_NAMESPACE if (!peripheral) return false; -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_7_0) - using OSXBluetooth::qt_OS_limit; - - if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0)) - return peripheral.state == CBPeripheralStateConnected; -#endif - // Either SDK or the target is below 10.9/7.0 ... - if (![peripheral respondsToSelector:@selector(isConnected)]) - return false; - - // Ugly cast to deal with id being a pointer ... - return reinterpret_cast<quintptr>([peripheral performSelector:@selector(isConnected)]); + return peripheral.state == CBPeripheralStateConnected; } - (void)disconnectFromDevice @@ -389,7 +352,7 @@ QT_END_NAMESPACE Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)"); if (servicesToDiscoverDetails.contains(serviceUuid)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO <<"already discovering for " + qCWarning(QT_BT_OSX) << "already discovering for" << serviceUuid; return; } @@ -402,11 +365,11 @@ QT_END_NAMESPACE return; } - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown service uuid " + qCWarning(QT_BT_OSX) << "unknown service uuid" << serviceUuid; if (notifier) { - emit notifier->CBCentralManagerError(serviceUuid, + emit notifier->CBManagerError(serviceUuid, QLowEnergyService::UnknownError); } } @@ -499,9 +462,9 @@ QT_END_NAMESPACE const QLowEnergyHandle maxHandle = std::numeric_limits<QLowEnergyHandle>::max(); if (nHandles >= maxHandle || lastValidHandle > maxHandle - nHandles) { // Well, that's unlikely :) But we must be sure. - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "can not allocate more handles"; + qCWarning(QT_BT_OSX) << "can not allocate more handles"; if (notifier) - notifier->CBCentralManagerError(serviceUuid, QLowEnergyService::OperationError); + notifier->CBManagerError(serviceUuid, QLowEnergyService::OperationError); return; } @@ -603,7 +566,7 @@ QT_END_NAMESPACE const LERequest request(requests.dequeue()); if (request.type == LERequest::CharRead) { if (!charMap.contains(request.handle)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic with handle" + qCWarning(QT_BT_OSX) << "characteristic with handle" << request.handle << "not found"; return [self performNextRequest]; } @@ -613,7 +576,7 @@ QT_END_NAMESPACE [peripheral readValueForCharacteristic:charMap[request.handle]]; } else { if (!descMap.contains(request.handle)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "descriptor with handle" + qCWarning(QT_BT_OSX) << "descriptor with handle" << request.handle << "not found"; return [self performNextRequest]; } @@ -640,8 +603,8 @@ QT_END_NAMESPACE if (request.type == LERequest::DescWrite) { if (!descMap.contains(request.handle)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "handle: " - << request.handle << " not found"; + qCWarning(QT_BT_OSX) << "handle:" << request.handle + << "not found"; return [self performNextRequest]; } @@ -649,8 +612,7 @@ QT_END_NAMESPACE ObjCStrongReference<NSData> data(data_from_bytearray(request.value)); if (!data) { // Even if qtData.size() == 0, we still need NSData object. - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed " - "to allocate an NSData object"; + qCWarning(QT_BT_OSX) << "failed to allocate an NSData object"; return [self performNextRequest]; } @@ -661,8 +623,8 @@ QT_END_NAMESPACE return [peripheral writeValue:data.data() forDescriptor:descriptor]; } else { if (!charMap.contains(request.handle)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic with " - "handle: " << request.handle << " not found"; + qCWarning(QT_BT_OSX) << "characteristic with handle:" + << request.handle << "not found"; return [self performNextRequest]; } @@ -687,7 +649,7 @@ QT_END_NAMESPACE ObjCStrongReference<NSData> data(data_from_bytearray(request.value)); if (!data) { // Even if qtData.size() == 0, we still need NSData object. - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate NSData object"; + qCWarning(QT_BT_OSX) << "failed to allocate NSData object"; return [self performNextRequest]; } @@ -717,11 +679,10 @@ QT_END_NAMESPACE Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle (0)"); if (!charMap.contains(charHandle)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO - << "unknown characteristic handle " + qCWarning(QT_BT_OSX) << "unknown characteristic handle" << charHandle; if (notifier) { - emit notifier->CBCentralManagerError(serviceUuid, + emit notifier->CBManagerError(serviceUuid, QLowEnergyService::DescriptorWriteError); } return; @@ -732,10 +693,9 @@ QT_END_NAMESPACE // it back, so check _now_ that we really have this descriptor. const QBluetoothUuid qtUuid(QBluetoothUuid::ClientCharacteristicConfiguration); if (![self descriptor:qtUuid forCharacteristic:charMap[charHandle]]) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO - << "no client characteristic configuration found"; + qCWarning(QT_BT_OSX) << "no client characteristic configuration found"; if (notifier) { - emit notifier->CBCentralManagerError(serviceUuid, + emit notifier->CBManagerError(serviceUuid, QLowEnergyService::DescriptorWriteError); } return; @@ -760,9 +720,9 @@ QT_END_NAMESPACE QT_BT_MAC_AUTORELEASEPOOL; if (!charMap.contains(charHandle)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic: " << charHandle << " not found"; + qCWarning(QT_BT_OSX) << "characteristic:" << charHandle << "not found"; if (notifier) { - emit notifier->CBCentralManagerError(serviceUuid, + emit notifier->CBManagerError(serviceUuid, QLowEnergyService::CharacteristicReadError); } @@ -789,10 +749,9 @@ QT_END_NAMESPACE QT_BT_MAC_AUTORELEASEPOOL; if (!charMap.contains(charHandle)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic: " - << charHandle << " not found"; + qCWarning(QT_BT_OSX) << "characteristic:" << charHandle << "not found"; if (notifier) { - emit notifier->CBCentralManagerError(serviceUuid, + emit notifier->CBManagerError(serviceUuid, QLowEnergyService::CharacteristicWriteError); } return; @@ -816,10 +775,9 @@ QT_END_NAMESPACE Q_ASSERT_X(descHandle, Q_FUNC_INFO, "invalid descriptor handle (0)"); if (!descMap.contains(descHandle)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "handle:" - << descHandle << "not found"; + qCWarning(QT_BT_OSX) << "handle:" << descHandle << "not found"; if (notifier) { - emit notifier->CBCentralManagerError(serviceUuid, + emit notifier->CBManagerError(serviceUuid, QLowEnergyService::DescriptorReadError); } return; @@ -842,10 +800,9 @@ QT_END_NAMESPACE Q_ASSERT_X(descHandle, Q_FUNC_INFO, "invalid descriptor handle (0)"); if (!descMap.contains(descHandle)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "handle: " - << descHandle << " not found"; + qCWarning(QT_BT_OSX) << "handle:" << descHandle << "not found"; if (notifier) { - emit notifier->CBCentralManagerError(serviceUuid, + emit notifier->CBManagerError(serviceUuid, QLowEnergyService::DescriptorWriteError); } return; @@ -1029,27 +986,25 @@ QT_END_NAMESPACE if ([obj isKindOfClass:[CBCharacteristic class]]) { CBCharacteristic *const ch = static_cast<CBCharacteristic *>(obj); if (!charMap.key(ch)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected " - "characteristic, no handle found"; + qCWarning(QT_BT_OSX) << "unexpected characteristic, no handle found"; return false; } } else if ([obj isKindOfClass:[CBDescriptor class]]) { CBDescriptor *const d = static_cast<CBDescriptor *>(obj); if (!descMap.key(d)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected " - "descriptor, no handle found"; + qCWarning(QT_BT_OSX) << "unexpected descriptor, no handle found"; return false; } } else { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid object, " - "characteristic or descriptor required"; + qCWarning(QT_BT_OSX) << "invalid object, characteristic " + "or descriptor required"; return false; } if (valuesToWrite.contains(obj)) { // It can be a result of some previous errors - for example, // we never got a callback from a previous write. - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "already has a cached value for this " + qCWarning(QT_BT_OSX) << "already has a cached value for this " "object, the value will be replaced"; } @@ -1114,7 +1069,7 @@ QT_END_NAMESPACE // and reset managerState from CentralManagerUpdating. managerState = CentralManagerIdle; if (notifier) - emit notifier->CBCentralManagerError(QLowEnergyController::InvalidBluetoothAdapterError); + emit notifier->CBManagerError(QLowEnergyController::InvalidBluetoothAdapterError); } return; } @@ -1133,7 +1088,7 @@ QT_END_NAMESPACE // TODO: we need a better error + // what will happen if later the state changes to PoweredOn??? if (notifier) - emit notifier->CBCentralManagerError(QLowEnergyController::InvalidBluetoothAdapterError); + emit notifier->CBManagerError(QLowEnergyController::InvalidBluetoothAdapterError); } return; } @@ -1153,30 +1108,6 @@ QT_END_NAMESPACE } } -- (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals -{ - Q_UNUSED(central) - - // This method is required for iOS before 7.0 and OS X below 10.9. - Q_ASSERT_X(manager, Q_FUNC_INFO, "invalid central manager (nil)"); - - if (managerState != OSXBluetooth::CentralManagerConnecting) { - // Canceled by calling -disconnectFromDevice method. - return; - } - - managerState = OSXBluetooth::CentralManagerIdle; - - if (!peripherals || peripherals.count != 1) { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO <<"unexpected number of peripherals (!= 1)"; - if (notifier) - emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError); - } else { - peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain]; - [self connectToPeripheral]; - } -} - - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)aPeripheral { Q_UNUSED(central) @@ -1207,7 +1138,7 @@ QT_END_NAMESPACE managerState = OSXBluetooth::CentralManagerIdle; // TODO: better error mapping is required. if (notifier) - notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError); + notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError); } - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)aPeripheral @@ -1221,9 +1152,9 @@ QT_END_NAMESPACE if (error && managerState == OSXBluetooth::CentralManagerDisconnecting) { managerState = OSXBluetooth::CentralManagerIdle; - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to disconnect"; + qCWarning(QT_BT_OSX) << "failed to disconnect"; if (notifier) - emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError); + emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError); } else { managerState = OSXBluetooth::CentralManagerIdle; if (notifier) @@ -1248,7 +1179,7 @@ QT_END_NAMESPACE NSLog(@"%s failed with error %@", Q_FUNC_INFO, error); // TODO: better error mapping required. if (notifier) - emit notifier->CBCentralManagerError(QLowEnergyController::UnknownError); + emit notifier->CBManagerError(QLowEnergyController::UnknownError); } else { [self discoverIncludedServices]; } @@ -1344,7 +1275,7 @@ QT_END_NAMESPACE NSLog(@"%s failed with error: %@", Q_FUNC_INFO, error); // We did not discover any characteristics and can not discover descriptors, // inform our delegate (it will set a service state also). - emit notifier->CBCentralManagerError(qt_uuid(service.UUID), QLowEnergyController::UnknownError); + emit notifier->CBManagerError(qt_uuid(service.UUID), QLowEnergyController::UnknownError); } else { [self readCharacteristics:service]; } @@ -1379,7 +1310,7 @@ QT_END_NAMESPACE if (chHandle && chHandle == currentReadHandle) { currentReadHandle = 0; requestPending = false; - emit notifier->CBCentralManagerError(qtUuid, QLowEnergyService::CharacteristicReadError); + emit notifier->CBManagerError(qtUuid, QLowEnergyService::CharacteristicReadError); [self performNextRequest]; } return; @@ -1402,7 +1333,7 @@ QT_END_NAMESPACE // updated values ... // TODO: this must be properly tested. if (!chHandle) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "unexpected update notification, " + qCCritical(QT_BT_OSX) << "unexpected update notification, " "no characteristic handle found"; return; } @@ -1481,7 +1412,7 @@ QT_END_NAMESPACE if (dHandle && dHandle == currentReadHandle) { currentReadHandle = 0; requestPending = false; - emit notifier->CBCentralManagerError(qtUuid, QLowEnergyService::DescriptorReadError); + emit notifier->CBManagerError(qtUuid, QLowEnergyService::DescriptorReadError); [self performNextRequest]; } return; @@ -1513,7 +1444,7 @@ QT_END_NAMESPACE } } else { if (!dHandle) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "unexpected value update notification, " + qCCritical(QT_BT_OSX) << "unexpected value update notification, " "no descriptor handle found"; return; } @@ -1556,13 +1487,13 @@ QT_END_NAMESPACE // Error or not, but the cached value has to be deleted ... const QByteArray valueToReport(valuesToWrite.value(characteristic, QByteArray())); if (!valuesToWrite.remove(characteristic)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no updated value found" - " for characteristic"; + qCWarning(QT_BT_OSX) << "no updated value found " + "for characteristic"; } if (error) { NSLog(@"%s failed with error %@", Q_FUNC_INFO, error); - emit notifier->CBCentralManagerError(qt_uuid(characteristic.service.UUID), + emit notifier->CBManagerError(qt_uuid(characteristic.service.UUID), QLowEnergyService::CharacteristicWriteError); } else { const QLowEnergyHandle cHandle = charMap.key(characteristic); @@ -1592,16 +1523,15 @@ QT_END_NAMESPACE // Error or not, a value (if any) must be removed. const QByteArray valueToReport(valuesToWrite.value(descriptor, QByteArray())); if (!valuesToWrite.remove(descriptor)) - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no updated value found"; + qCWarning(QT_BT_OSX) << "no updated value found"; if (error) { NSLog(@"%s failed with error %@", Q_FUNC_INFO, error); - emit notifier->CBCentralManagerError(qt_uuid(descriptor.characteristic.service.UUID), + emit notifier->CBManagerError(qt_uuid(descriptor.characteristic.service.UUID), QLowEnergyService::DescriptorWriteError); } else { const QLowEnergyHandle dHandle = descMap.key(descriptor); - Q_ASSERT_X(dHandle, Q_FUNC_INFO, - "descriptor not found in the descriptors map"); + Q_ASSERT_X(dHandle, Q_FUNC_INFO, "descriptor not found in the descriptors map"); emit notifier->descriptorWritten(dHandle, valueToReport); } @@ -1631,7 +1561,7 @@ QT_END_NAMESPACE if (error) { NSLog(@"%s failed with error %@", Q_FUNC_INFO, error); // In Qt's API it's a descriptor write actually. - emit notifier->CBCentralManagerError(qt_uuid(characteristic.service.UUID), + emit notifier->CBManagerError(qt_uuid(characteristic.service.UUID), QLowEnergyService::DescriptorWriteError); } else if (nRemoved) { const QLowEnergyHandle dHandle = descMap.key(descriptor); diff --git a/src/bluetooth/osx/osxbtcentralmanager_p.h b/src/bluetooth/osx/osxbtcentralmanager_p.h index 3eee2190..697d922c 100644 --- a/src/bluetooth/osx/osxbtcentralmanager_p.h +++ b/src/bluetooth/osx/osxbtcentralmanager_p.h @@ -72,7 +72,7 @@ class QLowEnergyServicePrivate; namespace OSXBluetooth { -class LECentralNotifier; +class LECBManagerNotifier; enum CentralManagerState { @@ -141,7 +141,7 @@ QT_END_NAMESPACE QT_PREPEND_NAMESPACE(QBluetoothUuid) deviceUuid; - QT_PREPEND_NAMESPACE(OSXBluetooth)::LECentralNotifier *notifier; + QT_PREPEND_NAMESPACE(OSXBluetooth)::LECBManagerNotifier *notifier; // Quite a verbose service discovery machinery // (a "graph traversal"). @@ -170,7 +170,7 @@ QT_END_NAMESPACE CBPeripheral *peripheral; } -- (id)initWith:(QT_PREPEND_NAMESPACE(OSXBluetooth)::LECentralNotifier *)notifier; +- (id)initWith:(QT_PREPEND_NAMESPACE(OSXBluetooth)::LECBManagerNotifier *)notifier; - (void)dealloc; // IMPORTANT: _all_ these methods are to be executed on qt_LE_queue, diff --git a/src/bluetooth/osx/osxbtdeviceinquiry.mm b/src/bluetooth/osx/osxbtdeviceinquiry.mm index 0420a67e..79a8a92c 100644 --- a/src/bluetooth/osx/osxbtdeviceinquiry.mm +++ b/src/bluetooth/osx/osxbtdeviceinquiry.mm @@ -73,8 +73,7 @@ QT_USE_NAMESPACE [m_inquiry setUpdateNewDeviceNames:NO];//Useless, disable! m_delegate = delegate; } else { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create " - "a device inquiry"; + qCCritical(QT_BT_OSX) << "failed to create a device inquiry"; } m_active = false; @@ -113,8 +112,7 @@ QT_USE_NAMESPACE if (result != kIOReturnSuccess) { // QtBluetooth will probably convert an error into UnknownError, // loosing the actual information. - qCWarning(QT_BT_OSX) << Q_FUNC_INFO <<"failed with " - "IOKit error code: " << result; + qCWarning(QT_BT_OSX) << "failed with IOKit error code:" << result; m_active = false; } @@ -130,6 +128,8 @@ QT_USE_NAMESPACE const IOReturn res = [m_inquiry stop]; if (res != kIOReturnSuccess) m_active = true; + else + qCDebug(QT_BT_OSX) << "-stop, success (waiting for 'inquiryComplete')"; return res; } @@ -152,7 +152,7 @@ QT_USE_NAMESPACE if (error != kIOReturnSuccess) { // QtBluetooth has not too many error codes, 'UnknownError' is not really // useful, report the actual error code here: - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "IOKit error code: " << error; + qCWarning(QT_BT_OSX) << "IOKit error code: " << error; m_delegate->error(sender, error); } else { m_delegate->inquiryFinished(sender); diff --git a/src/bluetooth/osx/osxbtdevicepair.mm b/src/bluetooth/osx/osxbtdevicepair.mm index dbb2fa4b..dcaa3536 100644 --- a/src/bluetooth/osx/osxbtdevicepair.mm +++ b/src/bluetooth/osx/osxbtdevicepair.mm @@ -41,7 +41,6 @@ #include "osxbtutility_p.h" #include <QtCore/qloggingcategory.h> -#include <QtCore/qsysinfo.h> #include <QtCore/qdebug.h> QT_BEGIN_NAMESPACE @@ -88,18 +87,8 @@ QT_USE_NAMESPACE - (void)dealloc { -#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) - // Stop also sets a delegate to nil (Apple's docs). - // 10.9 only. - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) - [m_pairing stop]; - else - [m_pairing setDelegate:nil]; -#else - [m_pairing setDelegate:nil]; -#endif + [m_pairing stop]; [m_pairing release]; - [super dealloc]; } @@ -116,14 +105,13 @@ QT_USE_NAMESPACE // Device is autoreleased. IOBluetoothDevice *const device = [IOBluetoothDevice deviceWithAddress:&iobtAddress]; if (!device) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a device " - "to pair with"; + qCCritical(QT_BT_OSX) << "failed to create a device to pair with"; return kIOReturnError; } m_pairing = [[IOBluetoothDevicePair pairWithDevice:device] retain]; if (!m_pairing) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create pair"; + qCCritical(QT_BT_OSX) << "failed to create pair"; return kIOReturnError; } diff --git a/src/bluetooth/osx/osxbtl2capchannel.mm b/src/bluetooth/osx/osxbtl2capchannel.mm index e18e9e25..02ec4f90 100644 --- a/src/bluetooth/osx/osxbtl2capchannel.mm +++ b/src/bluetooth/osx/osxbtl2capchannel.mm @@ -104,13 +104,13 @@ QT_USE_NAMESPACE withPSM:(BluetoothL2CAPChannelID)psm { if (address.isNull()) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid peer address"; + qCCritical(QT_BT_OSX) << "invalid peer address"; return kIOReturnNoDevice; } // Can never be called twice. if (connected || device || channel) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "connection is already active"; + qCCritical(QT_BT_OSX) << "connection is already active"; return kIOReturnStillOpen; } @@ -119,13 +119,13 @@ QT_USE_NAMESPACE const BluetoothDeviceAddress iobtAddress = OSXBluetooth::iobluetooth_address(address); device = [IOBluetoothDevice deviceWithAddress:&iobtAddress]; if (!device) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a device"; + qCCritical(QT_BT_OSX) << "failed to create a device"; return kIOReturnNoDevice; } const IOReturn status = [device openL2CAPChannelAsync:&channel withPSM:psm delegate:self]; if (status != kIOReturnSuccess) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to open L2CAP channel"; + qCCritical(QT_BT_OSX) << "failed to open L2CAP channel"; // device is still autoreleased. device = nil; return status; 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 diff --git a/src/bluetooth/osx/osxbtledeviceinquiry_p.h b/src/bluetooth/osx/osxbtledeviceinquiry_p.h index 71e8ef53..b58904e6 100644 --- a/src/bluetooth/osx/osxbtledeviceinquiry_p.h +++ b/src/bluetooth/osx/osxbtledeviceinquiry_p.h @@ -58,7 +58,6 @@ #include <QtCore/qelapsedtimer.h> #include <QtCore/qglobal.h> -#include <QtCore/qatomic.h> #include <QtCore/qlist.h> #include <Foundation/Foundation.h> @@ -67,8 +66,20 @@ QT_BEGIN_NAMESPACE class QBluetoothUuid; +namespace OSXBluetooth +{ + +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); + enum LEInquiryState { InquiryStarting, @@ -81,29 +92,27 @@ enum LEInquiryState @interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) : NSObject { - QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCScopedPointer<NSMutableSet> uuids; - QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCScopedPointer<CBCentralManager> manager; + LECBManagerNotifier *notifier; + ObjCScopedPointer<NSMutableSet> uuids; + ObjCScopedPointer<CBCentralManager> manager; QList<QBluetoothDeviceInfo> devices; - LEInquiryState internalState; - QT_PREPEND_NAMESPACE(QAtomicInt) state; + int inquiryTimeoutMS; // Timers to check if we can execute delayed callbacks: QT_PREPEND_NAMESPACE(QElapsedTimer) errorTimer; QT_PREPEND_NAMESPACE(QElapsedTimer) scanTimer; } -- (id)init; +- (id)initWithNotifier:(LECBManagerNotifier *)aNotifier; - (void)dealloc; -// IMPORTANT: both 'start' and 'stop' are to be executed on the "Qt's LE queue". -- (void)start; +// IMPORTANT: both 'startWithTimeout' and 'stop' +// can be executed only on the "Qt's LE queue". +- (void)startWithTimeout:(int)timeout; - (void)stop; -- (LEInquiryState)inquiryState; -- (const QList<QBluetoothDeviceInfo> &)discoveredDevices; - @end #endif diff --git a/src/bluetooth/osx/osxbtnotifier_p.h b/src/bluetooth/osx/osxbtnotifier_p.h index 6cb2b019..0ffd7f51 100644 --- a/src/bluetooth/osx/osxbtnotifier_p.h +++ b/src/bluetooth/osx/osxbtnotifier_p.h @@ -52,7 +52,9 @@ // +#include "qbluetoothdevicediscoveryagent.h" #include "qlowenergycontroller.h" +#include "qbluetoothdeviceinfo.h" #include "qbluetoothuuid.h" #include "qbluetooth.h" @@ -68,11 +70,14 @@ class QLowEnergyServicePrivate; namespace OSXBluetooth { -class LECentralNotifier : public QObject +class LECBManagerNotifier : public QObject { Q_OBJECT Q_SIGNALS: + void deviceDiscovered(QBluetoothDeviceInfo deviceInfo); + void discoveryFinished(); + void connected(); void disconnected(); @@ -85,9 +90,10 @@ Q_SIGNALS: void descriptorWritten(QLowEnergyHandle descHandle, const QByteArray &value); void LEnotSupported(); - void CBCentralManagerError(QLowEnergyController::Error error); - void CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error); - void CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error); + void CBManagerError(QBluetoothDeviceDiscoveryAgent::Error error); + void CBManagerError(QLowEnergyController::Error error); + void CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error); + void CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error); }; diff --git a/src/bluetooth/osx/osxbtobexsession.mm b/src/bluetooth/osx/osxbtobexsession.mm index 9e324405..b8e604c8 100644 --- a/src/bluetooth/osx/osxbtobexsession.mm +++ b/src/bluetooth/osx/osxbtobexsession.mm @@ -203,7 +203,7 @@ QList<OBEXHeader> qt_bluetooth_headers(const uint8_t *data, std::size_t length) break; } default: - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid header format"; + qCWarning(QT_BT_OSX) << "invalid header format"; return empty; } @@ -358,7 +358,7 @@ bool check_connect_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode response = e->u.connectCommandResponseData.serverResponseOpCode; return response == kOBEXResponseCodeSuccessWithFinalBit; } else { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected event type"; + qCWarning(QT_BT_OSX) << "unexpected event type"; error = kOBEXGeneralError; return false; } @@ -378,7 +378,7 @@ bool check_put_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &re return response == kOBEXResponseCodeContinueWithFinalBit || response == kOBEXResponseCodeSuccessWithFinalBit; } else { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected event type"; + qCWarning(QT_BT_OSX) << "unexpected event type"; error = kOBEXGeneralError; return false; } @@ -395,7 +395,7 @@ bool check_abort_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode & response = e->u.abortCommandResponseData.serverResponseOpCode; return response == kOBEXResponseCodeSuccessWithFinalBit; } else { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected event type"; + qCWarning(QT_BT_OSX) << "unexpected event type"; return false; } } @@ -443,13 +443,13 @@ QT_USE_NAMESPACE const BluetoothDeviceAddress addr(OSXBluetooth::iobluetooth_address(deviceAddress)); device = [[IOBluetoothDevice deviceWithAddress:&addr] retain]; if (!device) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an IOBluetoothDevice"; + qCWarning(QT_BT_OSX) << "failed to create an IOBluetoothDevice"; return self; } session = [[IOBluetoothOBEXSession alloc] initWithDevice:device channelID:port]; if (!session) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an OBEX session"; + qCWarning(QT_BT_OSX) << "failed to create an OBEX session"; return self; } @@ -474,7 +474,7 @@ QT_USE_NAMESPACE - (OBEXError)OBEXConnect { if (!session) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid session (nil)"; + qCWarning(QT_BT_OSX) << "invalid session (nil)"; return kOBEXGeneralError; } @@ -517,7 +517,7 @@ QT_USE_NAMESPACE } if (currentRequest != OBEXConnect) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called while there is no " + qCWarning(QT_BT_OSX) << "called while there is no " "active connect request"; return; } @@ -596,7 +596,7 @@ QT_USE_NAMESPACE Q_ASSERT_X(session, Q_FUNC_INFO, "invalid OBEX session (nil)"); if (currentRequest != OBEXAbort) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called while there " + qCWarning(QT_BT_OSX) << "called while there " "is no ABORT request"; return; } @@ -628,13 +628,13 @@ QT_USE_NAMESPACE // a payload. const qint64 fileSize = input->size(); if (fileSize <= 0 || fileSize >= std::numeric_limits<uint32_t>::max()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid input file size"; + qCWarning(QT_BT_OSX) << "invalid input file size"; return kOBEXBadArgumentError; } ObjCStrongReference<NSMutableData> headers([[NSMutableData alloc] init], false); if (!headers) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate headers"; + qCWarning(QT_BT_OSX) << "failed to allocate headers"; return kOBEXNoResourcesError; } @@ -644,16 +644,14 @@ QT_USE_NAMESPACE if (connectionIDFound) { if (!append_four_byte_header(headers, kOBEXHeaderIDConnectionID, connectionID)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to " - "append connection ID header"; + qCWarning(QT_BT_OSX) << "failed to append connection ID header"; return kOBEXNoResourcesError; } } if (name.length()) { if (!append_unicode_header(headers, kOBEXHeaderIDName, name)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to append " - "a unicode string"; + qCWarning(QT_BT_OSX) << "failed to append a unicode string"; return kOBEXNoResourcesError; } } @@ -666,7 +664,7 @@ QT_USE_NAMESPACE if (!chunk || ![chunk length]) { // We do not support PUT-DELETE (?) // At least the first chunk is expected to be non-empty. - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid input stream"; + qCWarning(QT_BT_OSX) << "invalid input stream"; return kOBEXBadArgumentError; } @@ -713,7 +711,7 @@ QT_USE_NAMESPACE } if (currentRequest != OBEXPut) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called while the current " + qCWarning(QT_BT_OSX) << "called while the current " "request is not a put request"; return; } @@ -735,8 +733,7 @@ QT_USE_NAMESPACE // 0 for the headers length, no more headers. ObjCStrongReference<NSMutableData> chunk(next_data_chunk(*inputStream, session, 0, lastChunk)); if (!chunk && !lastChunk) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to " - "allocate the next memory chunk"; + qCWarning(QT_BT_OSX) << "failed to allocate the next memory chunk"; return; } @@ -752,8 +749,7 @@ QT_USE_NAMESPACE refCon:Q_NULLPTR]; if (status != kOBEXSuccess) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to " - "send the next memory chunk"; + qCWarning(QT_BT_OSX) << "failed to send the next memory chunk"; currentRequest = OBEXNoop; if (delegate) // Response code is not important here. delegate->OBEXPutError(kOBEXNoResourcesError, 0); diff --git a/src/bluetooth/osx/osxbtperipheralmanager.mm b/src/bluetooth/osx/osxbtperipheralmanager.mm new file mode 100644 index 00000000..4731fdd2 --- /dev/null +++ b/src/bluetooth/osx/osxbtperipheralmanager.mm @@ -0,0 +1,754 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qlowenergycharacteristicdata.h" +#include "qlowenergydescriptordata.h" +#include "osxbtperipheralmanager_p.h" +#include "qlowenergyservicedata.h" +#include "osxbtnotifier_p.h" +#include "qbluetooth.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qlist.h> + +#include <algorithm> +#include <limits> +#include <set> + +namespace +{ + +CBCharacteristicProperties cb_properties(const QLowEnergyCharacteristicData &data) +{ + // Direct 'mapping' is ok. + return CBCharacteristicProperties(int(data.properties())); +} + +CBAttributePermissions cb_permissions(const QLowEnergyCharacteristicData &data) +{ + using QLEC = QLowEnergyCharacteristic; + + const auto props = data.properties(); + CBAttributePermissions cbFlags = {}; + + if ((props & QLEC::Write) || (props & QLEC::WriteNoResponse) + || (props & QLEC::WriteSigned)) { + cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsWriteable); + } + + if (props & QLEC::Read) + cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsReadable); + + if (data.writeConstraints() & QBluetooth::AttEncryptionRequired) + cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsWriteEncryptionRequired); + + if (data.readConstraints() & QBluetooth::AttEncryptionRequired) + cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsReadEncryptionRequired); + + return cbFlags; +} + +ObjCStrongReference<CBMutableCharacteristic> create_characteristic(const QLowEnergyCharacteristicData &data) +{ + const ObjCStrongReference<CBMutableCharacteristic> ch([[CBMutableCharacteristic alloc] initWithType:cb_uuid(data.uuid()) + properties:cb_properties(data) + value:nil + permissions:cb_permissions(data)], + false /*do not retain*/); + return ch; +} + +ObjCStrongReference<CBMutableDescriptor> create_descriptor(const QLowEnergyDescriptorData &data) +{ + // CoreBluetooth supports only: + /* + "That said, only two of these are currently supported when creating local, + mutable descriptors: the characteristic user description descriptor and + the characteristic format descriptor, represented by the CBUUID constants + CBUUIDCharacteristicUserDescriptionString and CBUUIDCharacteristicFormatString" + */ + + if (data.uuid() != QBluetoothUuid::CharacteristicUserDescription && + data.uuid() != QBluetoothUuid::CharacteristicPresentationFormat) { + qCWarning(QT_BT_OSX) << "unsupported descriptor" << data.uuid(); + return {}; + } + + QT_BT_MAC_AUTORELEASEPOOL + + // Descriptors are immutable with CoreBluetooth, that's why we + // have to provide a value here and not able to change it later. + ObjCStrongReference<NSObject> value; + if (data.uuid() == QBluetoothUuid::CharacteristicUserDescription) { + const QString asQString(QString::fromUtf8(data.value())); + value.reset(asQString.toNSString()); + } else { + const auto nsData = data_from_bytearray(data.value()); + value.reset(nsData.data()); + } + + const ObjCStrongReference<CBMutableDescriptor> d([[CBMutableDescriptor alloc] + initWithType:cb_uuid(data.uuid()) + value:value], false /*do not retain*/); + return d; +} + +quint32 qt_countGATTEntries(const QLowEnergyServiceData &data) +{ + const auto maxu32 = std::numeric_limits<quint32>::max(); + // + 1 for a service itself. + quint32 nEntries = 1 + quint32(data.includedServices().count()); + for (const auto &ch : data.characteristics()) { + if (maxu32 - 2 < nEntries) + return {}; + nEntries += 2; + if (maxu32 - ch.descriptors().count() < nEntries) + return {}; + nEntries += ch.descriptors().count(); + } + + return nEntries; +} + +} + +@interface QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) (PrivateAPI) + +- (void)addConnectedCentral:(CBCentral *)central; +- (void)removeConnectedCentral:(CBCentral *)central; +- (CBService *)findIncludedService:(const QBluetoothUuid &)qtUUID; + +- (void)addIncludedServices:(const QLowEnergyServiceData &)data + to:(CBMutableService *)cbService + qtService:(QLowEnergyServicePrivate *)qtService; + +- (void)addCharacteristicsAndDescriptors:(const QLowEnergyServiceData &)data + to:(CBMutableService *)cbService + qtService:(QLowEnergyServicePrivate *)qtService; + +- (CBATTError)validateWriteRequest:(CBATTRequest *)request; + +@end + +@implementation QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) + +- (id)initWith:(LECBManagerNotifier *)aNotifier +{ + if (self = [super init]) { + Q_ASSERT(aNotifier); + notifier = aNotifier; + state = PeripheralState::idle; + nextServiceToAdd = {}; + connectedCentrals.reset([[NSMutableSet alloc] init]); + } + + return self; +} + +- (void)dealloc +{ + [self detach]; + [super dealloc]; +} + +- (QSharedPointer<QLowEnergyServicePrivate>)addService:(const QLowEnergyServiceData &)data +{ + using QLES = QLowEnergyService; + + const auto nEntries = qt_countGATTEntries(data); + if (!nEntries || nEntries > std::numeric_limits<QLowEnergyHandle>::max() - lastHandle) { + qCCritical(QT_BT_OSX) << "addService: not enough handles"; + return {}; + } + + QT_BT_MAC_AUTORELEASEPOOL + + const BOOL primary = data.type() == QLowEnergyServiceData::ServiceTypePrimary; + const auto cbUUID = cb_uuid(data.uuid()); + + const ObjCStrongReference<CBMutableService> + newCBService([[CBMutableService alloc] initWithType:cbUUID primary:primary], + false /*do not retain*/); + + if (!newCBService) { + qCCritical(QT_BT_OSX) << "addService: failed to create CBMutableService"; + return {}; + } + + auto newQtService = QSharedPointer<QLowEnergyServicePrivate>::create(); + newQtService->state = QLowEnergyService::LocalService; + newQtService->uuid = data.uuid(); + newQtService->type = primary ? QLES::PrimaryService : QLES::IncludedService; + newQtService->startHandle = ++lastHandle; + // Controller will be set by ... controller :) + + [self addIncludedServices:data to:newCBService qtService:newQtService.data()]; + [self addCharacteristicsAndDescriptors:data to:newCBService qtService:newQtService.data()]; + + services.push_back(newCBService); + serviceIndex[data.uuid()] = newCBService; + + return newQtService; +} + +- (void) setParameters:(const QLowEnergyAdvertisingParameters &)parameters + data:(const QLowEnergyAdvertisingData &)data + scanResponse:(const QLowEnergyAdvertisingData &)scanResponse +{ + Q_UNUSED(parameters) + + // This is the last method we call on the controller's thread + // before starting advertising on the Qt's LE queue. + // From Apple's docs: + /* + - (void)startAdvertising:(NSDictionary *)advertisementData + + Advertises peripheral manager data. + + * advertisementData + + - An optional dictionary containing the data you want to advertise. + The possible keys of an advertisementData dictionary are detailed in CBCentralManagerDelegate + Protocol Reference. That said, only two of the keys are supported for peripheral manager objects: + CBAdvertisementDataLocalNameKey and CBAdvertisementDataServiceUUIDsKey. + */ + + QT_BT_MAC_AUTORELEASEPOOL + + advertisementData.reset([[NSMutableDictionary alloc] init]); + if (!advertisementData) { + qCWarning(QT_BT_OSX) << "setParameters: failed to allocate " + "NSMutableDictonary (advertisementData)"; + return; + } + + auto localName = scanResponse.localName(); + if (!localName.size()) + localName = data.localName(); + + if (localName.size()) { + [advertisementData setObject:localName.toNSString() + forKey:CBAdvertisementDataLocalNameKey]; + } + + if (!data.services().count() && !scanResponse.services().count()) + return; + + const ObjCScopedPointer<NSMutableArray> uuids([[NSMutableArray alloc] init]); + if (!uuids) { + qCWarning(QT_BT_OSX) << "setParameters: failed to allocate " + "NSMutableArray (services uuids)"; + return; + } + + + for (const auto &qtUUID : data.services()) { + const auto cbUUID = cb_uuid(qtUUID); + if (cbUUID) + [uuids addObject:cbUUID]; + } + + for (const auto &qtUUID : scanResponse.services()) { + const auto cbUUID = cb_uuid(qtUUID); + if (cbUUID) + [uuids addObject:cbUUID]; + } + + if ([uuids count]) { + [advertisementData setObject:uuids + forKey:CBAdvertisementDataServiceUUIDsKey]; + } +} + +- (void)startAdvertising +{ + state = PeripheralState::waitingForPowerOn; + if (manager) + [manager setDelegate:nil]; + manager.reset([[CBPeripheralManager alloc] initWithDelegate:self + queue:OSXBluetooth::qt_LE_queue()]); +} + +- (void)stopAdvertising +{ + [manager stopAdvertising]; + state = PeripheralState::idle; +} + +- (void)detach +{ + if (notifier) { + notifier->disconnect(); + notifier->deleteLater(); + notifier = nullptr; + } + + if (state == PeripheralState::advertising) { + [manager stopAdvertising]; + [manager setDelegate:nil]; + state = PeripheralState::idle; + } +} + +- (void)write:(const QByteArray &)value + charHandle:(QLowEnergyHandle)charHandle +{ + if (!notifier) + return; + + QT_BT_MAC_AUTORELEASEPOOL + + if (!charMap.contains(charHandle)) { + emit notifier->CBManagerError(QLowEnergyController::UnknownError); + return; + } + + const auto nsData = data_from_bytearray(value); + charValues[charHandle] = nsData; + updateQueue.push_back(UpdateRequest{charHandle, nsData}); + [self sendUpdateRequests]; +} + +- (void) addServicesToPeripheral +{ + Q_ASSERT(manager); + + if (nextServiceToAdd < services.size()) + [manager addService:services[nextServiceToAdd++]]; +} + +// CBPeripheralManagerDelegate: + +- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral +{ + if (peripheral != manager || !notifier) + return; + + if (peripheral.state == CBPeripheralManagerStatePoweredOn) { + // "Bluetooth is currently powered on and is available to use." + if (state == PeripheralState::waitingForPowerOn) { + [manager removeAllServices]; + nextServiceToAdd = {}; + state = PeripheralState::advertising; + [self addServicesToPeripheral]; + } + return; + } + + /* + "A state with a value lower than CBPeripheralManagerStatePoweredOn implies that + advertising has stopped and that any connected centrals have been disconnected." + */ + + [connectedCentrals removeAllObjects]; + + if (state == PeripheralState::advertising) { + state = PeripheralState::waitingForPowerOn; + } else if (state == PeripheralState::connected) { + state = PeripheralState::idle; + emit notifier->disconnected(); + } + + // The next four states are _below_ "powered off"; according to the docs: + /* + "In addition, the local database is cleared and all services must be + explicitly added again." + */ + + if (peripheral.state == CBPeripheralManagerStateUnauthorized || + peripheral.state == CBPeripheralManagerStateUnsupported) { + emit notifier->LEnotSupported(); + state = PeripheralState::idle; + } +} + +- (void)peripheralManager:(CBPeripheralManager *)peripheral + willRestoreState:(NSDictionary *)dict +{ + Q_UNUSED(peripheral) + Q_UNUSED(dict) + // NOOP atm. +} + +- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral + error:(NSError *)error +{ + if (peripheral != manager || !notifier) + return; + + if (error) { + NSLog(@"failed to start advertising, error: %@", error); + state = PeripheralState::idle; + emit notifier->CBManagerError(QLowEnergyController::AdvertisingError); + } +} + +- (void)peripheralManager:(CBPeripheralManager *)peripheral + didAddService:(CBService *)service error:(NSError *)error +{ + Q_UNUSED(service) + + if (peripheral != manager || !notifier) + return; + + if (error) { + NSLog(@"failed to add a service, error: %@", error); + emit notifier->CBManagerError(QLowEnergyController::AdvertisingError); + state = PeripheralState::idle; + return; + } + + if (nextServiceToAdd == services.size()) + [manager startAdvertising:[advertisementData count] ? advertisementData.data() : nil]; + else + [self addServicesToPeripheral]; +} + +- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central + didSubscribeToCharacteristic:(CBCharacteristic *)characteristic +{ + Q_UNUSED(characteristic) + + if (peripheral != manager || !notifier) + return; + + [self addConnectedCentral:central]; +} + +- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central + didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic +{ + Q_UNUSED(characteristic) + + if (peripheral != manager || !notifier) + return; + + [self removeConnectedCentral:central]; +} + +- (void)peripheralManager:(CBPeripheralManager *)peripheral + didReceiveReadRequest:(CBATTRequest *)request +{ + if (peripheral != manager || !notifier) + return; + + QT_BT_MAC_AUTORELEASEPOOL + + const auto handle = charMap.key(request.characteristic); + if (!handle || !charValues.contains(handle)) { + qCWarning(QT_BT_OSX) << "invalid read request, unknown characteristic"; + [manager respondToRequest:request withResult:CBATTErrorInvalidHandle]; + return; + } + + const auto &value = charValues[handle]; + if (request.offset > [value length]) { + qCWarning(QT_BT_OSX) << "invalid offset in a read request"; + [manager respondToRequest:request withResult:CBATTErrorInvalidOffset]; + return; + } + + [self addConnectedCentral:request.central]; + + NSData *dataToSend = nil; + if (!request.offset) { + dataToSend = value; + } else { + dataToSend = [value subdataWithRange: + NSMakeRange(request.offset, [value length] - request.offset)]; + } + + request.value = dataToSend; + [manager respondToRequest:request withResult:CBATTErrorSuccess]; +} + +- (void)peripheralManager:(CBPeripheralManager *)peripheral + didReceiveWriteRequests:(NSArray *)requests +{ + QT_BT_MAC_AUTORELEASEPOOL + + if (peripheral != manager || !notifier) { + // Detached already. + return; + } + + // We first test if all requests are valid + // since CoreBluetooth requires "all or none" + // and respond only _once_ to the first one. + for (CBATTRequest *request in requests) { + const auto status = [self validateWriteRequest:request]; + if (status != CBATTErrorSuccess) { + [manager respondToRequest:[requests objectAtIndex:0] + withResult:status]; + return; + } + } + + std::set<QLowEnergyHandle> updated; + + for (CBATTRequest *request in requests) { + // Transition to 'connected' if needed. + [self addConnectedCentral:request.central]; + + const auto charHandle = charMap.key(request.characteristic); + updated.insert(charHandle); + NSMutableData *const data = static_cast<NSMutableData *>(charValues[charHandle]); + [data replaceBytesInRange:NSMakeRange(request.offset, request.value.length) + withBytes:data.bytes]; + } + + for (const auto handle : updated) + emit notifier->characteristicUpdated(handle, qt_bytearray(charValues[handle])); + + if (requests.count) { + [manager respondToRequest:[requests objectAtIndex:0] + withResult:CBATTErrorSuccess]; + } +} + +- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral +{ + if (peripheral != manager || !notifier) { + // Detached. + return; + } + + [self sendUpdateRequests]; +} + +- (void)sendUpdateRequests +{ + QT_BT_MAC_AUTORELEASEPOOL + + while (updateQueue.size()) { + const auto &request = updateQueue.front(); + Q_ASSERT(charMap.contains(request.charHandle)); + const BOOL res = [manager updateValue:request.value + forCharacteristic:static_cast<CBMutableCharacteristic *>(charMap[request.charHandle]) + onSubscribedCentrals:nil]; + if (!res) { + // Have to wait for the 'ManagerIsReadyToUpdate'. + break; + } + + updateQueue.pop_front(); + } +} + +// Private API: + +- (void)addConnectedCentral:(CBCentral *)central +{ + if (!central) + return; + + if (!notifier) { + // We were detached. + return; + } + + QT_BT_MAC_AUTORELEASEPOOL + + if (state == PeripheralState::advertising) { + state = PeripheralState::connected; + [manager stopAdvertising]; + emit notifier->connected(); + } + + if (![connectedCentrals containsObject:central.identifier]) + [connectedCentrals addObject:central.identifier]; +} + +- (void)removeConnectedCentral:(CBCentral *)central +{ + if (!notifier) { + // Detached. + return; + } + + QT_BT_MAC_AUTORELEASEPOOL + + if ([connectedCentrals containsObject:central.identifier]) + [connectedCentrals removeObject:central.identifier]; + + if (state == PeripheralState::connected && ![connectedCentrals count]) { + state = PeripheralState::idle; + emit notifier->disconnected(); + } +} + +- (CBService *)findIncludedService:(const QBluetoothUuid &)qtUUID +{ + const auto it = serviceIndex.find(qtUUID); + if (it == serviceIndex.end()) + return nil; + + return it->second; +} + +- (void)addIncludedServices:(const QLowEnergyServiceData &)data + to:(CBMutableService *)cbService + qtService:(QLowEnergyServicePrivate *)qtService +{ + Q_ASSERT(cbService); + Q_ASSERT(qtService); + + QT_BT_MAC_AUTORELEASEPOOL + + ObjCScopedPointer<NSMutableArray> included([[NSMutableArray alloc] init]); + if (!included) { + qCWarning(QT_BT_OSX) << "addIncludedSerivces: failed " + "to allocate NSMutableArray"; + return; + } + + for (auto includedService : data.includedServices()) { + if (CBService *cbs = [self findIncludedService:includedService->serviceUuid()]) { + [included addObject:cbs]; + qtService->includedServices << includedService->serviceUuid(); + ++lastHandle; + } else { + qCWarning(QT_BT_OSX) << "can not use" << includedService->serviceUuid() + << "as included, it has to be added first"; + } + } + + if ([included count]) + cbService.includedServices = included; +} + +- (void)addCharacteristicsAndDescriptors:(const QLowEnergyServiceData &)data + to:(CBMutableService *)cbService + qtService:(QLowEnergyServicePrivate *)qtService +{ + Q_ASSERT(cbService); + Q_ASSERT(qtService); + + QT_BT_MAC_AUTORELEASEPOOL + + ObjCScopedPointer<NSMutableArray> newCBChars([[NSMutableArray alloc] init]); + if (!newCBChars) { + qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: " + "failed to allocate NSMutableArray " + "(characteristics)"; + return; + } + + for (const auto &ch : data.characteristics()) { + const auto cbChar(create_characteristic(ch)); + if (!cbChar) { + qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: " + "failed to allocate a characteristic"; + continue; + } + + const auto nsData(data_from_bytearray(ch.value())); + if (!nsData) { + qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: " + "addService: failed to allocate NSData (char value)"; + continue; + } + + [newCBChars addObject:cbChar]; + + const auto declHandle = ++lastHandle; + // CB part: + charMap[declHandle] = cbChar; + charValues[declHandle] = data_from_bytearray(ch.value()); + // QT part: + QLowEnergyServicePrivate::CharData charData; + charData.valueHandle = ++lastHandle; + charData.uuid = ch.uuid(); + charData.properties = ch.properties(); + charData.value = ch.value(); + + const ObjCScopedPointer<NSMutableArray> newCBDescs([[NSMutableArray alloc] init]); + if (!newCBDescs) { + qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: " + "failed to allocate NSMutableArray " + "(descriptors)"; + continue; + } + + for (const auto &desc : ch.descriptors()) { + // CB part: + const auto cbDesc(create_descriptor(desc)); + const auto descHandle = ++lastHandle; + if (cbDesc) { + // See comments in create_descriptor on + // why cbDesc can be nil. + [newCBDescs addObject:cbDesc]; + } + // QT part: + QLowEnergyServicePrivate::DescData descData; + descData.uuid = desc.uuid(); + descData.value = desc.value(); + charData.descriptorList.insert(descHandle, descData); + } + + if ([newCBDescs count]) + cbChar.data().descriptors = newCBDescs.data(); // retains + + qtService->characteristicList.insert(declHandle, charData); + } + + if ([newCBChars count]) + cbService.characteristics = newCBChars.data(); +} + +- (CBATTError)validateWriteRequest:(CBATTRequest *)request +{ + Q_ASSERT(request); + + QT_BT_MAC_AUTORELEASEPOOL + + const auto handle = charMap.key(request.characteristic); + if (!handle || !charValues.contains(handle)) + return CBATTErrorInvalidHandle; + + NSMutableData *data = static_cast<NSMutableData *>(charValues[handle]); + if (request.offset > data.length || request.value.length > data.length - request.offset) + return CBATTErrorInvalidOffset; + + return CBATTErrorSuccess; +} + +@end diff --git a/src/bluetooth/osx/osxbtperipheralmanager_p.h b/src/bluetooth/osx/osxbtperipheralmanager_p.h new file mode 100644 index 00000000..30ddd073 --- /dev/null +++ b/src/bluetooth/osx/osxbtperipheralmanager_p.h @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 OSXBTPERIPHERALMANAGER_P_H +#define OSXBTPERIPHERALMANAGER_P_H + + +#include "osxbtutility_p.h" + +#include "qlowenergyadvertisingparameters.h" +#include "qlowenergyserviceprivate_p.h" +#include "qbluetoothuuid.h" +#include "qbluetooth.h" + +#include <QtCore/qsharedpointer.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qsysinfo.h> +#include <QtCore/qglobal.h> +#include <QtCore/qmap.h> + +#include <vector> +#include <deque> +#include <map> + +#include <Foundation/Foundation.h> + +#include "osxbluetooth_p.h" + +QT_BEGIN_NAMESPACE + +class QLowEnergyServiceData; + +namespace OSXBluetooth +{ + +class LECBManagerNotifier; + +} + +QT_END_NAMESPACE + + +// Exposing names in a header is ugly, but constant QT_PREPEND_NAMESPACE is even worse ... +// After all, this header is to be included only in its own and controller's *.mm files. + +QT_USE_NAMESPACE + +using namespace OSXBluetooth; + + +template<class Type> +using GenericLEMap = QMap<QLowEnergyHandle, Type>; + +enum class PeripheralState +{ + idle, + waitingForPowerOn, + advertising, + connected +}; + +struct UpdateRequest +{ + UpdateRequest() = default; + UpdateRequest(QLowEnergyHandle handle, const ObjCStrongReference<NSData> &val) + : charHandle(handle), + value(val) + { + } + + QLowEnergyHandle charHandle = {}; + ObjCStrongReference<NSData> value; +}; + +@interface QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) : NSObject<CBPeripheralManagerDelegate> +{ + ObjCScopedPointer<CBPeripheralManager> manager; + LECBManagerNotifier *notifier; + + QLowEnergyHandle lastHandle; + // Services in this vector are placed in such order: + // the one that has included services, must + // follow its included services to avoid exceptions from CBPeripheralManager. + std::vector<ObjCStrongReference<CBMutableService>> services; + decltype(services.size()) nextServiceToAdd; + + // Lookup map for included services: + std::map<QBluetoothUuid, CBService *> serviceIndex; + ObjCScopedPointer<NSMutableDictionary> advertisementData; + + GenericLEMap<CBCharacteristic *> charMap; + GenericLEMap<ObjCStrongReference<NSData>> charValues; + + std::deque<UpdateRequest> updateQueue; + + ObjCScopedPointer<NSMutableSet> connectedCentrals; + + PeripheralState state; +} + +- (id)initWith:(LECBManagerNotifier *)notifier; +- (void)dealloc; + +- (QSharedPointer<QLowEnergyServicePrivate>)addService:(const QLowEnergyServiceData &)data; +- (void) setParameters:(const QLowEnergyAdvertisingParameters &)parameters + data:(const QLowEnergyAdvertisingData &)data + scanResponse:(const QLowEnergyAdvertisingData &)scanResponse; + +// To be executed on the Qt's special BTLE dispatch queue. +- (void)startAdvertising; +- (void)stopAdvertising; +- (void)detach; + +- (void)write:(const QByteArray &)value + charHandle:(QLowEnergyHandle)charHandle; + + +// CBPeripheralManagerDelegate's callbacks (BTLE queue). +- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral; +- (void)peripheralManager:(CBPeripheralManager *)peripheral + willRestoreState:(NSDictionary *)dict; +- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral + error:(NSError *)error; +- (void)peripheralManager:(CBPeripheralManager *)peripheral + didAddService:(CBService *)service error:(NSError *)error; +- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central + didSubscribeToCharacteristic:(CBCharacteristic *)characteristic; +- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central + didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic; +- (void)peripheralManager:(CBPeripheralManager *)peripheral + didReceiveReadRequest:(CBATTRequest *)request; +- (void)peripheralManager:(CBPeripheralManager *)peripheral + didReceiveWriteRequests:(NSArray *)requests; +- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral; + +@end + +#endif diff --git a/src/bluetooth/osx/osxbtrfcommchannel.mm b/src/bluetooth/osx/osxbtrfcommchannel.mm index e929f335..ea679ec8 100644 --- a/src/bluetooth/osx/osxbtrfcommchannel.mm +++ b/src/bluetooth/osx/osxbtrfcommchannel.mm @@ -97,13 +97,13 @@ QT_USE_NAMESPACE withChannelID:(BluetoothRFCOMMChannelID)channelID { if (address.isNull()) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid peer address"; + qCCritical(QT_BT_OSX) << "invalid peer address"; return kIOReturnNoDevice; } // Can never be called twice. if (connected || device || channel) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "connection is already active"; + qCCritical(QT_BT_OSX) << "connection is already active"; return kIOReturnStillOpen; } @@ -112,14 +112,14 @@ QT_USE_NAMESPACE const BluetoothDeviceAddress iobtAddress = OSXBluetooth::iobluetooth_address(address); device = [IOBluetoothDevice deviceWithAddress:&iobtAddress]; if (!device) { // TODO: do I always check this BTW??? Apple's docs say nothing about nil. - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a device"; + qCCritical(QT_BT_OSX) << "failed to create a device"; return kIOReturnNoDevice; } const IOReturn status = [device openRFCOMMChannelAsync:&channel withChannelID:channelID delegate:self]; if (status != kIOReturnSuccess) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to open L2CAP channel"; + qCCritical(QT_BT_OSX) << "failed to open L2CAP channel"; // device is still autoreleased. device = nil; return status; diff --git a/src/bluetooth/osx/osxbtsdpinquiry.mm b/src/bluetooth/osx/osxbtsdpinquiry.mm index a1d6af1c..a0bfdeef 100644 --- a/src/bluetooth/osx/osxbtsdpinquiry.mm +++ b/src/bluetooth/osx/osxbtsdpinquiry.mm @@ -184,7 +184,7 @@ using namespace OSXBluetooth; if (qtFilters.size()) { array.reset([[NSMutableArray alloc] init]); if (!array) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate an uuid filter"; + qCCritical(QT_BT_OSX) << "failed to allocate an uuid filter"; return kIOReturnError; } @@ -195,7 +195,7 @@ using namespace OSXBluetooth; } if (int([array count]) != qtFilters.size()) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an uuid filter"; + qCCritical(QT_BT_OSX) << "failed to create an uuid filter"; return kIOReturnError; } } @@ -203,7 +203,7 @@ using namespace OSXBluetooth; const BluetoothDeviceAddress iobtAddress(iobluetooth_address(address)); ObjCScopedPointer<IOBluetoothDevice> newDevice([[IOBluetoothDevice deviceWithAddress:&iobtAddress] retain]); if (!newDevice) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an IOBluetoothDevice object"; + qCCritical(QT_BT_OSX) << "failed to create an IOBluetoothDevice object"; return kIOReturnError; } @@ -217,7 +217,7 @@ using namespace OSXBluetooth; result = [device performSDPQuery:self]; if (result != kIOReturnSuccess) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to start an SDP query"; + qCCritical(QT_BT_OSX) << "failed to start an SDP query"; device = oldDevice.take(); } else { isActive = true; diff --git a/src/bluetooth/osx/osxbtutility.mm b/src/bluetooth/osx/osxbtutility.mm index 942fed1c..adbc327b 100644 --- a/src/bluetooth/osx/osxbtutility.mm +++ b/src/bluetooth/osx/osxbtutility.mm @@ -37,6 +37,7 @@ ** ****************************************************************************/ +#include "qlowenergycharacteristicdata.h" #include "qbluetoothaddress.h" #include "osxbtutility_p.h" #include "qbluetoothuuid.h" @@ -70,6 +71,8 @@ Q_LOGGING_CATEGORY(QT_BT_OSX, "qt.bluetooth.ios") namespace OSXBluetooth { +const int defaultLEScanTimeoutMS = 25000; + QString qt_address(NSString *address) { if (address && address.length) { @@ -350,11 +353,6 @@ dispatch_queue_t qt_LE_queue() return leQueue.data(); } -unsigned qt_LE_deviceInquiryLength() -{ - return 10; -} - } QT_END_NAMESPACE diff --git a/src/bluetooth/osx/osxbtutility_p.h b/src/bluetooth/osx/osxbtutility_p.h index de3d3ea2..2d11692d 100644 --- a/src/bluetooth/osx/osxbtutility_p.h +++ b/src/bluetooth/osx/osxbtutility_p.h @@ -55,13 +55,14 @@ #include <QtCore/qloggingcategory.h> #include <QtCore/qscopedpointer.h> -#include <QtCore/qsysinfo.h> #include <QtCore/qglobal.h> #include <Foundation/Foundation.h> QT_BEGIN_NAMESPACE +class QLowEnergyCharacteristicData; +class QBluetoothAddress; class QBluetoothUuid; namespace OSXBluetooth { @@ -280,7 +281,7 @@ QString qt_address(NSString *address); #ifndef QT_IOS_BLUETOOTH -class QBluetoothAddress qt_address(const BluetoothDeviceAddress *address); +QBluetoothAddress qt_address(const BluetoothDeviceAddress *address); BluetoothDeviceAddress iobluetooth_address(const QBluetoothAddress &address); ObjCStrongReference<IOBluetoothSDPUUID> iobluetooth_uuid(const QBluetoothUuid &uuid); @@ -298,20 +299,9 @@ QByteArray qt_bytearray(NSData *data); QByteArray qt_bytearray(NSObject *data); ObjCStrongReference<NSData> data_from_bytearray(const QByteArray & qtData); -inline QSysInfo::MacVersion qt_OS_limit(QSysInfo::MacVersion osxVersion, QSysInfo::MacVersion iosVersion) -{ -#ifdef Q_OS_OSX - Q_UNUSED(iosVersion) - return osxVersion; -#else - Q_UNUSED(osxVersion) - return iosVersion; -#endif -} - dispatch_queue_t qt_LE_queue(); -// LE scan, in seconds. -unsigned qt_LE_deviceInquiryLength(); + +extern const int defaultLEScanTimeoutMS; } // namespace OSXBluetooth diff --git a/src/bluetooth/qbluetooth.cpp b/src/bluetooth/qbluetooth.cpp index 483fe65a..37a4774d 100644 --- a/src/bluetooth/qbluetooth.cpp +++ b/src/bluetooth/qbluetooth.cpp @@ -101,5 +101,6 @@ namespace QBluetooth { Q_LOGGING_CATEGORY(QT_BT, "qt.bluetooth") Q_LOGGING_CATEGORY(QT_BT_ANDROID, "qt.bluetooth.android") Q_LOGGING_CATEGORY(QT_BT_BLUEZ, "qt.bluetooth.bluez") +Q_LOGGING_CATEGORY(QT_BT_WINRT, "qt.bluetooth.winphone") QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent.cpp index b6faee75..b033ae3c 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent.cpp @@ -40,9 +40,12 @@ #include "qbluetoothdevicediscoveryagent.h" #include "qbluetoothdevicediscoveryagent_p.h" +#include <QtCore/qloggingcategory.h> QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(QT_BT) + /*! \class QBluetoothDeviceDiscoveryAgent \inmodule QtBluetooth @@ -88,6 +91,8 @@ QT_BEGIN_NAMESPACE platform. The error is set in response to a call to \l start(). An example for such cases are iOS versions below 5.0 which do not support Bluetooth device search at all. This value was introduced by Qt 5.5. + \value UnsupportedDiscoveryMethod One of the requested discovery methods is not supported by + the current platform. This value was introduced by Qt 5.8. \value UnknownError An unknown error has occurred. */ @@ -110,6 +115,22 @@ QT_BEGIN_NAMESPACE */ /*! + \enum QBluetoothDeviceDiscoveryAgent::DiscoveryMethod + + This enum descibes the type of discovery method employed by the QBluetoothDeviceDiscoveryAgent. + + \value NoMethod The discovery is not possible. None of the available + methods are supported. + \value ClassicMethod The discovery process searches for Bluetooth Classic + (BaseRate) devices. + \value LowEnergyMethod The discovery process searches for Bluetooth Low Energy + devices. + + \sa supportedDiscoveryMethods() + \since 5.8 +*/ + +/*! \fn void QBluetoothDeviceDiscoveryAgent::deviceDiscovered(const QBluetoothDeviceInfo &info) This signal is emitted when the Bluetooth device described by \a info is discovered. @@ -232,16 +253,106 @@ QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices() } /*! + Sets the maximum search time for Bluetooth Low Energy device search to + \a timeout in milliseconds. If \a timeout is \c 0 the discovery runs + until \l stop() is called. + + This reflects the fact that the discovery process for Bluetooth Low Energy devices + is mostly open ended. The platform continues to look for more devices until the search is + manually stopped. The timeout ensures that the search is aborted after \a timeout milliseconds. + Of course, it is still possible to manually abort the discovery by calling \l stop(). + + The new timeout value does not take effect until the device search is restarted. + In addition the timeout does not affect the classic Bluetooth device search. Depending on + the platform the classic search may add more time to the total discovery process + beyond \a timeout. + + \sa lowEnergyDiscoveryTimeout() + \since 5.8 + */ +void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout) +{ + Q_D(QBluetoothDeviceDiscoveryAgent); + + // cannot deliberately turn it off + if (d->lowEnergySearchTimeout < 0 || timeout < 0) { + qCDebug(QT_BT) << "The Bluetooth Low Energy device discovery timeout cannot be negative " + "or set on a backend which does not support this feature."; + return; + } + + d->lowEnergySearchTimeout = timeout; +} + +/*! + Returns a timeout in milliseconds that is applied to the Bluetooth Low Energy device search. + A value of \c -1 implies that the platform does not support this property and the timeout for + the device search cannot be adjusted. A return value of \c 0 + implies a never-ending search which must be manually stopped via \l stop(). + + \sa setLowEnergyDiscoveryTimeout() + \since 5.8 + */ +int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const +{ + Q_D(const QBluetoothDeviceDiscoveryAgent); + return d->lowEnergySearchTimeout; +} + +/*! + \fn QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() + + This function returns the discovery methods supported by the current platform. + It can be used to limit the scope of the device discovery. + + \since 5.8 +*/ + +/*! Starts Bluetooth device discovery, if it is not already started. The deviceDiscovered() signal is emitted as each device is discovered. The finished() signal - is emitted once device discovery is complete. + is emitted once device discovery is complete. The discovery utilizes the maximum set of + supported discovery methods on the platform. + + \sa supportedDiscoveryMethods() */ void QBluetoothDeviceDiscoveryAgent::start() { Q_D(QBluetoothDeviceDiscoveryAgent); if (!isActive() && d->lastError != InvalidBluetoothAdapterError) - d->start(); + d->start(supportedDiscoveryMethods()); +} + +/*! + Start Bluetooth device discovery, if it is not already started and the provided + \a methods are supported. + The discovery \a methods limit the scope of the device search. + For example, if the target service or device is a Bluetooth Low Energy device, + this function could be used to limit the search to Bluetooth Low Energy devices and + thereby reduces the discovery time significantly. + + \since 5.8 +*/ +void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods) +{ + if (methods == NoMethod) + return; + + DiscoveryMethods supported = + QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods(); + + Q_D(QBluetoothDeviceDiscoveryAgent); + if (!((supported & methods) == methods)) { + d->lastError = UnsupportedDiscoveryMethod; + d->errorString = QBluetoothDeviceDiscoveryAgent::tr("One or more device discovery methods " + "are not supported on this platform"); + emit error(d->lastError); + return; + } + + if (!isActive() && d->lastError != InvalidBluetoothAdapterError) + d->start(methods); } /*! diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent.h b/src/bluetooth/qbluetoothdevicediscoveryagent.h index 954ae704..84087605 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent.h +++ b/src/bluetooth/qbluetoothdevicediscoveryagent.h @@ -65,6 +65,7 @@ public: PoweredOffError, InvalidBluetoothAdapterError, UnsupportedPlatformError, + UnsupportedDiscoveryMethod, UnknownError = 100 // New errors must be added before Unknown error }; Q_ENUM(Error) @@ -75,6 +76,15 @@ public: }; Q_ENUM(InquiryType) + enum DiscoveryMethod + { + NoMethod = 0x0, + ClassicMethod = 0x01, + LowEnergyMethod = 0x02, + }; + Q_DECLARE_FLAGS(DiscoveryMethods, DiscoveryMethod) + Q_FLAG(DiscoveryMethods) + explicit QBluetoothDeviceDiscoveryAgent(QObject *parent = Q_NULLPTR); explicit QBluetoothDeviceDiscoveryAgent(const QBluetoothAddress &deviceAdapter, QObject *parent = Q_NULLPTR); @@ -91,8 +101,13 @@ public: QList<QBluetoothDeviceInfo> discoveredDevices() const; + void setLowEnergyDiscoveryTimeout(int msTimeout); + int lowEnergyDiscoveryTimeout() const; + + static DiscoveryMethods supportedDiscoveryMethods(); public Q_SLOTS: void start(); + void start(DiscoveryMethods method); void stop(); Q_SIGNALS: @@ -116,6 +131,8 @@ private: #endif }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods) + QT_END_NAMESPACE #endif diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp index be9a4e31..411e7a2b 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp @@ -66,6 +66,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( leScanTimeout(0), pendingCancel(false), pendingStart(false), + lowEnergySearchTimeout(25000), q_ptr(parent) { adapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter", @@ -93,8 +94,16 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const return m_active != NoScanActive; } -void QBluetoothDeviceDiscoveryAgentPrivate::start() +QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() { + return (LowEnergyMethod | ClassicMethod); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods) +{ + //TODO Implement discovery method handling (see input parameter) + requestedMethods = methods; + if (pendingCancel) { pendingStart = true; return; @@ -128,6 +137,32 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start() return; } + // check Android v23+ permissions + // -> BTLE search requires android.permission.ACCESS_COARSE_LOCATION + if (requestedMethods && QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) { + QString permission(QLatin1String("android.permission.ACCESS_COARSE_LOCATION")); + + // do we have required permission already, if so nothing to do + if (QtAndroidPrivate::checkPermission(permission) == QtAndroidPrivate::PermissionsResult::Denied) { + qCWarning(QT_BT_ANDROID) << "Requesting ACCESS_COARSE_LOCATION permission"; + + QAndroidJniEnvironment env; + const QHash<QString, QtAndroidPrivate::PermissionsResult> results = + QtAndroidPrivate::requestPermissionsSync(env, QStringList() << permission); + if (!results.contains(permission) + || results[permission] == QtAndroidPrivate::PermissionsResult::Denied) + { + qCWarning(QT_BT_ANDROID) << "Search not possible due to missing permission (ACCESS_COARSE_LOCATION)"; + lastError = QBluetoothDeviceDiscoveryAgent::UnknownError; + errorString = QBluetoothDeviceDiscoveryAgent::tr("Missing Location permission. Search is not possible"); + emit q->error(lastError); + return; + } + } + + qCWarning(QT_BT_ANDROID) << "ACCESS_COARSE_LOCATION permission available"; + } + // install Java BroadcastReceiver if (!receiver) { // SDP based device discovery @@ -140,18 +175,35 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start() discoveredDevices.clear(); - const bool success = adapter.callMethod<jboolean>("startDiscovery"); - if (!success) { - lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError; - errorString = QBluetoothDeviceDiscoveryAgent::tr("Discovery cannot be started"); - emit q->error(lastError); - return; - } + // by arbitrary definition we run classic search first + if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) { + const bool success = adapter.callMethod<jboolean>("startDiscovery"); + if (!success) { + lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError; + errorString = QBluetoothDeviceDiscoveryAgent::tr("Classic Discovery cannot be started"); + emit q->error(lastError); + return; + } - m_active = SDPScanActive; + m_active = SDPScanActive; + qCDebug(QT_BT_ANDROID) + << "QBluetoothDeviceDiscoveryAgentPrivate::start() - Classic search successfully started."; + } else { + // LE search only requested + Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); - qCDebug(QT_BT_ANDROID) - << "QBluetoothDeviceDiscoveryAgentPrivate::start() - successfully executed."; + if (QtAndroidPrivate::androidSdkVersion() < 18) { + qCDebug(QT_BT_ANDROID) << "Skipping Bluetooth Low Energy device scan due to" + "insufficient Android version."; + m_active = NoScanActive; + lastError = QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod; + errorString = QBluetoothDeviceDiscoveryAgent::tr("Low Energy Discovery not supported"); + emit q->error(lastError); + return; + } + + startLowEnergyScan(); + } } void QBluetoothDeviceDiscoveryAgentPrivate::stop() @@ -192,7 +244,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::processSdpDiscoveryFinished() emit q->canceled(); } else if (pendingStart) { pendingStart = pendingCancel = false; - start(); + start(requestedMethods); } else { // check that it didn't finish due to turned off Bluetooth Device const int state = adapter.callMethod<jint>("getState"); @@ -204,6 +256,13 @@ void QBluetoothDeviceDiscoveryAgentPrivate::processSdpDiscoveryFinished() return; } + // no BTLE scan requested + if (!(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) { + m_active = NoScanActive; + emit q->finished(); + return; + } + // start LE scan if supported if (QtAndroidPrivate::androidSdkVersion() < 18) { qCDebug(QT_BT_ANDROID) << "Skipping Bluetooth Low Energy device scan"; @@ -285,15 +344,21 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startLowEnergyScan() return; } + // wait interval and sum up what was found if (!leScanTimeout) { leScanTimeout = new QTimer(this); leScanTimeout->setSingleShot(true); - leScanTimeout->setInterval(25000); connect(leScanTimeout, &QTimer::timeout, this, &QBluetoothDeviceDiscoveryAgentPrivate::stopLowEnergyScan); } - leScanTimeout->start(); + if (lowEnergySearchTimeout > 0) { // otherwise no timeout and stop() required + leScanTimeout->setInterval(lowEnergySearchTimeout); + leScanTimeout->start(); + } + + qCDebug(QT_BT_ANDROID) + << "QBluetoothDeviceDiscoveryAgentPrivate::start() - Low Energy search successfully started."; } void QBluetoothDeviceDiscoveryAgentPrivate::stopLowEnergyScan() diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp index 0243d31f..5288eaf8 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp @@ -68,10 +68,12 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( adapterBluez5(0), discoveryTimer(0), useExtendedDiscovery(false), + lowEnergySearchTimeout(-1), // remains -1 on BlueZ 4 -> timeout not supported q_ptr(parent) { Q_Q(QBluetoothDeviceDiscoveryAgent); if (isBluez5()) { + lowEnergySearchTimeout = 20000; managerBluez5 = new OrgFreedesktopDBusObjectManagerInterface( QStringLiteral("org.bluez"), QStringLiteral("/"), @@ -115,8 +117,17 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const return (adapter || adapterBluez5); } -void QBluetoothDeviceDiscoveryAgentPrivate::start() +QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() { + return (ClassicMethod | LowEnergyMethod); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods /*methods*/) +{ + // Currently both BlueZ backends do not distinguish discovery methods. + // The DBus API's always return both device types. Therefore we ignore + // the passed in methods. + if (pendingCancel == true) { pendingStart = true; return; @@ -273,16 +284,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startBluez5() } } - // wait 20s and sum up what was found + // wait interval and sum up what was found if (!discoveryTimer) { discoveryTimer = new QTimer(q); discoveryTimer->setSingleShot(true); - discoveryTimer->setInterval(20000); // 20s QObject::connect(discoveryTimer, SIGNAL(timeout()), q, SLOT(_q_discoveryFinished())); } - discoveryTimer->start(); + if (lowEnergySearchTimeout > 0) { // otherwise no timeout and stop() required + discoveryTimer->setInterval(lowEnergySearchTimeout); + discoveryTimer->start(); + } } void QBluetoothDeviceDiscoveryAgentPrivate::stop() @@ -436,7 +449,9 @@ void QBluetoothDeviceDiscoveryAgentPrivate::_q_propertyChanged(const QString &na pendingStart = false; pendingCancel = false; - start(); + // start parameter ignored since Bluez 4 doesn't distinguish them + start(QBluetoothDeviceDiscoveryAgent::ClassicMethod + | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); } else { // happens when agent is created while other agent called StopDiscovery() if (!adapter) @@ -514,7 +529,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::_q_discoveryFinished() } else if (pendingStart) { pendingStart = false; pendingCancel = false; - start(); + start(QBluetoothDeviceDiscoveryAgent::ClassicMethod + | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); } else { emit q->finished(); } diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm index 0e4b460f..3f4c6755 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm @@ -37,16 +37,17 @@ ** ****************************************************************************/ -#include "qbluetoothdevicediscoverytimer_osx_p.h" #include "qbluetoothdevicediscoveryagent.h" #include "osx/osxbtledeviceinquiry_p.h" #include "qbluetoothlocaldevice.h" #include "qbluetoothdeviceinfo.h" +#include "osx/osxbtnotifier_p.h" #include "osx/osxbtutility_p.h" #include "osx/uistrings_p.h" #include "qbluetoothuuid.h" #include <QtCore/qloggingcategory.h> +#include <QtCore/qobject.h> #include <QtCore/qglobal.h> #include <QtCore/qstring.h> #include <QtCore/qdebug.h> @@ -56,12 +57,24 @@ QT_BEGIN_NAMESPACE -using OSXBluetooth::ObjCScopedPointer; +namespace +{ + +void registerQDeviceDiscoveryMetaType() +{ + static bool initDone = false; + if (!initDone) { + qRegisterMetaType<QBluetoothDeviceInfo>(); + qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>(); + initDone = true; + } +} + +}//namespace -class QBluetoothDeviceDiscoveryAgentPrivate +class QBluetoothDeviceDiscoveryAgentPrivate : public QObject { friend class QBluetoothDeviceDiscoveryAgent; - friend class OSXBluetooth::DDATimerHandler; public: QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &address, @@ -70,17 +83,16 @@ public: bool isActive() const; - void start(); + void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods m); void stop(); private: - typedef QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) LEDeviceInquiryObjC; + using LEDeviceInquiryObjC = QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry); void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error); void LEnotSupported(); void LEdeviceFound(const QBluetoothDeviceInfo &info); void LEinquiryFinished(); - void checkLETimeout(); void setError(QBluetoothDeviceDiscoveryAgent::Error, const QString &text = QString()); @@ -91,63 +103,30 @@ private: QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType; - typedef ObjCScopedPointer<LEDeviceInquiryObjC> LEDeviceInquiry; + using LEDeviceInquiry = OSXBluetooth::ObjCScopedPointer<LEDeviceInquiryObjC>; LEDeviceInquiry inquiryLE; - typedef QList<QBluetoothDeviceInfo> DevicesList; + using DevicesList = QList<QBluetoothDeviceInfo>; DevicesList discoveredDevices; bool startPending; bool stopPending; - QScopedPointer<OSXBluetooth::DDATimerHandler> timer; + int lowEnergySearchTimeout; }; -namespace OSXBluetooth { - -DDATimerHandler::DDATimerHandler(QBluetoothDeviceDiscoveryAgentPrivate *d) - : owner(d) -{ - Q_ASSERT_X(owner, Q_FUNC_INFO, "invalid pointer"); - - timer.setSingleShot(false); - connect(&timer, &QTimer::timeout, this, &DDATimerHandler::onTimer); -} - -void DDATimerHandler::start(int msec) -{ - Q_ASSERT_X(msec > 0, Q_FUNC_INFO, "invalid time interval"); - if (timer.isActive()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "timer is active"; - return; - } - - timer.start(msec); -} - -void DDATimerHandler::stop() -{ - timer.stop(); -} - -void DDATimerHandler::onTimer() -{ - Q_ASSERT(owner); - owner->checkLETimeout(); -} - -} - QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &adapter, QBluetoothDeviceDiscoveryAgent *q) : q_ptr(q), lastError(QBluetoothDeviceDiscoveryAgent::NoError), inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry), startPending(false), - stopPending(false) + stopPending(false), + lowEnergySearchTimeout(OSXBluetooth::defaultLEScanTimeoutMS) { Q_UNUSED(adapter); + registerQDeviceDiscoveryMetaType(); Q_ASSERT_X(q != Q_NULLPTR, Q_FUNC_INFO, "invalid q_ptr (null)"); } @@ -158,7 +137,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) { // Local variable to be retained ... LEDeviceInquiryObjC *inq = inquiryLE.data(); - dispatch_async(leQueue, ^{ + dispatch_sync(leQueue, ^{ [inq stop]; }); } @@ -175,7 +154,7 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const return inquiryLE; } -void QBluetoothDeviceDiscoveryAgentPrivate::start() +void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods /*methods*/) { Q_ASSERT_X(!isActive(), Q_FUNC_INFO, "called on active device discovery agent"); @@ -186,26 +165,37 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start() using namespace OSXBluetooth; - inquiryLE.reset([[LEDeviceInquiryObjC alloc] init]); + QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier); + // Connections: + using ErrMemFunPtr = void (LECBManagerNotifier::*)(QBluetoothDeviceDiscoveryAgent::Error); + notifier->connect(notifier.data(), ErrMemFunPtr(&LECBManagerNotifier::CBManagerError), + this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError); + notifier->connect(notifier.data(), &LECBManagerNotifier::LEnotSupported, + this, &QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported); + notifier->connect(notifier.data(), &LECBManagerNotifier::discoveryFinished, + this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished); + notifier->connect(notifier.data(), &LECBManagerNotifier::deviceDiscovered, + this, &QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound); + + inquiryLE.reset([[LEDeviceInquiryObjC alloc] initWithNotifier:notifier.data()]); + if (inquiryLE) + notifier.take(); // Whatever happens next, inquiryLE is already the owner ... + dispatch_queue_t leQueue(qt_LE_queue()); if (!leQueue || !inquiryLE) { setError(QBluetoothDeviceDiscoveryAgent::UnknownError, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED)); emit q_ptr->error(lastError); + return; } discoveredDevices.clear(); setError(QBluetoothDeviceDiscoveryAgent::NoError); - // CoreBluetooth does not have a timeout. We start a timer here - // and check if scan really started and if yes if we have a timeout. - timer.reset(new OSXBluetooth::DDATimerHandler(this)); - timer->start(2000); - // Create a local variable - to have a strong referece in a block. LEDeviceInquiryObjC *inq = inquiryLE.data(); dispatch_async(leQueue, ^{ - [inq start]; + [inq startWithTimeout:lowEnergySearchTimeout]; }); } @@ -223,7 +213,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop() // Create a local variable - to have a strong referece in a block. LEDeviceInquiryObjC *inq = inquiryLE.data(); - dispatch_async(leQueue, ^{ + dispatch_sync(leQueue, ^{ [inq stop]; }); // We consider LE scan to be stopped immediately and @@ -241,7 +231,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDisco Q_FUNC_INFO, "unexpected error"); inquiryLE.reset(); - timer->stop(); startPending = false; stopPending = false; @@ -252,7 +241,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDisco void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported() { inquiryLE.reset(); - timer->stop(); startPending = false; stopPending = false; @@ -281,7 +269,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound(const QBluetoothDevice void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished() { inquiryLE.reset(); - timer->stop(); if (stopPending && !startPending) { stopPending = false; @@ -289,47 +276,14 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished() } else if (startPending) { startPending = false; stopPending = false; - start(); + // always the same method for start() on iOS + // classic search not supported + start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); } else { emit q_ptr->finished(); } } -void QBluetoothDeviceDiscoveryAgentPrivate::checkLETimeout() -{ - Q_ASSERT_X(inquiryLE, Q_FUNC_INFO, "LE device inquiry is nil"); - - using namespace OSXBluetooth; - - const LEInquiryState state([inquiryLE inquiryState]); - if (state == InquiryStarting || state == InquiryActive) - return; // Wait ... - - if (state == ErrorPoweredOff) - return LEinquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError); - - if (state == ErrorLENotSupported) - return LEnotSupported(); - - if (state == InquiryFinished) { - // Process found devices if any ... - const QList<QBluetoothDeviceInfo> leDevices([inquiryLE discoveredDevices]); - foreach (const QBluetoothDeviceInfo &info, leDevices) { - // We were cancelled on a previous device discovered signal ... - if (!inquiryLE) - break; - LEdeviceFound(info); - } - - if (inquiryLE) - LEinquiryFinished(); - return; - } - - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected inquiry state in LE timeout"; - // Actually, this deserves an assert :) -} - void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error, const QString &text) { @@ -373,7 +327,7 @@ QBluetoothDeviceDiscoveryAgent::QBluetoothDeviceDiscoveryAgent( d_ptr(new QBluetoothDeviceDiscoveryAgentPrivate(deviceAdapter, this)) { if (!deviceAdapter.isNull()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "local device address is " + qCWarning(QT_BT_OSX) << "local device address is " "not available, provided address is ignored"; d_ptr->setError(InvalidBluetoothAdapterError); } @@ -399,14 +353,39 @@ QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices() return d_ptr->discoveredDevices; } +QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() +{ + return LowEnergyMethod; +} + void QBluetoothDeviceDiscoveryAgent::start() { if (d_ptr->lastError != InvalidBluetoothAdapterError) { if (!isActive()) - d_ptr->start(); + d_ptr->start(supportedDiscoveryMethods()); else - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "already started"; + qCDebug(QT_BT_OSX) << "already started"; + } +} + +void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods) +{ + if (methods == NoMethod) + return; + + DiscoveryMethods supported = + QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods(); + + if (!((supported & methods) == methods)) { + d_ptr->lastError = UnsupportedDiscoveryMethod; + d_ptr->errorString = QBluetoothDeviceDiscoveryAgent::tr("One or more device discovery methods " + "are not supported on this platform"); + emit error(d_ptr->lastError); + return; } + + if (!isActive() && d_ptr->lastError != InvalidBluetoothAdapterError) + d_ptr->start(methods); } void QBluetoothDeviceDiscoveryAgent::stop() @@ -430,4 +409,21 @@ QString QBluetoothDeviceDiscoveryAgent::errorString() const return d_ptr->errorString; } +int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const +{ + return d_ptr->lowEnergySearchTimeout; +} + +void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout) +{ + // cannot deliberately turn it off + if (timeout < 0) { + qCDebug(QT_BT_OSX) << "The Bluetooth Low Energy device discovery timeout cannot be negative."; + return; + } + + d_ptr->lowEnergySearchTimeout = timeout; + return; +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm index 63eab2b9..b308f7cc 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm @@ -37,22 +37,23 @@ ** ****************************************************************************/ -#include "qbluetoothdevicediscoverytimer_osx_p.h" #include "qbluetoothdevicediscoveryagent.h" #include "osx/osxbtledeviceinquiry_p.h" #include "osx/osxbtdeviceinquiry_p.h" #include "qbluetoothlocaldevice.h" #include "osx/osxbtsdpinquiry_p.h" #include "qbluetoothdeviceinfo.h" +#include "osx/osxbtnotifier_p.h" #include "osx/osxbtutility_p.h" #include "osx/osxbluetooth_p.h" #include "osx/uistrings_p.h" #include "qbluetoothhostinfo.h" +#include "qbluetoothaddress.h" +#include "osx/uistrings_p.h" #include "qbluetoothuuid.h" #include <QtCore/qloggingcategory.h> #include <QtCore/qscopedpointer.h> -#include <QtCore/qdatetime.h> #include <QtCore/qglobal.h> #include <QtCore/qstring.h> #include <QtCore/qdebug.h> @@ -62,23 +63,40 @@ QT_BEGIN_NAMESPACE -using OSXBluetooth::ObjCScopedPointer; +namespace +{ + +void registerQDeviceDiscoveryMetaType() +{ + static bool initDone = false; + if (!initDone) { + qRegisterMetaType<QBluetoothDeviceInfo>(); + qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>(); + initDone = true; + } +} + +}//namespace -class QBluetoothDeviceDiscoveryAgentPrivate : public OSXBluetooth::DeviceInquiryDelegate +class QBluetoothDeviceDiscoveryAgentPrivate : public QObject, + public OSXBluetooth::DeviceInquiryDelegate { friend class QBluetoothDeviceDiscoveryAgent; - friend class OSXBluetooth::DDATimerHandler; public: - typedef QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) LEDeviceInquiryObjC; + template<class T> + using ObjCScopedPointer = OSXBluetooth::ObjCScopedPointer<T>; + using LEDeviceInquiryObjC = QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry); QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress & address, QBluetoothDeviceDiscoveryAgent *q); - virtual ~QBluetoothDeviceDiscoveryAgentPrivate(); + + ~QBluetoothDeviceDiscoveryAgentPrivate() Q_DECL_OVERRIDE; bool isValid() const; bool isActive() const; - void start(); + void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods); + void startClassic(); void startLE(); void stop(); @@ -94,7 +112,6 @@ private: void error(IOBluetoothDeviceInquiry *inq, IOReturn error) Q_DECL_OVERRIDE; void deviceFound(IOBluetoothDeviceInquiry *inq, IOBluetoothDevice *device) Q_DECL_OVERRIDE; - // void LEinquiryFinished(); void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error); void LEnotSupported(); @@ -104,9 +121,8 @@ private: void deviceFound(const QBluetoothDeviceInfo &newDeviceInfo); void setError(IOReturn error, const QString &text = QString()); - void setError(QBluetoothDeviceDiscoveryAgent::Error, const QString &text = QString()); - - void checkLETimeout(); + void setError(QBluetoothDeviceDiscoveryAgent::Error, + const QString &text = QString()); QBluetoothDeviceDiscoveryAgent *q_ptr; AgentState agentState; @@ -121,56 +137,22 @@ private: QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType; - typedef ObjCScopedPointer<DeviceInquiryObjC> DeviceInquiry; + using DeviceInquiry = ObjCScopedPointer<DeviceInquiryObjC>; DeviceInquiry inquiry; - typedef ObjCScopedPointer<LEDeviceInquiryObjC> LEDeviceInquiry; + using LEDeviceInquiry = ObjCScopedPointer<LEDeviceInquiryObjC>; LEDeviceInquiry inquiryLE; - typedef ObjCScopedPointer<IOBluetoothHostController> HostController; + using HostController = ObjCScopedPointer<IOBluetoothHostController>; HostController hostController; - typedef QList<QBluetoothDeviceInfo> DevicesList; + using DevicesList = QList<QBluetoothDeviceInfo>; DevicesList discoveredDevices; - QScopedPointer<OSXBluetooth::DDATimerHandler> timer; + int lowEnergySearchTimeout; + QBluetoothDeviceDiscoveryAgent::DiscoveryMethods requestedMethods; }; -namespace OSXBluetooth { - -DDATimerHandler::DDATimerHandler(QBluetoothDeviceDiscoveryAgentPrivate *d) - : owner(d) -{ - Q_ASSERT_X(owner, Q_FUNC_INFO, "invalid pointer"); - - timer.setSingleShot(false); - connect(&timer, &QTimer::timeout, this, &DDATimerHandler::onTimer); -} - -void DDATimerHandler::start(int msec) -{ - Q_ASSERT_X(msec > 0, Q_FUNC_INFO, "invalid time interval"); - if (timer.isActive()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "timer is active"; - return; - } - - timer.start(msec); -} - -void DDATimerHandler::stop() -{ - timer.stop(); -} - -void DDATimerHandler::onTimer() -{ - Q_ASSERT(owner); - owner->checkLETimeout(); -} - -} - QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &adapter, QBluetoothDeviceDiscoveryAgent *q) : q_ptr(q), @@ -179,26 +161,22 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(con startPending(false), stopPending(false), lastError(QBluetoothDeviceDiscoveryAgent::NoError), - inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry) + inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry), + lowEnergySearchTimeout(OSXBluetooth::defaultLEScanTimeoutMS), + requestedMethods(QBluetoothDeviceDiscoveryAgent::ClassicMethod + | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) { + registerQDeviceDiscoveryMetaType(); + Q_ASSERT_X(q != Q_NULLPTR, Q_FUNC_INFO, "invalid q_ptr (null)"); HostController controller([[IOBluetoothHostController defaultController] retain]); if (!controller || [controller powerState] != kBluetoothHCIPowerStateON) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "no default host " - "controller or adapter is off"; - return; - } - - DeviceInquiry newInquiry([[DeviceInquiryObjC alloc]initWithDelegate:this]); - if (!newInquiry) { // Obj-C's way of "reporting errors": - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to " - "initialize an inquiry"; + qCCritical(QT_BT_OSX) << "no default host controller or adapter is off"; return; } hostController.reset(controller.take()); - inquiry.reset(newInquiry.take()); } QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() @@ -208,7 +186,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) { // Local variable to be retained ... LEDeviceInquiryObjC *inq = inquiryLE.data(); - dispatch_async(leQueue, ^{ + dispatch_sync(leQueue, ^{ [inq stop]; }); } @@ -217,12 +195,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() bool QBluetoothDeviceDiscoveryAgentPrivate::isValid() const { - // 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. - return hostController && [hostController powerState] == kBluetoothHCIPowerStateON - && inquiry; + return hostController && [hostController powerState] == kBluetoothHCIPowerStateON; } bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const @@ -236,23 +209,56 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const return agentState != NonActive; } -void QBluetoothDeviceDiscoveryAgentPrivate::start() +void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods) { - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "called on invalid device discovery agent"); - Q_ASSERT_X(!isActive(), Q_FUNC_INFO, "called on active device discovery agent"); - Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError, - Q_FUNC_INFO, "called with an invalid Bluetooth adapter"); + Q_ASSERT(isValid()); + Q_ASSERT(!isActive()); + Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError); + Q_ASSERT(methods & (QBluetoothDeviceDiscoveryAgent::ClassicMethod + | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)); + + requestedMethods = methods; if (stopPending) { startPending = true; return; } - agentState = ClassicScan; + // This function (re)starts the scan(s) from the scratch; + // starting from Classic if it's in 'methods' (or LE scan if not). + agentState = NonActive; discoveredDevices.clear(); setError(QBluetoothDeviceDiscoveryAgent::NoError); + if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) + return startClassic(); + + startLE(); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::startClassic() +{ + Q_ASSERT(isValid()); + Q_ASSERT(!isActive()); + Q_ASSERT(lastError == QBluetoothDeviceDiscoveryAgent::NoError); + Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod); + Q_ASSERT(agentState == NonActive); + + if (!inquiry) { + // The first Classic scan for this DDA. + inquiry.reset([[DeviceInquiryObjC alloc]initWithDelegate:this]); + if (!inquiry) { + qCCritical(QT_BT_OSX) << "failed to initialize an Classic device inquiry"; + setError(QBluetoothDeviceDiscoveryAgent::UnknownError, + QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED)); + emit q_ptr->error(lastError); + return; + } + } + + agentState = ClassicScan; + const IOReturn res = [inquiry start]; if (res != kIOReturnSuccess) { setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED)); @@ -263,13 +269,29 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start() void QBluetoothDeviceDiscoveryAgentPrivate::startLE() { - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "called on invalid device discovery agent"); - Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError, - Q_FUNC_INFO, "called with an invalid Bluetooth adapter"); + Q_ASSERT(isValid()); + Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError); + Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); using namespace OSXBluetooth; - inquiryLE.reset([[LEDeviceInquiryObjC alloc] init]); + QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier); + // Connections: + using ErrMemFunPtr = void (LECBManagerNotifier::*)(QBluetoothDeviceDiscoveryAgent::Error); + notifier->connect(notifier.data(), ErrMemFunPtr(&LECBManagerNotifier::CBManagerError), + this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError); + notifier->connect(notifier.data(), &LECBManagerNotifier::LEnotSupported, + this, &QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported); + notifier->connect(notifier.data(), &LECBManagerNotifier::discoveryFinished, + this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished); + using DeviceMemFunPtr = void (QBluetoothDeviceDiscoveryAgentPrivate::*)(const QBluetoothDeviceInfo &); + notifier->connect(notifier.data(), &LECBManagerNotifier::deviceDiscovered, + this, DeviceMemFunPtr(&QBluetoothDeviceDiscoveryAgentPrivate::deviceFound)); + + // Check queue and create scanner: + inquiryLE.reset([[LEDeviceInquiryObjC alloc] initWithNotifier:notifier.data()]); + if (inquiryLE) + notifier.take(); // Whatever happens next, inquiryLE is already the owner ... dispatch_queue_t leQueue(qt_LE_queue()); if (!leQueue || !inquiryLE) { @@ -277,18 +299,15 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startLE() QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED_LE)); agentState = NonActive; emit q_ptr->error(lastError); + return; } + // Now start in on LE queue: agentState = LEScan; - // CoreBluetooth does not have a timeout. We start a timer here - // and check if scan is active/finished/finished with error(s). - timer.reset(new OSXBluetooth::DDATimerHandler(this)); - timer->start(2000); - // We need the local variable so that it's retained ... LEDeviceInquiryObjC *inq = inquiryLE.data(); dispatch_async(leQueue, ^{ - [inq start]; + [inq startWithTimeout:lowEnergySearchTimeout]; }); } @@ -310,7 +329,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop() if (agentState == ClassicScan) { const IOReturn res = [inquiry stop]; if (res != kIOReturnSuccess) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to stop"; + qCWarning(QT_BT_OSX) << "failed to stop"; startPending = prevStart; stopPending = false; setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STOPPED)); @@ -321,7 +340,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop() Q_ASSERT(leQueue); // We need the local variable so that it's retained ... LEDeviceInquiryObjC *inq = inquiryLE.data(); - dispatch_async(leQueue, ^{ + dispatch_sync(leQueue, ^{ [inq stop]; }); // We consider LE scan to be stopped immediately and @@ -346,13 +365,17 @@ void QBluetoothDeviceDiscoveryAgentPrivate::inquiryFinished(IOBluetoothDeviceInq } else if (startPending) { startPending = false; stopPending = false; - start(); + start(requestedMethods); } else { // We can be here _only_ if a classic scan - // finished in a normal way (not cancelled). + // finished in a normal way (not cancelled) + // and requestedMethods includes LowEnergyMethod. // startLE() will take care of old devices // not supporting Bluetooth 4.0. - startLE(); + if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) + startLE(); + else + emit q_ptr->finished(); } } @@ -384,7 +407,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(IOBluetoothDeviceInquiry // Let's collect some info about this device: const QBluetoothAddress deviceAddress(OSXBluetooth::qt_address([device getAddress])); if (deviceAddress.isNull()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid Bluetooth address"; + qCWarning(QT_BT_OSX) << "invalid Bluetooth address"; return; } @@ -414,8 +437,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::setError(IOReturn error, const QStri setError(QBluetoothDeviceDiscoveryAgent::UnknownError, text); } -void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error, - const QString &text) +void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error, const QString &text) { lastError = error; @@ -442,53 +464,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAg } if (lastError != QBluetoothDeviceDiscoveryAgent::NoError) - qCDebug(QT_BT_OSX) << "error set: "<<errorString; -} - -void QBluetoothDeviceDiscoveryAgentPrivate::checkLETimeout() -{ - Q_ASSERT_X(agentState == LEScan, Q_FUNC_INFO, "invalid agent state"); - Q_ASSERT_X(inquiryLE, Q_FUNC_INFO, "LE device inquiry is nil"); - - using namespace OSXBluetooth; - - const LEInquiryState state([inquiryLE inquiryState]); - if (state == InquiryStarting || state == InquiryActive) - return; // Wait ... - - if (state == ErrorPoweredOff) - return LEinquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError); - - if (state == ErrorLENotSupported) - return LEnotSupported(); - - if (state == InquiryFinished) { - // Process found devices if any ... - const QList<QBluetoothDeviceInfo> leDevices([inquiryLE discoveredDevices]); - foreach (const QBluetoothDeviceInfo &info, leDevices) { - // We were cancelled on a previous device discovered signal ... - if (agentState != LEScan) - break; - deviceFound(info); - } - - if (agentState == LEScan) - LEinquiryFinished(); - return; - } - - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected inquiry state in LE timeout"; - // Actually, this deserves an assert :) + qCDebug(QT_BT_OSX) << "error set:"<<errorString; } void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error) { - // At the moment the only error reported can be 'powered off' error, it happens - // after the LE scan started (so we have LE support and this is a real PoweredOffError). - Q_ASSERT(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError); + Q_ASSERT(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError + || error == QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); - timer->stop(); inquiryLE.reset(); + + startPending = false; + stopPending = false; agentState = NonActive; setError(error); emit q_ptr->error(lastError); @@ -496,9 +483,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDisco void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported() { - // Not supported is not an error (we still have 'Classic'). qCDebug(QT_BT_OSX) << "no Bluetooth LE support"; - LEinquiryFinished(); + + if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) { + // Having both Classic | LE means this is not an error. + LEinquiryFinished(); + } else { + // In the past this was never an error, that's why we have + // LEnotSupported as a special method. But now, since + // we can have separate Classic/LE scans, we have to report it + // as UnsupportedDiscoveryMethod. + LEinquiryError(QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); + } } void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished() @@ -506,7 +502,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished() // The same logic as in inquiryFinished, but does not start LE scan. agentState = NonActive; inquiryLE.reset(); - timer->stop(); if (stopPending && !startPending) { stopPending = false; @@ -514,7 +509,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished() } else if (startPending) { startPending = false; stopPending = false; - start(); //Start from a classic scan again. + start(requestedMethods); //Start again. } else { emit q_ptr->finished(); } @@ -574,7 +569,7 @@ QBluetoothDeviceDiscoveryAgent::InquiryType QBluetoothDeviceDiscoveryAgent::inqu return d_ptr->inquiryType; } -void QBluetoothDeviceDiscoveryAgent::setInquiryType(QBluetoothDeviceDiscoveryAgent::InquiryType type) +void QBluetoothDeviceDiscoveryAgent::setInquiryType(InquiryType type) { d_ptr->inquiryType = type; } @@ -584,12 +579,33 @@ QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices() return d_ptr->discoveredDevices; } +QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() +{ + return ClassicMethod | LowEnergyMethod; +} + void QBluetoothDeviceDiscoveryAgent::start() { + start(supportedDiscoveryMethods()); +} + +void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods) +{ + if (methods == NoMethod) + return; + + if ((supportedDiscoveryMethods() & methods) != methods) { + d_ptr->lastError = UnsupportedDiscoveryMethod; + d_ptr->errorString = tr("One or more device discovery methods " + "are not supported on this platform"); + emit error(d_ptr->lastError); + return; + } + if (d_ptr->lastError != InvalidBluetoothAdapterError) { if (d_ptr->isValid()) { if (!isActive()) - d_ptr->start(); + d_ptr->start(methods); } else { // We previously failed to initialize d_ptr correctly: // either some memory allocation problem or @@ -626,4 +642,21 @@ QString QBluetoothDeviceDiscoveryAgent::errorString() const return d_ptr->errorString; } +void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout) +{ + // cannot deliberately turn it off + if (timeout < 0) { + qCDebug(QT_BT_OSX) << "The Bluetooth Low Energy device discovery timeout cannot be negative."; + return; + } + + d_ptr->lowEnergySearchTimeout = timeout; + return; +} + +int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const +{ + return d_ptr->lowEnergySearchTimeout; +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp index 09076d6d..e3646db9 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp @@ -53,6 +53,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( QBluetoothDeviceDiscoveryAgent *parent) : inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry), lastError(QBluetoothDeviceDiscoveryAgent::NoError), + lowEnergySearchTimeout(-1), q_ptr(parent) { Q_UNUSED(deviceAdapter); @@ -68,7 +69,12 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const return false; } -void QBluetoothDeviceDiscoveryAgentPrivate::start() +QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() +{ + return QBluetoothDeviceDiscoveryAgent::NoMethod; +} + +void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods) { Q_Q(QBluetoothDeviceDiscoveryAgent); lastError = QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError; diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h index f49ff8b7..de8006be 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h @@ -78,10 +78,14 @@ class QDBusVariant; QT_END_NAMESPACE #endif +#ifdef QT_WINRT_BLUETOOTH +class QWinRTBluetoothDeviceDiscoveryWorker; +#endif + QT_BEGIN_NAMESPACE class QBluetoothDeviceDiscoveryAgentPrivate -#if defined(QT_ANDROID_BLUETOOTH) +#if defined(QT_ANDROID_BLUETOOTH) || defined(QT_WINRT_BLUETOOTH) : public QObject { Q_OBJECT @@ -95,7 +99,7 @@ public: QBluetoothDeviceDiscoveryAgent *parent); ~QBluetoothDeviceDiscoveryAgentPrivate(); - void start(); + void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods); void stop(); bool isActive() const; @@ -155,6 +159,20 @@ private: QTimer extendedDiscoveryTimer; #endif +#ifdef QT_WINRT_BLUETOOTH +private slots: + void registerDevice(const QBluetoothDeviceInfo &info); + void onScanFinished(); + void onScanCanceled(); + +private: + void disconnectAndClearWorker(); + QPointer<QWinRTBluetoothDeviceDiscoveryWorker> worker; + QTimer *leScanTimer; +#endif + + int lowEnergySearchTimeout; + QBluetoothDeviceDiscoveryAgent::DiscoveryMethods requestedMethods; QBluetoothDeviceDiscoveryAgent *q_ptr; }; diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp new file mode 100644 index 00000000..2b44c7c8 --- /dev/null +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp @@ -0,0 +1,545 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbluetoothdevicediscoveryagent.h" +#include "qbluetoothdevicediscoveryagent_p.h" +#include "qbluetoothaddress.h" +#include "qbluetoothuuid.h" +#include "qfunctions_winrt.h" + +#include <QtCore/QLoggingCategory> +#include <QtCore/private/qeventdispatcher_winrt_p.h> + +#include <wrl.h> +#include <windows.devices.enumeration.h> +#include <windows.devices.bluetooth.h> +#include <windows.foundation.collections.h> + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Devices; +using namespace ABI::Windows::Devices::Bluetooth; +using namespace ABI::Windows::Devices::Enumeration; + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) + +#define WARN_AND_RETURN_IF_FAILED(msg, ret) \ + if (FAILED(hr)) { \ + qCWarning(QT_BT_WINRT) << msg; \ + ret; \ + } + +class QWinRTBluetoothDeviceDiscoveryWorker : public QObject +{ + Q_OBJECT +public: + explicit QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods); + ~QWinRTBluetoothDeviceDiscoveryWorker(); + void start(); + +private: + void startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); + void onDeviceDiscoveryFinished(IAsyncOperation<DeviceInformationCollection *> *op, + QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); + void gatherDeviceInformation(IDeviceInformation *deviceInfo, + QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); + void gatherMultipleDeviceInformation(IVectorView<DeviceInformation *> *devices, + QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); + void setupLEDeviceWatcher(); + void bluetoothInfoFromDeviceIdAsync(HSTRING deviceId); + void bluetoothInfoFromLeDeviceIdAsync(HSTRING deviceId); + HRESULT onClassicBluetoothDeviceFoundAsync(IAsyncOperation<BluetoothDevice *> *, AsyncStatus); + HRESULT onBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status); + void decreaseAndCheckPendingDevices(); + +public slots: + void handleLeTimeout(); + +Q_SIGNALS: + void deviceFound(const QBluetoothDeviceInfo &info); + void scanFinished(); + void scanCanceled(); + +public: + quint8 requestedModes; + +private: + ComPtr<IDeviceWatcher> m_leDeviceWatcher; + EventRegistrationToken m_leDeviceAddedToken; + int m_pendingDevices; +}; + +QWinRTBluetoothDeviceDiscoveryWorker::QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods) + : requestedModes(methods) + , m_pendingDevices(0) +{ + qRegisterMetaType<QBluetoothDeviceInfo>(); +} + +QWinRTBluetoothDeviceDiscoveryWorker::~QWinRTBluetoothDeviceDiscoveryWorker() +{ + if (m_leDeviceWatcher && m_leDeviceAddedToken.value) { + HRESULT hr; + hr = m_leDeviceWatcher->remove_Added(m_leDeviceAddedToken); + Q_ASSERT_SUCCEEDED(hr); + } +} + +void QWinRTBluetoothDeviceDiscoveryWorker::start() +{ + QEventDispatcherWinRT::runOnXamlThread([this]() { + if (requestedModes & QBluetoothDeviceDiscoveryAgent::ClassicMethod) + startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::ClassicMethod); + + if (requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) + startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); + return S_OK; + }); + + qCDebug(QT_BT_WINRT) << "Worker started"; +} + +void QWinRTBluetoothDeviceDiscoveryWorker::startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode) +{ + HString deviceSelector; + ComPtr<IDeviceInformationStatics> deviceInformationStatics; + HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), &deviceInformationStatics); + WARN_AND_RETURN_IF_FAILED("Could not obtain device information statics", return); + if (mode == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) { + ComPtr<IBluetoothLEDeviceStatics> bluetoothLeDeviceStatics; + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &bluetoothLeDeviceStatics); + WARN_AND_RETURN_IF_FAILED("Could not obtain bluetooth LE device statics", return); + bluetoothLeDeviceStatics->GetDeviceSelector(deviceSelector.GetAddressOf()); + } else { + ComPtr<IBluetoothDeviceStatics> bluetoothDeviceStatics; + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothDevice).Get(), &bluetoothDeviceStatics); + WARN_AND_RETURN_IF_FAILED("Could not obtain bluetooth device statics", return); + bluetoothDeviceStatics->GetDeviceSelector(deviceSelector.GetAddressOf()); + } + ComPtr<IAsyncOperation<DeviceInformationCollection *>> op; + hr = deviceInformationStatics->FindAllAsyncAqsFilter(deviceSelector.Get(), &op); + WARN_AND_RETURN_IF_FAILED("Could not start bluetooth device discovery operation", return); + hr = op->put_Completed( + Callback<IAsyncOperationCompletedHandler<DeviceInformationCollection *>>([this, mode](IAsyncOperation<DeviceInformationCollection *> *op, AsyncStatus) { + onDeviceDiscoveryFinished(op, mode); + return S_OK; + }).Get()); + WARN_AND_RETURN_IF_FAILED("Could not add callback to bluetooth device discovery operation", return); +} + +void QWinRTBluetoothDeviceDiscoveryWorker::onDeviceDiscoveryFinished(IAsyncOperation<DeviceInformationCollection *> *op, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode) +{ + qCDebug(QT_BT_WINRT) << (mode == QBluetoothDeviceDiscoveryAgent::ClassicMethod ? "BT" : "BTLE") + << " scan completed"; + ComPtr<IVectorView<DeviceInformation *>> devices; + HRESULT hr; + hr = op->GetResults(&devices); + Q_ASSERT_SUCCEEDED(hr); + gatherMultipleDeviceInformation(devices.Get(), mode); +} + +void QWinRTBluetoothDeviceDiscoveryWorker::gatherDeviceInformation(IDeviceInformation *deviceInfo, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode) +{ + HString deviceId; + HRESULT hr; + hr = deviceInfo->get_Id(deviceId.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + if (mode == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) + bluetoothInfoFromLeDeviceIdAsync(deviceId.Get()); + else + bluetoothInfoFromDeviceIdAsync(deviceId.Get()); +} + +void QWinRTBluetoothDeviceDiscoveryWorker::gatherMultipleDeviceInformation(IVectorView<DeviceInformation *> *devices, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode) +{ + quint32 deviceCount; + HRESULT hr = devices->get_Size(&deviceCount); + Q_ASSERT_SUCCEEDED(hr); + m_pendingDevices += deviceCount; + for (quint32 i = 0; i < deviceCount; ++i) { + ComPtr<IDeviceInformation> device; + hr = devices->GetAt(i, &device); + Q_ASSERT_SUCCEEDED(hr); + gatherDeviceInformation(device.Get(), mode); + } +} + +void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() +{ + HString deviceSelector; + ComPtr<IDeviceInformationStatics> deviceInformationStatics; + HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), &deviceInformationStatics); + WARN_AND_RETURN_IF_FAILED("Could not obtain device information statics", return); + ComPtr<IBluetoothLEDeviceStatics> bluetoothLeDeviceStatics; + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &bluetoothLeDeviceStatics); + WARN_AND_RETURN_IF_FAILED("Could not obtain bluetooth LE device statics", return); + hr = bluetoothLeDeviceStatics->GetDeviceSelector(deviceSelector.GetAddressOf()); + WARN_AND_RETURN_IF_FAILED("Could not obtain device selector string", return); + hr = deviceInformationStatics->CreateWatcherAqsFilter(deviceSelector.Get(), &m_leDeviceWatcher); + WARN_AND_RETURN_IF_FAILED("Could not create le device watcher", return); + auto deviceAddedCallback = + Callback<ITypedEventHandler<DeviceWatcher *, DeviceInformation *>>([this](IDeviceWatcher *, IDeviceInformation *deviceInfo) + { + HString deviceId; + HRESULT hr; + hr = deviceInfo->get_Id(deviceId.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + bluetoothInfoFromLeDeviceIdAsync(deviceId.Get()); + return S_OK; + }); + hr = m_leDeviceWatcher->add_Added(deviceAddedCallback.Get(), &m_leDeviceAddedToken); + WARN_AND_RETURN_IF_FAILED("Could not add \"device added\" callback", return); + hr = m_leDeviceWatcher->Start(); + WARN_AND_RETURN_IF_FAILED("Could not start device watcher", return); +} + +void QWinRTBluetoothDeviceDiscoveryWorker::handleLeTimeout() +{ + // pendingDevices might be <0 if devices were added after the intitial scan was completed + if (m_pendingDevices <= 0) + emit scanFinished(); + else + emit scanCanceled(); + deleteLater(); +} + +// "deviceFound" will be emitted at the end of the deviceFromIdOperation callback +void QWinRTBluetoothDeviceDiscoveryWorker::bluetoothInfoFromDeviceIdAsync(HSTRING deviceId) +{ + ComPtr<IBluetoothDeviceStatics> deviceStatics; + HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothDevice).Get(), &deviceStatics); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IAsyncOperation<BluetoothDevice *>> deviceFromIdOperation; + // on Windows 10 FromIdAsync might ask for device permission. We cannot wait here but have to handle that asynchronously + hr = deviceStatics->FromIdAsync(deviceId, &deviceFromIdOperation); + if (FAILED(hr)) { + decreaseAndCheckPendingDevices(); + qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id"; + return; + } + QEventDispatcherWinRT::runOnXamlThread([deviceFromIdOperation, this]() { + HRESULT hr; + hr = deviceFromIdOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<BluetoothDevice *>> + (this, &QWinRTBluetoothDeviceDiscoveryWorker::onClassicBluetoothDeviceFoundAsync).Get()); + if (FAILED(hr)) { + decreaseAndCheckPendingDevices(); + qCWarning(QT_BT_WINRT) << "Could not register device found callback"; + return S_OK; + } + return S_OK; + }); + Q_ASSERT_SUCCEEDED(hr); +} + +// "deviceFound" will be emitted at the end of the deviceFromIdOperation callback +void QWinRTBluetoothDeviceDiscoveryWorker::bluetoothInfoFromLeDeviceIdAsync(HSTRING deviceId) +{ + ComPtr<IBluetoothLEDeviceStatics> deviceStatics; + HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &deviceStatics); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation; + // on Windows 10 FromIdAsync might ask for device permission. We cannot wait here but have to handle that asynchronously + hr = deviceStatics->FromIdAsync(deviceId, &deviceFromIdOperation); + if (FAILED(hr)) { + decreaseAndCheckPendingDevices(); + qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id"; + return; + } + QEventDispatcherWinRT::runOnXamlThread([deviceFromIdOperation, this]() { + HRESULT hr; + hr = deviceFromIdOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<BluetoothLEDevice *>> + (this, &QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFoundAsync).Get()); + if (FAILED(hr)) { + decreaseAndCheckPendingDevices(); + qCWarning(QT_BT_WINRT) << "Could not register device found callback"; + return S_OK; + } + return S_OK; + }); +} + +HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onClassicBluetoothDeviceFoundAsync(IAsyncOperation<BluetoothDevice *> *op, AsyncStatus status) +{ + if (status != AsyncStatus::Completed) { + decreaseAndCheckPendingDevices(); + return S_OK; + } + + ComPtr<IBluetoothDevice> device; + HRESULT hr = op->GetResults(&device); + Q_ASSERT_SUCCEEDED(hr); + + if (!device) { + decreaseAndCheckPendingDevices(); + return S_OK; + } + UINT64 address; + HString name; + ComPtr<IBluetoothClassOfDevice> classOfDevice; + UINT32 classOfDeviceInt; + hr = device->get_BluetoothAddress(&address); + Q_ASSERT_SUCCEEDED(hr); + hr = device->get_Name(name.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + const QString btName = QString::fromWCharArray(WindowsGetStringRawBuffer(name.Get(), nullptr)); + hr = device->get_ClassOfDevice(&classOfDevice); + Q_ASSERT_SUCCEEDED(hr); + hr = classOfDevice->get_RawValue(&classOfDeviceInt); + Q_ASSERT_SUCCEEDED(hr); + IVectorView <Rfcomm::RfcommDeviceService *> *deviceServices; + hr = device->get_RfcommServices(&deviceServices); + Q_ASSERT_SUCCEEDED(hr); + uint serviceCount; + hr = deviceServices->get_Size(&serviceCount); + Q_ASSERT_SUCCEEDED(hr); + QList<QBluetoothUuid> uuids; + for (uint i = 0; i < serviceCount; ++i) { + ComPtr<Rfcomm::IRfcommDeviceService> service; + hr = deviceServices->GetAt(i, &service); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<Rfcomm::IRfcommServiceId> id; + hr = service->get_ServiceId(&id); + Q_ASSERT_SUCCEEDED(hr); + GUID uuid; + hr = id->get_Uuid(&uuid); + Q_ASSERT_SUCCEEDED(hr); + uuids.append(QBluetoothUuid(uuid)); + } + + qCDebug(QT_BT_WINRT) << "Discovered BT device: " << QString::number(address) << btName + << "Num UUIDs" << uuids.count(); + + QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, classOfDeviceInt); + info.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration); + info.setServiceUuids(uuids, QBluetoothDeviceInfo::DataIncomplete); + info.setCached(true); + + QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection, + Q_ARG(QBluetoothDeviceInfo, info)); + decreaseAndCheckPendingDevices(); + return S_OK; +} + +HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status) +{ + if (status != AsyncStatus::Completed) { + decreaseAndCheckPendingDevices(); + return S_OK; + } + + ComPtr<IBluetoothLEDevice> device; + HRESULT hr = op->GetResults(&device); + Q_ASSERT_SUCCEEDED(hr); + + if (!device) { + decreaseAndCheckPendingDevices(); + return S_OK; + } + UINT64 address; + HString name; + hr = device->get_BluetoothAddress(&address); + Q_ASSERT_SUCCEEDED(hr); + hr = device->get_Name(name.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + const QString btName = QString::fromWCharArray(WindowsGetStringRawBuffer(name.Get(), nullptr)); + IVectorView <GenericAttributeProfile::GattDeviceService *> *deviceServices; + hr = device->get_GattServices(&deviceServices); + Q_ASSERT_SUCCEEDED(hr); + uint serviceCount; + hr = deviceServices->get_Size(&serviceCount); + Q_ASSERT_SUCCEEDED(hr); + QList<QBluetoothUuid> uuids; + for (uint i = 0; i < serviceCount; ++i) { + ComPtr<GenericAttributeProfile::IGattDeviceService> service; + hr = deviceServices->GetAt(i, &service); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<Rfcomm::IRfcommServiceId> id; + GUID uuid; + hr = service->get_Uuid(&uuid); + Q_ASSERT_SUCCEEDED(hr); + uuids.append(QBluetoothUuid(uuid)); + } + + qCDebug(QT_BT_WINRT) << "Discovered BTLE device: " << QString::number(address) << btName + << "Num UUIDs" << uuids.count(); + + QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, 0); + info.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration); + info.setServiceUuids(uuids, QBluetoothDeviceInfo::DataIncomplete); + info.setCached(true); + + QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection, + Q_ARG(QBluetoothDeviceInfo, info)); + decreaseAndCheckPendingDevices(); + return S_OK; +} + +void QWinRTBluetoothDeviceDiscoveryWorker::decreaseAndCheckPendingDevices() +{ + --m_pendingDevices; + if (m_pendingDevices == 0) + setupLEDeviceWatcher(); +} + +QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( + const QBluetoothAddress &deviceAdapter, + QBluetoothDeviceDiscoveryAgent *parent) + + : inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry), + lastError(QBluetoothDeviceDiscoveryAgent::NoError), + lowEnergySearchTimeout(25000), + q_ptr(parent), + leScanTimer(0) +{ + Q_UNUSED(deviceAdapter); +} + +QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() +{ + disconnectAndClearWorker(); +} + +bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const +{ + return worker; +} + +QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() +{ + return (ClassicMethod | LowEnergyMethod); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods) +{ + if (worker) + return; + + worker = new QWinRTBluetoothDeviceDiscoveryWorker(methods); + discoveredDevices.clear(); + connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound, + this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice); + connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished, + this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished); + connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanCanceled, + this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled); + worker->start(); + + if (lowEnergySearchTimeout > 0 && methods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) { // otherwise no timeout and stop() required + if (!leScanTimer) { + leScanTimer = new QTimer(this); + leScanTimer->setSingleShot(true); + } + connect(leScanTimer, &QTimer::timeout, + worker, &QWinRTBluetoothDeviceDiscoveryWorker::handleLeTimeout); + leScanTimer->setInterval(lowEnergySearchTimeout); + leScanTimer->start(); + } +} + +void QBluetoothDeviceDiscoveryAgentPrivate::stop() +{ + Q_Q(QBluetoothDeviceDiscoveryAgent); + if (worker) { + disconnectAndClearWorker(); + emit q->canceled(); + } + if (leScanTimer) { + leScanTimer->stop(); + worker->deleteLater(); + } +} + +void QBluetoothDeviceDiscoveryAgentPrivate::registerDevice(const QBluetoothDeviceInfo &info) +{ + Q_Q(QBluetoothDeviceDiscoveryAgent); + + for (QList<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin(); + iter != discoveredDevices.end(); ++iter) { + if (iter->address() == info.address()) { + qCDebug(QT_BT_WINRT) << "Updating device" << iter->name() << iter->address(); + // merge service uuids + QList<QBluetoothUuid> uuids = iter->serviceUuids(); + uuids.append(info.serviceUuids()); + const QSet<QBluetoothUuid> uuidSet = uuids.toSet(); + if (iter->serviceUuids().count() != uuidSet.count()) + iter->setServiceUuids(uuidSet.toList(), QBluetoothDeviceInfo::DataIncomplete); + if (iter->coreConfigurations() != info.coreConfigurations()) + iter->setCoreConfigurations(QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration); + return; + } + } + + discoveredDevices << info; + emit q->deviceDiscovered(info); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished() +{ + Q_Q(QBluetoothDeviceDiscoveryAgent); + disconnectAndClearWorker(); + emit q->finished(); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled() +{ + Q_Q(QBluetoothDeviceDiscoveryAgent); + disconnectAndClearWorker(); + emit q->canceled(); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::disconnectAndClearWorker() +{ + Q_Q(QBluetoothDeviceDiscoveryAgent); + if (!worker) + return; + + disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanCanceled, + this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled); + disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished, + this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished); + disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound, + q, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered); + if (leScanTimer) { + disconnect(leScanTimer, &QTimer::timeout, + worker, &QWinRTBluetoothDeviceDiscoveryWorker::handleLeTimeout); + } + worker.clear(); +} + +QT_END_NAMESPACE + +#include <qbluetoothdevicediscoveryagent_winrt.moc> diff --git a/src/bluetooth/qbluetoothdevicediscoverytimer_osx_p.h b/src/bluetooth/qbluetoothdevicediscoverytimer_osx_p.h deleted file mode 100644 index 88906ffd..00000000 --- a/src/bluetooth/qbluetoothdevicediscoverytimer_osx_p.h +++ /dev/null @@ -1,85 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 QBLUETOOTHDEVICEDISCOVERYTIMER_OSX_P_H -#define QBLUETOOTHDEVICEDISCOVERYTIMER_OSX_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 <QtCore/qglobal.h> -#include <QtCore/qtimer.h> - -QT_BEGIN_NAMESPACE - -class QBluetoothDeviceDiscoveryAgentPrivate; - -namespace OSXBluetooth { - -class DDATimerHandler : public QObject -{ - Q_OBJECT - -public: - DDATimerHandler(QBluetoothDeviceDiscoveryAgentPrivate *d); - - void start(int msec); - void stop(); - -private slots: - void onTimer(); - -private: - QTimer timer; - QBluetoothDeviceDiscoveryAgentPrivate *owner; -}; - -} - -QT_END_NAMESPACE - -#endif // QBLUETOOTHDEVICEDISCOVERYTIMER_OSX_P_H diff --git a/src/bluetooth/qbluetoothlocaldevice_osx.mm b/src/bluetooth/qbluetoothlocaldevice_osx.mm index c005e2ea..45fa310a 100644 --- a/src/bluetooth/qbluetoothlocaldevice_osx.mm +++ b/src/bluetooth/qbluetoothlocaldevice_osx.mm @@ -118,29 +118,25 @@ QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(QBluetoothLocalDevice HostController defaultController([[IOBluetoothHostController defaultController] retain]); if (!defaultController) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to " - "init a host controller object"; + qCCritical(QT_BT_OSX) << "failed to init a host controller object"; return; } if (!address.isNull()) { NSString *const hciAddress = [defaultController addressAsString]; if (!hciAddress) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to " - "obtain an address"; + qCCritical(QT_BT_OSX) << "failed to obtain an address"; return; } BluetoothDeviceAddress iobtAddress = {}; if (IOBluetoothNSStringToDeviceAddress(hciAddress, &iobtAddress) != kIOReturnSuccess) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid " - "local device's address"; + qCCritical(QT_BT_OSX) << "invalid local device's address"; return; } if (address != OSXBluetooth::qt_address(&iobtAddress)) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid " - "local device's address"; + qCCritical(QT_BT_OSX) << "invalid local device's address"; return; } } @@ -183,8 +179,7 @@ void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &addre if ([device isPaired]) { emitPairingFinished(address, pairing, true); } else if ([pos.value() start] != kIOReturnSuccess) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to " - "start a new pairing request"; + qCCritical(QT_BT_OSX) << "failed to start a new pairing request"; emitError(QBluetoothLocalDevice::PairingError, true); } return; @@ -195,8 +190,7 @@ void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &addre // it'll just finish with success (skipping any intermediate steps). PairingRequest newRequest([[ObjCPairingRequest alloc] initWithTarget:address delegate:this], false); if (!newRequest) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to " - "allocate a new pairing request"; + qCCritical(QT_BT_OSX) << "failed to allocate a new pairing request"; emitError(QBluetoothLocalDevice::PairingError, true); return; } @@ -205,8 +199,7 @@ void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &addre const IOReturn result = [newRequest start]; if (result != kIOReturnSuccess) { pairingRequests.erase(pos); - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to " - "start a new pairing request"; + qCCritical(QT_BT_OSX) << "failed to start a new pairing request"; emitError(QBluetoothLocalDevice::PairingError, true); } } diff --git a/src/bluetooth/qbluetoothserver_osx.mm b/src/bluetooth/qbluetoothserver_osx.mm index 549a07cc..8896651d 100644 --- a/src/bluetooth/qbluetoothserver_osx.mm +++ b/src/bluetooth/qbluetoothserver_osx.mm @@ -95,7 +95,7 @@ QBluetoothServerPrivate::QBluetoothServerPrivate(QSInfo::Protocol type, QBluetoo { Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); if (serverType == QSInfo::UnknownProtocol) - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown protocol"; + qCWarning(QT_BT_OSX) << "unknown protocol"; } QBluetoothServerPrivate::~QBluetoothServerPrivate() @@ -116,7 +116,7 @@ bool QBluetoothServerPrivate::startListener(quint16 realPort) Q_ASSERT_X(realPort, Q_FUNC_INFO, "invalid port"); if (serverType == QSInfo::UnknownProtocol) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol"; + qCWarning(QT_BT_OSX) << "invalid protocol"; return false; } @@ -217,7 +217,7 @@ void QBluetoothServerPrivate::registerServer(QBluetoothServerPrivate *server, qu Q_ASSERT_X(!psmIsBusy(port), Q_FUNC_INFO, "port is busy"); busyPSMs()[port] = server; } else { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "can not register a server " + qCWarning(QT_BT_OSX) << "can not register a server " "with unknown protocol type"; } } @@ -234,7 +234,7 @@ QBluetoothServerPrivate *QBluetoothServerPrivate::registeredServer(quint16 port, if (it != busyPSMs().end()) return it.value(); } else { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol"; + qCWarning(QT_BT_OSX) << "invalid protocol"; } return Q_NULLPTR; @@ -251,17 +251,17 @@ void QBluetoothServerPrivate::unregisterServer(QBluetoothServerPrivate *server) if (it != busyChannels().end()) { busyChannels().erase(it); } else { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "server is not registered"; + qCWarning(QT_BT_OSX) << "server is not registered"; } } else if (type == QSInfo::L2capProtocol) { ServerMapIterator it = busyPSMs().find(port); if (it != busyPSMs().end()) { busyPSMs().erase(it); } else { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "server is not registered"; + qCWarning(QT_BT_OSX) << "server is not registered"; } } else { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol"; + qCWarning(QT_BT_OSX) << "invalid protocol"; } } @@ -292,16 +292,15 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) typedef QBluetoothServerPrivate::ObjCListener ObjCListener; if (d_ptr->listener) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "already in listen mode, " - "close server first"; + qCWarning(QT_BT_OSX) << "already in listen mode, close server first"; return false; } const QBluetoothLocalDevice device(address); if (!device.isValid()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "device does not support Bluetooth or " + qCWarning(QT_BT_OSX) << "device does not support Bluetooth or" << address.toString() - << " is not a valid local adapter"; + << "is not a valid local adapter"; d_ptr->lastError = UnknownError; emit error(UnknownError); return false; @@ -309,7 +308,7 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) const QBluetoothLocalDevice::HostMode hostMode = device.hostMode(); if (hostMode == QBluetoothLocalDevice::HostPoweredOff) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "Bluetooth device is powered off"; + qCWarning(QT_BT_OSX) << "Bluetooth device is powered off"; d_ptr->lastError = PoweredOffError; emit error(PoweredOffError); return false; @@ -318,7 +317,7 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) const QSInfo::Protocol type = d_ptr->serverType; if (type == QSInfo::UnknownProtocol) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol"; + qCWarning(QT_BT_OSX) << "invalid protocol"; d_ptr->lastError = UnsupportedProtocolError; emit error(d_ptr->lastError); return false; @@ -332,14 +331,14 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) if (port) { if (type == QSInfo::RfcommProtocol) { if (d_ptr->channelIsBusy(port)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO <<"server port: " - << port << "already registered"; + qCWarning(QT_BT_OSX) << "server port:" << port + << "already registered"; d_ptr->lastError = ServiceAlreadyRegisteredError; } } else { if (d_ptr->psmIsBusy(port)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "server port: " - << port << "already registered"; + qCWarning(QT_BT_OSX) << "server port:" << port + << "already registered"; d_ptr->lastError = ServiceAlreadyRegisteredError; } } @@ -354,7 +353,7 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) } if (!port) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "all ports are busy"; + qCWarning(QT_BT_OSX) << "all ports are busy"; d_ptr->lastError = ServiceAlreadyRegisteredError; emit error(d_ptr->lastError); return false; diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm index 5c4efca2..4a52b379 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm +++ b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm @@ -335,7 +335,7 @@ void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryError(IOBluetoothDevice * { Q_UNUSED(device) - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "inquiry failed with IOKit code: " << int(errorCode); + qCWarning(QT_BT_OSX) << "inquiry failed with IOKit code:" << int(errorCode); discoveredDevices.clear(); // TODO: find a better mapping from IOReturn to QBluetoothServiceDiscoveryAgent::Error. diff --git a/src/bluetooth/qbluetoothserviceinfo_osx.mm b/src/bluetooth/qbluetoothserviceinfo_osx.mm index d6aa7592..a25653ab 100644 --- a/src/bluetooth/qbluetoothserviceinfo_osx.mm +++ b/src/bluetooth/qbluetoothserviceinfo_osx.mm @@ -46,7 +46,6 @@ #include <QtCore/qloggingcategory.h> #include <QtCore/qvariant.h> -#include <QtCore/qsysinfo.h> #include <QtCore/qglobal.h> #include <QtCore/qmutex.h> #include <QtCore/qmap.h> @@ -56,44 +55,6 @@ QT_BEGIN_NAMESPACE -namespace { - -// This is not in osxbtutility_p, since it's not required -// in general and just fixes the problem with SDK < 10.9, -// where we have to care about about IOBluetoothSDPServiceRecordRef. -class ServiceRecordRefGuard -{ -public: - ServiceRecordRefGuard() - : recordRef(Q_NULLPTR) - { - } - explicit ServiceRecordRefGuard(IOBluetoothSDPServiceRecordRef r) - : recordRef(r) - { - } - ~ServiceRecordRefGuard() - { - if (recordRef) // Requires non-NULL pointers. - CFRelease(recordRef); - } - - void reset(IOBluetoothSDPServiceRecordRef r) - { - if (recordRef) - CFRelease(recordRef); - // Take the ownership: - recordRef = r; - } - -private: - IOBluetoothSDPServiceRecordRef recordRef; - - Q_DISABLE_COPY(ServiceRecordRefGuard) -}; - -} - class QBluetoothServiceInfoPrivate { public: @@ -144,48 +105,23 @@ bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &loca serviceDict(iobluetooth_service_dictionary(*q_ptr)); if (!serviceDict) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a service dictionary"; + qCWarning(QT_BT_OSX) << "failed to create a service dictionary"; return false; } - ServiceRecordRefGuard refGuard; SDPRecord newRecord; -#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) { - newRecord.reset([[IOBluetoothSDPServiceRecord - publishedServiceRecordWithDictionary:serviceDict] retain]); - } else { -#else - { -#endif - IOBluetoothSDPServiceRecordRef recordRef = Q_NULLPTR; - // With ARC this will require a different cast? - const IOReturn status = IOBluetoothAddServiceDict((CFDictionaryRef)serviceDict.data(), &recordRef); - if (status != kIOReturnSuccess) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record"; - return false; - } - - refGuard.reset(recordRef); - newRecord.reset([[IOBluetoothSDPServiceRecord withSDPServiceRecordRef:recordRef] retain]); - // It's weird, but ... it's not possible to release a record ref yet. - } + newRecord.reset([[IOBluetoothSDPServiceRecord + publishedServiceRecordWithDictionary:serviceDict] retain]); if (!newRecord) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record"; - // In case of SDK < 10.9 it's not possible to remove a service record ... - // no way to obtain record handle yet. + qCWarning(QT_BT_OSX) << "failed to register a service record"; return false; } BluetoothSDPServiceRecordHandle newRecordHandle = 0; if ([newRecord getServiceRecordHandle:&newRecordHandle] != kIOReturnSuccess) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record"; -#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) - [newRecord removeServiceRecord]; -#endif - // With SDK < 10.9 there is no way to unregister at this point ... + qCWarning(QT_BT_OSX) << "failed to register a service record"; + [newRecord removeServiceRecord]; return false; } @@ -211,17 +147,8 @@ bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &loca } if (!configured) { -#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) { - [newRecord removeServiceRecord]; - } else { -#else - {// Just to balance braces ... -#endif - IOBluetoothRemoveServiceWithRecordHandle(newRecordHandle); - } - - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record"; + [newRecord removeServiceRecord]; + qCWarning(QT_BT_OSX) << "failed to register a service record"; return false; } @@ -247,16 +174,7 @@ bool QBluetoothServiceInfoPrivate::unregisterService() Q_ASSERT_X(serviceRecord, Q_FUNC_INFO, "service registered, but serviceRecord is nil"); -#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) { - [serviceRecord removeServiceRecord]; - } else { -#else - { -#endif - // Assert on newRecordHandle? Is 0 a valid/invalid handle? - IOBluetoothRemoveServiceWithRecordHandle(serviceRecordHandle); - } + [serviceRecord removeServiceRecord]; serviceRecord.reset(nil); diff --git a/src/bluetooth/qbluetoothsocket_osx.mm b/src/bluetooth/qbluetoothsocket_osx.mm index 69663e58..75712868 100644 --- a/src/bluetooth/qbluetoothsocket_osx.mm +++ b/src/bluetooth/qbluetoothsocket_osx.mm @@ -452,7 +452,7 @@ void QBluetoothSocket::connectToService(const QBluetoothServiceInfo &service, Op } if (state() != UnconnectedState && state() != ServiceLookupState) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called on a busy socket"; + qCWarning(QT_BT_OSX) << "called on a busy socket"; d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS); setSocketError(OperationError); return; @@ -468,8 +468,8 @@ void QBluetoothSocket::connectToService(const QBluetoothServiceInfo &service, Op } else { // Try service discovery. if (service.serviceUuid().isNull()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "No port, " - "no PSM, and no UUID provided, unable to connect"; + qCWarning(QT_BT_OSX) << "No port, no PSM, and no " + "UUID provided, unable to connect"; return; } @@ -489,7 +489,7 @@ void QBluetoothSocket::connectToService(const QBluetoothAddress &address, const } if (state() != QBluetoothSocket::UnconnectedState) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called on a busy socket"; + qCWarning(QT_BT_OSX) << "called on a busy socket"; d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS); setSocketError(QBluetoothSocket::OperationError); return; @@ -513,7 +513,7 @@ void QBluetoothSocket::connectToService(const QBluetoothAddress &address, quint1 } if (state() != QBluetoothSocket::UnconnectedState) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called on a busy socket"; + qCWarning(QT_BT_OSX) << "called on a busy socket"; d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS); setSocketError(OperationError); return; @@ -554,7 +554,7 @@ void QBluetoothSocket::setSocketState(QBluetoothSocket::SocketState state) // We can register for L2CAP/RFCOMM open notifications, // that's different from 'listen' and is implemented // in QBluetoothServer. - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "listening sockets are not supported"; + qCWarning(QT_BT_OSX) << "listening sockets are not supported"; } } diff --git a/src/bluetooth/qbluetoothtransferreply_osx.mm b/src/bluetooth/qbluetoothtransferreply_osx.mm index 99c6cab1..02133860 100644 --- a/src/bluetooth/qbluetoothtransferreply_osx.mm +++ b/src/bluetooth/qbluetoothtransferreply_osx.mm @@ -177,7 +177,7 @@ void QBluetoothTransferReplyOSXPrivate::sendConnect(const QBluetoothAddress &dev errorString.clear(); if (device.isNull() || !channelID) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid device address or port"; + qCWarning(QT_BT_OSX) << "invalid device address or port"; setReplyError(QBluetoothTransferReply::HostNotFoundError, QCoreApplication::translate(TRANSFER_REPLY, TR_INVAL_TARGET)); return; @@ -186,7 +186,7 @@ void QBluetoothTransferReplyOSXPrivate::sendConnect(const QBluetoothAddress &dev OBEXSession newSession([[ObjCOBEXSession alloc] initWithDelegate:this remoteDevice:device channelID:channelID]); if (!newSession) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate OSXBTOBEXSession object"; + qCWarning(QT_BT_OSX) << "failed to allocate OSXBTOBEXSession object"; setReplyError(QBluetoothTransferReply::UnknownError, QCoreApplication::translate(TRANSFER_REPLY, TR_SESSION_NO_START)); @@ -201,7 +201,7 @@ void QBluetoothTransferReplyOSXPrivate::sendConnect(const QBluetoothAddress &dev if ([session isConnected]) sendPut();// Connected, send a PUT request. } else { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "OBEXConnect failed"; + qCWarning(QT_BT_OSX) << "OBEXConnect failed"; if (error == QBluetoothTransferReply::NoError) { // The error is not set yet. @@ -355,7 +355,7 @@ QBluetoothTransferReplyOSX::QBluetoothTransferReplyOSX(QIODevice *input, if (input) { QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection); } else { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid input stream (null)"; + qCWarning(QT_BT_OSX) << "invalid input stream (null)"; osx_d_ptr->requestComplete = true; osx_d_ptr->errorString = QCoreApplication::translate(TRANSFER_REPLY, TR_INVALID_DEVICE); osx_d_ptr->error = FileNotFoundError; @@ -416,7 +416,7 @@ bool QBluetoothTransferReplyOSX::start() if (!osx_d_ptr->isActive()) { // Step 0: find a channelID. if (request().address().isNull()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid device address"; + qCWarning(QT_BT_OSX) << "invalid device address"; osx_d_ptr->setReplyError(HostNotFoundError, QCoreApplication::translate(TRANSFER_REPLY, TR_INVAL_TARGET)); return false; @@ -455,7 +455,8 @@ void QBluetoothTransferReplyOSX::serviceDiscoveryFinished() void QBluetoothTransferReplyOSX::serviceDiscoveryError(QBluetoothServiceDiscoveryAgent::Error errorCode) { - Q_ASSERT_X(osx_d_ptr->agent.data(), Q_FUNC_INFO, "invalid service discovery agent (null)"); + Q_ASSERT_X(osx_d_ptr->agent.data(), Q_FUNC_INFO, + "invalid service discovery agent (null)"); if (errorCode == QBluetoothServiceDiscoveryAgent::PoweredOffError) { // There's nothing else we can do. diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp index c152ded2..2b28a873 100644 --- a/src/bluetooth/qlowenergycontroller.cpp +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -198,13 +198,17 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT) This signal is emitted when the controller successfully connects to the remote Low Energy device (if the controller is in the \l CentralRole) or if a remote Low Energy device connected to the controller (if the controller is in the \l PeripheralRole). + On iOS and OS X this signal is not reliable if the controller is in the \l PeripheralRole + - the controller only guesses that some central connected to our peripheral as + soon as this central tries to write/read a characteristic/descriptor. */ /*! \fn void QLowEnergyController::disconnected() This signal is emitted when the controller disconnects from the remote - Low Energy device or vice versa. + Low Energy device or vice versa. On iOS and OS X this signal is unreliable + if the controller is in the \l PeripheralRole. */ /*! @@ -308,6 +312,9 @@ void QLowEnergyControllerPrivate::setError( bool QLowEnergyControllerPrivate::isValidLocalAdapter() { +#ifdef QT_WINRT_BLUETOOTH + return true; +#endif if (localAdapter.isNull()) return false; @@ -687,6 +694,11 @@ void QLowEnergyController::connectToDevice() { Q_D(QLowEnergyController); + if (role() != CentralRole) { + qCWarning(QT_BT) << "Connection can only be established while in central role"; + return; + } + if (!d->isValidLocalAdapter()) { d->setError(QLowEnergyController::InvalidBluetoothAdapterError); return; diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm index 47f65965..6e85e630 100644 --- a/src/bluetooth/qlowenergycontroller_osx.mm +++ b/src/bluetooth/qlowenergycontroller_osx.mm @@ -38,11 +38,14 @@ ** ****************************************************************************/ +#include "osx/osxbtnotifier_p.h" #include "osx/osxbtutility_p.h" #include "osx/uistrings_p.h" + #include "qlowenergyserviceprivate_p.h" #include "qlowenergycontroller_osx_p.h" +#include "qlowenergyservicedata.h" #include "qbluetoothlocaldevice.h" #include "qbluetoothdeviceinfo.h" #include "qlowenergycontroller.h" @@ -51,7 +54,6 @@ #include <QtCore/qloggingcategory.h> #include <QtCore/qsharedpointer.h> #include <QtCore/qbytearray.h> -#include <QtCore/qsysinfo.h> #include <QtCore/qglobal.h> #include <QtCore/qstring.h> #include <QtCore/qlist.h> @@ -84,8 +86,7 @@ ServicePrivate qt_createLEService(QLowEnergyControllerPrivateOSX *controller, CB CBUUID *const cbUuid = cbService.UUID; if (!cbUuid) { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "invalid service, " - "UUID is nil"; + qCDebug(QT_BT_OSX) << "invalid service, UUID is nil"; return ServicePrivate(); } @@ -102,18 +103,12 @@ ServicePrivate qt_createLEService(QLowEnergyControllerPrivateOSX *controller, CB // TODO: isPrimary is ... always 'NO' - to be investigated. /* - #if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0) - using OSXBluetooth::qt_OS_limit; - if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_6_0)) { - if (!cbService.isPrimary) { - // Our guess included/not was probably wrong. - newService->type &= ~QLowEnergyService::PrimaryService; - newService->type |= QLowEnergyService::IncludedService; - } + if (!cbService.isPrimary) { + // Our guess included/not was probably wrong. + newService->type &= ~QLowEnergyService::PrimaryService; + newService->type |= QLowEnergyService::IncludedService; } - #endif */ - // No such property before 10_9/6_0. return newService; } @@ -136,36 +131,8 @@ UUIDList qt_servicesUuids(NSArray *services) } -QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController *q) - : q_ptr(q), - lastError(QLowEnergyController::NoError), - controllerState(QLowEnergyController::UnconnectedState), - addressType(QLowEnergyController::PublicAddress) -{ - registerQLowEnergyControllerMetaType(); - - // This is the "wrong" constructor - no valid device UUID to connect later. - Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)"); - - using OSXBluetooth::LECentralNotifier; - - // We still create a manager, to simplify error handling later. - QScopedPointer<LECentralNotifier> notifier(new LECentralNotifier); - centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()]); - if (!centralManager) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO - << "failed to initialize central manager"; - return; - } else if (!connectSlots(notifier.data())) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO - << "failed to connect to notifier's signals"; - } - - // Ownership was taken by central manager. - notifier.take(); -} - -QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController *q, +QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController::Role r, + QLowEnergyController *q, const QBluetoothDeviceInfo &deviceInfo) : q_ptr(q), deviceUuid(deviceInfo.deviceUuid()), @@ -178,44 +145,67 @@ QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyControl Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)"); - using OSXBluetooth::LECentralNotifier; + using OSXBluetooth::LECBManagerNotifier; + + role = r; - QScopedPointer<LECentralNotifier> notifier(new LECentralNotifier); - centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()]); - if (!centralManager) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO - << "failed to initialize central manager"; + QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier); + if (role == QLowEnergyController::PeripheralRole) { +#ifndef Q_OS_TVOS + peripheralManager.reset([[ObjCPeripheralManager alloc] initWith:notifier.data()]); + if (!peripheralManager) { + qCWarning(QT_BT_OSX) << "failed to initialize peripheral manager"; + return; + } +#else + qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform"; return; - } else if (!connectSlots(notifier.data())) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO - << "failed to connect to notifier's signals"; +#endif + } else { + centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()]); + if (!centralManager) { + qCWarning(QT_BT_OSX) << "failed to initialize central manager"; + return; + } } + if (!connectSlots(notifier.data())) { + qCWarning(QT_BT_OSX) << "failed to connect to notifier's signal(s)"; + } // Ownership was taken by central manager. notifier.take(); } QLowEnergyControllerPrivateOSX::~QLowEnergyControllerPrivateOSX() { - // TODO: dispatch_sync 'setDelegate:Q_NULLPRT' to our CBCentralManager's delegate. - if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) { - ObjCCentralManager *manager = centralManager.data(); - dispatch_sync(leQueue, ^{ - [manager detach]; - }); + if (const auto leQueue = OSXBluetooth::qt_LE_queue()) { + if (role == QLowEnergyController::CentralRole) { + const auto manager = centralManager.data(); + dispatch_sync(leQueue, ^{ + [manager detach]; + }); + } else { +#ifndef Q_OS_TVOS + const auto manager = peripheralManager.data(); + dispatch_sync(leQueue, ^{ + [manager detach]; + }); +#endif + } } } bool QLowEnergyControllerPrivateOSX::isValid() const { +#ifdef Q_OS_TVOS return centralManager; +#else + return centralManager || peripheralManager; +#endif } void QLowEnergyControllerPrivateOSX::_q_connected() { - Q_ASSERT_X(controllerState == QLowEnergyController::ConnectingState, - Q_FUNC_INFO, "invalid state"); - controllerState = QLowEnergyController::ConnectedState; emit q_ptr->stateChanged(QLowEnergyController::ConnectedState); @@ -226,10 +216,11 @@ void QLowEnergyControllerPrivateOSX::_q_disconnected() { controllerState = QLowEnergyController::UnconnectedState; - invalidateServices(); + if (role == QLowEnergyController::CentralRole) + invalidateServices(); + emit q_ptr->stateChanged(QLowEnergyController::UnconnectedState); emit q_ptr->disconnected(); - } void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished() @@ -258,7 +249,7 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished() continue; if (discoveredServices.contains(newService->uuid)) { // It's a bit stupid we first created it ... - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "discovered service with a duplicated UUID " + qCDebug(QT_BT_OSX) << "discovered service with a duplicated UUID" << newService->uuid; continue; } @@ -310,7 +301,7 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished() toVisitNext.resetWithoutRetain([[NSMutableArray alloc] init]); } } else { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no services found"; + qCDebug(QT_BT_OSX) << "no services found"; } for (ServiceMap::const_iterator it = discoveredServices.constBegin(); it != discoveredServices.constEnd(); ++it) { @@ -332,7 +323,7 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDetailsDiscoveryFinished(QSharedP Q_ASSERT(service); if (!discoveredServices.contains(service->uuid)) { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "unknown service uuid: " + qCDebug(QT_BT_OSX) << "unknown service uuid:" << service->uuid; return; } @@ -357,7 +348,7 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicRead(QLowEnergyHandle char QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle)); if (!characteristic.isValid()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown characteristic"; + qCWarning(QT_BT_OSX) << "unknown characteristic"; return; } @@ -374,14 +365,14 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicWritten(QLowEnergyHandle c ServicePrivate service(serviceForHandle(charHandle)); if (service.isNull()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "can not find service for characteristic handle " + qCWarning(QT_BT_OSX) << "can not find service for characteristic handle" << charHandle; return; } QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle)); if (!characteristic.isValid()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown characteristic"; + qCWarning(QT_BT_OSX) << "unknown characteristic"; return; } @@ -410,7 +401,7 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicUpdated(QLowEnergyHandle c QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle)); if (!characteristic.isValid()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown characteristic"; + qCWarning(QT_BT_OSX) << "unknown characteristic"; return; } @@ -427,7 +418,7 @@ void QLowEnergyControllerPrivateOSX::_q_descriptorRead(QLowEnergyHandle dHandle, const QLowEnergyDescriptor qtDescriptor(descriptorForHandle(dHandle)); if (!qtDescriptor.isValid()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown descriptor " << dHandle; + qCWarning(QT_BT_OSX) << "unknown descriptor" << dHandle; return; } @@ -443,7 +434,7 @@ void QLowEnergyControllerPrivateOSX::_q_descriptorWritten(QLowEnergyHandle dHand const QLowEnergyDescriptor qtDescriptor(descriptorForHandle(dHandle)); if (!qtDescriptor.isValid()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown descriptor " << dHandle; + qCWarning(QT_BT_OSX) << "unknown descriptor" << dHandle; return; } @@ -461,7 +452,7 @@ void QLowEnergyControllerPrivateOSX::_q_LEnotSupported() // be supported. } -void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(QLowEnergyController::Error errorCode) +void QLowEnergyControllerPrivateOSX::_q_CBManagerError(QLowEnergyController::Error errorCode) { // Errors reported during connect and general errors. @@ -478,8 +469,8 @@ void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(QLowEnergyControll // a service/characteristic - related error. } -void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(const QBluetoothUuid &serviceUuid, - QLowEnergyController::Error errorCode) +void QLowEnergyControllerPrivateOSX::_q_CBManagerError(const QBluetoothUuid &serviceUuid, + QLowEnergyController::Error errorCode) { // Errors reported while discovering service details etc. Q_UNUSED(errorCode) // TODO: setError? @@ -489,16 +480,16 @@ void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(const QBluetoothUu ServicePrivate qtService(discoveredServices.value(serviceUuid)); qtService->setState(QLowEnergyService::InvalidService); } else { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "error reported for unknown service " + qCDebug(QT_BT_OSX) << "error reported for unknown service" << serviceUuid; } } -void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(const QBluetoothUuid &serviceUuid, - QLowEnergyService::ServiceError errorCode) +void QLowEnergyControllerPrivateOSX::_q_CBManagerError(const QBluetoothUuid &serviceUuid, + QLowEnergyService::ServiceError errorCode) { if (!discoveredServices.contains(serviceUuid)) { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "unknown service uuid: " + qCDebug(QT_BT_OSX) << "unknown service uuid:" << serviceUuid; return; } @@ -514,10 +505,12 @@ void QLowEnergyControllerPrivateOSX::connectToDevice() Q_FUNC_INFO, "invalid state"); Q_ASSERT_X(!deviceUuid.isNull(), Q_FUNC_INFO, "invalid private controller (no device uuid)"); + Q_ASSERT_X(role != QLowEnergyController::PeripheralRole, + Q_FUNC_INFO, "invalid role (peripheral)"); dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); if (!leQueue) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found"; + qCWarning(QT_BT_OSX) << "no LE queue found"; setErrorDescription(QLowEnergyController::UnknownError); return; } @@ -537,10 +530,12 @@ void QLowEnergyControllerPrivateOSX::discoverServices() Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller"); Q_ASSERT_X(controllerState != QLowEnergyController::UnconnectedState, Q_FUNC_INFO, "not connected to peripheral"); + Q_ASSERT_X(role != QLowEnergyController::PeripheralRole, + Q_FUNC_INFO, "invalid role (peripheral)"); dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); if (!leQueue) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found"; + qCWarning(QT_BT_OSX) << "no LE queue found"; setErrorDescription(QLowEnergyController::UnknownError); return; } @@ -559,20 +554,21 @@ void QLowEnergyControllerPrivateOSX::discoverServiceDetails(const QBluetoothUuid Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller"); if (controllerState != QLowEnergyController::DiscoveredState) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO - << "can not discover service details in the current state, " - << "QLowEnergyController::DiscoveredState is expected"; + // This will also exclude peripheral role, since controller + // can never be in discovered state ... + qCWarning(QT_BT_OSX) << "can not discover service details in the current state, " + "QLowEnergyController::DiscoveredState is expected"; return; } if (!discoveredServices.contains(serviceUuid)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown service: " << serviceUuid; + qCWarning(QT_BT_OSX) << "unknown service: " << serviceUuid; return; } dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); if (!leQueue) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found"; + qCWarning(QT_BT_OSX) << "no LE queue found"; return; } @@ -593,32 +589,37 @@ void QLowEnergyControllerPrivateOSX::setNotifyValue(QSharedPointer<QLowEnergySer Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)"); Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller"); + if (role == QLowEnergyController::PeripheralRole) { + qCWarning(QT_BT_OSX) << "invalid role (peripheral)"; + service->setError(QLowEnergyService::DescriptorWriteError); + return; + } + if (newValue.size() > 2) { // Qt's API requires an error on such write. // With Core Bluetooth we do not write any descriptor, // but instead call a special method. So it's better to // intercept wrong data size here: - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "client characteristic configuration descriptor " + qCWarning(QT_BT_OSX) << "client characteristic configuration descriptor" "is 2 bytes, but value size is: " << newValue.size(); service->setError(QLowEnergyService::DescriptorWriteError); return; } if (!discoveredServices.contains(service->uuid)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid: " - << service->uuid << " found"; + qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << "found"; return; } if (!service->characteristicList.contains(charHandle)) { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle: " - << charHandle << " found"; + qCDebug(QT_BT_OSX) << "no characteristic with handle:" + << charHandle << "found"; return; } dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); if (!leQueue) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found"; + qCWarning(QT_BT_OSX) << "no LE queue found"; return; } ObjCCentralManager *manager = centralManager.data(); @@ -637,21 +638,26 @@ void QLowEnergyControllerPrivateOSX::readCharacteristic(QSharedPointer<QLowEnerg Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)"); Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller"); + if (role == QLowEnergyController::PeripheralRole) { + qCWarning(QT_BT_OSX) << "invalid role (peripheral)"; + return; + } + if (!discoveredServices.contains(service->uuid)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid:" + qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << "found"; return; } if (!service->characteristicList.contains(charHandle)) { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle:" + qCDebug(QT_BT_OSX) << "no characteristic with handle:" << charHandle << "found"; return; } dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); if (!leQueue) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found"; + qCWarning(QT_BT_OSX) << "no LE queue found"; return; } // Attention! We have to copy UUID. @@ -669,36 +675,47 @@ void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEner Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)"); Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller"); - // We can work only with services, found on a given peripheral - // (== created by the given LE controller), - // otherwise we can not write anything at all. + // We can work only with services found on a given peripheral + // (== created by the given LE controller). + if (!discoveredServices.contains(service->uuid)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid: " + qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << " found"; return; } if (!service->characteristicList.contains(charHandle)) { - qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle: " + qCDebug(QT_BT_OSX) << "no characteristic with handle:" << charHandle << " found"; return; } dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); if (!leQueue) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found"; + qCWarning(QT_BT_OSX) << "no LE queue found"; return; } - // Attention! Copy objects! - const QBluetoothUuid serviceUuid(service->uuid); + // Attention! We have to copy objects! const QByteArray newValueCopy(newValue); - ObjCCentralManager *const manager = centralManager.data(); - dispatch_async(leQueue, ^{ - [manager write:newValueCopy - charHandle:charHandle + if (role == QLowEnergyController::CentralRole) { + const QBluetoothUuid serviceUuid(service->uuid); + const auto manager = centralManager.data(); + dispatch_async(leQueue, ^{ + [manager write:newValueCopy + charHandle:charHandle onService:serviceUuid withResponse:mode == QLowEnergyService::WriteWithResponse]; - }); + }); + } else { +#ifndef Q_OS_TVOS + const auto manager = peripheralManager.data(); + dispatch_async(leQueue, ^{ + [manager write:newValueCopy charHandle:charHandle]; + }); +#else + qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform"; +#endif + } } quint16 QLowEnergyControllerPrivateOSX::updateValueOfCharacteristic(QLowEnergyHandle charHandle, @@ -728,15 +745,20 @@ void QLowEnergyControllerPrivateOSX::readDescriptor(QSharedPointer<QLowEnergySer Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)"); Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller"); + if (role == QLowEnergyController::PeripheralRole) { + qCWarning(QT_BT_OSX) << "invalid role (peripheral)"; + return; + } + if (!discoveredServices.contains(service->uuid)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid:" + qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << "found"; return; } dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); if (!leQueue) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found"; + qCWarning(QT_BT_OSX) << "no LE queue found"; return; } // Attention! Copy objects! @@ -755,18 +777,23 @@ void QLowEnergyControllerPrivateOSX::writeDescriptor(QSharedPointer<QLowEnergySe Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)"); Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller"); + if (role == QLowEnergyController::PeripheralRole) { + qCWarning(QT_BT_OSX) << "invalid role (peripheral)"; + return; + } + // We can work only with services found on a given peripheral // (== created by the given LE controller), // otherwise we can not write anything at all. if (!discoveredServices.contains(service->uuid)) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid: " + qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << " found"; return; } dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); if (!leQueue) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found"; + qCWarning(QT_BT_OSX) << "no LE queue found"; return; } // Attention! Copy objects! @@ -868,17 +895,23 @@ void QLowEnergyControllerPrivateOSX::setErrorDescription(QLowEnergyController::E errorString.clear(); break; case QLowEnergyController::UnknownRemoteDeviceError: - errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_RDEV_NO_FOUND); + errorString = QLowEnergyController::tr("Remote device cannot be found"); break; case QLowEnergyController::InvalidBluetoothAdapterError: - errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_NO_LOCAL_DEV); + errorString = QLowEnergyController::tr("Cannot find local adapter"); break; case QLowEnergyController::NetworkError: - errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_IO_ERROR); + errorString = QLowEnergyController::tr("Error occurred during connection I/O"); + break; + case QLowEnergyController::ConnectionError: + errorString = QLowEnergyController::tr("Error occurred trying to connect to remote device."); + break; + case QLowEnergyController::AdvertisingError: + errorString = QLowEnergyController::tr("Error occurred trying to start advertising"); break; case QLowEnergyController::UnknownError: default: - errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_UNKNOWN_ERROR); + errorString = QLowEnergyController::tr("Unknown Error"); break; } } @@ -893,38 +926,38 @@ void QLowEnergyControllerPrivateOSX::invalidateServices() discoveredServices.clear(); } -bool QLowEnergyControllerPrivateOSX::connectSlots(OSXBluetooth::LECentralNotifier *notifier) +bool QLowEnergyControllerPrivateOSX::connectSlots(OSXBluetooth::LECBManagerNotifier *notifier) { - using OSXBluetooth::LECentralNotifier; + using OSXBluetooth::LECBManagerNotifier; Q_ASSERT_X(notifier, Q_FUNC_INFO, "invalid notifier object (null)"); - bool ok = connect(notifier, &LECentralNotifier::connected, + bool ok = connect(notifier, &LECBManagerNotifier::connected, this, &QLowEnergyControllerPrivateOSX::_q_connected); - ok = ok && connect(notifier, &LECentralNotifier::disconnected, + ok = ok && connect(notifier, &LECBManagerNotifier::disconnected, this, &QLowEnergyControllerPrivateOSX::_q_disconnected); - ok = ok && connect(notifier, &LECentralNotifier::serviceDiscoveryFinished, + ok = ok && connect(notifier, &LECBManagerNotifier::serviceDiscoveryFinished, this, &QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished); - ok = ok && connect(notifier, &LECentralNotifier::serviceDetailsDiscoveryFinished, + ok = ok && connect(notifier, &LECBManagerNotifier::serviceDetailsDiscoveryFinished, this, &QLowEnergyControllerPrivateOSX::_q_serviceDetailsDiscoveryFinished); - ok = ok && connect(notifier, &LECentralNotifier::characteristicRead, + ok = ok && connect(notifier, &LECBManagerNotifier::characteristicRead, this, &QLowEnergyControllerPrivateOSX::_q_characteristicRead); - ok = ok && connect(notifier, &LECentralNotifier::characteristicWritten, + ok = ok && connect(notifier, &LECBManagerNotifier::characteristicWritten, this, &QLowEnergyControllerPrivateOSX::_q_characteristicWritten); - ok = ok && connect(notifier, &LECentralNotifier::characteristicUpdated, + ok = ok && connect(notifier, &LECBManagerNotifier::characteristicUpdated, this, &QLowEnergyControllerPrivateOSX::_q_characteristicUpdated); - ok = ok && connect(notifier, &LECentralNotifier::descriptorRead, + ok = ok && connect(notifier, &LECBManagerNotifier::descriptorRead, this, &QLowEnergyControllerPrivateOSX::_q_descriptorRead); - ok = ok && connect(notifier, &LECentralNotifier::descriptorWritten, + ok = ok && connect(notifier, &LECBManagerNotifier::descriptorWritten, this, &QLowEnergyControllerPrivateOSX::_q_descriptorWritten); - ok = ok && connect(notifier, &LECentralNotifier::LEnotSupported, + ok = ok && connect(notifier, &LECBManagerNotifier::LEnotSupported, this, &QLowEnergyControllerPrivateOSX::_q_LEnotSupported); - ok = ok && connect(notifier, SIGNAL(CBCentralManagerError(QLowEnergyController::Error)), - this, SLOT(_q_CBCentralManagerError(QLowEnergyController::Error))); - ok = ok && connect(notifier, SIGNAL(CBCentralManagerError(const QBluetoothUuid &, QLowEnergyController::Error)), - this, SLOT(_q_CBCentralManagerError(const QBluetoothUuid &, QLowEnergyController::Error))); - ok = ok && connect(notifier, SIGNAL(CBCentralManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)), - this, SLOT(_q_CBCentralManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError))); + ok = ok && connect(notifier, SIGNAL(CBManagerError(QLowEnergyController::Error)), + this, SLOT(_q_CBManagerError(QLowEnergyController::Error))); + ok = ok && connect(notifier, SIGNAL(CBManagerError(const QBluetoothUuid &, QLowEnergyController::Error)), + this, SLOT(_q_CBManagerError(const QBluetoothUuid &, QLowEnergyController::Error))); + ok = ok && connect(notifier, SIGNAL(CBManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)), + this, SLOT(_q_CBManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError))); if (!ok) notifier->disconnect(); @@ -935,26 +968,24 @@ bool QLowEnergyControllerPrivateOSX::connectSlots(OSXBluetooth::LECentralNotifie QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddress, QObject *parent) : QObject(parent), - d_ptr(new QLowEnergyControllerPrivateOSX(this)) + d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this)) { OSX_D_PTR; - osx_d_ptr->role = CentralRole; osx_d_ptr->remoteAddress = remoteAddress; osx_d_ptr->localAddress = QBluetoothLocalDevice().address(); - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "construction with remote address " + qCWarning(QT_BT_OSX) << "construction with remote address " "is not supported!"; } QLowEnergyController::QLowEnergyController(const QBluetoothDeviceInfo &remoteDevice, QObject *parent) : QObject(parent), - d_ptr(new QLowEnergyControllerPrivateOSX(this, remoteDevice)) + d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this, remoteDevice)) { OSX_D_PTR; - osx_d_ptr->role = CentralRole; osx_d_ptr->localAddress = QBluetoothLocalDevice().address(); // That's the only "real" ctor - with Core Bluetooth we need a _valid_ deviceUuid // from 'remoteDevice'. @@ -964,24 +995,23 @@ QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddres const QBluetoothAddress &localAddress, QObject *parent) : QObject(parent), - d_ptr(new QLowEnergyControllerPrivateOSX(this)) + d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this)) { OSX_D_PTR; - osx_d_ptr->role = CentralRole; osx_d_ptr->remoteAddress = remoteAddress; osx_d_ptr->localAddress = localAddress; - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "construction with remote/local " + qCWarning(QT_BT_OSX) << "construction with remote/local " "addresses is not supported!"; } QLowEnergyController::QLowEnergyController(QObject *parent) - : QObject(parent), d_ptr(new QLowEnergyControllerPrivateOSX(this)) + : QObject(parent), + d_ptr(new QLowEnergyControllerPrivateOSX(PeripheralRole, this)) { OSX_D_PTR; - osx_d_ptr->role = PeripheralRole; osx_d_ptr->localAddress = QBluetoothLocalDevice().address(); } @@ -1059,11 +1089,16 @@ void QLowEnergyController::connectToDevice() // A memory allocation problem. if (!osx_d_ptr->isValid()) - return osx_d_ptr->_q_CBCentralManagerError(UnknownError); + return osx_d_ptr->_q_CBManagerError(UnknownError); + + if (role() == PeripheralRole) { + qCWarning(QT_BT_OSX) << "can not connect in peripheral role"; + return osx_d_ptr->_q_CBManagerError(ConnectionError); + } // No QBluetoothDeviceInfo provided during construction. if (osx_d_ptr->deviceUuid.isNull()) - return osx_d_ptr->_q_CBCentralManagerError(UnknownRemoteDeviceError); + return osx_d_ptr->_q_CBManagerError(UnknownRemoteDeviceError); if (osx_d_ptr->controllerState != UnconnectedState) return; @@ -1078,6 +1113,11 @@ void QLowEnergyController::disconnectFromDevice() OSX_D_PTR; + if (role() != CentralRole) { + qCWarning(QT_BT_OSX) << "can not disconnect while in central role"; + return osx_d_ptr->_q_CBManagerError(ConnectionError); + } + if (osx_d_ptr->isValid()) { const ControllerState oldState = osx_d_ptr->controllerState; @@ -1101,14 +1141,19 @@ void QLowEnergyController::disconnectFromDevice() emit stateChanged(UnconnectedState); } } else { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "qt LE queue is nil," - << "can not dispatch 'disconnect'"; + qCCritical(QT_BT_OSX) << "qt LE queue is nil, " + "can not dispatch 'disconnect'"; } } } void QLowEnergyController::discoverServices() { + if (role() == PeripheralRole) { + qCWarning(QT_BT_OSX) << "invalid role (peripheral)"; + return; + } + if (state() != ConnectedState) return; @@ -1159,30 +1204,126 @@ void QLowEnergyController::startAdvertising(const QLowEnergyAdvertisingParameter const QLowEnergyAdvertisingData &advertisingData, const QLowEnergyAdvertisingData &scanResponseData) { - Q_UNUSED(params); - Q_UNUSED(advertisingData); - Q_UNUSED(scanResponseData); - qCWarning(QT_BT_OSX) << "LE advertising not implemented for OS X"; +#ifdef Q_OS_TVOS + Q_UNUSED(params) + Q_UNUSED(advertisingData) + Q_UNUSED(scanResponseData) + qCWarning(QT_BT_OSX) << "advertising is not supported on your platform"; +#else + OSX_D_PTR; + + if (!osx_d_ptr->isValid()) + return osx_d_ptr->_q_CBManagerError(UnknownError); + + if (role() != PeripheralRole) { + qCWarning(QT_BT_OSX) << "invalid role"; + return; + } + + if (state() != UnconnectedState) { + qCWarning(QT_BT_OSX) << "invalid state" << state(); + return; + } + + auto leQueue(OSXBluetooth::qt_LE_queue()); + if (!leQueue) { + qCWarning(QT_BT_OSX) << "no LE queue found"; + osx_d_ptr->setErrorDescription(QLowEnergyController::UnknownError); + return; + } + + [osx_d_ptr->peripheralManager setParameters:params + data:advertisingData + scanResponse:scanResponseData]; + + osx_d_ptr->controllerState = AdvertisingState; + emit stateChanged(AdvertisingState); + + const auto manager = osx_d_ptr->peripheralManager.data(); + dispatch_async(leQueue, ^{ + [manager startAdvertising]; + }); +#endif } void QLowEnergyController::stopAdvertising() { - qCWarning(QT_BT_OSX) << "LE advertising not implemented for OS X"; +#ifdef Q_OS_TVOS + qCWarning(QT_BT_OSX) << "advertising is not supported on your platform"; +#else + OSX_D_PTR; + + if (!osx_d_ptr->isValid()) + return osx_d_ptr->_q_CBManagerError(UnknownError); + + if (state() != AdvertisingState) { + qCDebug(QT_BT_OSX) << "called in state" << state(); + return; + } + + if (const auto leQueue = OSXBluetooth::qt_LE_queue()) { + const auto manager = osx_d_ptr->peripheralManager.data(); + dispatch_sync(leQueue, ^{ + [manager stopAdvertising]; + }); + + osx_d_ptr->controllerState = UnconnectedState; + emit stateChanged(UnconnectedState); + } else { + qCWarning(QT_BT_OSX) << "no LE queue found"; + osx_d_ptr->setErrorDescription(QLowEnergyController::UnknownError); + return; + } +#endif } -QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData &service, +QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData &data, QObject *parent) { - Q_UNUSED(service); - Q_UNUSED(parent); - qCWarning(QT_BT_OSX) << "GATT server functionality not implemented for OS X"; +#ifdef Q_OS_TVOS + Q_UNUSED(data) + Q_UNUSED(parent) + qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform"; +#else + OSX_D_PTR; + + if (!osx_d_ptr->isValid()) { + osx_d_ptr->_q_CBManagerError(UnknownError); + return nullptr; + } + + if (role() != PeripheralRole) { + qCWarning(QT_BT_OSX) << "not in peripheral role"; + return nullptr; + } + + if (state() != UnconnectedState) { + qCWarning(QT_BT_OSX) << "invalid state"; + return nullptr; + } + + if (!data.isValid()) { + qCWarning(QT_BT_OSX) << "invalid service"; + return nullptr; + } + + for (auto includedService : data.includedServices()) + includedService->d_ptr->type |= QLowEnergyService::IncludedService; + + if (const auto servicePrivate = [osx_d_ptr->peripheralManager addService:data]) { + servicePrivate->setController(osx_d_ptr); + osx_d_ptr->discoveredServices.insert(servicePrivate->uuid, servicePrivate); + return new QLowEnergyService(servicePrivate, parent); + } +#endif + return nullptr; } void QLowEnergyController::requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms) { Q_UNUSED(params); - qCWarning(QT_BT_OSX) << "Connection update not implemented for OS X"; + qCWarning(QT_BT_OSX) << "Connection update not implemented on your platform"; } QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h index 853e1f9b..b7399ed7 100644 --- a/src/bluetooth/qlowenergycontroller_osx_p.h +++ b/src/bluetooth/qlowenergycontroller_osx_p.h @@ -51,6 +51,7 @@ // We mean it. // +#include "osx/osxbtperipheralmanager_p.h" #include "qlowenergyserviceprivate_p.h" #include "osx/osxbtcentralmanager_p.h" #include "qlowenergycontroller_p.h" @@ -61,6 +62,7 @@ #include "qbluetoothuuid.h" #include <QtCore/qsharedpointer.h> +#include <QtCore/qsysinfo.h> #include <QtCore/qglobal.h> #include <QtCore/qstring.h> #include <QtCore/qmap.h> @@ -70,13 +72,13 @@ QT_BEGIN_NAMESPACE namespace OSXBluetooth { -class LECentralNotifier; +class LECBManagerNotifier; } class QByteArray; -// While suffix 'OSX', it's also for iOS. +// Suffix 'OSX' is a legacy, it's also iOS. class QLowEnergyControllerPrivateOSX : public QLowEnergyControllerPrivate { friend class QLowEnergyController; @@ -84,9 +86,8 @@ class QLowEnergyControllerPrivateOSX : public QLowEnergyControllerPrivate Q_OBJECT public: - QLowEnergyControllerPrivateOSX(QLowEnergyController *q); - QLowEnergyControllerPrivateOSX(QLowEnergyController *q, - const QBluetoothDeviceInfo &uuid); + QLowEnergyControllerPrivateOSX(QLowEnergyController::Role role, QLowEnergyController *q, + const QBluetoothDeviceInfo &info = QBluetoothDeviceInfo()); ~QLowEnergyControllerPrivateOSX(); bool isValid() const; @@ -105,18 +106,15 @@ private Q_SLOTS: void _q_descriptorWritten(QLowEnergyHandle charHandle, const QByteArray &value); void _q_LEnotSupported(); - void _q_CBCentralManagerError(QLowEnergyController::Error error); - void _q_CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error); - void _q_CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error); + void _q_CBManagerError(QLowEnergyController::Error error); + void _q_CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error); + void _q_CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error); private: void connectToDevice(); void discoverServices(); void discoverServiceDetails(const QBluetoothUuid &serviceUuid); - // TODO: all these read/write /setNotify can be simplified - - // by just passing either characteristic or descriptor (that - // has all needed information - service, handle, etc.). void setNotifyValue(QSharedPointer<QLowEnergyServicePrivate> service, QLowEnergyHandle charHandle, const QByteArray &newValue); @@ -149,7 +147,7 @@ private: void setErrorDescription(QLowEnergyController::Error errorCode); void invalidateServices(); - bool connectSlots(OSXBluetooth::LECentralNotifier *notifier); + bool connectSlots(OSXBluetooth::LECBManagerNotifier *notifier); QLowEnergyController *q_ptr; QBluetoothUuid deviceUuid; @@ -170,6 +168,12 @@ private: typedef OSXBluetooth::ObjCScopedPointer<ObjCCentralManager> CentralManager; CentralManager centralManager; +#ifndef Q_OS_TVOS + typedef QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) ObjCPeripheralManager; + typedef OSXBluetooth::ObjCScopedPointer<ObjCPeripheralManager> PeripheralManager; + PeripheralManager peripheralManager; +#endif + typedef QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate> > ServiceMap; typedef ServiceMap::const_iterator ConstServiceIterator; typedef ServiceMap::iterator ServiceIterator; diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index 2256025c..38e38752 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -82,6 +82,24 @@ QT_END_NAMESPACE #elif defined(QT_ANDROID_BLUETOOTH) #include <QtAndroidExtras/QAndroidJniObject> #include "android/lowenergynotificationhub_p.h" +#elif defined(QT_WINRT_BLUETOOTH) +#include <wrl.h> + +namespace ABI { + namespace Windows { + namespace Devices { + namespace Bluetooth { + struct IBluetoothLEDevice; + namespace GenericAttributeProfile { + struct IGattDeviceService; + struct IGattCharacteristic; + } + } + } + } +} + +class QWinRTLowEnergyServiceHandler; #endif #include <functional> @@ -414,6 +432,16 @@ private slots: QLowEnergyService::ServiceError errorCode); void characteristicChanged(int charHandle, const QByteArray &data); void serviceError(int attributeHandle, QLowEnergyService::ServiceError errorCode); +#elif defined(QT_WINRT_BLUETOOTH) +private: + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> mDevice; + EventRegistrationToken mStatusChangedToken; + + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDeviceService> getNativeService(const QBluetoothUuid &serviceUuid); + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattCharacteristic> getNativeCharacteristic(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid); + + void obtainIncludedServices(QSharedPointer<QLowEnergyServicePrivate> servicePointer, + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDeviceService> nativeService); #endif private: QLowEnergyController *q_ptr; diff --git a/src/bluetooth/qlowenergycontroller_winrt.cpp b/src/bluetooth/qlowenergycontroller_winrt.cpp new file mode 100644 index 00000000..25371e74 --- /dev/null +++ b/src/bluetooth/qlowenergycontroller_winrt.cpp @@ -0,0 +1,1019 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qlowenergycontroller_p.h" + +#include <QtCore/qfunctions_winrt.h> +#include <QtCore/QLoggingCategory> +#include <private/qeventdispatcher_winrt_p.h> + +#include <functional> +#include <robuffer.h> +#include <windows.devices.enumeration.h> +#include <windows.devices.bluetooth.h> +#include <windows.foundation.collections.h> +#include <windows.storage.streams.h> + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Devices; +using namespace ABI::Windows::Devices::Bluetooth; +using namespace ABI::Windows::Devices::Bluetooth::GenericAttributeProfile; +using namespace ABI::Windows::Devices::Enumeration; +using namespace ABI::Windows::Storage::Streams; + +QT_BEGIN_NAMESPACE + +typedef ITypedEventHandler<BluetoothLEDevice *, IInspectable *> StatusHandler; +typedef GattReadClientCharacteristicConfigurationDescriptorResult ClientCharConfigDescriptorResult; +typedef IGattReadClientCharacteristicConfigurationDescriptorResult IClientCharConfigDescriptorResult; + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) + +static QVector<QBluetoothUuid> getIncludedServiceIds(const ComPtr<IGattDeviceService> &service) +{ + QVector<QBluetoothUuid> result; + ComPtr<IGattDeviceService2> service2; + HRESULT hr = service.As(&service2); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IVectorView<GattDeviceService *>> includedServices; + hr = service2->GetAllIncludedServices(&includedServices); + Q_ASSERT_SUCCEEDED(hr); + + uint count; + hr = includedServices->get_Size(&count); + Q_ASSERT_SUCCEEDED(hr); + for (uint i = 0; i < count; ++i) { + ComPtr<IGattDeviceService> includedService; + hr = includedServices->GetAt(i, &includedService); + Q_ASSERT_SUCCEEDED(hr); + GUID guuid; + hr = includedService->get_Uuid(&guuid); + Q_ASSERT_SUCCEEDED(hr); + const QBluetoothUuid service(guuid); + result << service; + + result << getIncludedServiceIds(includedService); + } + return result; +} + +static QByteArray byteArrayFromBuffer(const ComPtr<IBuffer> &buffer, bool isWCharString = false) +{ + ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess; + HRESULT hr = buffer.As(&byteAccess); + Q_ASSERT_SUCCEEDED(hr); + char *data; + hr = byteAccess->Buffer(reinterpret_cast<byte **>(&data)); + Q_ASSERT_SUCCEEDED(hr); + UINT32 size; + hr = buffer->get_Length(&size); + Q_ASSERT_SUCCEEDED(hr); + if (isWCharString) { + QString valueString = QString::fromUtf16(reinterpret_cast<ushort *>(data)).left(size / 2); + return valueString.toUtf8(); + } + return QByteArray(data, size); +} + +static QByteArray byteArrayFromGattResult(const ComPtr<IGattReadResult> &gattResult, bool isWCharString = false) +{ + ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; + HRESULT hr; + hr = gattResult->get_Value(&buffer); + Q_ASSERT_SUCCEEDED(hr); + return byteArrayFromBuffer(buffer, isWCharString); +} + +class QWinRTLowEnergyServiceHandler : public QObject +{ + Q_OBJECT +public: + QWinRTLowEnergyServiceHandler(const QBluetoothUuid &service, const ComPtr<IGattDeviceService2> &deviceService) + : mService(service) + , mDeviceService(deviceService) + { + qCDebug(QT_BT_WINRT) << __FUNCTION__; + } + + ~QWinRTLowEnergyServiceHandler() + { + } + +public slots: + void obtainCharList() + { + quint16 startHandle = 0; + quint16 endHandle = 0; + qCDebug(QT_BT_WINRT) << __FUNCTION__; + ComPtr<IVectorView<GattCharacteristic *>> characteristics; + HRESULT hr = mDeviceService->GetAllCharacteristics(&characteristics); + Q_ASSERT_SUCCEEDED(hr); + if (!characteristics) { + emit charListObtained(mService, mCharacteristicList, startHandle, endHandle); + QThread::currentThread()->quit(); + return; + } + + uint characteristicsCount; + hr = characteristics->get_Size(&characteristicsCount); + Q_ASSERT_SUCCEEDED(hr); + for (uint i = 0; i < characteristicsCount; ++i) { + ComPtr<IGattCharacteristic> characteristic; + hr = characteristics->GetAt(i, &characteristic); + Q_ASSERT_SUCCEEDED(hr); + quint16 handle; + hr = characteristic->get_AttributeHandle(&handle); + Q_ASSERT_SUCCEEDED(hr); + QLowEnergyServicePrivate::CharData charData; + charData.valueHandle = handle + 1; + if (startHandle == 0 || startHandle > handle) + startHandle = handle; + if (endHandle == 0 || endHandle < handle) + endHandle = handle; + GUID guuid; + hr = characteristic->get_Uuid(&guuid); + Q_ASSERT_SUCCEEDED(hr); + charData.uuid = QBluetoothUuid(guuid); + GattCharacteristicProperties properties; + hr = characteristic->get_CharacteristicProperties(&properties); + Q_ASSERT_SUCCEEDED(hr); + charData.properties = QLowEnergyCharacteristic::PropertyTypes(properties); + if (charData.properties & QLowEnergyCharacteristic::Read) { + ComPtr<IAsyncOperation<GattReadResult *>> readOp; + hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IGattReadResult> readResult; + hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + if (readResult) + charData.value = byteArrayFromGattResult(readResult); + } + ComPtr<IGattCharacteristic2> characteristic2; + hr = characteristic.As(&characteristic2); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IVectorView<GattDescriptor *>> descriptors; + hr = characteristic2->GetAllDescriptors(&descriptors); + Q_ASSERT_SUCCEEDED(hr); + uint descriptorCount; + hr = descriptors->get_Size(&descriptorCount); + Q_ASSERT_SUCCEEDED(hr); + for (uint j = 0; j < descriptorCount; ++j) { + QLowEnergyServicePrivate::DescData descData; + ComPtr<IGattDescriptor> descriptor; + hr = descriptors->GetAt(j, &descriptor); + Q_ASSERT_SUCCEEDED(hr); + quint16 descHandle; + hr = descriptor->get_AttributeHandle(&descHandle); + Q_ASSERT_SUCCEEDED(hr); + GUID descriptorUuid; + hr = descriptor->get_Uuid(&descriptorUuid); + Q_ASSERT_SUCCEEDED(hr); + descData.uuid = QBluetoothUuid(descriptorUuid); + if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { + ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp; + hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IClientCharConfigDescriptorResult> readResult; + hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + GattClientCharacteristicConfigurationDescriptorValue value; + hr = readResult->get_ClientCharacteristicConfigurationDescriptor(&value); + Q_ASSERT_SUCCEEDED(hr); + quint16 result = 0; + bool correct = false; + if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) { + result |= GattClientCharacteristicConfigurationDescriptorValue_Indicate; + correct = true; + } + if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) { + result |= GattClientCharacteristicConfigurationDescriptorValue_Notify; + correct = true; + } + if (value == GattClientCharacteristicConfigurationDescriptorValue_None) { + correct = true; + } + if (!correct) + continue; + + descData.value = QByteArray(2, Qt::Uninitialized); + qToLittleEndian(result, descData.value.data()); + } else { + ComPtr<IAsyncOperation<GattReadResult *>> readOp; + hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IGattReadResult> readResult; + hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription) + descData.value = byteArrayFromGattResult(readResult, true); + else + descData.value = byteArrayFromGattResult(readResult); + } + charData.descriptorList.insert(descHandle, descData); + } + mCharacteristicList.insert(handle, charData); + } + emit charListObtained(mService, mCharacteristicList, startHandle, endHandle); + QThread::currentThread()->quit(); + } + +public: + QBluetoothUuid mService; + ComPtr<IGattDeviceService2> mDeviceService; + QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> mCharacteristicList; + +signals: + void charListObtained(const QBluetoothUuid &service, QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList, + QLowEnergyHandle startHandle, QLowEnergyHandle endHandle); +}; + +QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() + : QObject(), + state(QLowEnergyController::UnconnectedState), + error(QLowEnergyController::NoError) +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__; +} + +QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() +{ + if (mDevice && mStatusChangedToken.value) + mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); +} + +void QLowEnergyControllerPrivate::init() +{ +} + +void QLowEnergyControllerPrivate::connectToDevice() +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__; + Q_Q(QLowEnergyController); + if (remoteDevice.isNull()) { + qWarning() << "Invalid/null remote device address"; + setError(QLowEnergyController::UnknownRemoteDeviceError); + return; + } + + setState(QLowEnergyController::ConnectingState); + + ComPtr<IBluetoothLEDeviceStatics> deviceStatics; + HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &deviceStatics); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation; + hr = deviceStatics->FromBluetoothAddressAsync(remoteDevice.toUInt64(), &deviceFromIdOperation); + Q_ASSERT_SUCCEEDED(hr); + hr = QWinRTFunctions::await(deviceFromIdOperation, mDevice.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + + if (!mDevice) { + qCDebug(QT_BT_WINRT) << "Could not find LE device"; + setError(QLowEnergyController::InvalidBluetoothAdapterError); + setState(QLowEnergyController::UnconnectedState); + } + BluetoothConnectionStatus status; + hr = mDevice->get_ConnectionStatus(&status); + Q_ASSERT_SUCCEEDED(hr); + hr = QEventDispatcherWinRT::runOnXamlThread([this, q]() { + HRESULT hr; + hr = mDevice->add_ConnectionStatusChanged(Callback<StatusHandler>([this, q](IBluetoothLEDevice *dev, IInspectable *) { + BluetoothConnectionStatus status; + HRESULT hr; + hr = dev->get_ConnectionStatus(&status); + Q_ASSERT_SUCCEEDED(hr); + if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { + setState(QLowEnergyController::ConnectedState); + emit q->connected(); + } else if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Disconnected) { + setState(QLowEnergyController::UnconnectedState); + emit q->disconnected(); + } + return S_OK; + }).Get(), &mStatusChangedToken); + Q_ASSERT_SUCCEEDED(hr); + return S_OK; + }); + Q_ASSERT_SUCCEEDED(hr); + + if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { + setState(QLowEnergyController::ConnectedState); + emit q->connected(); + return; + } + + ComPtr<IVectorView <GattDeviceService *>> deviceServices; + hr = mDevice->get_GattServices(&deviceServices); + Q_ASSERT_SUCCEEDED(hr); + uint serviceCount; + hr = deviceServices->get_Size(&serviceCount); + Q_ASSERT_SUCCEEDED(hr); + // Windows Phone automatically connects to the device as soon as a service value is read/written. + // Thus we read one value in order to establish the connection. + for (uint i = 0; i < serviceCount; ++i) { + ComPtr<IGattDeviceService> service; + hr = deviceServices->GetAt(i, &service); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IGattDeviceService2> service2; + hr = service.As(&service2); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IVectorView<GattCharacteristic *>> characteristics; + hr = service2->GetAllCharacteristics(&characteristics); + if (hr == E_ACCESSDENIED) { + // Everything will work as expected up until this point if the manifest capabilties + // for bluetooth LE are not set. + qCWarning(QT_BT_WINRT) << "Could not obtain characteristic list. Please check your " + "manifest capabilities"; + setState(QLowEnergyController::UnconnectedState); + setError(QLowEnergyController::ConnectionError); + return; + } else { + Q_ASSERT_SUCCEEDED(hr); + } + uint characteristicsCount; + hr = characteristics->get_Size(&characteristicsCount); + Q_ASSERT_SUCCEEDED(hr); + for (uint j = 0; j < characteristicsCount; ++j) { + ComPtr<IGattCharacteristic> characteristic; + hr = characteristics->GetAt(j, &characteristic); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IAsyncOperation<GattReadResult *>> op; + GattCharacteristicProperties props; + hr = characteristic->get_CharacteristicProperties(&props); + Q_ASSERT_SUCCEEDED(hr); + if (!(props & GattCharacteristicProperties_Read)) + continue; + hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode::BluetoothCacheMode_Uncached, &op); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IGattReadResult> result; + hr = QWinRTFunctions::await(op, result.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; + hr = result->get_Value(&buffer); + Q_ASSERT_SUCCEEDED(hr); + if (!buffer) { + qCDebug(QT_BT_WINRT) << "Problem reading value"; + setError(QLowEnergyController::ConnectionError); + setState(QLowEnergyController::UnconnectedState); + } + return; + } + } +} + +void QLowEnergyControllerPrivate::disconnectFromDevice() +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__; + Q_Q(QLowEnergyController); + setState(QLowEnergyController::UnconnectedState); + emit q->disconnected(); +} + +ComPtr<IGattDeviceService> QLowEnergyControllerPrivate::getNativeService(const QBluetoothUuid &serviceUuid) +{ + ComPtr<IGattDeviceService> deviceService; + HRESULT hr; + hr = mDevice->GetGattService(serviceUuid, &deviceService); + if (FAILED(hr)) + qCDebug(QT_BT_WINRT) << "Could not obtain native service for Uuid" << serviceUuid; + return deviceService; +} + +ComPtr<IGattCharacteristic> QLowEnergyControllerPrivate::getNativeCharacteristic(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid) +{ + ComPtr<IGattDeviceService> service = getNativeService(serviceUuid); + if (!service) + return nullptr; + + ComPtr<IVectorView<GattCharacteristic *>> characteristics; + HRESULT hr = service->GetCharacteristics(charUuid, &characteristics); + RETURN_IF_FAILED("Could not obtain native characteristics for service", return nullptr); + ComPtr<IGattCharacteristic> characteristic; + hr = characteristics->GetAt(0, &characteristic); + RETURN_IF_FAILED("Could not obtain first characteristic for service", return nullptr); + return characteristic; +} + +void QLowEnergyControllerPrivate::obtainIncludedServices(QSharedPointer<QLowEnergyServicePrivate> servicePointer, + ComPtr<IGattDeviceService> service) +{ + Q_Q(QLowEnergyController); + ComPtr<IGattDeviceService2> service2; + HRESULT hr = service.As(&service2); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IVectorView<GattDeviceService *>> includedServices; + hr = service2->GetAllIncludedServices(&includedServices); + Q_ASSERT_SUCCEEDED(hr); + + uint count; + hr = includedServices->get_Size(&count); + Q_ASSERT_SUCCEEDED(hr); + for (uint i = 0; i < count; ++i) { + ComPtr<IGattDeviceService> includedService; + hr = includedServices->GetAt(i, &includedService); + Q_ASSERT_SUCCEEDED(hr); + GUID guuid; + hr = includedService->get_Uuid(&guuid); + Q_ASSERT_SUCCEEDED(hr); + const QBluetoothUuid includedUuid(guuid); + QSharedPointer<QLowEnergyServicePrivate> includedPointer; + if (serviceList.contains(includedUuid)) { + includedPointer = serviceList.value(includedUuid); + } else { + QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate(); + priv->uuid = includedUuid; + priv->setController(this); + + includedPointer = QSharedPointer<QLowEnergyServicePrivate>(priv); + serviceList.insert(includedUuid, includedPointer); + } + includedPointer->type |= QLowEnergyService::IncludedService; + servicePointer->includedServices.append(includedUuid); + + obtainIncludedServices(includedPointer, includedService); + + emit q->serviceDiscovered(includedUuid); + } +} + +void QLowEnergyControllerPrivate::discoverServices() +{ + Q_Q(QLowEnergyController); + + qCDebug(QT_BT_WINRT) << "Service discovery initiated"; + ComPtr<IVectorView<GattDeviceService *>> deviceServices; + HRESULT hr = mDevice->get_GattServices(&deviceServices); + Q_ASSERT_SUCCEEDED(hr); + uint serviceCount; + hr = deviceServices->get_Size(&serviceCount); + Q_ASSERT_SUCCEEDED(hr); + for (uint i = 0; i < serviceCount; ++i) { + ComPtr<IGattDeviceService> deviceService; + hr = deviceServices->GetAt(i, &deviceService); + Q_ASSERT_SUCCEEDED(hr); + GUID guuid; + hr = deviceService->get_Uuid(&guuid); + Q_ASSERT_SUCCEEDED(hr); + const QBluetoothUuid service(guuid); + + QSharedPointer<QLowEnergyServicePrivate> pointer; + if (serviceList.contains(service)) { + pointer = serviceList.value(service); + } else { + QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate(); + priv->uuid = service; + priv->setController(this); + + pointer = QSharedPointer<QLowEnergyServicePrivate>(priv); + serviceList.insert(service, pointer); + } + pointer->type |= QLowEnergyService::PrimaryService; + + obtainIncludedServices(pointer, deviceService); + + emit q->serviceDiscovered(service); + } + + setState(QLowEnergyController::DiscoveredState); + emit q->discoveryFinished(); +} + +void QLowEnergyControllerPrivate::discoverServiceDetails(const QBluetoothUuid &service) +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__ << service; + if (!serviceList.contains(service)) { + qCWarning(QT_BT_WINRT) << "Discovery done of unknown service:" + << service.toString(); + return; + } + + ComPtr<IGattDeviceService> deviceService = getNativeService(service); + if (!deviceService) { + qCDebug(QT_BT_WINRT) << "Could not obtain native service for uuid " << service; + return; + } + + //update service data + QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service); + + pointer->setState(QLowEnergyService::DiscoveringServices); + ComPtr<IGattDeviceService2> deviceService2; + HRESULT hr = deviceService.As(&deviceService2); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IVectorView<GattDeviceService *>> deviceServices; + hr = deviceService2->GetAllIncludedServices(&deviceServices); + Q_ASSERT_SUCCEEDED(hr); + uint serviceCount; + hr = deviceServices->get_Size(&serviceCount); + Q_ASSERT_SUCCEEDED(hr); + for (uint i = 0; i < serviceCount; ++i) { + ComPtr<IGattDeviceService> includedService; + hr = deviceServices->GetAt(i, &includedService); + Q_ASSERT_SUCCEEDED(hr); + GUID guuid; + hr = includedService->get_Uuid(&guuid); + Q_ASSERT_SUCCEEDED(hr); + + const QBluetoothUuid service(guuid); + if (service.isNull()) { + qCDebug(QT_BT_WINRT) << "Could not find service"; + return; + } + + pointer->includedServices.append(service); + + // update the type of the included service + QSharedPointer<QLowEnergyServicePrivate> otherService = serviceList.value(service); + if (!otherService.isNull()) + otherService->type |= QLowEnergyService::IncludedService; + } + + QWinRTLowEnergyServiceHandler *worker = new QWinRTLowEnergyServiceHandler(service, deviceService2); + QThread *thread = new QThread; + worker->moveToThread(thread); + connect(thread, &QThread::started, worker, &QWinRTLowEnergyServiceHandler::obtainCharList); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + connect(thread, &QThread::finished, worker, &QObject::deleteLater); + connect(worker, &QWinRTLowEnergyServiceHandler::charListObtained, + [this, thread](const QBluetoothUuid &service, QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList + , QLowEnergyHandle startHandle, QLowEnergyHandle endHandle) { + if (!serviceList.contains(service)) { + qCWarning(QT_BT_WINRT) << "Discovery done of unknown service:" + << service.toString(); + return; + } + + QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service); + pointer->startHandle = startHandle; + pointer->endHandle = endHandle; + pointer->characteristicList = charList; + pointer->setState(QLowEnergyService::ServiceDiscovered); + thread->exit(0); + }); + thread->start(); +} + +void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters &, const QLowEnergyAdvertisingData &, const QLowEnergyAdvertisingData &) +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivate::stopAdvertising() +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivate::requestConnectionUpdate(const QLowEnergyConnectionParameters &) +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivate::readCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle) +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle; + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) { + qCDebug(QT_BT_WINRT) << charHandle << "could not be found in service" << service->uuid; + service->setError(QLowEnergyService::CharacteristicReadError); + return; + } + + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([charHandle, service, this]() { + const QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle); + if (!(charData.properties & QLowEnergyCharacteristic::Read)) + qCDebug(QT_BT_WINRT) << "Read flag is not set for characteristic" << charData.uuid; + + ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid); + if (!characteristic) { + qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid + << "from service" << service->uuid; + service->setError(QLowEnergyService::CharacteristicReadError); + return S_OK; + } + ComPtr<IAsyncOperation<GattReadResult*>> readOp; + HRESULT hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); + Q_ASSERT_SUCCEEDED(hr); + auto readCompletedLambda = [charData, charHandle, service] + (IAsyncOperation<GattReadResult*> *op, AsyncStatus status) + { + if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { + qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "read operation failed."; + service->setError(QLowEnergyService::CharacteristicReadError); + return S_OK; + } + ComPtr<IGattReadResult> characteristicValue; + HRESULT hr; + hr = op->GetResults(&characteristicValue); + if (FAILED(hr)) { + qCDebug(QT_BT_WINRT) << "Could not obtain result for characteristic" << charHandle; + service->setError(QLowEnergyService::CharacteristicReadError); + return S_OK; + } + + const QByteArray value = byteArrayFromGattResult(characteristicValue); + QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle); + charData.value = value; + service->characteristicList.insert(charHandle, charData); + emit service->characteristicRead(QLowEnergyCharacteristic(service, charHandle), value); + return S_OK; + }; + hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattReadResult *>>(readCompletedLambda).Get()); + Q_ASSERT_SUCCEEDED(hr); + return S_OK; + }); + Q_ASSERT_SUCCEEDED(hr); +} + +void QLowEnergyControllerPrivate::readDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descHandle) +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle; + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) { + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "in characteristic" << charHandle + << "cannot be found in service" << service->uuid; + service->setError(QLowEnergyService::DescriptorReadError); + return; + } + + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([charHandle, descHandle, service, this]() { + QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle); + ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid); + if (!characteristic) { + qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid + << "from service" << service->uuid; + service->setError(QLowEnergyService::DescriptorReadError); + return S_OK; + } + + // Get native descriptor + if (!charData.descriptorList.contains(descHandle)) + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "cannot be found in characteristic" << charHandle; + QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle); + if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { + ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp; + HRESULT hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); + Q_ASSERT_SUCCEEDED(hr); + auto readCompletedLambda = [&charData, charHandle, &descData, descHandle, service] + (IAsyncOperation<ClientCharConfigDescriptorResult *> *op, AsyncStatus status) + { + if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "read operation failed"; + service->setError(QLowEnergyService::DescriptorReadError); + return S_OK; + } + ComPtr<IClientCharConfigDescriptorResult> iValue; + HRESULT hr; + hr = op->GetResults(&iValue); + if (FAILED(hr)) { + qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle; + service->setError(QLowEnergyService::DescriptorReadError); + return S_OK; + } + GattClientCharacteristicConfigurationDescriptorValue value; + hr = iValue->get_ClientCharacteristicConfigurationDescriptor(&value); + if (FAILED(hr)) { + qCDebug(QT_BT_WINRT) << "Could not obtain value for descriptor" << descHandle; + service->setError(QLowEnergyService::DescriptorReadError); + return S_OK; + } + quint16 result = 0; + bool correct = false; + if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) { + result |= QLowEnergyCharacteristic::Indicate; + correct = true; + } + if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) { + result |= QLowEnergyCharacteristic::Notify; + correct = true; + } + if (value == GattClientCharacteristicConfigurationDescriptorValue_None) + correct = true; + if (!correct) { + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle + << "read operation failed. Obtained unexpected value."; + service->setError(QLowEnergyService::DescriptorReadError); + return S_OK; + } + descData.value = QByteArray(2, Qt::Uninitialized); + qToLittleEndian(result, descData.value.data()); + charData.descriptorList.insert(descHandle, descData); + emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle), + descData.value); + return S_OK; + }; + hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<ClientCharConfigDescriptorResult *>>(readCompletedLambda).Get()); + Q_ASSERT_SUCCEEDED(hr); + return S_OK; + } else { + ComPtr<IVectorView<GattDescriptor *>> descriptors; + HRESULT hr = characteristic->GetDescriptors(descData.uuid, &descriptors); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IGattDescriptor> descriptor; + hr = descriptors->GetAt(0, &descriptor); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IAsyncOperation<GattReadResult*>> readOp; + hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); + Q_ASSERT_SUCCEEDED(hr); + auto readCompletedLambda = [&charData, charHandle, &descData, descHandle, service] + (IAsyncOperation<GattReadResult*> *op, AsyncStatus status) + { + if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "read operation failed"; + service->setError(QLowEnergyService::DescriptorReadError); + return S_OK; + } + ComPtr<IGattReadResult> descriptorValue; + HRESULT hr; + hr = op->GetResults(&descriptorValue); + if (FAILED(hr)) { + qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle; + service->setError(QLowEnergyService::DescriptorReadError); + return S_OK; + } + if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription) + descData.value = byteArrayFromGattResult(descriptorValue, true); + else + descData.value = byteArrayFromGattResult(descriptorValue); + charData.descriptorList.insert(descHandle, descData); + emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle), + descData.value); + return S_OK; + }; + hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattReadResult *>>(readCompletedLambda).Get()); + return S_OK; + } + }); + Q_ASSERT_SUCCEEDED(hr); +} + +void QLowEnergyControllerPrivate::writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QByteArray &newValue, + QLowEnergyService::WriteMode mode) +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << newValue << mode; + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) { + qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "cannot be found in service" << service->uuid; + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } + + QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle); + const bool writeWithResponse = mode == QLowEnergyService::WriteWithResponse; + if (!(charData.properties & (writeWithResponse ? QLowEnergyCharacteristic::Write : QLowEnergyCharacteristic::WriteNoResponse))) + qCDebug(QT_BT_WINRT) << "Write flag is not set for characteristic" << charHandle; + + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([charData, charHandle, this, service, newValue, writeWithResponse]() { + ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid); + if (!characteristic) { + qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid + << "from service" << service->uuid; + service->setError(QLowEnergyService::CharacteristicWriteError); + return S_OK; + } + ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory; + HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), &bufferFactory); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; + const int length = newValue.length(); + hr = bufferFactory->Create(length, &buffer); + Q_ASSERT_SUCCEEDED(hr); + hr = buffer->put_Length(length); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess; + hr = buffer.As(&byteAccess); + Q_ASSERT_SUCCEEDED(hr); + byte *bytes; + hr = byteAccess->Buffer(&bytes); + Q_ASSERT_SUCCEEDED(hr); + memcpy(bytes, newValue, length); + ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp; + GattWriteOption option = writeWithResponse ? GattWriteOption_WriteWithResponse : GattWriteOption_WriteWithoutResponse; + hr = characteristic->WriteValueWithOptionAsync(buffer.Get(), option, &writeOp); + Q_ASSERT_SUCCEEDED(hr); + auto writeCompletedLambda =[charData, charHandle, newValue, service, this] + (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status) + { + if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { + qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "write operation failed"; + service->setError(QLowEnergyService::CharacteristicWriteError); + return S_OK; + } + GattCommunicationStatus result; + HRESULT hr; + hr = op->GetResults(&result); + if (hr == E_BLUETOOTH_ATT_INVALID_ATTRIBUTE_VALUE_LENGTH) { + qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "write operation was tried with invalid value length"; + service->setError(QLowEnergyService::CharacteristicWriteError); + return S_OK; + } + Q_ASSERT_SUCCEEDED(hr); + if (result != GattCommunicationStatus_Success) { + qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "write operation failed"; + service->setError(QLowEnergyService::CharacteristicWriteError); + return S_OK; + } + // only update cache when property is readable. Otherwise it remains + // empty. + if (charData.properties & QLowEnergyCharacteristic::Read) + updateValueOfCharacteristic(charHandle, newValue, false); + emit service->characteristicWritten(QLowEnergyCharacteristic(service, charHandle), newValue); + return S_OK; + }; + hr = writeOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus>>(writeCompletedLambda).Get()); + Q_ASSERT_SUCCEEDED(hr); + return S_OK; + }); + Q_ASSERT_SUCCEEDED(hr); +} + +void QLowEnergyControllerPrivate::writeDescriptor( + const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descHandle, + const QByteArray &newValue) +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle << newValue; + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) { + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "in characteristic" << charHandle + << "could not be found in service" << service->uuid; + service->setError(QLowEnergyService::DescriptorWriteError); + return; + } + + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([charHandle, descHandle, this, service, newValue]() { + const QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle); + ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid); + if (!characteristic) { + qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid + << "from service" << service->uuid; + service->setError(QLowEnergyService::DescriptorWriteError); + return S_OK; + } + + // Get native descriptor + if (!charData.descriptorList.contains(descHandle)) + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "could not be found in Characteristic" << charHandle; + + QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle); + if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { + GattClientCharacteristicConfigurationDescriptorValue value; + quint16 intValue = qFromLittleEndian<quint16>(newValue); + if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Indicate && intValue & GattClientCharacteristicConfigurationDescriptorValue_Notify) { + qCWarning(QT_BT_WINRT) << "Setting both Indicate and Notify is not supported on WinRT"; + value = (GattClientCharacteristicConfigurationDescriptorValue)(GattClientCharacteristicConfigurationDescriptorValue_Indicate | GattClientCharacteristicConfigurationDescriptorValue_Notify); + } else if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Indicate) { + value = GattClientCharacteristicConfigurationDescriptorValue_Indicate; + } else if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Notify) { + value = GattClientCharacteristicConfigurationDescriptorValue_Notify; + } else if (intValue == 0) { + value = GattClientCharacteristicConfigurationDescriptorValue_None; + } else { + Q_ASSERT(false); + } + ComPtr<IAsyncOperation<enum GattCommunicationStatus>> writeOp; + HRESULT hr = characteristic->WriteClientCharacteristicConfigurationDescriptorAsync(value, &writeOp); + Q_ASSERT_SUCCEEDED(hr); + auto writeCompletedLambda = [charHandle, descHandle, newValue, service, this] + (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status) + { + if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed"; + service->setError(QLowEnergyService::DescriptorWriteError); + return S_OK; + } + GattCommunicationStatus result; + HRESULT hr; + hr = op->GetResults(&result); + if (FAILED(hr)) { + qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle; + service->setError(QLowEnergyService::DescriptorWriteError); + return S_OK; + } + if (result != GattCommunicationStatus_Success) { + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed"; + service->setError(QLowEnergyService::DescriptorWriteError); + return S_OK; + } + updateValueOfDescriptor(charHandle, descHandle, newValue, false); + emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue); + return S_OK; + }; + hr = writeOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus >>(writeCompletedLambda).Get()); + Q_ASSERT_SUCCEEDED(hr); + } else { + ComPtr<IVectorView<GattDescriptor *>> descriptors; + HRESULT hr = characteristic->GetDescriptors(descData.uuid, &descriptors); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IGattDescriptor> descriptor; + hr = descriptors->GetAt(0, &descriptor); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory; + hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), &bufferFactory); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; + const int length = newValue.length(); + hr = bufferFactory->Create(length, &buffer); + Q_ASSERT_SUCCEEDED(hr); + hr = buffer->put_Length(length); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess; + hr = buffer.As(&byteAccess); + Q_ASSERT_SUCCEEDED(hr); + byte *bytes; + hr = byteAccess->Buffer(&bytes); + Q_ASSERT_SUCCEEDED(hr); + memcpy(bytes, newValue, length); + ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp; + hr = descriptor->WriteValueAsync(buffer.Get(), &writeOp); + Q_ASSERT_SUCCEEDED(hr); + auto writeCompletedLambda = [charHandle, descHandle, newValue, service, this] + (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status) + { + if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed"; + service->setError(QLowEnergyService::DescriptorWriteError); + return S_OK; + } + GattCommunicationStatus result; + HRESULT hr; + hr = op->GetResults(&result); + if (FAILED(hr)) { + qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle; + service->setError(QLowEnergyService::DescriptorWriteError); + return S_OK; + } + if (result != GattCommunicationStatus_Success) { + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed"; + service->setError(QLowEnergyService::DescriptorWriteError); + return S_OK; + } + updateValueOfDescriptor(charHandle, descHandle, newValue, false); + emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue); + return S_OK; + }; + hr = writeOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus>>(writeCompletedLambda).Get()); + Q_ASSERT_SUCCEEDED(hr); + return S_OK; + } + return S_OK; + }); + Q_ASSERT_SUCCEEDED(hr); +} + + +void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &, QLowEnergyHandle) +{ + Q_UNIMPLEMENTED(); +} + +QT_END_NAMESPACE + +#include "qlowenergycontroller_winrt.moc" diff --git a/src/bluetooth/qlowenergyservice_osx.mm b/src/bluetooth/qlowenergyservice_osx.mm index 97d64040..52c2ac87 100644 --- a/src/bluetooth/qlowenergyservice_osx.mm +++ b/src/bluetooth/qlowenergyservice_osx.mm @@ -208,7 +208,9 @@ void QLowEnergyService::writeCharacteristic(const QLowEnergyCharacteristic &ch, WriteMode mode) { QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr); - if (controller == Q_NULLPTR || state() != ServiceDiscovered || !contains(ch)) { + if (controller == Q_NULLPTR || + (controller->role == QLowEnergyController::CentralRole && state() != ServiceDiscovered) || + !contains(ch)) { d_ptr->setError(QLowEnergyService::OperationError); return; } @@ -250,6 +252,8 @@ void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor, { QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr); if (controller == Q_NULLPTR || state() != ServiceDiscovered || !contains(descriptor)) { + // This operation error also includes LE controller in the peripheral role: + // on iOS/OS X - descriptors are immutable. d_ptr->setError(OperationError); return; } diff --git a/src/imports/bluetooth/plugin.cpp b/src/imports/bluetooth/plugin.cpp index eca5736e..d6c47e35 100644 --- a/src/imports/bluetooth/plugin.cpp +++ b/src/imports/bluetooth/plugin.cpp @@ -57,7 +57,7 @@ QT_USE_NAMESPACE class QBluetoothQmlPlugin : public QQmlExtensionPlugin { Q_OBJECT - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: QBluetoothQmlPlugin(QObject *parent = 0) : QQmlExtensionPlugin(parent) { initResources(); } void registerTypes(const char *uri) diff --git a/src/imports/nfc/plugin.cpp b/src/imports/nfc/plugin.cpp index 5f2ccd26..b71f1f24 100644 --- a/src/imports/nfc/plugin.cpp +++ b/src/imports/nfc/plugin.cpp @@ -59,7 +59,7 @@ QT_USE_NAMESPACE class QNfcQmlPlugin : public QQmlExtensionPlugin { Q_OBJECT - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: QNfcQmlPlugin(QObject *parent = 0) : QQmlExtensionPlugin(parent) { initResources(); } diff --git a/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro b/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro index 36c88cdc..e012ae52 100644 --- a/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro +++ b/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro @@ -4,3 +4,7 @@ CONFIG += testcase QT = core concurrent bluetooth testlib osx:QT += widgets + +config_bluez:qtHaveModule(dbus) { + DEFINES += QT_BLUEZ_BLUETOOTH +} diff --git a/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp b/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp index 192173c8..efc4d8a6 100644 --- a/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp +++ b/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp @@ -77,8 +77,13 @@ private slots: void tst_deviceDiscovery_data(); void tst_deviceDiscovery(); + + void tst_discoveryTimeout(); + + void tst_discoveryMethods(); private: int noOfLocalDevices; + bool isBluez5Runtime = false; }; tst_QBluetoothDeviceDiscoveryAgent::tst_QBluetoothDeviceDiscoveryAgent() @@ -91,12 +96,59 @@ tst_QBluetoothDeviceDiscoveryAgent::~tst_QBluetoothDeviceDiscoveryAgent() { } +#ifdef QT_BLUEZ_BLUETOOTH +// This section was adopted from tst_qloggingcategory.cpp +QString logMessage; + +QByteArray qMyMessageFormatString(QtMsgType type, const QMessageLogContext &context, + const QString &str) +{ + QByteArray message; + message.append(context.category); + switch (type) { + case QtDebugMsg: message.append(".debug"); break; + case QtInfoMsg: message.append(".info"); break; + case QtWarningMsg: message.append(".warning"); break; + case QtCriticalMsg:message.append(".critical"); break; + case QtFatalMsg: message.append(".fatal"); break; + } + message.append(": "); + message.append(qPrintable(str)); + + return message.simplified(); +} + +static void myCustomMessageHandler(QtMsgType type, + const QMessageLogContext &context, + const QString &msg) +{ + logMessage = qMyMessageFormatString(type, context, msg); +} +#endif + + + void tst_QBluetoothDeviceDiscoveryAgent::initTestCase() { qRegisterMetaType<QBluetoothDeviceInfo>(); qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::InquiryType>(); +#ifdef QT_BLUEZ_BLUETOOTH + // To distinguish Bluez 4 and 5 we peek into the debug output + // of first Bluetooth ctor. It executes a runtime test and prints the result + // as logging output. This avoids more complex runtime detection logic within this unit test. + QtMessageHandler oldMessageHandler; + oldMessageHandler = qInstallMessageHandler(myCustomMessageHandler); + + noOfLocalDevices = QBluetoothLocalDevice::allDevices().count(); + qInstallMessageHandler(oldMessageHandler); + isBluez5Runtime = logMessage.contains(QStringLiteral("Bluez 5")); + if (isBluez5Runtime) + qDebug() << "BlueZ 5 runtime detected."; +#else noOfLocalDevices = QBluetoothLocalDevice::allDevices().count(); +#endif + if (!noOfLocalDevices) return; @@ -419,10 +471,10 @@ void tst_QBluetoothDeviceDiscoveryAgent::tst_deviceDiscovery() } } } -#ifdef Q_OS_IOS - //On iOS, we do not have access to the local device/adapter, numberOfAdapters is 0, +#if defined(Q_OS_IOS) || defined(Q_OS_WINRT) + //On iOS/WinRT, we do not have access to the local device/adapter, numberOfAdapters is 0, //so we skip this test at all. - QSKIP("iOS: no local Bluetooth device available. Skipping remaining part of test."); + QSKIP("iOS/WinRT: no local Bluetooth device available. Skipping remaining part of test."); #endif //For multiple Bluetooth adapter do the check only for GeneralUnlimitedInquiry. @@ -431,6 +483,165 @@ void tst_QBluetoothDeviceDiscoveryAgent::tst_deviceDiscovery() } } + +void tst_QBluetoothDeviceDiscoveryAgent::tst_discoveryTimeout() +{ + QBluetoothDeviceDiscoveryAgent agent; + + // check default values +#if defined(Q_OS_OSX) || defined(Q_OS_IOS) || defined(Q_OS_ANDROID) || defined(Q_OS_WINRT) + QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000); + agent.setLowEnergyDiscoveryTimeout(-1); // negative ignored + QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000); + agent.setLowEnergyDiscoveryTimeout(20000); + QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000); +#elif defined(QT_BLUEZ_BLUETOOTH) + if (isBluez5Runtime) { + QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000); + agent.setLowEnergyDiscoveryTimeout(-1); // negative ignored + QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000); + agent.setLowEnergyDiscoveryTimeout(25000); + QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000); + } else { + QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1); + agent.setLowEnergyDiscoveryTimeout(20000); // feature not supported -> ignored + QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1); + } +#else + QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1); + agent.setLowEnergyDiscoveryTimeout(20000); // feature not supported -> ignored + QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1); +#endif +} + +void tst_QBluetoothDeviceDiscoveryAgent::tst_discoveryMethods() +{ + const QBluetoothLocalDevice localDevice; + if (localDevice.allDevices().size() != 1) { + // On iOS it returns 0 but we still have working BT. +#ifndef Q_OS_IOS + QSKIP("This test expects exactly one local device working"); +#endif + } + + const QBluetoothDeviceDiscoveryAgent::DiscoveryMethods + supportedMethods = QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods(); + + QVERIFY(supportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod); + + QBluetoothDeviceDiscoveryAgent::DiscoveryMethod + unsupportedMethods = QBluetoothDeviceDiscoveryAgent::NoMethod; + QBluetoothDeviceInfo::CoreConfiguration + expectedConfiguration = QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; + + if (supportedMethods == QBluetoothDeviceDiscoveryAgent::ClassicMethod) { + unsupportedMethods = QBluetoothDeviceDiscoveryAgent::LowEnergyMethod; + expectedConfiguration = QBluetoothDeviceInfo::BaseRateCoreConfiguration; + } else if (supportedMethods == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) { + unsupportedMethods = QBluetoothDeviceDiscoveryAgent::ClassicMethod; + expectedConfiguration = QBluetoothDeviceInfo::LowEnergyCoreConfiguration; + } + + QBluetoothDeviceDiscoveryAgent agent; + QSignalSpy finishedSpy(&agent, SIGNAL(finished())); + QSignalSpy errorSpy(&agent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error))); + QSignalSpy discoveredSpy(&agent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo))); + + // NoMethod - should just immediately return: + agent.start(QBluetoothDeviceDiscoveryAgent::NoMethod); + QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError); + QVERIFY(!agent.isActive()); + QCOMPARE(finishedSpy.size(), 0); + QCOMPARE(errorSpy.size(), 0); + QCOMPARE(discoveredSpy.size(), 0); + + if (unsupportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod) { + agent.start(unsupportedMethods); + QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); + QVERIFY(!agent.isActive()); + QVERIFY(finishedSpy.isEmpty()); + QCOMPARE(errorSpy.size(), 1); + errorSpy.clear(); + QVERIFY(discoveredSpy.isEmpty()); + } + + // Start discovery, probably both Classic and LE methods: + agent.start(supportedMethods); + QVERIFY(agent.isActive()); + QVERIFY(errorSpy.isEmpty()); + + +#define RUN_DISCOVERY(maxTimeout, step, condition) \ + for (int scanTime = maxTimeout; (condition) && scanTime > 0; scanTime -= step) \ + QTest::qWait(step); + + // Wait for up to MaxScanTime for the scan to finish + const int timeStep = 15000; + RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty()) + + QVERIFY(!agent.isActive()); + QVERIFY(errorSpy.size() <= 1); + + if (errorSpy.size()) { + // For example, old iOS device could report it supports LE method, + // but it actually does not. + QVERIFY(supportedMethods == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); + QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); + } else { + QVERIFY(finishedSpy.count() == 1); + QVERIFY(agent.error() == QBluetoothDeviceDiscoveryAgent::NoError); + QVERIFY(agent.errorString().isEmpty()); + + while (!discoveredSpy.isEmpty()) { + const QBluetoothDeviceInfo info = + qvariant_cast<QBluetoothDeviceInfo>(discoveredSpy.takeFirst().at(0)); + QVERIFY(info.isValid()); + QVERIFY(info.coreConfigurations() & expectedConfiguration); + } + } + + if (unsupportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod) + return; + + // Both methods were reported as supported. We already tested them + // above, now let's test first Classic then LE. + finishedSpy.clear(); + errorSpy.clear(); + discoveredSpy.clear(); + + agent.start(QBluetoothDeviceDiscoveryAgent::ClassicMethod); + QVERIFY(agent.isActive()); + QVERIFY(errorSpy.isEmpty()); + QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError); + + RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty()) + + QVERIFY(!agent.isActive()); + QVERIFY(errorSpy.isEmpty()); + QCOMPARE(finishedSpy.size(), 1); + + finishedSpy.clear(); + discoveredSpy.clear(); + + agent.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); + QVERIFY(agent.isActive()); + QVERIFY(errorSpy.isEmpty()); + + RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty() && errorSpy.isEmpty()) + + QVERIFY(!agent.isActive()); + QVERIFY(errorSpy.size() <= 1); + + if (errorSpy.size()) { + QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); + qDebug() << "QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods is inaccurate" + " on your platform/with your device, LowEnergyMethod is not supported"; + } else { + QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError); + QCOMPARE(finishedSpy.size(), 1); + } +} + QTEST_MAIN(tst_QBluetoothDeviceDiscoveryAgent) #include "tst_qbluetoothdevicediscoveryagent.moc" diff --git a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp index 773c673b..4acfe4d1 100644 --- a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp +++ b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp @@ -120,7 +120,7 @@ tst_QLowEnergyController::~tst_QLowEnergyController() void tst_QLowEnergyController::initTestCase() { -#ifndef Q_OS_MAC +#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT) if (remoteDevice.isNull() || QBluetoothLocalDevice::allDevices().isEmpty()) { qWarning("No remote device or local adapter found."); @@ -244,7 +244,7 @@ void tst_QLowEnergyController::tst_connect() { QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); -#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) || defined(Q_OS_WINRT) if (!remoteDeviceInfo.isValid()) #else if (localAdapters.isEmpty() || !remoteDeviceInfo.isValid()) @@ -260,7 +260,7 @@ void tst_QLowEnergyController::tst_connect() else QCOMPARE(control.remoteName(), remoteDeviceInfo.name()); -#if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) +#if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(Q_OS_WINRT) const QBluetoothAddress localAdapter = localAdapters.at(0).address(); QCOMPARE(control.localAddress(), localAdapter); QVERIFY(!control.localAddress().isNull()); @@ -403,7 +403,7 @@ void tst_QLowEnergyController::tst_connect() void tst_QLowEnergyController::tst_concurrentDiscovery() { -#ifndef Q_OS_MAC +#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT) QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); if (localAdapters.isEmpty()) QSKIP("No local Bluetooth device found. Skipping test."); @@ -440,7 +440,7 @@ void tst_QLowEnergyController::tst_concurrentDiscovery() 30000); } -#ifdef Q_OS_ANDROID +#if defined(Q_OS_ANDROID) || defined(Q_OS_WINRT) QCOMPARE(control.state(), QLowEnergyController::ConnectedState); QCOMPARE(control2.state(), QLowEnergyController::ConnectedState); control2.disconnectFromDevice(); @@ -1642,7 +1642,7 @@ void tst_QLowEnergyController::tst_defaultBehavior() void tst_QLowEnergyController::tst_writeCharacteristic() { -#ifndef Q_OS_MAC +#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT) QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); if (localAdapters.isEmpty()) QSKIP("No local Bluetooth device found. Skipping test."); @@ -1816,7 +1816,7 @@ void tst_QLowEnergyController::tst_writeCharacteristic() void tst_QLowEnergyController::tst_readWriteDescriptor() { -#ifndef Q_OS_MAC +#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT) QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); if (localAdapters.isEmpty()) QSKIP("No local Bluetooth device found. Skipping test."); @@ -2239,7 +2239,7 @@ void tst_QLowEnergyController::tst_customProgrammableDevice() */ void tst_QLowEnergyController::tst_errorCases() { -#ifndef Q_OS_MAC +#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT) QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); if (localAdapters.isEmpty()) QSKIP("No local Bluetooth device found. Skipping test."); @@ -2461,7 +2461,7 @@ void tst_QLowEnergyController::tst_errorCases() */ void tst_QLowEnergyController::tst_writeCharacteristicNoResponse() { -#ifndef Q_OS_MAC +#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT) QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); if (localAdapters.isEmpty()) QSKIP("No local Bluetooth device found. Skipping test."); |