diff options
-rw-r--r-- | config.tests/winrt_btle_no_pairing/main.cpp | 40 | ||||
-rw-r--r-- | config.tests/winrt_btle_no_pairing/winrt.pro | 3 | ||||
-rw-r--r-- | src/bluetooth/bluetooth.pro | 9 | ||||
-rw-r--r-- | src/bluetooth/configure.json | 13 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp | 152 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothutils_winrt.cpp | 79 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothutils_winrt_p.h | 62 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycharacteristic.h | 1 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller.cpp | 10 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_winrt_new.cpp | 1307 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_winrt_new_p.h | 151 | ||||
-rw-r--r-- | src/bluetooth/qlowenergydescriptor.h | 1 |
12 files changed, 1820 insertions, 8 deletions
diff --git a/config.tests/winrt_btle_no_pairing/main.cpp b/config.tests/winrt_btle_no_pairing/main.cpp new file mode 100644 index 00000000..665e357a --- /dev/null +++ b/config.tests/winrt_btle_no_pairing/main.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtConnectivity module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <wrl.h> +#include <windows.devices.bluetooth.h> + +#if defined(_WIN32) && defined(__INTEL_COMPILER) +#error "Windows ICC fails to build the WinRT backend (QTBUG-68026)." +#endif + +int main() +{ + (void)Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDeviceService3>().Get(); + return 0; +} diff --git a/config.tests/winrt_btle_no_pairing/winrt.pro b/config.tests/winrt_btle_no_pairing/winrt.pro new file mode 100644 index 00000000..d60fd242 --- /dev/null +++ b/config.tests/winrt_btle_no_pairing/winrt.pro @@ -0,0 +1,3 @@ +SOURCES += main.cpp + +!winrt: LIBS += runtimeobject.lib diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index 193ed217..2bcdee12 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -229,10 +229,17 @@ qtConfig(bluez) { qbluetoothservicediscoveryagent_winrt.cpp \ qbluetoothserviceinfo_winrt.cpp \ qbluetoothsocket_winrt.cpp \ + qbluetoothutils_winrt.cpp \ qlowenergycontroller_winrt.cpp PRIVATE_HEADERS += qlowenergycontroller_winrt_p.h \ - qbluetoothsocket_winrt_p.h + qbluetoothsocket_winrt_p.h \ + qbluetoothutils_winrt_p.h + + qtConfig(winrt_btle_no_pairing) { + SOURCES += qlowenergycontroller_winrt_new.cpp + PRIVATE_HEADERS += qlowenergycontroller_winrt_new_p.h + } lessThan(WINDOWS_SDK_VERSION, 14393) { DEFINES += QT_WINRT_LIMITED_SERVICEDISCOVERY diff --git a/src/bluetooth/configure.json b/src/bluetooth/configure.json index 3153aca6..53923e12 100644 --- a/src/bluetooth/configure.json +++ b/src/bluetooth/configure.json @@ -28,6 +28,11 @@ "label": "WinRT Bluetooth API", "type": "compile", "test": "winrt_bt" + }, + "winrt_btle_no_pairing": { + "label": "WinRT extended bluetooth low energy API", + "type": "compile", + "test": "winrt_btle_no_pairing" } }, @@ -51,6 +56,11 @@ "label": "WinRT Bluetooth API (desktop & UWP)", "condition": "config.win32 && tests.winrt_bt", "output": [ "privateFeature" ] + }, + "winrt_btle_no_pairing": { + "label": "WinRT advanced bluetooth low energy API (desktop & UWP)", + "condition": "config.win32 && features.winrt_bt && tests.winrt_btle_no_pairing", + "output": [ "privateFeature" ] } }, @@ -75,7 +85,8 @@ Only classic Bluetooth will be available." "bluez", "bluez_le", "linux_crypto_api", - "winrt_bt" + "winrt_bt", + "winrt_btle_no_pairing" ] } ] diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp index ef2a69b1..13c550cc 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp @@ -47,6 +47,8 @@ #endif #include "qfunctions_winrt.h" +#include <QtBluetooth/private/qtbluetoothglobal_p.h> +#include <QtBluetooth/private/qbluetoothutils_winrt_p.h> #include <QtCore/QLoggingCategory> #include <QtCore/private/qeventdispatcher_winrt_p.h> @@ -105,7 +107,10 @@ private: CheckForPairing, OmitPairingCheck }; - HRESULT onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device, PairingCheck pairingCheck = CheckForPairing); + HRESULT onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device, PairingCheck pairingCheck); +#if QT_CONFIG(winrt_btle_no_pairing) + HRESULT onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device); +#endif public slots: void finishDiscovery(); @@ -120,6 +125,10 @@ public: private: ComPtr<IBluetoothLEAdvertisementWatcher> m_leWatcher; EventRegistrationToken m_leDeviceAddedToken; +#if QT_CONFIG(winrt_btle_no_pairing) + QMutex m_foundDevicesMutex; + QMap<quint64, QVector<QBluetoothUuid>> m_foundLEDevicesMap; +#endif QVector<quint64> m_foundLEDevices; int m_pendingPairedDevices; @@ -249,15 +258,64 @@ void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() { HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisementWatcher).Get(), &m_leWatcher); Q_ASSERT_SUCCEEDED(hr); +#if QT_CONFIG(winrt_btle_no_pairing) + if (supportsNewLEApi()) { + hr = m_leWatcher->put_ScanningMode(BluetoothLEScanningMode_Active); + Q_ASSERT_SUCCEEDED(hr); + } +#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); Q_ASSERT_SUCCEEDED(hr); - if (m_foundLEDevices.contains(address)) - return S_OK; +#if QT_CONFIG(winrt_btle_no_pairing) + if (supportsNewLEApi()) { + ComPtr<IBluetoothLEAdvertisement> ad; + hr = args->get_Advertisement(&ad); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IVector<GUID>> guids; + hr = ad->get_ServiceUuids(&guids); + Q_ASSERT_SUCCEEDED(hr); + quint32 size; + hr = guids->get_Size(&size); + Q_ASSERT_SUCCEEDED(hr); + QVector<QBluetoothUuid> serviceUuids; + for (quint32 i = 0; i < size; ++i) { + GUID guid; + hr = guids->GetAt(i, &guid); + Q_ASSERT_SUCCEEDED(hr); + 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; + QVector<QBluetoothUuid> foundServices = m_foundLEDevicesMap.value(address); + bool newServiceAdded = false; + for (const QBluetoothUuid &uuid : qAsConst(serviceUuids)) { + if (!foundServices.contains(uuid)) { + foundServices.append(uuid); + newServiceAdded = true; + } + } + if (!newServiceAdded) + return S_OK; + m_foundLEDevicesMap[address] = foundServices; + } else { + m_foundLEDevicesMap.insert(address, serviceUuids); + } - m_foundLEDevices.append(address); + locker.unlock(); + } else +#endif + { + if (m_foundLEDevices.contains(address)) + return S_OK; + m_foundLEDevices.append(address); + } leBluetoothInfoFromAddressAsync(address); return S_OK; }).Get(), &m_leDeviceAddedToken); @@ -431,7 +489,12 @@ HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onPairedBluetoothLEDeviceFoundAsyn HRESULT hr; hr = op->GetResults(&device); Q_ASSERT_SUCCEEDED(hr); - return onBluetoothLEDeviceFound(device, OmitPairingCheck); +#if QT_CONFIG(winrt_btle_no_pairing) + if (supportsNewLEApi()) + return onBluetoothLEDeviceFound(device); + else +#endif + return onBluetoothLEDeviceFound(device, OmitPairingCheck); } HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status) @@ -443,7 +506,12 @@ HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFoundAsync(IAsy HRESULT hr; hr = op->GetResults(&device); Q_ASSERT_SUCCEEDED(hr); - return onBluetoothLEDeviceFound(device, PairingCheck::CheckForPairing); +#if QT_CONFIG(winrt_btle_no_pairing) + if (supportsNewLEApi()) + return onBluetoothLEDeviceFound(device); + else +#endif + return onBluetoothLEDeviceFound(device, PairingCheck::CheckForPairing); } HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device, PairingCheck pairingCheck) @@ -545,6 +613,78 @@ HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFound(ComPtr<IB return S_OK; } +#if QT_CONFIG(winrt_btle_no_pairing) +HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device) +{ + if (!device) { + qCDebug(QT_BT_WINRT) << "onBluetoothLEDeviceFound: No device given"; + return S_OK; + } + + UINT64 address; + HString name; + HRESULT 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)); + + ComPtr<IBluetoothLEDevice2> device2; + hr = device.As(&device2); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IDeviceInformation> deviceInfo; + hr = device2->get_DeviceInformation(&deviceInfo); + Q_ASSERT_SUCCEEDED(hr); + if (!deviceInfo) { + qCDebug(QT_BT_WINRT) << "onBluetoothLEDeviceFound: Could not obtain device information"; + return S_OK; + } + ComPtr<IDeviceInformation2> deviceInfo2; + hr = deviceInfo.As(&deviceInfo2); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IDeviceInformationPairing> pairing; + hr = deviceInfo2->get_Pairing(&pairing); + Q_ASSERT_SUCCEEDED(hr); + boolean isPaired; + hr = pairing->get_IsPaired(&isPaired); + Q_ASSERT_SUCCEEDED(hr); + QList<QBluetoothUuid> uuids; + + // Use the services obtained from the advertisement data if the device is not paired + if (!isPaired) { + uuids = m_foundLEDevicesMap.value(address).toList(); + } else { + IVectorView <GenericAttributeProfile::GattDeviceService *> *deviceServices; + hr = device->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<GenericAttributeProfile::IGattDeviceService> service; + hr = deviceServices->GetAt(i, &service); + Q_ASSERT_SUCCEEDED(hr); + 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)); + return S_OK; +} +#endif // QT_CONFIG(winrt_btle_no_pairing) + QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( const QBluetoothAddress &deviceAdapter, QBluetoothDeviceDiscoveryAgent *parent) diff --git a/src/bluetooth/qbluetoothutils_winrt.cpp b/src/bluetooth/qbluetoothutils_winrt.cpp new file mode 100644 index 00000000..1d44221b --- /dev/null +++ b/src/bluetooth/qbluetoothutils_winrt.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbluetoothutils_winrt_p.h" +#include <QtBluetooth/private/qtbluetoothglobal_p.h> + +#include <QtCore/qfunctions_winrt.h> + +#include <wrl.h> +#include <windows.foundation.metadata.h> + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation::Metadata; + +QT_BEGIN_NAMESPACE + +bool supportsNewLEApi() +{ + static bool initialized = false; + static boolean apiPresent = false; + if (initialized) + return apiPresent; + + initialized = true; +#if !QT_CONFIG(winrt_btle_no_pairing) + return apiPresent; +#endif + + ComPtr<IApiInformationStatics> apiInformationStatics; + HRESULT hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Foundation_Metadata_ApiInformation).Get(), + IID_PPV_ARGS(&apiInformationStatics)); + if (FAILED(hr)) + return apiPresent; + + const HStringReference valueRef(L"Windows.Foundation.UniversalApiContract"); + hr = apiInformationStatics->IsApiContractPresentByMajor( + valueRef.Get(), 4, &apiPresent); + apiPresent = SUCCEEDED(hr) && apiPresent; + return apiPresent; +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothutils_winrt_p.h b/src/bluetooth/qbluetoothutils_winrt_p.h new file mode 100644 index 00000000..c272bae1 --- /dev/null +++ b/src/bluetooth/qbluetoothutils_winrt_p.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBLUETOOTHUTILS_WINRT_P_H +#define QBLUETOOTHUTILS_WINRT_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/QtGlobal> + +QT_BEGIN_NAMESPACE + +bool supportsNewLEApi(); + +QT_END_NAMESPACE + +#endif // QBLUETOOTHSOCKET_WINRT_P_H diff --git a/src/bluetooth/qlowenergycharacteristic.h b/src/bluetooth/qlowenergycharacteristic.h index cb747e32..9b27d621 100644 --- a/src/bluetooth/qlowenergycharacteristic.h +++ b/src/bluetooth/qlowenergycharacteristic.h @@ -103,6 +103,7 @@ protected: friend class QLowEnergyControllerPrivateCommon; friend class QLowEnergyControllerPrivateOSX; friend class QLowEnergyControllerPrivateWinRT; + friend class QLowEnergyControllerPrivateWinRTNew; QLowEnergyCharacteristicPrivate *data = nullptr; QLowEnergyCharacteristic(QSharedPointer<QLowEnergyServicePrivate> p, QLowEnergyHandle handle); diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp index 6b4c17d6..8fc044fb 100644 --- a/src/bluetooth/qlowenergycontroller.cpp +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -55,7 +55,11 @@ #elif defined(QT_ANDROID_BLUETOOTH) #include "qlowenergycontroller_android_p.h" #elif defined(QT_WINRT_BLUETOOTH) +#include "qtbluetoothglobal_p.h" #include "qlowenergycontroller_winrt_p.h" +#if QT_CONFIG(winrt_btle_no_pairing) +#include "qlowenergycontroller_winrt_new_p.h" +#endif #else #include "qlowenergycontroller_p.h" #endif @@ -65,6 +69,7 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_BT) +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) /*! \class QLowEnergyController @@ -310,7 +315,12 @@ static QLowEnergyControllerPrivate *privateController(QLowEnergyController::Role return new QLowEnergyControllerPrivateAndroid(); #elif defined(QT_WINRT_BLUETOOTH) Q_UNUSED(role); +#if QT_CONFIG(winrt_btle_no_pairing) + return createWinRTLowEnergyController(); +#else + qCDebug(QT_BT_WINRT) << "Using pre 15063 low energy controller"; return new QLowEnergyControllerPrivateWinRT(); +#endif #else Q_UNUSED(role); return new QLowEnergyControllerPrivateCommon(); diff --git a/src/bluetooth/qlowenergycontroller_winrt_new.cpp b/src/bluetooth/qlowenergycontroller_winrt_new.cpp new file mode 100644 index 00000000..7d7d1145 --- /dev/null +++ b/src/bluetooth/qlowenergycontroller_winrt_new.cpp @@ -0,0 +1,1307 @@ +/**************************************************************************** +** +** 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_winrt_new_p.h" +#include "qlowenergycontroller_winrt_p.h" + +#include <QtBluetooth/QLowEnergyCharacteristicData> +#include <QtBluetooth/QLowEnergyDescriptorData> +#include <QtBluetooth/private/qbluetoothutils_winrt_p.h> + +#ifdef CLASSIC_APP_BUILD +#define Q_OS_WINRT +#endif +#include <QtCore/qfunctions_winrt.h> +#include <QtCore/QtEndian> +#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.foundation.metadata.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::Foundation::Metadata; +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 ITypedEventHandler<GattCharacteristic *, GattValueChangedEventArgs *> ValueChangedHandler; +typedef GattReadClientCharacteristicConfigurationDescriptorResult ClientCharConfigDescriptorResult; +typedef IGattReadClientCharacteristicConfigurationDescriptorResult IClientCharConfigDescriptorResult; + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) + +QLowEnergyControllerPrivate *createWinRTLowEnergyController() +{ + if (supportsNewLEApi()) { + qCDebug(QT_BT_WINRT) << "Using new low energy controller"; + return new QLowEnergyControllerPrivateWinRTNew(); + } + + qCDebug(QT_BT_WINRT) << "Using pre 15063 low energy controller"; + return new QLowEnergyControllerPrivateWinRT(); +} + +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, int(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 QWinRTLowEnergyServiceHandlerNew : public QObject +{ + Q_OBJECT +public: + QWinRTLowEnergyServiceHandlerNew(const QBluetoothUuid &service, + const ComPtr<IGattDeviceService2> &deviceService) + : mService(service) + , mDeviceService(deviceService) + { + qCDebug(QT_BT_WINRT) << __FUNCTION__; + } + + ~QWinRTLowEnergyServiceHandlerNew() + { + } + +public slots: + void obtainCharList() + { + QVector<QBluetoothUuid> indicateChars; + 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, indicateChars, startHandle, endHandle); + QThread::currentThread()->quit(); + return; + } + + uint characteristicsCount; + hr = characteristics->get_Size(&characteristicsCount); + + // If there are no characteristics, we assume that the device is not paired (and not + // discovered by Windows) and we use new API (GetCharacteristicsAsync) to discover them + // without pairing. + if (characteristicsCount == 0) { + ComPtr<IGattDeviceService3> deviceService3; + hr = mDeviceService.As(&deviceService3); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IAsyncOperation<GattCharacteristicsResult*>> asyncResult; + deviceService3->GetCharacteristicsAsync(&asyncResult); + hr = asyncResult->put_Completed( + Callback<IAsyncOperationCompletedHandler<GattCharacteristicsResult*>>( + [this](IAsyncOperation<GattCharacteristicsResult*> *, AsyncStatus status) { + if (status != AsyncStatus::Completed) { + qCDebug(QT_BT_WINRT) << "Could not obtain characteristics"; + return S_OK; + } + // TODO We should check if we found any characteristics. It makes no sense but + // there is a possibility that device doesn't state any characteristics under a service. + // So, for sanity, we should not continue endless loop here. + obtainCharList(); + return S_OK; + }).Get()); + Q_ASSERT_SUCCEEDED(hr); + return; + } + + Q_ASSERT_SUCCEEDED(hr); + mCharacteristicsCountToBeDiscovered = characteristicsCount; + for (uint i = 0; i < characteristicsCount; ++i) { + ComPtr<IGattCharacteristic> characteristic; + hr = characteristics->GetAt(i, &characteristic); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<IGattCharacteristic3> characteristic3; + hr = characteristic.As(&characteristic3); + Q_ASSERT_SUCCEEDED(hr); + + // For some strange reason, Windows doesn't discover descriptors of characteristics (if not paired). + // 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); + Q_ASSERT_SUCCEEDED(hr); + hr = descAsyncResult->put_Completed( + Callback<IAsyncOperationCompletedHandler<GattDescriptorsResult*>>( + [this, characteristic](IAsyncOperation<GattDescriptorsResult*> *, AsyncStatus status) { + if (status != AsyncStatus::Completed) { + qCDebug(QT_BT_WINRT) << "Could not obtain descriptors"; + return S_OK; + } + quint16 handle; + + HRESULT hr = characteristic->get_AttributeHandle(&handle); + Q_ASSERT_SUCCEEDED(hr); + 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); + 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); + } + + QVector<QBluetoothUuid> indicateChars; + ComPtr<IVectorView<GattDescriptor *>> descriptors; + + ComPtr<IGattCharacteristic2> characteristic2; + hr = characteristic.As(&characteristic2); + Q_ASSERT_SUCCEEDED(hr); + + 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()); + indicateChars << charData.uuid; + } 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); + mCharacteristicsCountToBeDiscovered--; + if (mCharacteristicsCountToBeDiscovered == 0) { + emit charListObtained(mService, mCharacteristicList, indicateChars, + mStartHandle, mEndHandle); + QThread::currentThread()->quit(); + } + return S_OK; + }).Get()); + Q_ASSERT_SUCCEEDED(hr); + } + } + +public: + QBluetoothUuid mService; + ComPtr<IGattDeviceService2> mDeviceService; + QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> mCharacteristicList; + uint mCharacteristicsCountToBeDiscovered; + quint16 mStartHandle = 0; + quint16 mEndHandle = 0; + +signals: + void charListObtained(const QBluetoothUuid &service, QHash<QLowEnergyHandle, + QLowEnergyServicePrivate::CharData> charList, + QVector<QBluetoothUuid> indicateChars, + QLowEnergyHandle startHandle, QLowEnergyHandle endHandle); +}; + +QLowEnergyControllerPrivateWinRTNew::QLowEnergyControllerPrivateWinRTNew() + : QLowEnergyControllerPrivate() +{ + registerQLowEnergyControllerMetaType(); +} + +QLowEnergyControllerPrivateWinRTNew::~QLowEnergyControllerPrivateWinRTNew() +{ + if (mDevice && mStatusChangedToken.value) + mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); + + qCDebug(QT_BT_WINRT) << "Unregistering " << mValueChangedTokens.count() << " value change tokens"; + for (const ValueChangedEntry &entry : qAsConst(mValueChangedTokens)) + entry.characteristic->remove_ValueChanged(entry.token); +} + +void QLowEnergyControllerPrivateWinRTNew::init() +{ +} + +void QLowEnergyControllerPrivateWinRTNew::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 (state == QLowEnergyController::ConnectingState + && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { + setState(QLowEnergyController::ConnectedState); + emit q->connected(); + } else if (state == QLowEnergyController::ConnectedState + && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Disconnected) { + setError(QLowEnergyController::RemoteHostClosedError); + 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 doesn't provide any explicit connect/reconnect. We need to 'start using' the device + // and windows will initiate connection as a cause of that. + if (serviceCount == 0) { + // If we don't have any services discovered yet (for devices not paired), the simplest + // way to initiate connect is to start discovering services. It's not exactly how Qt API + // expects it to be but IMHO doesn't do any harm either. Services will already be discovered + // when coonnection state changes to 'connected'. + ComPtr<IBluetoothLEDevice3> device3; + hr = mDevice.As(&device3); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IAsyncOperation<GenericAttributeProfile::GattDeviceServicesResult *>> asyncResult; + hr = device3->GetGattServicesAsync(&asyncResult); + Q_ASSERT_SUCCEEDED(hr); + hr = asyncResult->put_Completed( + Callback<IAsyncOperationCompletedHandler<GenericAttributeProfile::GattDeviceServicesResult *>>( + [this, q](IAsyncOperation<GenericAttributeProfile::GattDeviceServicesResult *> *, AsyncStatus status) { + if (status != AsyncStatus::Completed) { + qCDebug(QT_BT_WINRT) << "Could not obtain services"; + return S_OK; + } + setState(QLowEnergyController::ConnectedState); + emit q->connected(); + return S_OK; + }).Get()); + Q_ASSERT_SUCCEEDED(hr); + } else { + // 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 if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Connecting to device failed: " + << qt_error_string(hr); + setError(QLowEnergyController::ConnectionError); + setState(QLowEnergyController::UnconnectedState); + return; + } + 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()); + if (hr == E_INVALIDARG) { + // E_INVALIDARG happens when user tries to connect to a device that was paired + // before but is not available. + qCDebug(QT_BT_WINRT) << "Could not obtain characteristic read result that triggers" + "device connection. Is the device reachable?"; + setError(QLowEnergyController::ConnectionError); + setState(QLowEnergyController::UnconnectedState); + return; + } + 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 QLowEnergyControllerPrivateWinRTNew::disconnectFromDevice() +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__; + Q_Q(QLowEnergyController); + setState(QLowEnergyController::UnconnectedState); + emit q->disconnected(); + if (mDevice && mStatusChangedToken.value) { + mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); + mStatusChangedToken.value = 0; + } +} + +ComPtr<IGattDeviceService> QLowEnergyControllerPrivateWinRTNew::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> QLowEnergyControllerPrivateWinRTNew::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 QLowEnergyControllerPrivateWinRTNew::registerForValueChanges(const QBluetoothUuid &serviceUuid, + const QBluetoothUuid &charUuid) +{ + qCDebug(QT_BT_WINRT) << "Registering characteristic" << charUuid << "in service" + << serviceUuid << "for value changes"; + for (const ValueChangedEntry &entry : qAsConst(mValueChangedTokens)) { + GUID guuid; + HRESULT hr; + hr = entry.characteristic->get_Uuid(&guuid); + Q_ASSERT_SUCCEEDED(hr); + if (QBluetoothUuid(guuid) == charUuid) + return; + } + ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(serviceUuid, charUuid); + + EventRegistrationToken token; + HRESULT hr; + hr = characteristic->add_ValueChanged( + Callback<ValueChangedHandler>( + [this](IGattCharacteristic *characteristic, IGattValueChangedEventArgs *args) { + HRESULT hr; + quint16 handle; + hr = characteristic->get_AttributeHandle(&handle); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IBuffer> buffer; + hr = args->get_CharacteristicValue(&buffer); + Q_ASSERT_SUCCEEDED(hr); + characteristicChanged(handle, byteArrayFromBuffer(buffer)); + return S_OK; + }).Get(), &token); + Q_ASSERT_SUCCEEDED(hr); + mValueChangedTokens.append(ValueChangedEntry(characteristic, token)); + qCDebug(QT_BT_WINRT) << "Characteristic" << charUuid << "in service" + << serviceUuid << "registered for value changes"; +} + +void QLowEnergyControllerPrivateWinRTNew::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); + // Some devices return ERROR_ACCESS_DISABLED_BY_POLICY + if (FAILED(hr)) + return; + + 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 QLowEnergyControllerPrivateWinRTNew::discoverServices() +{ + Q_Q(QLowEnergyController); + + qCDebug(QT_BT_WINRT) << "Service discovery initiated"; + + ComPtr<IBluetoothLEDevice3> device3; + HRESULT hr = mDevice.As(&device3); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IAsyncOperation<GenericAttributeProfile::GattDeviceServicesResult *>> asyncResult; + hr = device3->GetGattServicesAsync(&asyncResult); + Q_ASSERT_SUCCEEDED(hr); + hr = QEventDispatcherWinRT::runOnXamlThread( [asyncResult, q, this] () { + HRESULT hr = asyncResult->put_Completed( + Callback<IAsyncOperationCompletedHandler<GenericAttributeProfile::GattDeviceServicesResult *>>( + [this, q](IAsyncOperation<GenericAttributeProfile::GattDeviceServicesResult *> *, AsyncStatus status) { + if (status != AsyncStatus::Completed) { + qCDebug(QT_BT_WINRT) << "Could not obtain services"; + return S_OK; + } + 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(); + + return S_OK; + }).Get()); + Q_ASSERT_SUCCEEDED(hr); + return hr; + }); + Q_ASSERT_SUCCEEDED(hr); +} + +void QLowEnergyControllerPrivateWinRTNew::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); + if (FAILED(hr)) { // ERROR_ACCESS_DISABLED_BY_POLICY + qCDebug(QT_BT_WINRT) << "Could not obtain included services list for" << service; + pointer->setError(QLowEnergyService::UnknownError); + pointer->setState(QLowEnergyService::InvalidService); + return; + } + 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; + } + + QWinRTLowEnergyServiceHandlerNew *worker + = new QWinRTLowEnergyServiceHandlerNew(service, deviceService2); + QThread *thread = new QThread; + worker->moveToThread(thread); + connect(thread, &QThread::started, worker, &QWinRTLowEnergyServiceHandlerNew::obtainCharList); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + connect(thread, &QThread::finished, worker, &QObject::deleteLater); + connect(worker, &QWinRTLowEnergyServiceHandlerNew::charListObtained, + [this, thread](const QBluetoothUuid &service, QHash<QLowEnergyHandle, + 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(); + return; + } + + QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service); + pointer->startHandle = startHandle; + pointer->endHandle = endHandle; + pointer->characteristicList = charList; + + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([indicateChars, service, this]() { + for (const QBluetoothUuid &indicateChar : qAsConst(indicateChars)) + registerForValueChanges(service, indicateChar); + return S_OK; + }); + Q_ASSERT_SUCCEEDED(hr); + + pointer->setState(QLowEnergyService::ServiceDiscovered); + thread->exit(0); + }); + thread->start(); +} + +void QLowEnergyControllerPrivateWinRTNew::startAdvertising( + const QLowEnergyAdvertisingParameters &, + const QLowEnergyAdvertisingData &, + const QLowEnergyAdvertisingData &) +{ + setError(QLowEnergyController::AdvertisingError); + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivateWinRTNew::stopAdvertising() +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivateWinRTNew::requestConnectionUpdate(const QLowEnergyConnectionParameters &) +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivateWinRTNew::readCharacteristic( + const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle) +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle; + Q_ASSERT(!service.isNull()); + if (role == QLowEnergyController::PeripheralRole) { + service->setError(QLowEnergyService::CharacteristicReadError); + Q_UNIMPLEMENTED(); + return; + } + + 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 QLowEnergyControllerPrivateWinRTNew::readDescriptor( + const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descHandle) +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle; + Q_ASSERT(!service.isNull()); + if (role == QLowEnergyController::PeripheralRole) { + service->setError(QLowEnergyService::DescriptorReadError); + Q_UNIMPLEMENTED(); + return; + } + + 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()); + Q_ASSERT_SUCCEEDED(hr); + return S_OK; + } + }); + Q_ASSERT_SUCCEEDED(hr); +} + +void QLowEnergyControllerPrivateWinRTNew::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 (role == QLowEnergyController::PeripheralRole) { + service->setError(QLowEnergyService::CharacteristicWriteError); + Q_UNIMPLEMENTED(); + return; + } + 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 quint32 length = quint32(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, writeWithResponse, 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); + if (writeWithResponse) + 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 QLowEnergyControllerPrivateWinRTNew::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 (role == QLowEnergyController::PeripheralRole) { + service->setError(QLowEnergyService::DescriptorWriteError); + Q_UNIMPLEMENTED(); + return; + } + + 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 { + qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle + << "write operation failed: Invalid value"; + service->setError(QLowEnergyService::DescriptorWriteError); + return S_OK; + } + 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 quint32 length = quint32(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 QLowEnergyControllerPrivateWinRTNew::addToGenericAttributeList(const QLowEnergyServiceData &, + QLowEnergyHandle) +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivateWinRTNew::characteristicChanged( + quint16 charHandle, const QByteArray &data) +{ + QSharedPointer<QLowEnergyServicePrivate> service = + serviceForHandle(charHandle); + if (service.isNull()) + return; + + qCDebug(QT_BT_WINRT) << "Characteristic change notification" << service->uuid + << charHandle << data.toHex(); + + QLowEnergyCharacteristic characteristic = characteristicForHandle(charHandle); + if (!characteristic.isValid()) { + qCWarning(QT_BT_WINRT) << "characteristicChanged: Cannot find characteristic"; + return; + } + + // only update cache when property is readable. Otherwise it remains + // empty. + if (characteristic.properties() & QLowEnergyCharacteristic::Read) + updateValueOfCharacteristic(characteristic.attributeHandle(), + data, false); + emit service->characteristicChanged(characteristic, data); +} + +QT_END_NAMESPACE + +#include "qlowenergycontroller_winrt_new.moc" diff --git a/src/bluetooth/qlowenergycontroller_winrt_new_p.h b/src/bluetooth/qlowenergycontroller_winrt_new_p.h new file mode 100644 index 00000000..716d2d07 --- /dev/null +++ b/src/bluetooth/qlowenergycontroller_winrt_new_p.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QLOWENERGYCONTROLLERPRIVATEWINRT_NEW_P_H +#define QLOWENERGYCONTROLLERPRIVATEWINRT_NEW_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 <qglobal.h> +#include <QtCore/QQueue> +#include <QtCore/QVector> +#include <QtBluetooth/qbluetooth.h> +#include <QtBluetooth/qlowenergycharacteristic.h> +#include <QtBluetooth/qlowenergyservicedata.h> +#include "qlowenergycontroller.h" +#include "qlowenergycontrollerbase_p.h" + +#include <wrl.h> +#include <windows.devices.bluetooth.h> + +#include <functional> + +QT_BEGIN_NAMESPACE + +class QLowEnergyServiceData; +class QTimer; +class QWinRTLowEnergyServiceHandler; + +extern void registerQLowEnergyControllerMetaType(); + +QLowEnergyControllerPrivate *createWinRTLowEnergyController(); + +class QLowEnergyControllerPrivateWinRTNew final : public QLowEnergyControllerPrivate +{ + Q_OBJECT +public: + QLowEnergyControllerPrivateWinRTNew(); + ~QLowEnergyControllerPrivateWinRTNew() override; + + void init() override; + + void connectToDevice() override; + void disconnectFromDevice() override; + + void discoverServices() override; + void discoverServiceDetails(const QBluetoothUuid &service) override; + + void startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData) override; + void stopAdvertising() override; + + void requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms) override; + + // read data + void readCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle) override; + void readDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle) override; + + // write data + void writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QByteArray &newValue, QLowEnergyService::WriteMode mode) override; + void writeDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle, + const QByteArray &newValue) override; + + void addToGenericAttributeList(const QLowEnergyServiceData &service, + QLowEnergyHandle startHandle) override; + +private slots: + void characteristicChanged(quint16 charHandle, const QByteArray &data); + +private: + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> mDevice; + EventRegistrationToken mStatusChangedToken; + struct ValueChangedEntry { + ValueChangedEntry() {} + ValueChangedEntry(Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattCharacteristic> c, + EventRegistrationToken t) + : characteristic(c) + , token(t) + { + } + + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattCharacteristic> characteristic; + EventRegistrationToken token; + }; + QVector<ValueChangedEntry> mValueChangedTokens; + + 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 registerForValueChanges(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid); + + void obtainIncludedServices(QSharedPointer<QLowEnergyServicePrivate> servicePointer, + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDeviceService> nativeService); + +}; + +QT_END_NAMESPACE + +#endif // QLOWENERGYCONTROLLERPRIVATEWINRT_NEW_P_H diff --git a/src/bluetooth/qlowenergydescriptor.h b/src/bluetooth/qlowenergydescriptor.h index 9dd0cc20..62ca5fd3 100644 --- a/src/bluetooth/qlowenergydescriptor.h +++ b/src/bluetooth/qlowenergydescriptor.h @@ -85,6 +85,7 @@ protected: friend class QLowEnergyControllerPrivateCommon; friend class QLowEnergyControllerPrivateOSX; friend class QLowEnergyControllerPrivateWinRT; + friend class QLowEnergyControllerPrivateWinRTNew; QLowEnergyDescriptorPrivate *data = nullptr; QLowEnergyDescriptor(QSharedPointer<QLowEnergyServicePrivate> p, |