diff options
-rw-r--r-- | .qmake.conf | 2 | ||||
-rw-r--r-- | examples/bluetooth/btscanner/Info.plist | 41 | ||||
-rw-r--r-- | examples/bluetooth/btscanner/btscanner.pro | 2 | ||||
-rw-r--r-- | examples/bluetooth/heartrate-game/Info.plist | 43 | ||||
-rw-r--r-- | examples/bluetooth/heartrate-game/heartrate-game.pro | 2 | ||||
-rw-r--r-- | examples/bluetooth/heartrate-server/Info.plist | 43 | ||||
-rw-r--r-- | examples/bluetooth/heartrate-server/heartrate-server.pro | 2 | ||||
-rw-r--r-- | examples/bluetooth/lowenergyscanner/Info.plist | 39 | ||||
-rw-r--r-- | examples/bluetooth/lowenergyscanner/lowenergyscanner.pro | 2 | ||||
-rw-r--r-- | src/bluetooth/doc/src/bluetooth-le-overview.qdoc | 10 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtdeviceinquiry.mm | 65 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp | 206 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_winrt.cpp | 12 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_winrt_new.cpp | 847 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_winrt_new_p.h | 5 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_winrt_p.h | 1 | ||||
-rw-r--r-- | src/nfc/qnearfieldtarget_android.cpp | 6 |
17 files changed, 838 insertions, 490 deletions
diff --git a/.qmake.conf b/.qmake.conf index d7055d75..6d03a03f 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -2,4 +2,4 @@ load(qt_build_config) DEFINES += QT_NO_FOREACH QT_NO_JAVA_STYLE_ITERATORS QT_NO_LINKED_LIST -MODULE_VERSION = 5.15.6 +MODULE_VERSION = 5.15.7 diff --git a/examples/bluetooth/btscanner/Info.plist b/examples/bluetooth/btscanner/Info.plist new file mode 100644 index 00000000..49fd2191 --- /dev/null +++ b/examples/bluetooth/btscanner/Info.plist @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDisplayName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIconFile</key> + <string>${ASSETCATALOG_COMPILER_APPICON_NAME}</string> + <key>CFBundleIdentifier</key> + <string>${PRODUCT_BUNDLE_IDENTIFIER}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>${QMAKE_SHORT_VERSION}</string> + <key>CFBundleSignature</key> + <string>${QMAKE_PKGINFO_TYPEINFO}</string> + <key>CFBundleVersion</key> + <string>${QMAKE_FULL_VERSION}</string> + <key>LSRequiresIPhoneOS</key> + <true/> + <key>MinimumOSVersion</key> + <string>${IPHONEOS_DEPLOYMENT_TARGET}</string> + <key>NSBluetoothAlwaysUsageDescription</key> + <string>Qt's BT scanner wants to access your Bluetooth adapter!</string> + <key>UILaunchStoryboardName</key> + <string>LaunchScreen</string> + <key>UISupportedInterfaceOrientations</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationPortraitUpsideDown</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> +</dict> +</plist> diff --git a/examples/bluetooth/btscanner/btscanner.pro b/examples/bluetooth/btscanner/btscanner.pro index 904ea5a8..d5fd66ae 100644 --- a/examples/bluetooth/btscanner/btscanner.pro +++ b/examples/bluetooth/btscanner/btscanner.pro @@ -9,6 +9,8 @@ SOURCES = \ device.cpp \ service.cpp +ios: QMAKE_INFO_PLIST = Info.plist + HEADERS = \ device.h \ service.h diff --git a/examples/bluetooth/heartrate-game/Info.plist b/examples/bluetooth/heartrate-game/Info.plist new file mode 100644 index 00000000..d3c8c281 --- /dev/null +++ b/examples/bluetooth/heartrate-game/Info.plist @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDisplayName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIconFile</key> + <string>${ASSETCATALOG_COMPILER_APPICON_NAME}</string> + <key>CFBundleIdentifier</key> + <string>${PRODUCT_BUNDLE_IDENTIFIER}</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>${QMAKE_SHORT_VERSION}</string> + <key>CFBundleSignature</key> + <string>${QMAKE_PKGINFO_TYPEINFO}</string> + <key>CFBundleVersion</key> + <string>${QMAKE_FULL_VERSION}</string> + <key>LSRequiresIPhoneOS</key> + <true/> + <key>MinimumOSVersion</key> + <string>${IPHONEOS_DEPLOYMENT_TARGET}</string> + <key>NOTE</key> + <string>This file was generated by Qt/QMake.</string> + <key>NSBluetoothAlwaysUsageDescription</key> + <string>Qt's Heartrate-game needs Bluetooth LE</string> + <key>NSBluetoothPeripheralUsageDescription</key> + <string>Qt's Heartrate-game needs Bluetooth LE</string> + <key>UILaunchStoryboardName</key> + <string>LaunchScreen</string> + <key>UISupportedInterfaceOrientations</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationPortraitUpsideDown</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> +</dict> +</plist> diff --git a/examples/bluetooth/heartrate-game/heartrate-game.pro b/examples/bluetooth/heartrate-game/heartrate-game.pro index fcec0bd2..a0827d89 100644 --- a/examples/bluetooth/heartrate-game/heartrate-game.pro +++ b/examples/bluetooth/heartrate-game/heartrate-game.pro @@ -19,6 +19,8 @@ SOURCES += main.cpp \ devicehandler.cpp \ bluetoothbaseclass.cpp +ios: QMAKE_INFO_PLIST = Info.plist + RESOURCES += qml.qrc \ images.qrc diff --git a/examples/bluetooth/heartrate-server/Info.plist b/examples/bluetooth/heartrate-server/Info.plist new file mode 100644 index 00000000..b50431e8 --- /dev/null +++ b/examples/bluetooth/heartrate-server/Info.plist @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDisplayName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIconFile</key> + <string>${ASSETCATALOG_COMPILER_APPICON_NAME}</string> + <key>CFBundleIdentifier</key> + <string>${PRODUCT_BUNDLE_IDENTIFIER}</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>${QMAKE_SHORT_VERSION}</string> + <key>CFBundleSignature</key> + <string>${QMAKE_PKGINFO_TYPEINFO}</string> + <key>CFBundleVersion</key> + <string>${QMAKE_FULL_VERSION}</string> + <key>LSRequiresIPhoneOS</key> + <true/> + <key>MinimumOSVersion</key> + <string>${IPHONEOS_DEPLOYMENT_TARGET}</string> + <key>NOTE</key> + <string>This file was generated by Qt/QMake.</string> + <key>NSBluetoothAlwaysUsageDescription</key> + <string>This is Qt's Heartrate server example, it needs your Bluetooth radio!</string> + <key>NSBluetoothPeripheralUsageDescription</key> + <string>Well, this is Qt's Heartrate server, it needs your Bluetooth radio if you don't mind!</string> + <key>UILaunchStoryboardName</key> + <string>LaunchScreen</string> + <key>UISupportedInterfaceOrientations</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationPortraitUpsideDown</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> +</dict> +</plist> diff --git a/examples/bluetooth/heartrate-server/heartrate-server.pro b/examples/bluetooth/heartrate-server/heartrate-server.pro index 0333ffc9..992f2d66 100644 --- a/examples/bluetooth/heartrate-server/heartrate-server.pro +++ b/examples/bluetooth/heartrate-server/heartrate-server.pro @@ -7,5 +7,7 @@ CONFIG += c++11 SOURCES += main.cpp +ios: QMAKE_INFO_PLIST = Info.plist + target.path = $$[QT_INSTALL_EXAMPLES]/bluetooth/heartrate-server INSTALLS += target diff --git a/examples/bluetooth/lowenergyscanner/Info.plist b/examples/bluetooth/lowenergyscanner/Info.plist new file mode 100644 index 00000000..7f056858 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/Info.plist @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDisplayName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIconFile</key> + <string>${ASSETCATALOG_COMPILER_APPICON_NAME}</string> + <key>CFBundleIdentifier</key> + <string>${PRODUCT_BUNDLE_IDENTIFIER}</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>${QMAKE_SHORT_VERSION}</string> + <key>CFBundleSignature</key> + <string>${QMAKE_PKGINFO_TYPEINFO}</string> + <key>CFBundleVersion</key> + <string>${QMAKE_FULL_VERSION}</string> + <key>LSRequiresIPhoneOS</key> + <true/> + <key>MinimumOSVersion</key> + <string>${IPHONEOS_DEPLOYMENT_TARGET}</string> + <key>NSBluetoothAlwaysUsageDescription</key> + <string>Qt LE scanner wants to access your Bluetooth adapter</string> + <key>UILaunchStoryboardName</key> + <string>LaunchScreen</string> + <key>UISupportedInterfaceOrientations</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationPortraitUpsideDown</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> +</dict> +</plist> diff --git a/examples/bluetooth/lowenergyscanner/lowenergyscanner.pro b/examples/bluetooth/lowenergyscanner/lowenergyscanner.pro index f3378b4c..934f6cfa 100644 --- a/examples/bluetooth/lowenergyscanner/lowenergyscanner.pro +++ b/examples/bluetooth/lowenergyscanner/lowenergyscanner.pro @@ -10,6 +10,8 @@ SOURCES += main.cpp \ serviceinfo.cpp \ characteristicinfo.cpp +ios: QMAKE_INFO_PLIST = Info.plist + OTHER_FILES += assets/*.qml HEADERS += \ diff --git a/src/bluetooth/doc/src/bluetooth-le-overview.qdoc b/src/bluetooth/doc/src/bluetooth-le-overview.qdoc index 7bfc0c44..b161e362 100644 --- a/src/bluetooth/doc/src/bluetooth-le-overview.qdoc +++ b/src/bluetooth/doc/src/bluetooth-le-overview.qdoc @@ -286,4 +286,14 @@ Low Energy devices. In general characteristic and descriptor value updates on the peripheral device use the same methods as connecting Bluetooth Low Energy devices. + + \note To use QtBluetooth (in both central and peripheral roles) on iOS, you have to provide an Info.plist + file containing the usage description. According to the CoreBluetooth's documentation: + + "Important + + Your app will crash if its Info.plist doesn’t include usage description keys for the types of data + it needs to access. To access Core Bluetooth APIs on apps linked on or after iOS 13, include the + NSBluetoothAlwaysUsageDescription key. In iOS 12 and earlier, include NSBluetoothPeripheralUsageDescription + to access Bluetooth peripheral data." */ diff --git a/src/bluetooth/osx/osxbtdeviceinquiry.mm b/src/bluetooth/osx/osxbtdeviceinquiry.mm index 3a77c1f7..2fd0d2db 100644 --- a/src/bluetooth/osx/osxbtdeviceinquiry.mm +++ b/src/bluetooth/osx/osxbtdeviceinquiry.mm @@ -41,15 +41,22 @@ #include "osxbtutility_p.h" #include <QtCore/qloggingcategory.h> +#include <QtCore/qtimer.h> #include <QtCore/qdebug.h> +#include <memory> + QT_USE_NAMESPACE +const uint8_t IOBlueoothInquiryLengthS = 15; + @implementation QT_MANGLE_NAMESPACE(OSXBTDeviceInquiry) { IOBluetoothDeviceInquiry *m_inquiry; bool m_active; DarwinBluetooth::DeviceInquiryDelegate *m_delegate;//C++ "delegate" + + std::unique_ptr<QTimer> watchDog; } - (id)initWithDelegate:(DarwinBluetooth::DeviceInquiryDelegate *)delegate @@ -60,7 +67,13 @@ QT_USE_NAMESPACE m_inquiry = [[IOBluetoothDeviceInquiry inquiryWithDelegate:self] retain]; if (m_inquiry) { - [m_inquiry setInquiryLength:15]; + // Inquiry length is 15 seconds. Starting from macOS 10.15.7 + // (the lowest version I was able to test on, though initially + // the problem was found on macOS 11, arm64 machine and then + // confirmed on macOS 12 Beta 4), it seems to be ignored, + // thus scan never stops. See -start for how we try to prevent + // this. + [m_inquiry setInquiryLength:IOBlueoothInquiryLengthS]; [m_inquiry setUpdateNewDeviceNames:NO];//Useless, disable! m_delegate = delegate; } else { @@ -102,9 +115,21 @@ QT_USE_NAMESPACE const IOReturn result = [m_inquiry start]; if (result != kIOReturnSuccess) { // QtBluetooth will probably convert an error into UnknownError, - // loosing the actual information. + // losing the actual information. qCWarning(QT_BT_OSX) << "failed with IOKit error code:" << result; m_active = false; + } else { + // Docs say it's 10 s. by default, we set it to 15 s. (see -initWithDelegate:), + // and it may fail to finish. + watchDog.reset(new QTimer); + watchDog->connect(watchDog.get(), &QTimer::timeout, watchDog.get(), [self]{ + qCWarning(QT_BT_OSX, "Manually interrupting IOBluetoothDeviceInquiry"); + [self stop]; + }); + + watchDog->setSingleShot(true); + watchDog->setInterval(IOBlueoothInquiryLengthS * 2 * 1000); // Let's make it twice as long. + watchDog->start(); } return result; @@ -112,40 +137,36 @@ QT_USE_NAMESPACE - (IOReturn)stop { - if (m_active) { - Q_ASSERT_X(m_inquiry, Q_FUNC_INFO, "active but nil inquiry"); + if (!m_active) + return kIOReturnSuccess; - m_active = false; - const IOReturn res = [m_inquiry stop]; - if (res != kIOReturnSuccess) - m_active = true; - else - qCDebug(QT_BT_OSX) << "-stop, success (waiting for 'inquiryComplete')"; - - return res; - } + Q_ASSERT_X(m_inquiry, Q_FUNC_INFO, "active but nil inquiry"); - return kIOReturnSuccess; + return [m_inquiry stop]; } - (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry *)sender error:(IOReturn)error aborted:(BOOL)aborted { - Q_UNUSED(aborted) + if (!m_active) + return; if (sender != m_inquiry) // Can never happen in the current version. return; - m_active = false; - Q_ASSERT_X(m_delegate, Q_FUNC_INFO, "invalid device inquiry delegate (null)"); - if (error != kIOReturnSuccess) { + if (error != kIOReturnSuccess && !aborted) { // QtBluetooth has not too many error codes, 'UnknownError' is not really // useful, report the actual error code here: qCWarning(QT_BT_OSX) << "IOKit error code: " << error; m_delegate->error(error); + // Let watchDog to stop it, calling -stop at timeout, otherwise, + // it looks like inquiry continues and keeps reporting new devices found. } else { + // Either a normal completion or from a timer slot. + watchDog.reset(); + m_active = false; m_delegate->inquiryFinished(); } } @@ -156,13 +177,19 @@ QT_USE_NAMESPACE if (sender != m_inquiry) // Can never happen in the current version. return; + if (!m_active) { + // We are not expecting new device(s) to be found after we reported 'finished'. + qCWarning(QT_BT_OSX, "IOBluetooth device found after inquiry complete/interrupted"); + return; + } + Q_ASSERT_X(m_delegate, Q_FUNC_INFO, "invalid device inquiry delegate (null)"); m_delegate->classicDeviceFound(device); } - (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry *)sender { - Q_UNUSED(sender) + Q_UNUSED(sender); } @end diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp index 90ebbdd1..66834827 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp @@ -155,6 +155,7 @@ private: #if QT_CONFIG(winrt_btle_no_pairing) HRESULT onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device); #endif + HRESULT onBluetoothLEAdvertisementReceived(IBluetoothLEAdvertisementReceivedEventArgs *args); public slots: void finishDiscovery(); @@ -334,113 +335,74 @@ void QWinRTBluetoothDeviceDiscoveryWorker::gatherMultipleDeviceInformation(quint } } -void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() +HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEAdvertisementReceived(IBluetoothLEAdvertisementReceivedEventArgs *args) { - HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisementWatcher).Get(), &m_leWatcher); - EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not create advertisment watcher", - QBluetoothDeviceDiscoveryAgent::Error::UnknownError, - return); + quint64 address; + HRESULT hr; + hr = args->get_BluetoothAddress(&address); + EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not obtain bluetooth address", + QBluetoothDeviceDiscoveryAgent::Error::UnknownError, + return S_OK); + qint16 rssi; + hr = args->get_RawSignalStrengthInDBm(&rssi); + EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not obtain signal strength", + QBluetoothDeviceDiscoveryAgent::Error::UnknownError, + return S_OK); + ComPtr<IBluetoothLEAdvertisement> ad; + hr = args->get_Advertisement(&ad); + EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could get advertisement", + QBluetoothDeviceDiscoveryAgent::Error::UnknownError, + return S_OK); + const ManufacturerData manufacturerData = extractManufacturerData(ad); + QBluetoothDeviceInfo::Fields changedFields = QBluetoothDeviceInfo::Field::None; + if (!m_foundLEManufacturerData.contains(address)) { + m_foundLEManufacturerData.insert(address, manufacturerData); + changedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData); + } else if (m_foundLEManufacturerData.value(address) != manufacturerData) { + m_foundLEManufacturerData[address] = manufacturerData; + changedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData); + } #if QT_CONFIG(winrt_btle_no_pairing) if (supportsNewLEApi()) { - hr = m_leWatcher->put_ScanningMode(BluetoothLEScanningMode_Active); - EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not set scanning mode", - QBluetoothDeviceDiscoveryAgent::Error::UnknownError, - return); - } -#endif // winrt_btle_no_pairing - hr = m_leWatcher->add_Received(Callback<ITypedEventHandler<BluetoothLEAdvertisementWatcher *, BluetoothLEAdvertisementReceivedEventArgs *>>([this](IBluetoothLEAdvertisementWatcher *, IBluetoothLEAdvertisementReceivedEventArgs *args) { - quint64 address; - HRESULT hr; - hr = args->get_BluetoothAddress(&address); - EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not obtain bluetooth address", - QBluetoothDeviceDiscoveryAgent::Error::UnknownError, - return S_OK); - qint16 rssi; - hr = args->get_RawSignalStrengthInDBm(&rssi); - EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not obtain signal strength", + ComPtr<IVector<GUID>> guids; + hr = ad->get_ServiceUuids(&guids); + EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not obtain service uuid list", QBluetoothDeviceDiscoveryAgent::Error::UnknownError, return S_OK); - ComPtr<IBluetoothLEAdvertisement> ad; - hr = args->get_Advertisement(&ad); - EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could get advertisement", + quint32 size; + hr = guids->get_Size(&size); + EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not obtain service uuid list size", QBluetoothDeviceDiscoveryAgent::Error::UnknownError, return S_OK); - const ManufacturerData manufacturerData = extractManufacturerData(ad); - QBluetoothDeviceInfo::Fields changedFields = QBluetoothDeviceInfo::Field::None; - if (!m_foundLEManufacturerData.contains(address)) { - m_foundLEManufacturerData.insert(address, manufacturerData); - changedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData); - } else if (m_foundLEManufacturerData.value(address) != manufacturerData) { - m_foundLEManufacturerData[address] = manufacturerData; - changedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData); - } -#if QT_CONFIG(winrt_btle_no_pairing) - if (supportsNewLEApi()) { - ComPtr<IVector<GUID>> guids; - hr = ad->get_ServiceUuids(&guids); - EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not obtain service uuid list", - QBluetoothDeviceDiscoveryAgent::Error::UnknownError, - return S_OK); - quint32 size; - hr = guids->get_Size(&size); - QVector<QBluetoothUuid> serviceUuids; - EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not obtain service uuid list size", + QVector<QBluetoothUuid> serviceUuids; + for (quint32 i = 0; i < size; ++i) { + GUID guid; + hr = guids->GetAt(i, &guid); + EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not obtain uuid", QBluetoothDeviceDiscoveryAgent::Error::UnknownError, return S_OK); - for (quint32 i = 0; i < size; ++i) { - GUID guid; - hr = guids->GetAt(i, &guid); - EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not obtain uuid", - QBluetoothDeviceDiscoveryAgent::Error::UnknownError, - return S_OK); - QBluetoothUuid uuid(guid); - serviceUuids.append(uuid); + QBluetoothUuid uuid(guid); + serviceUuids.append(uuid); + } + QMutexLocker locker(&m_foundDevicesMutex); + // Merge newly found services with list of currently found ones + if (m_foundLEDevicesMap.contains(address)) { + if (size == 0) + return S_OK; + const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address); + QVector<QBluetoothUuid> foundServices = adInfo.services; + if (adInfo.rssi != rssi) { + m_foundLEDevicesMap[address].rssi = rssi; + changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI); } - QMutexLocker locker(&m_foundDevicesMutex); - // Merge newly found services with list of currently found ones - if (m_foundLEDevicesMap.contains(address)) { - if (size == 0) - return S_OK; - const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address); - QVector<QBluetoothUuid> foundServices = adInfo.services; - if (adInfo.rssi != rssi) { - m_foundLEDevicesMap[address].rssi = rssi; - changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI); - } - bool newServiceAdded = false; - for (const QBluetoothUuid &uuid : qAsConst(serviceUuids)) { - if (!foundServices.contains(uuid)) { - foundServices.append(uuid); - newServiceAdded = true; - } + bool newServiceAdded = false; + for (const QBluetoothUuid &uuid : qAsConst(serviceUuids)) { + if (!foundServices.contains(uuid)) { + foundServices.append(uuid); + newServiceAdded = true; } - if (!newServiceAdded) { - if (!changedFields.testFlag(QBluetoothDeviceInfo::Field::None)) { - QMetaObject::invokeMethod(this, "deviceDataChanged", Qt::AutoConnection, - Q_ARG(QBluetoothAddress, QBluetoothAddress(address)), - Q_ARG(QBluetoothDeviceInfo::Fields, changedFields), - Q_ARG(qint16, rssi), - Q_ARG(ManufacturerData, manufacturerData)); - } - return S_OK; - } - m_foundLEDevicesMap[address].services = foundServices; - } else { - LEAdvertisingInfo info; - info.services = std::move(serviceUuids); - info.rssi = rssi; - m_foundLEDevicesMap.insert(address, info); } - - locker.unlock(); - } else -#endif - { - if (m_foundLEDevices.contains(address)) { - if (m_foundLEDevices.value(address) != rssi) { - m_foundLEDevices[address] = rssi; - changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI); - } + if (!newServiceAdded) { if (!changedFields.testFlag(QBluetoothDeviceInfo::Field::None)) { QMetaObject::invokeMethod(this, "deviceDataChanged", Qt::AutoConnection, Q_ARG(QBluetoothAddress, QBluetoothAddress(address)), @@ -450,9 +412,59 @@ void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() } return S_OK; } - m_foundLEDevices.insert(address, rssi); + m_foundLEDevicesMap[address].services = foundServices; + } else { + LEAdvertisingInfo info; + info.services = std::move(serviceUuids); + info.rssi = rssi; + m_foundLEDevicesMap.insert(address, info); + } + + locker.unlock(); + } else +#endif // QT_CONFIG(winrt_btle_no_pairing) + { + if (m_foundLEDevices.contains(address)) { + if (m_foundLEDevices.value(address) != rssi) { + m_foundLEDevices[address] = rssi; + changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI); + } + if (!changedFields.testFlag(QBluetoothDeviceInfo::Field::None)) { + QMetaObject::invokeMethod(this, "deviceDataChanged", Qt::AutoConnection, + Q_ARG(QBluetoothAddress, QBluetoothAddress(address)), + Q_ARG(QBluetoothDeviceInfo::Fields, changedFields), + Q_ARG(qint16, rssi), + Q_ARG(ManufacturerData, manufacturerData)); + } + return S_OK; } - leBluetoothInfoFromAddressAsync(address); + m_foundLEDevices.insert(address, rssi); + } + leBluetoothInfoFromAddressAsync(address); + return S_OK; +} + +void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() +{ + HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisementWatcher).Get(), &m_leWatcher); + EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not create advertisment watcher", + QBluetoothDeviceDiscoveryAgent::Error::UnknownError, + return); +#if QT_CONFIG(winrt_btle_no_pairing) + if (supportsNewLEApi()) { + hr = m_leWatcher->put_ScanningMode(BluetoothLEScanningMode_Active); + EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not set scanning mode", + QBluetoothDeviceDiscoveryAgent::Error::UnknownError, + return); + } +#endif // QT_CONFIG(winrt_btle_no_pairing) + QPointer<QWinRTBluetoothDeviceDiscoveryWorker> thisPointer(this); + hr = m_leWatcher->add_Received( + Callback<ITypedEventHandler<BluetoothLEAdvertisementWatcher *, BluetoothLEAdvertisementReceivedEventArgs *>>( + [thisPointer](IBluetoothLEAdvertisementWatcher *, IBluetoothLEAdvertisementReceivedEventArgs *args) { + if (thisPointer) + return thisPointer->onBluetoothLEAdvertisementReceived(args); + return S_OK; }).Get(), &m_leDeviceAddedToken); EMIT_WORKER_ERROR_AND_RETURN_IF_FAILED("Could not add device callback", diff --git a/src/bluetooth/qlowenergycontroller_winrt.cpp b/src/bluetooth/qlowenergycontroller_winrt.cpp index abe3392a..5b14a92c 100644 --- a/src/bluetooth/qlowenergycontroller_winrt.cpp +++ b/src/bluetooth/qlowenergycontroller_winrt.cpp @@ -260,14 +260,22 @@ void QLowEnergyControllerPrivateWinRT::init() void QLowEnergyControllerPrivateWinRT::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); + // Queue the device connecting to happen in the background + QMetaObject::invokeMethod(this, + &QLowEnergyControllerPrivateWinRT::doConnectToDevice, + Qt::QueuedConnection); +} + +void QLowEnergyControllerPrivateWinRT::doConnectToDevice() +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__; + Q_Q(QLowEnergyController); ComPtr<IBluetoothLEDeviceStatics> deviceStatics; HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &deviceStatics); diff --git a/src/bluetooth/qlowenergycontroller_winrt_new.cpp b/src/bluetooth/qlowenergycontroller_winrt_new.cpp index f951d1a6..a6371c0a 100644 --- a/src/bluetooth/qlowenergycontroller_winrt_new.cpp +++ b/src/bluetooth/qlowenergycontroller_winrt_new.cpp @@ -52,6 +52,7 @@ #include <QtCore/qfunctions_winrt.h> #include <QtCore/QtEndian> #include <QtCore/QLoggingCategory> +#include <QtCore/QDeadlineTimer> #include <private/qeventdispatcher_winrt_p.h> #include <functional> @@ -81,10 +82,16 @@ typedef ITypedEventHandler<GattCharacteristic *, GattValueChangedEventArgs *> Va typedef GattReadClientCharacteristicConfigurationDescriptorResult ClientCharConfigDescriptorResult; typedef IGattReadClientCharacteristicConfigurationDescriptorResult IClientCharConfigDescriptorResult; -#define EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, ret) \ +#define EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr) \ if (FAILED(hr)) { \ emitErrorAndQuitThread(hr); \ - ret; \ + return; \ + } + +#define EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, message) \ + if (FAILED(hr)) { \ + emitErrorAndQuitThread(message); \ + return; \ } #define WARN_AND_CONTINUE_IF_FAILED(hr, msg) \ @@ -93,6 +100,13 @@ typedef IGattReadClientCharacteristicConfigurationDescriptorResult IClientCharCo continue; \ } +#define DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, msg) \ + if (FAILED(hr)) { \ + qCWarning(QT_BT_WINRT) << msg; \ + --mCharacteristicsCountToBeDiscovered; \ + continue; \ + } + #define CHECK_FOR_DEVICE_CONNECTION_ERROR_IMPL(this, hr, msg, ret) \ if (FAILED(hr)) { \ qCWarning(QT_BT_WINRT) << msg; \ @@ -115,6 +129,8 @@ typedef IGattReadClientCharacteristicConfigurationDescriptorResult IClientCharCo Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT_SERVICE_THREAD) +static constexpr qint64 kMaxConnectTimeout = 20000; // 20 sec + QLowEnergyControllerPrivate *createWinRTLowEnergyController() { if (supportsNewLEApi()) { @@ -153,6 +169,7 @@ public: ~QWinRTLowEnergyServiceHandlerNew() { + qCDebug(QT_BT_WINRT) << __FUNCTION__; } public slots: @@ -163,24 +180,24 @@ public slots: ComPtr<IAsyncOperation<GattCharacteristicsResult *>> characteristicsOp; ComPtr<IGattCharacteristicsResult> characteristicsResult; HRESULT hr = mDeviceService->GetCharacteristicsAsync(&characteristicsOp); - EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr); hr = QWinRTFunctions::await(characteristicsOp, characteristicsResult.GetAddressOf(), QWinRTFunctions::ProcessMainThreadEvents, 5000); - EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr); GattCommunicationStatus status; hr = characteristicsResult->get_Status(&status); - EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr); if (status != GattCommunicationStatus_Success) { emitErrorAndQuitThread(QLatin1String("Could not obtain char list")); return; } ComPtr<IVectorView<GattCharacteristic *>> characteristics; hr = characteristicsResult->get_Characteristics(&characteristics); - EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr); uint characteristicsCount; hr = characteristics->get_Size(&characteristicsCount); - EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr); mCharacteristicsCountToBeDiscovered = characteristicsCount; for (uint i = 0; i < characteristicsCount; ++i) { @@ -204,185 +221,126 @@ public slots: // Qt API assumes that all characteristics and their descriptors are discovered in one go. // So we start 'GetDescriptorsAsync' for each discovered characteristic and finish only // when GetDescriptorsAsync for all characteristics return. - ComPtr<IAsyncOperation<GattDescriptorsResult*>> descAsyncResult; - hr = characteristic3->GetDescriptorsAsync(&descAsyncResult); - if (FAILED(hr)) { - qCWarning(QT_BT_WINRT) << "Could not obtain list of descriptors"; + ComPtr<IAsyncOperation<GattDescriptorsResult *>> descAsyncOp; + hr = characteristic3->GetDescriptorsAsync(&descAsyncOp); + DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not obtain list of descriptors") + + ComPtr<IGattDescriptorsResult> descResult; + hr = QWinRTFunctions::await(descAsyncOp, descResult.GetAddressOf()); + DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor read result") + + quint16 handle; + hr = characteristic->get_AttributeHandle(&handle); + DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED( + hr, "Could not obtain characteristic's attribute handle") + QLowEnergyServicePrivate::CharData charData; + charData.valueHandle = handle + 1; + if (mStartHandle == 0 || mStartHandle > handle) + mStartHandle = handle; + if (mEndHandle == 0 || mEndHandle < handle) + mEndHandle = handle; + GUID guuid; + hr = characteristic->get_Uuid(&guuid); + DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not obtain characteristic's Uuid") + charData.uuid = QBluetoothUuid(guuid); + GattCharacteristicProperties properties; + hr = characteristic->get_CharacteristicProperties(&properties); + DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, + "Could not obtain characteristic's properties") + charData.properties = QLowEnergyCharacteristic::PropertyTypes(properties & 0xff); + if (charData.properties & QLowEnergyCharacteristic::Read) { + ComPtr<IAsyncOperation<GattReadResult *>> readOp; + hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, + &readOp); + DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not read characteristic") + ComPtr<IGattReadResult> readResult; + hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); + DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, + "Could not obtain characteristic read result") + if (!readResult) + qCWarning(QT_BT_WINRT) << "Characteristic read result is null"; + else + charData.value = byteArrayFromGattResult(readResult); + } + mCharacteristicList.insert(handle, charData); + + ComPtr<IVectorView<GattDescriptor *>> descriptors; + + GattCommunicationStatus commStatus; + hr = descResult->get_Status(&commStatus); + if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { + qCWarning(QT_BT_WINRT) << "Descriptor operation failed"; --mCharacteristicsCountToBeDiscovered; continue; } - hr = descAsyncResult->put_Completed( - Callback<IAsyncOperationCompletedHandler<GattDescriptorsResult*>>( - [this, characteristic] - (IAsyncOperation<GattDescriptorsResult *> *op, - AsyncStatus status) { - if (status != AsyncStatus::Completed) { - qCWarning(QT_BT_WINRT) << "Descriptor operation unsuccessful"; - --mCharacteristicsCountToBeDiscovered; - checkAllCharacteristicsDiscovered(); - return S_OK; - } - quint16 handle; - HRESULT hr = characteristic->get_AttributeHandle(&handle); - if (FAILED(hr)) { - qCWarning(QT_BT_WINRT) << "Could not obtain characteristic's attribute handle"; - --mCharacteristicsCountToBeDiscovered; - checkAllCharacteristicsDiscovered(); - return S_OK; - } - QLowEnergyServicePrivate::CharData charData; - charData.valueHandle = handle + 1; - if (mStartHandle == 0 || mStartHandle > handle) - mStartHandle = handle; - if (mEndHandle == 0 || mEndHandle < handle) - mEndHandle = handle; - GUID guuid; - hr = characteristic->get_Uuid(&guuid); - if (FAILED(hr)) { - qCWarning(QT_BT_WINRT) << "Could not obtain characteristic's Uuid"; - --mCharacteristicsCountToBeDiscovered; - checkAllCharacteristicsDiscovered(); - return S_OK; - } - charData.uuid = QBluetoothUuid(guuid); - GattCharacteristicProperties properties; - hr = characteristic->get_CharacteristicProperties(&properties); - if (FAILED(hr)) { - qCWarning(QT_BT_WINRT) << "Could not obtain characteristic's properties"; - --mCharacteristicsCountToBeDiscovered; - checkAllCharacteristicsDiscovered(); - return S_OK; - } - charData.properties = QLowEnergyCharacteristic::PropertyTypes(properties & 0xff); - if (charData.properties & QLowEnergyCharacteristic::Read) { - ComPtr<IAsyncOperation<GattReadResult *>> readOp; - hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, - &readOp); - if (FAILED(hr)) { - qCWarning(QT_BT_WINRT) << "Could not read characteristic"; - --mCharacteristicsCountToBeDiscovered; - checkAllCharacteristicsDiscovered(); - return S_OK; + hr = descResult->get_Descriptors(&descriptors); + DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not obtain list of descriptors") + + uint descriptorCount; + hr = descriptors->get_Size(&descriptorCount); + DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not obtain list of descriptors' size") + for (uint j = 0; j < descriptorCount; ++j) { + QLowEnergyServicePrivate::DescData descData; + ComPtr<IGattDescriptor> descriptor; + hr = descriptors->GetAt(j, &descriptor); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor") + quint16 descHandle; + hr = descriptor->get_AttributeHandle(&descHandle); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor's attribute handle") + GUID descriptorUuid; + hr = descriptor->get_Uuid(&descriptorUuid); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor's Uuid") + descData.uuid = QBluetoothUuid(descriptorUuid); + charData.descriptorList.insert(descHandle, descData); + if (descData.uuid == QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)) { + ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp; + hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync( + &readOp); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value") + ComPtr<IClientCharConfigDescriptorResult> readResult; + hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not await descriptor read result") + GattClientCharacteristicConfigurationDescriptorValue value; + hr = readResult->get_ClientCharacteristicConfigurationDescriptor(&value); + WARN_AND_CONTINUE_IF_FAILED(hr, + "Could not get descriptor value from result") + 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()); + mIndicateChars << charData.uuid; + } else { + ComPtr<IAsyncOperation<GattReadResult *>> readOp; + hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, + &readOp); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value") ComPtr<IGattReadResult> readResult; hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); - if (FAILED(hr)) { - qCWarning(QT_BT_WINRT) << "Could not obtain characteristic read result"; - --mCharacteristicsCountToBeDiscovered; - checkAllCharacteristicsDiscovered(); - return S_OK; - } - if (!readResult) - qCWarning(QT_BT_WINRT) << "Characteristic read result is null"; + WARN_AND_CONTINUE_IF_FAILED(hr, "Could await descriptor read result") + if (descData.uuid == QBluetoothUuid::DescriptorType::CharacteristicUserDescription) + descData.value = byteArrayFromGattResult(readResult, true); else - charData.value = byteArrayFromGattResult(readResult); + descData.value = byteArrayFromGattResult(readResult); } - mCharacteristicList.insert(handle, charData); - - ComPtr<IVectorView<GattDescriptor *>> descriptors; - - ComPtr<IGattDescriptorsResult> result; - hr = op->GetResults(&result); - if (FAILED(hr)) { - qCWarning(QT_BT_WINRT) << "Could not obtain descriptor read result"; - --mCharacteristicsCountToBeDiscovered; - checkAllCharacteristicsDiscovered(); - return S_OK; - } - GattCommunicationStatus commStatus; - hr = result->get_Status(&commStatus); - if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { - qCWarning(QT_BT_WINRT) << "Descriptor operation failed"; - --mCharacteristicsCountToBeDiscovered; - checkAllCharacteristicsDiscovered(); - return S_OK; - } - - hr = result->get_Descriptors(&descriptors); - if (FAILED(hr)) { - qCWarning(QT_BT_WINRT) << "Could not obtain list of descriptors"; - --mCharacteristicsCountToBeDiscovered; - checkAllCharacteristicsDiscovered(); - return S_OK; - } - - uint descriptorCount; - hr = descriptors->get_Size(&descriptorCount); - if (FAILED(hr)) { - qCWarning(QT_BT_WINRT) << "Could not obtain list of descriptors' size"; - --mCharacteristicsCountToBeDiscovered; - checkAllCharacteristicsDiscovered(); - return S_OK; - } - for (uint j = 0; j < descriptorCount; ++j) { - QLowEnergyServicePrivate::DescData descData; - ComPtr<IGattDescriptor> descriptor; - hr = descriptors->GetAt(j, &descriptor); - WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor") - quint16 descHandle; - hr = descriptor->get_AttributeHandle(&descHandle); - WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor's attribute handle") - GUID descriptorUuid; - hr = descriptor->get_Uuid(&descriptorUuid); - WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor's Uuid") - descData.uuid = QBluetoothUuid(descriptorUuid); - charData.descriptorList.insert(descHandle, descData); - if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { - ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp; - hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); - WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value") - ComPtr<IClientCharConfigDescriptorResult> readResult; - hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); - WARN_AND_CONTINUE_IF_FAILED(hr, "Could not await descriptor read result") - GattClientCharacteristicConfigurationDescriptorValue value; - hr = readResult->get_ClientCharacteristicConfigurationDescriptor(&value); - WARN_AND_CONTINUE_IF_FAILED(hr, "Could not get descriptor value from result") - 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()); - mIndicateChars << charData.uuid; - } else { - ComPtr<IAsyncOperation<GattReadResult *>> readOp; - hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, - &readOp); - WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value") - ComPtr<IGattReadResult> readResult; - hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); - WARN_AND_CONTINUE_IF_FAILED(hr, "Could await descriptor read result") - if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription) - descData.value = byteArrayFromGattResult(readResult, true); - else - descData.value = byteArrayFromGattResult(readResult); - } - charData.descriptorList.insert(descHandle, descData); - } - - mCharacteristicList.insert(handle, charData); - --mCharacteristicsCountToBeDiscovered; - checkAllCharacteristicsDiscovered(); - return S_OK; - }).Get()); - if (FAILED(hr)) { - qCWarning(QT_BT_WINRT) << "Could not register descriptor callback"; - --mCharacteristicsCountToBeDiscovered; - continue; + charData.descriptorList.insert(descHandle, descData); } + + mCharacteristicList.insert(handle, charData); + --mCharacteristicsCountToBeDiscovered; } checkAllCharacteristicsDiscovered(); } @@ -432,6 +390,292 @@ void QWinRTLowEnergyServiceHandlerNew::emitErrorAndQuitThread(const QString &err QThread::currentThread()->quit(); } +class QWinRTLowEnergyConnectionHandler : public QObject +{ + Q_OBJECT +public: + explicit QWinRTLowEnergyConnectionHandler(const QBluetoothAddress &address) : mAddress(address) + { + qCDebug(QT_BT_WINRT) << __FUNCTION__; + // This should be checked before the handler is created + Q_ASSERT(!mAddress.isNull()); + qRegisterMetaType<ComPtr<IBluetoothLEDevice>>("ComPtr<IBluetoothLEDevice>"); + } + ~QWinRTLowEnergyConnectionHandler() + { + qCDebug(QT_BT_WINRT) << __FUNCTION__; + // To close the COM library gracefully, each successful call to + // CoInitialize, including those that return S_FALSE, must be balanced + // by a corresponding call to CoUninitialize. + if (mInitialized == S_OK || mInitialized == S_FALSE) + CoUninitialize(); + } + +public slots: + void connectToDevice(); + void handleDeviceDisconnectRequest(); + +signals: + void deviceConnected(ComPtr<IBluetoothLEDevice> device); + void errorOccurred(const QString &error); + +private: + void connectToPairedDevice(); + void connectToUnpairedDevice(); + void emitErrorAndQuitThread(const QString &error); + void emitErrorAndQuitThread(const char *error); + void emitConnectedAndQuitThread(); + + ComPtr<IBluetoothLEDevice> mDevice = nullptr; + ComPtr<IGattSession> mGattSession = nullptr; + const QBluetoothAddress mAddress; + bool mAbortConnection = false; + HRESULT mInitialized = E_UNEXPECTED; // some error code +}; + +void QWinRTLowEnergyConnectionHandler::connectToDevice() +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__; + mInitialized = CoInitialize(NULL); + qCDebug(QT_BT_WINRT) << qt_error_string(mInitialized); + + auto earlyExit = [this]() { return mAbortConnection; }; + ComPtr<IBluetoothLEDeviceStatics> deviceStatics; + HRESULT hr = GetActivationFactory( + HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), + &deviceStatics); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain device factory"); + ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation; + hr = deviceStatics->FromBluetoothAddressAsync(mAddress.toUInt64(), &deviceFromIdOperation); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not find LE device from address"); + hr = QWinRTFunctions::await(deviceFromIdOperation, mDevice.GetAddressOf(), + QWinRTFunctions::ProcessMainThreadEvents, 5000, earlyExit); + if (FAILED(hr) || !mDevice) { + emitErrorAndQuitThread("Could not find LE device"); + return; + } + + // get GattSession: 1. get device id + ComPtr<IBluetoothLEDevice4> device4; + hr = mDevice.As(&device4); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not cast device"); + + ComPtr<IBluetoothDeviceId> deviceId; + hr = device4->get_BluetoothDeviceId(&deviceId); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not get bluetooth device id"); + + // get GattSession: 2. get session statics + ComPtr<IGattSessionStatics> sessionStatics; + hr = GetActivationFactory( + HString::MakeReference( + RuntimeClass_Windows_Devices_Bluetooth_GenericAttributeProfile_GattSession) + .Get(), + &sessionStatics); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain GattSession statics"); + + // get GattSession: 3. get session + ComPtr<IAsyncOperation<GattSession *>> gattSessionFromIdOperation; + hr = sessionStatics->FromDeviceIdAsync(deviceId.Get(), &gattSessionFromIdOperation); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not get GattSession from id"); + hr = QWinRTFunctions::await(gattSessionFromIdOperation, mGattSession.GetAddressOf(), + QWinRTFunctions::ProcessMainThreadEvents, 5000, earlyExit); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not complete Gatt session acquire"); + + BluetoothConnectionStatus status; + hr = mDevice->get_ConnectionStatus(&status); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain device's connection status"); + if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { + emitConnectedAndQuitThread(); + return; + } + + QBluetoothLocalDevice localDevice; + QBluetoothLocalDevice::Pairing pairing = localDevice.pairingStatus(mAddress); + if (pairing == QBluetoothLocalDevice::Unpaired) + connectToUnpairedDevice(); + else + connectToPairedDevice(); +} + +void QWinRTLowEnergyConnectionHandler::handleDeviceDisconnectRequest() +{ + mAbortConnection = true; + // Disconnect from the QLowEnergyControllerPrivateWinRT, so that it does + // not get notifications. It's absolutely fine to keep doing smth in + // background, as multiple connections to the same device should be handled + // correctly by OS. + disconnect(this, &QWinRTLowEnergyConnectionHandler::deviceConnected, nullptr, nullptr); + disconnect(this, &QWinRTLowEnergyConnectionHandler::errorOccurred, nullptr, nullptr); +} + +void QWinRTLowEnergyConnectionHandler::connectToPairedDevice() +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__; + ComPtr<IBluetoothLEDevice3> device3; + HRESULT hr = mDevice.As(&device3); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not cast device"); + ComPtr<IAsyncOperation<GattDeviceServicesResult *>> deviceServicesOp; + auto earlyExit = [this]() { return mAbortConnection; }; + QDeadlineTimer deadline(kMaxConnectTimeout); + while (!mAbortConnection && !deadline.hasExpired()) { + hr = device3->GetGattServicesAsync(&deviceServicesOp); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain services"); + ComPtr<IGattDeviceServicesResult> deviceServicesResult; + hr = QWinRTFunctions::await(deviceServicesOp, deviceServicesResult.GetAddressOf(), + QWinRTFunctions::ProcessThreadEvents, 5000, earlyExit); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not await services operation"); + + GattCommunicationStatus commStatus; + hr = deviceServicesResult->get_Status(&commStatus); + if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { + emitErrorAndQuitThread("Service operation failed"); + return; + } + + ComPtr<IVectorView<GattDeviceService *>> deviceServices; + hr = deviceServicesResult->get_Services(&deviceServices); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain list of services"); + uint serviceCount; + hr = deviceServices->get_Size(&serviceCount); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain service count"); + + if (serviceCount == 0) { + emitErrorAndQuitThread("Found devices without services"); + return; + } + + // Windows 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); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain service"); + ComPtr<IGattDeviceService3> service3; + hr = service.As(&service3); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not cast service"); + ComPtr<IAsyncOperation<GattCharacteristicsResult *>> characteristicsOp; + hr = service3->GetCharacteristicsAsync(&characteristicsOp); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain characteristic"); + ComPtr<IGattCharacteristicsResult> characteristicsResult; + hr = QWinRTFunctions::await(characteristicsOp, characteristicsResult.GetAddressOf(), + QWinRTFunctions::ProcessThreadEvents, 5000, earlyExit); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not await characteristic operation"); + GattCommunicationStatus commStatus; + hr = characteristicsResult->get_Status(&commStatus); + if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { + qCWarning(QT_BT_WINRT) << "Characteristic operation failed"; + break; + } + ComPtr<IVectorView<GattCharacteristic *>> characteristics; + hr = characteristicsResult->get_Characteristics(&characteristics); + if (hr == E_ACCESSDENIED) { + // Everything will work as expected up until this point if the + // manifest capabilties for bluetooth LE are not set. + emitErrorAndQuitThread("Could not obtain characteristic list. " + "Please check your manifest capabilities"); + return; + } + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain characteristic list"); + uint characteristicsCount; + hr = characteristics->get_Size(&characteristicsCount); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, + "Could not obtain characteristic list's size"); + for (uint j = 0; j < characteristicsCount; ++j) { + ComPtr<IGattCharacteristic> characteristic; + hr = characteristics->GetAt(j, &characteristic); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain characteristic"); + ComPtr<IAsyncOperation<GattReadResult *>> op; + GattCharacteristicProperties props; + hr = characteristic->get_CharacteristicProperties(&props); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2( + hr, "Could not obtain characteristic's properties"); + if (!(props & GattCharacteristicProperties_Read)) + continue; + hr = characteristic->ReadValueWithCacheModeAsync( + BluetoothCacheMode::BluetoothCacheMode_Uncached, &op); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not read characteristic value"); + ComPtr<IGattReadResult> result; + // Reading characteristics can take surprisingly long at the + // first time, so we need to have a large the timeout here. + hr = QWinRTFunctions::await(op, result.GetAddressOf(), + QWinRTFunctions::ProcessThreadEvents, 5000, earlyExit); + // E_ILLEGAL_METHOD_CALL will be the result for a device, that is not reachable at + // the moment. In this case we should jump back into the outer loop and keep trying. + if (hr == E_ILLEGAL_METHOD_CALL) + break; + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not await characteristic read"); + ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; + hr = result->get_Value(&buffer); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain characteristic value"); + if (!buffer) { + qCDebug(QT_BT_WINRT) << "Problem reading value"; + break; + } + + emitConnectedAndQuitThread(); + return; + } + } + } + // If we got here because of mAbortConnection == true, the error message + // will not be delivered, so it does not matter. But we need to terminate + // the thread anyway! + emitErrorAndQuitThread("Connect to device failed due to timeout!"); +} + +void QWinRTLowEnergyConnectionHandler::connectToUnpairedDevice() +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__; + ComPtr<IBluetoothLEDevice3> device3; + HRESULT hr = mDevice.As(&device3); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not cast device"); + ComPtr<IGattDeviceServicesResult> deviceServicesResult; + auto earlyExit = [this]() { return mAbortConnection; }; + QDeadlineTimer deadline(kMaxConnectTimeout); + while (!mAbortConnection && !deadline.hasExpired()) { + ComPtr<IAsyncOperation<GattDeviceServicesResult *>> deviceServicesOp; + hr = device3->GetGattServicesAsync(&deviceServicesOp); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain services"); + hr = QWinRTFunctions::await(deviceServicesOp, deviceServicesResult.GetAddressOf(), + QWinRTFunctions::ProcessMainThreadEvents, 0, earlyExit); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not await services operation"); + + GattCommunicationStatus commStatus; + hr = deviceServicesResult->get_Status(&commStatus); + if (commStatus == GattCommunicationStatus_Unreachable) + continue; + + if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { + emitErrorAndQuitThread("Service operation failed"); + return; + } + + emitConnectedAndQuitThread(); + return; + } + // If we got here because of mAbortConnection == true, the error message + // will not be delivered, so it does not matter. But we need to terminate + // the thread anyway! + emitErrorAndQuitThread("Connect to device failed due to timeout!"); +} + +void QWinRTLowEnergyConnectionHandler::emitErrorAndQuitThread(const QString &error) +{ + emit errorOccurred(error); + QThread::currentThread()->quit(); +} + +void QWinRTLowEnergyConnectionHandler::emitErrorAndQuitThread(const char *error) +{ + emitErrorAndQuitThread(QString::fromUtf8(error)); +} + +void QWinRTLowEnergyConnectionHandler::emitConnectedAndQuitThread() +{ + emit deviceConnected(mDevice); + QThread::currentThread()->quit(); +} + QLowEnergyControllerPrivateWinRTNew::QLowEnergyControllerPrivateWinRTNew() : QLowEnergyControllerPrivate() { @@ -445,7 +689,6 @@ QLowEnergyControllerPrivateWinRTNew::~QLowEnergyControllerPrivateWinRTNew() { unregisterFromStatusChanges(); unregisterFromValueChanges(); - mAbortPending = true; } void QLowEnergyControllerPrivateWinRTNew::init() @@ -455,47 +698,39 @@ void QLowEnergyControllerPrivateWinRTNew::init() void QLowEnergyControllerPrivateWinRTNew::connectToDevice() { qCDebug(QT_BT_WINRT) << __FUNCTION__; - mAbortPending = false; - 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); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain device factory", return) - ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation; - hr = deviceStatics->FromBluetoothAddressAsync(remoteDevice.toUInt64(), &deviceFromIdOperation); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not find LE device from address", return) - hr = QWinRTFunctions::await(deviceFromIdOperation, mDevice.GetAddressOf(), - QWinRTFunctions::ProcessMainThreadEvents, 5000); - if (FAILED(hr) || !mDevice) { - qCWarning(QT_BT_WINRT) << "Could not find LE device"; - setError(QLowEnergyController::InvalidBluetoothAdapterError); - setState(QLowEnergyController::UnconnectedState); - return; - } - BluetoothConnectionStatus status; - hr = mDevice->get_ConnectionStatus(&status); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain device's connection status", return) - if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { - setState(QLowEnergyController::ConnectedState); - emit q->connected(); - return; - } - - QBluetoothLocalDevice localDevice; - QBluetoothLocalDevice::Pairing pairing = localDevice.pairingStatus(remoteDevice); - if (pairing == QBluetoothLocalDevice::Unpaired) - connectToUnpairedDevice(); - else - connectToPairedDevice(); + QWinRTLowEnergyConnectionHandler *worker = new QWinRTLowEnergyConnectionHandler(remoteDevice); + QThread *thread = new QThread; + worker->moveToThread(thread); + connect(this, &QLowEnergyControllerPrivateWinRTNew::abortConnection, worker, + &QWinRTLowEnergyConnectionHandler::handleDeviceDisconnectRequest); + connect(thread, &QThread::started, worker, &QWinRTLowEnergyConnectionHandler::connectToDevice); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + connect(thread, &QThread::finished, worker, &QObject::deleteLater); + connect(worker, &QWinRTLowEnergyConnectionHandler::errorOccurred, this, + [this](const QString &msg) { handleConnectionError(msg.toUtf8().constData()); }); + connect(worker, &QWinRTLowEnergyConnectionHandler::deviceConnected, this, + [this](ComPtr<IBluetoothLEDevice> device) { + if (!device) { + handleConnectionError("Failed to get device"); + return; + } + mDevice = device; + if (!registerForStatusChanges()) { + handleConnectionError("Failed to register for changes"); + return; + } + Q_Q(QLowEnergyController); + setState(QLowEnergyController::ConnectedState); + emit q->connected(); + }); + thread->start(); } void QLowEnergyControllerPrivateWinRTNew::disconnectFromDevice() @@ -503,9 +738,9 @@ void QLowEnergyControllerPrivateWinRTNew::disconnectFromDevice() qCDebug(QT_BT_WINRT) << __FUNCTION__; Q_Q(QLowEnergyController); setState(QLowEnergyController::ClosingState); + emit abortConnection(); unregisterFromValueChanges(); unregisterFromStatusChanges(); - mAbortPending = true; mDevice = nullptr; setState(QLowEnergyController::UnconnectedState); emit q->disconnected(); @@ -689,6 +924,9 @@ void QLowEnergyControllerPrivateWinRTNew::obtainIncludedServices( ComPtr<IGattDeviceServicesResult> result; hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessMainThreadEvents, 5000); RETURN_IF_FAILED("Could not await service operation", return); + // The device can be disconnected by the time we return from await() + if (state != QLowEnergyController::DiscoveringState) + return; GattCommunicationStatus status; hr = result->get_Status(&status); if (FAILED(hr) || status != GattCommunicationStatus_Success) { @@ -735,6 +973,15 @@ void QLowEnergyControllerPrivateWinRTNew::obtainIncludedServices( HRESULT QLowEnergyControllerPrivateWinRTNew::onServiceDiscoveryFinished(ABI::Windows::Foundation::IAsyncOperation<GattDeviceServicesResult *> *op, AsyncStatus status) { + // Check if the device is in the proper state, because it can already be + // disconnected when the callback arrives. + // Also the callback can theoretically come when the connection is + // reestablisheed again (for example, if the user quickly clicks + // "Disconnect" and then "Connect" again in some UI). But we can probably + // omit such details, as we are connecting to the same device anyway. + if (state != QLowEnergyController::DiscoveringState) + return S_OK; + Q_Q(QLowEnergyController); if (status != AsyncStatus::Completed) { qCDebug(QT_BT_WINRT) << "Could not obtain services"; @@ -786,6 +1033,14 @@ HRESULT QLowEnergyControllerPrivateWinRTNew::onServiceDiscoveryFinished(ABI::Win pointer->type |= QLowEnergyService::PrimaryService; obtainIncludedServices(pointer, deviceService); + // The obtainIncludedServices method calls await(), so the device can be + // disconnected by the time we return from it. TODO - rewrite in an + // async way! + if (state != QLowEnergyController::DiscoveringState) { + emit q->discoveryFinished(); // Probably not needed when the device + // is already disconnected? + return S_OK; + } emit q->serviceDiscovered(service); } @@ -920,8 +1175,8 @@ void QLowEnergyControllerPrivateWinRTNew::discoverServiceDetails(const QBluetoot QLowEnergyServicePrivate::CharData> charList, QVector<QBluetoothUuid> indicateChars, QLowEnergyHandle startHandle, QLowEnergyHandle endHandle) { if (!serviceList.contains(service)) { - qCWarning(QT_BT_WINRT) << "Discovery done of unknown service:" - << service.toString(); + qCWarning(QT_BT_WINRT) + << "Discovery complete for unknown service:" << service.toString(); return; } @@ -1308,7 +1563,7 @@ void QLowEnergyControllerPrivateWinRTNew::writeCharacteristic( } // only update cache when property is readable. Otherwise it remains // empty. - if (charData.properties & QLowEnergyCharacteristic::Read) + if (thisPtr && charData.properties & QLowEnergyCharacteristic::Read) thisPtr->updateValueOfCharacteristic(charHandle, newValue, false); if (writeWithResponse) emit service->characteristicWritten(QLowEnergyCharacteristic(service, charHandle), @@ -1410,7 +1665,8 @@ void QLowEnergyControllerPrivateWinRTNew::writeDescriptor( service->setError(QLowEnergyService::DescriptorWriteError); return S_OK; } - thisPtr->updateValueOfDescriptor(charHandle, descHandle, newValue, false); + if (thisPtr) + thisPtr->updateValueOfDescriptor(charHandle, descHandle, newValue, false); emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue); return S_OK; @@ -1504,7 +1760,8 @@ void QLowEnergyControllerPrivateWinRTNew::writeDescriptor( service->setError(QLowEnergyService::DescriptorWriteError); return S_OK; } - thisPtr->updateValueOfDescriptor(charHandle, descHandle, newValue, false); + if (thisPtr) + thisPtr->updateValueOfDescriptor(charHandle, descHandle, newValue, false); emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue); return S_OK; @@ -1567,158 +1824,12 @@ void QLowEnergyControllerPrivateWinRTNew::handleServiceHandlerError(const QStrin setError(QLowEnergyController::ConnectionError); } -void QLowEnergyControllerPrivateWinRTNew::connectToPairedDevice() -{ - Q_Q(QLowEnergyController); - ComPtr<IBluetoothLEDevice3> device3; - HRESULT hr = mDevice.As(&device3); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return) - ComPtr<IAsyncOperation<GattDeviceServicesResult *>> deviceServicesOp; - while (!mAbortPending) { - hr = device3->GetGattServicesAsync(&deviceServicesOp); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain services", return) - ComPtr<IGattDeviceServicesResult> deviceServicesResult; - hr = QWinRTFunctions::await(deviceServicesOp, deviceServicesResult.GetAddressOf(), - QWinRTFunctions::ProcessThreadEvents, 5000); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await services operation", return) - - GattCommunicationStatus commStatus; - hr = deviceServicesResult->get_Status(&commStatus); - if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { - qCWarning(QT_BT_WINRT()) << "Service operation failed"; - setError(QLowEnergyController::ConnectionError); - setState(QLowEnergyController::UnconnectedState); - unregisterFromStatusChanges(); - return; - } - - ComPtr<IVectorView <GattDeviceService *>> deviceServices; - hr = deviceServicesResult->get_Services(&deviceServices); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain list of services", return) - uint serviceCount; - hr = deviceServices->get_Size(&serviceCount); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service count", return) - - if (serviceCount == 0) { - qCWarning(QT_BT_WINRT()) << "Found devices without services"; - setError(QLowEnergyController::ConnectionError); - setState(QLowEnergyController::UnconnectedState); - unregisterFromStatusChanges(); - return; - } - - // Windows 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); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service", return); - ComPtr<IGattDeviceService3> service3; - hr = service.As(&service3); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast service", return); - ComPtr<IAsyncOperation<GattCharacteristicsResult *>> characteristicsOp; - hr = service3->GetCharacteristicsAsync(&characteristicsOp); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic", return); - ComPtr<IGattCharacteristicsResult> characteristicsResult; - hr = QWinRTFunctions::await(characteristicsOp, characteristicsResult.GetAddressOf(), - QWinRTFunctions::ProcessThreadEvents, 5000); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await characteristic operation", return); - GattCommunicationStatus commStatus; - hr = characteristicsResult->get_Status(&commStatus); - if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { - qCWarning(QT_BT_WINRT) << "Characteristic operation failed"; - break; - } - ComPtr<IVectorView<GattCharacteristic *>> characteristics; - hr = characteristicsResult->get_Characteristics(&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); - unregisterFromStatusChanges(); - return; - } - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic list", return); - uint characteristicsCount; - hr = characteristics->get_Size(&characteristicsCount); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic list's size", return); - for (uint j = 0; j < characteristicsCount; ++j) { - ComPtr<IGattCharacteristic> characteristic; - hr = characteristics->GetAt(j, &characteristic); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic", return); - ComPtr<IAsyncOperation<GattReadResult *>> op; - GattCharacteristicProperties props; - hr = characteristic->get_CharacteristicProperties(&props); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic's properties", return); - if (!(props & GattCharacteristicProperties_Read)) - continue; - hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode::BluetoothCacheMode_Uncached, &op); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not read characteristic value", return); - ComPtr<IGattReadResult> result; - hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessThreadEvents, 500); - // E_ILLEGAL_METHOD_CALL will be the result for a device, that is not reachable at - // the moment. In this case we should jump back into the outer loop and keep trying. - if (hr == E_ILLEGAL_METHOD_CALL) - break; - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await characteristic read", return); - ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; - hr = result->get_Value(&buffer); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic value", return); - if (!buffer) { - qCDebug(QT_BT_WINRT) << "Problem reading value"; - break; - } - - setState(QLowEnergyController::ConnectedState); - emit q->connected(); - if (!registerForStatusChanges()) { - setError(QLowEnergyController::ConnectionError); - setState(QLowEnergyController::UnconnectedState); - return; - } - return; - } - } - } -} - -void QLowEnergyControllerPrivateWinRTNew::connectToUnpairedDevice() +void QLowEnergyControllerPrivateWinRTNew::handleConnectionError(const char *logMessage) { - if (!registerForStatusChanges()) { - setError(QLowEnergyController::ConnectionError); - setState(QLowEnergyController::UnconnectedState); - return; - } - ComPtr<IBluetoothLEDevice3> device3; - HRESULT hr = mDevice.As(&device3); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return) - ComPtr<IGattDeviceServicesResult> deviceServicesResult; - while (!mAbortPending) { - ComPtr<IAsyncOperation<GattDeviceServicesResult *>> deviceServicesOp; - hr = device3->GetGattServicesAsync(&deviceServicesOp); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain services", return) - hr = QWinRTFunctions::await(deviceServicesOp, deviceServicesResult.GetAddressOf(), - QWinRTFunctions::ProcessMainThreadEvents); - CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await services operation", return) - - GattCommunicationStatus commStatus; - hr = deviceServicesResult->get_Status(&commStatus); - if (commStatus == GattCommunicationStatus_Unreachable) - continue; - - if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { - qCWarning(QT_BT_WINRT()) << "Service operation failed"; - setError(QLowEnergyController::ConnectionError); - setState(QLowEnergyController::UnconnectedState); - unregisterFromStatusChanges(); - return; - } - - break; - } + qCWarning(QT_BT_WINRT) << logMessage; + setError(QLowEnergyController::ConnectionError); + setState(QLowEnergyController::UnconnectedState); + unregisterFromStatusChanges(); } QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_winrt_new_p.h b/src/bluetooth/qlowenergycontroller_winrt_new_p.h index c31408be..e8f29817 100644 --- a/src/bluetooth/qlowenergycontroller_winrt_new_p.h +++ b/src/bluetooth/qlowenergycontroller_winrt_new_p.h @@ -132,16 +132,15 @@ public: signals: void characteristicChanged(quint16 charHandle, const QByteArray &data); + void abortConnection(); private slots: void handleCharacteristicChanged(quint16 charHandle, const QByteArray &data); void handleServiceHandlerError(const QString &error); private: - void connectToPairedDevice(); - void connectToUnpairedDevice(); + void handleConnectionError(const char *logMessage); - bool mAbortPending = false; Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> mDevice; EventRegistrationToken mStatusChangedToken; struct ValueChangedEntry { diff --git a/src/bluetooth/qlowenergycontroller_winrt_p.h b/src/bluetooth/qlowenergycontroller_winrt_p.h index fedc52d9..a577342e 100644 --- a/src/bluetooth/qlowenergycontroller_winrt_p.h +++ b/src/bluetooth/qlowenergycontroller_winrt_p.h @@ -119,6 +119,7 @@ signals: private slots: void handleCharacteristicChanged(quint16 charHandle, const QByteArray &data); + void doConnectToDevice(); private: Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> mDevice; diff --git a/src/nfc/qnearfieldtarget_android.cpp b/src/nfc/qnearfieldtarget_android.cpp index c6b120e7..de59808c 100644 --- a/src/nfc/qnearfieldtarget_android.cpp +++ b/src/nfc/qnearfieldtarget_android.cpp @@ -253,6 +253,12 @@ QNearFieldTarget::RequestId NearFieldTarget::sendCommand(const QByteArray &comma // Writing QAndroidJniObject myNewVal = m_tagTech.callObjectMethod("transceive", "([B)[B", jba); if (catchJavaExceptions()) { + // Some devices (Samsung, Huawei) throw an exception when the card is lost: + // "android.nfc.TagLostException: Tag was lost". But there seems to be a bug that + // isConnected still reports true. So we need to invalidate the target as soon as + // possible and treat the card as lost. + handleTargetLost(); + reportError(QNearFieldTarget::CommandError, requestId); return requestId; } |