summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qlowenergycontroller_win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/qlowenergycontroller_win.cpp')
-rw-r--r--src/bluetooth/qlowenergycontroller_win.cpp1356
1 files changed, 1356 insertions, 0 deletions
diff --git a/src/bluetooth/qlowenergycontroller_win.cpp b/src/bluetooth/qlowenergycontroller_win.cpp
new file mode 100644
index 00000000..ced69685
--- /dev/null
+++ b/src/bluetooth/qlowenergycontroller_win.cpp
@@ -0,0 +1,1356 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.com>
+** 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_win_p.h"
+#include "qbluetoothdevicediscoveryagent_p.h"
+
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QIODevice> // for open modes
+#include <QtCore/QEvent>
+#include <QtCore/QMutex>
+#include <QtCore/QThread>
+#include <QtCore/QDataStream>
+#include <QtCore/QCoreApplication>
+
+#include <algorithm> // for std::max
+
+#include <SetupAPI.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS)
+
+Q_GLOBAL_STATIC(QLibrary, bluetoothapis)
+
+Q_GLOBAL_STATIC(QVector<QLowEnergyControllerPrivateWin32 *>, qControllers)
+static QMutex controllersGuard(QMutex::NonRecursive);
+
+const QEvent::Type CharactericticValueEventType = static_cast<QEvent::Type>(QEvent::User + 1);
+
+class CharactericticValueEvent : public QEvent
+{
+public:
+ explicit CharactericticValueEvent(const PBLUETOOTH_GATT_VALUE_CHANGED_EVENT gattValueChangedEvent)
+ : QEvent(CharactericticValueEventType)
+ , m_handle(0)
+ {
+ if (!gattValueChangedEvent || gattValueChangedEvent->CharacteristicValueDataSize == 0)
+ return;
+
+ m_handle = gattValueChangedEvent->ChangedAttributeHandle;
+
+ const PBTH_LE_GATT_CHARACTERISTIC_VALUE gattValue = gattValueChangedEvent->CharacteristicValue;
+ if (!gattValue)
+ return;
+
+ m_value = QByteArray(reinterpret_cast<const char *>(&gattValue->Data[0]),
+ int(gattValue->DataSize));
+ }
+
+ QByteArray m_value;
+ QLowEnergyHandle m_handle;
+};
+
+// Bit masks of ClientCharacteristicConfiguration value, see btle spec.
+namespace ClientCharacteristicConfigurationValue {
+enum { UseNotifications = 0x1, UseIndications = 0x2 };
+}
+
+static bool gattFunctionsResolved = false;
+
+static QBluetoothAddress getDeviceAddress(const QString &servicePath)
+{
+ const int firstbound = servicePath.lastIndexOf(QStringLiteral("_"));
+ const int lastbound = servicePath.indexOf(QLatin1Char('#'), firstbound);
+ const QString hex = servicePath.mid(firstbound + 1, lastbound - firstbound - 1);
+ bool ok = false;
+ return QBluetoothAddress(hex.toULongLong(&ok, 16));
+}
+
+static QString getServiceSystemPath(const QBluetoothAddress &deviceAddress,
+ const QBluetoothUuid &serviceUuid, int *systemErrorCode)
+{
+ const HDEVINFO deviceInfoSet = ::SetupDiGetClassDevs(
+ reinterpret_cast<const GUID *>(&serviceUuid),
+ nullptr,
+ nullptr,
+ DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+
+ if (deviceInfoSet == INVALID_HANDLE_VALUE) {
+ *systemErrorCode = int(::GetLastError());
+ return QString();
+ }
+
+ QString foundSystemPath;
+ DWORD index = 0;
+
+ for (;;) {
+ SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
+ ::ZeroMemory(&deviceInterfaceData, sizeof(deviceInterfaceData));
+ deviceInterfaceData.cbSize = sizeof(deviceInterfaceData);
+
+ if (!::SetupDiEnumDeviceInterfaces(
+ deviceInfoSet,
+ nullptr,
+ reinterpret_cast<const GUID *>(&serviceUuid),
+ index++,
+ &deviceInterfaceData)) {
+ *systemErrorCode = int(::GetLastError());
+ break;
+ }
+
+ DWORD deviceInterfaceDetailDataSize = 0;
+ if (!::SetupDiGetDeviceInterfaceDetail(
+ deviceInfoSet,
+ &deviceInterfaceData,
+ nullptr,
+ deviceInterfaceDetailDataSize,
+ &deviceInterfaceDetailDataSize,
+ nullptr)) {
+ const int error = int(::GetLastError());
+ if (error != ERROR_INSUFFICIENT_BUFFER) {
+ *systemErrorCode = error;
+ break;
+ }
+ }
+
+ SP_DEVINFO_DATA deviceInfoData;
+ ::ZeroMemory(&deviceInfoData, sizeof(deviceInfoData));
+ deviceInfoData.cbSize = sizeof(deviceInfoData);
+
+ QByteArray deviceInterfaceDetailDataBuffer(
+ int(deviceInterfaceDetailDataSize), 0);
+
+ PSP_INTERFACE_DEVICE_DETAIL_DATA deviceInterfaceDetailData =
+ reinterpret_cast<PSP_INTERFACE_DEVICE_DETAIL_DATA>
+ (deviceInterfaceDetailDataBuffer.data());
+
+ deviceInterfaceDetailData->cbSize =
+ sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
+
+ if (!::SetupDiGetDeviceInterfaceDetail(
+ deviceInfoSet,
+ &deviceInterfaceData,
+ deviceInterfaceDetailData,
+ DWORD(deviceInterfaceDetailDataBuffer.size()),
+ &deviceInterfaceDetailDataSize,
+ &deviceInfoData)) {
+ *systemErrorCode = int(::GetLastError());
+ break;
+ }
+
+ // We need to check on required device address which contains in a
+ // system path. As it is not enough to use only service UUID for this.
+ const auto candidateSystemPath = QString::fromWCharArray(deviceInterfaceDetailData->DevicePath);
+ const auto candidateDeviceAddress = getDeviceAddress(candidateSystemPath);
+ if (candidateDeviceAddress == deviceAddress) {
+ foundSystemPath = candidateSystemPath;
+ *systemErrorCode = NO_ERROR;
+ break;
+ }
+ }
+
+ ::SetupDiDestroyDeviceInfoList(deviceInfoSet);
+ return foundSystemPath;
+}
+
+static HANDLE openSystemDevice(
+ const QString &systemPath, QIODevice::OpenMode openMode, int *systemErrorCode)
+{
+ DWORD desiredAccess = 0;
+ DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+
+ if (openMode & QIODevice::ReadOnly) {
+ desiredAccess |= GENERIC_READ;
+ }
+
+ if (openMode & QIODevice::WriteOnly) {
+ desiredAccess |= GENERIC_WRITE;
+ shareMode &= ~DWORD(FILE_SHARE_WRITE);
+ }
+
+ const HANDLE hDevice = ::CreateFile(
+ reinterpret_cast<const wchar_t *>(systemPath.utf16()),
+ desiredAccess,
+ shareMode,
+ nullptr,
+ OPEN_EXISTING,
+ 0,
+ nullptr);
+
+ *systemErrorCode = (INVALID_HANDLE_VALUE == hDevice)
+ ? int(::GetLastError()) : NO_ERROR;
+ return hDevice;
+}
+
+static HANDLE openSystemService(const QBluetoothAddress &deviceAddress,
+ const QBluetoothUuid &service, QIODevice::OpenMode openMode, int *systemErrorCode)
+{
+ const QString serviceSystemPath = getServiceSystemPath(
+ deviceAddress, service, systemErrorCode);
+
+ if (*systemErrorCode != NO_ERROR)
+ return INVALID_HANDLE_VALUE;
+
+ const HANDLE hService = openSystemDevice(
+ serviceSystemPath, openMode, systemErrorCode);
+
+ if (*systemErrorCode != NO_ERROR)
+ return INVALID_HANDLE_VALUE;
+
+ return hService;
+}
+
+static void closeSystemDevice(HANDLE hDevice)
+{
+ if (hDevice && hDevice != INVALID_HANDLE_VALUE)
+ ::CloseHandle(hDevice);
+}
+
+static QVector<BTH_LE_GATT_SERVICE> enumeratePrimaryGattServices(
+ HANDLE hDevice, int *systemErrorCode)
+{
+ if (!gattFunctionsResolved) {
+ *systemErrorCode = ERROR_NOT_SUPPORTED;
+ return QVector<BTH_LE_GATT_SERVICE>();
+ }
+
+ QVector<BTH_LE_GATT_SERVICE> foundServices;
+ USHORT servicesCount = 0;
+ for (;;) {
+ const HRESULT hr = ::BluetoothGATTGetServices(
+ hDevice,
+ servicesCount,
+ foundServices.isEmpty() ? nullptr : &foundServices[0],
+ &servicesCount,
+ BLUETOOTH_GATT_FLAG_NONE);
+
+ if (SUCCEEDED(hr)) {
+ *systemErrorCode = NO_ERROR;
+ return foundServices;
+ } else {
+ const int error = WIN32_FROM_HRESULT(hr);
+ if (error == ERROR_MORE_DATA) {
+ foundServices.resize(servicesCount);
+ } else {
+ *systemErrorCode = error;
+ return QVector<BTH_LE_GATT_SERVICE>();
+ }
+ }
+ }
+}
+
+static QVector<BTH_LE_GATT_CHARACTERISTIC> enumerateGattCharacteristics(
+ HANDLE hService, PBTH_LE_GATT_SERVICE gattService, int *systemErrorCode)
+{
+ if (!gattFunctionsResolved) {
+ *systemErrorCode = ERROR_NOT_SUPPORTED;
+ return QVector<BTH_LE_GATT_CHARACTERISTIC>();
+ }
+
+ QVector<BTH_LE_GATT_CHARACTERISTIC> foundCharacteristics;
+ USHORT characteristicsCount = 0;
+ for (;;) {
+ const HRESULT hr = ::BluetoothGATTGetCharacteristics(
+ hService,
+ gattService,
+ characteristicsCount,
+ foundCharacteristics.isEmpty() ? nullptr : &foundCharacteristics[0],
+ &characteristicsCount,
+ BLUETOOTH_GATT_FLAG_NONE);
+
+ if (SUCCEEDED(hr)) {
+ *systemErrorCode = NO_ERROR;
+ return foundCharacteristics;
+ } else {
+ const int error = WIN32_FROM_HRESULT(hr);
+ if (error == ERROR_MORE_DATA) {
+ foundCharacteristics.resize(characteristicsCount);
+ } else {
+ *systemErrorCode = error;
+ return QVector<BTH_LE_GATT_CHARACTERISTIC>();
+ }
+ }
+ }
+}
+
+static QByteArray getGattCharacteristicValue(
+ HANDLE hService, PBTH_LE_GATT_CHARACTERISTIC gattCharacteristic, int *systemErrorCode)
+{
+ if (!gattFunctionsResolved) {
+ *systemErrorCode = ERROR_NOT_SUPPORTED;
+ return QByteArray();
+ }
+
+ QByteArray valueBuffer;
+ USHORT valueBufferSize = 0;
+ for (;;) {
+ const auto valuePtr = valueBuffer.isEmpty()
+ ? nullptr
+ : reinterpret_cast<PBTH_LE_GATT_CHARACTERISTIC_VALUE>(valueBuffer.data());
+
+ const HRESULT hr = ::BluetoothGATTGetCharacteristicValue(
+ hService,
+ gattCharacteristic,
+ valueBufferSize,
+ valuePtr,
+ &valueBufferSize,
+ BLUETOOTH_GATT_FLAG_NONE);
+
+ if (SUCCEEDED(hr)) {
+ *systemErrorCode = NO_ERROR;
+ return QByteArray(reinterpret_cast<const char *>(&valuePtr->Data[0]),
+ int(valuePtr->DataSize));
+ } else {
+ const int error = WIN32_FROM_HRESULT(hr);
+ if (error == ERROR_MORE_DATA) {
+ valueBuffer.resize(valueBufferSize);
+ valueBuffer.fill(0);
+ } else {
+ *systemErrorCode = error;
+ return QByteArray();
+ }
+ }
+ }
+}
+
+static void setGattCharacteristicValue(
+ HANDLE hService, PBTH_LE_GATT_CHARACTERISTIC gattCharacteristic,
+ const QByteArray &value, DWORD flags, int *systemErrorCode)
+{
+ if (!gattFunctionsResolved) {
+ *systemErrorCode = ERROR_NOT_SUPPORTED;
+ return;
+ }
+
+ QByteArray valueBuffer;
+ QDataStream out(&valueBuffer, QIODevice::WriteOnly);
+ ULONG dataSize = ULONG(value.size());
+ out.writeRawData(reinterpret_cast<const char *>(&dataSize), sizeof(dataSize));
+ out.writeRawData(value.constData(), value.size());
+
+ BTH_LE_GATT_RELIABLE_WRITE_CONTEXT reliableWriteContext = 0;
+
+ const HRESULT hr = ::BluetoothGATTSetCharacteristicValue(
+ hService,
+ gattCharacteristic,
+ reinterpret_cast<PBTH_LE_GATT_CHARACTERISTIC_VALUE>(valueBuffer.data()),
+ reliableWriteContext,
+ flags);
+
+ if (SUCCEEDED(hr))
+ *systemErrorCode = NO_ERROR;
+ else
+ *systemErrorCode = WIN32_FROM_HRESULT(hr);
+}
+
+static QVector<BTH_LE_GATT_DESCRIPTOR> enumerateGattDescriptors(
+ HANDLE hService, PBTH_LE_GATT_CHARACTERISTIC gattCharacteristic, int *systemErrorCode)
+{
+ if (!gattFunctionsResolved) {
+ *systemErrorCode = ERROR_NOT_SUPPORTED;
+ return QVector<BTH_LE_GATT_DESCRIPTOR>();
+ }
+
+ QVector<BTH_LE_GATT_DESCRIPTOR> foundDescriptors;
+ USHORT descriptorsCount = 0;
+ for (;;) {
+ const HRESULT hr = ::BluetoothGATTGetDescriptors(
+ hService,
+ gattCharacteristic,
+ descriptorsCount,
+ foundDescriptors.isEmpty() ? nullptr : &foundDescriptors[0],
+ &descriptorsCount,
+ BLUETOOTH_GATT_FLAG_NONE);
+
+ if (SUCCEEDED(hr)) {
+ *systemErrorCode = NO_ERROR;
+ return foundDescriptors;
+ } else {
+ const int error = WIN32_FROM_HRESULT(hr);
+ if (error == ERROR_MORE_DATA) {
+ foundDescriptors.resize(descriptorsCount);
+ } else {
+ *systemErrorCode = error;
+ return QVector<BTH_LE_GATT_DESCRIPTOR>();
+ }
+ }
+ }
+}
+
+static QByteArray getGattDescriptorValue(
+ HANDLE hService, PBTH_LE_GATT_DESCRIPTOR gattDescriptor, int *systemErrorCode)
+{
+ if (!gattFunctionsResolved) {
+ *systemErrorCode = ERROR_NOT_SUPPORTED;
+ return QByteArray();
+ }
+
+ QByteArray valueBuffer;
+ USHORT valueBufferSize = 0;
+ for (;;) {
+ const auto valuePtr = valueBuffer.isEmpty()
+ ? nullptr
+ : reinterpret_cast<PBTH_LE_GATT_DESCRIPTOR_VALUE>(valueBuffer.data());
+
+ const HRESULT hr = ::BluetoothGATTGetDescriptorValue(
+ hService,
+ gattDescriptor,
+ valueBufferSize,
+ valuePtr,
+ &valueBufferSize,
+ BLUETOOTH_GATT_FLAG_NONE);
+
+ if (SUCCEEDED(hr)) {
+ *systemErrorCode = NO_ERROR;
+ if (gattDescriptor->DescriptorType == CharacteristicUserDescription) {
+ QString valueString = QString::fromUtf16(reinterpret_cast<const ushort *>(&valuePtr->Data[0]),
+ valuePtr->DataSize/2);
+ return valueString.toUtf8();
+ }
+ return QByteArray(reinterpret_cast<const char *>(&valuePtr->Data[0]),
+ int(valuePtr->DataSize));
+ } else {
+ const int error = WIN32_FROM_HRESULT(hr);
+ if (error == ERROR_MORE_DATA) {
+ valueBuffer.resize(valueBufferSize);
+ valueBuffer.fill(0);
+ } else {
+ *systemErrorCode = error;
+ return QByteArray();
+ }
+ }
+ }
+}
+
+static void setGattDescriptorValue(
+ HANDLE hService, PBTH_LE_GATT_DESCRIPTOR gattDescriptor,
+ QByteArray value, int *systemErrorCode)
+{
+ if (!gattFunctionsResolved) {
+ *systemErrorCode = ERROR_NOT_SUPPORTED;
+ return;
+ }
+
+ const int requiredValueBufferSize = int(sizeof(BTH_LE_GATT_DESCRIPTOR_VALUE))
+ + value.size();
+
+ QByteArray valueBuffer(requiredValueBufferSize, 0);
+
+ PBTH_LE_GATT_DESCRIPTOR_VALUE gattValue = reinterpret_cast<
+ PBTH_LE_GATT_DESCRIPTOR_VALUE>(valueBuffer.data());
+
+ gattValue->DescriptorType = gattDescriptor->DescriptorType;
+
+ if (gattValue->DescriptorType == ClientCharacteristicConfiguration) {
+ QDataStream in(value);
+ quint8 u;
+ in >> u;
+
+ // We need to setup appropriate fields that allow to subscribe for events.
+ gattValue->ClientCharacteristicConfiguration.IsSubscribeToNotification =
+ bool(u & ClientCharacteristicConfigurationValue::UseNotifications);
+ gattValue->ClientCharacteristicConfiguration.IsSubscribeToIndication =
+ bool(u & ClientCharacteristicConfigurationValue::UseIndications);
+ }
+
+ gattValue->DataSize = ULONG(value.size());
+ ::memcpy(gattValue->Data, value.constData(), size_t(value.size()));
+
+ const HRESULT hr = ::BluetoothGATTSetDescriptorValue(
+ hService,
+ gattDescriptor,
+ gattValue,
+ BLUETOOTH_GATT_FLAG_NONE);
+
+ if (SUCCEEDED(hr))
+ *systemErrorCode = NO_ERROR;
+ else
+ *systemErrorCode = WIN32_FROM_HRESULT(hr);
+}
+
+static void WINAPI eventChangedCallbackEntry(
+ BTH_LE_GATT_EVENT_TYPE eventType, PVOID eventOutParameter, PVOID context)
+{
+ if ((eventType != CharacteristicValueChangedEvent) || !eventOutParameter || !context)
+ return;
+
+ QMutexLocker locker(&controllersGuard);
+ const auto target = static_cast<QLowEnergyControllerPrivateWin32 *>(context);
+ if (!qControllers->contains(target))
+ return;
+
+ CharactericticValueEvent *e = new CharactericticValueEvent(
+ reinterpret_cast<const PBLUETOOTH_GATT_VALUE_CHANGED_EVENT>(eventOutParameter));
+
+ QCoreApplication::postEvent(target, e);
+}
+
+static HANDLE registerEvent(
+ HANDLE hService, BTH_LE_GATT_CHARACTERISTIC gattCharacteristic,
+ PVOID context, int *systemErrorCode)
+{
+ if (!gattFunctionsResolved) {
+ *systemErrorCode = ERROR_NOT_SUPPORTED;
+ return INVALID_HANDLE_VALUE;
+ }
+
+ HANDLE hEvent = INVALID_HANDLE_VALUE;
+
+ BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION registration;
+ ::ZeroMemory(&registration, sizeof(registration));
+ registration.NumCharacteristics = 1;
+ registration.Characteristics[0] = gattCharacteristic;
+
+ const HRESULT hr = ::BluetoothGATTRegisterEvent(
+ hService,
+ CharacteristicValueChangedEvent,
+ &registration,
+ eventChangedCallbackEntry,
+ context,
+ &hEvent,
+ BLUETOOTH_GATT_FLAG_NONE);
+
+ if (SUCCEEDED(hr))
+ *systemErrorCode = NO_ERROR;
+ else
+ *systemErrorCode = WIN32_FROM_HRESULT(hr);
+
+ return hEvent;
+}
+
+static void unregisterEvent(HANDLE hEvent, int *systemErrorCode)
+{
+ if (!gattFunctionsResolved) {
+ *systemErrorCode = ERROR_NOT_SUPPORTED;
+ return;
+ }
+
+ const HRESULT hr = ::BluetoothGATTUnregisterEvent(
+ hEvent,
+ BLUETOOTH_GATT_FLAG_NONE);
+
+ if (SUCCEEDED(hr))
+ *systemErrorCode = NO_ERROR;
+ else
+ *systemErrorCode = WIN32_FROM_HRESULT(hr);
+}
+
+static QBluetoothUuid qtBluetoothUuidFromNativeLeUuid(const BTH_LE_UUID &uuid)
+{
+ return uuid.IsShortUuid ? QBluetoothUuid(uuid.Value.ShortUuid)
+ : QBluetoothUuid(uuid.Value.LongUuid);
+}
+
+static BTH_LE_UUID nativeLeUuidFromQtBluetoothUuid(const QBluetoothUuid &uuid)
+{
+ BTH_LE_UUID gattUuid;
+ ::ZeroMemory(&gattUuid, sizeof(gattUuid));
+ if (uuid.minimumSize() == 2) {
+ gattUuid.IsShortUuid = TRUE;
+ gattUuid.Value.ShortUuid = USHORT(uuid.data1); // other fields should be empty!
+ } else {
+ gattUuid.Value.LongUuid = uuid;
+ }
+ return gattUuid;
+}
+
+static BTH_LE_GATT_CHARACTERISTIC recoverNativeLeGattCharacteristic(
+ QLowEnergyHandle serviceHandle, QLowEnergyHandle characteristicHandle,
+ const QLowEnergyServicePrivate::CharData &characteristicData)
+{
+ BTH_LE_GATT_CHARACTERISTIC gattCharacteristic;
+
+ gattCharacteristic.ServiceHandle = serviceHandle;
+ gattCharacteristic.AttributeHandle = characteristicHandle;
+ gattCharacteristic.CharacteristicValueHandle = characteristicData.valueHandle;
+
+ gattCharacteristic.CharacteristicUuid = nativeLeUuidFromQtBluetoothUuid(
+ characteristicData.uuid);
+
+ gattCharacteristic.HasExtendedProperties = bool(characteristicData.properties
+ & QLowEnergyCharacteristic::ExtendedProperty);
+ gattCharacteristic.IsBroadcastable = bool(characteristicData.properties
+ & QLowEnergyCharacteristic::Broadcasting);
+ gattCharacteristic.IsIndicatable = bool(characteristicData.properties
+ & QLowEnergyCharacteristic::Indicate);
+ gattCharacteristic.IsNotifiable = bool(characteristicData.properties
+ & QLowEnergyCharacteristic::Notify);
+ gattCharacteristic.IsReadable = bool(characteristicData.properties
+ & QLowEnergyCharacteristic::Read);
+ gattCharacteristic.IsSignedWritable = bool(characteristicData.properties
+ & QLowEnergyCharacteristic::WriteSigned);
+ gattCharacteristic.IsWritable = bool(characteristicData.properties
+ & QLowEnergyCharacteristic::Write);
+ gattCharacteristic.IsWritableWithoutResponse = bool(characteristicData.properties
+ & QLowEnergyCharacteristic::WriteNoResponse);
+
+ return gattCharacteristic;
+}
+
+static BTH_LE_GATT_DESCRIPTOR_TYPE nativeLeGattDescriptorTypeFromUuid(
+ const QBluetoothUuid &uuid)
+{
+ switch (uuid.toUInt16()) {
+ case QBluetoothUuid::CharacteristicExtendedProperties:
+ return CharacteristicExtendedProperties;
+ case QBluetoothUuid::CharacteristicUserDescription:
+ return CharacteristicUserDescription;
+ case QBluetoothUuid::ClientCharacteristicConfiguration:
+ return ClientCharacteristicConfiguration;
+ case QBluetoothUuid::ServerCharacteristicConfiguration:
+ return ServerCharacteristicConfiguration;
+ case QBluetoothUuid::CharacteristicPresentationFormat:
+ return CharacteristicFormat;
+ case QBluetoothUuid::CharacteristicAggregateFormat:
+ return CharacteristicAggregateFormat;
+ default:
+ return CustomDescriptor;
+ }
+}
+
+static BTH_LE_GATT_DESCRIPTOR recoverNativeLeGattDescriptor(
+ QLowEnergyHandle serviceHandle, QLowEnergyHandle characteristicHandle,
+ QLowEnergyHandle descriptorHandle,
+ const QLowEnergyServicePrivate::DescData &descriptorData)
+{
+ BTH_LE_GATT_DESCRIPTOR gattDescriptor;
+
+ gattDescriptor.ServiceHandle = serviceHandle;
+ gattDescriptor.CharacteristicHandle = characteristicHandle;
+ gattDescriptor.AttributeHandle = descriptorHandle;
+
+ gattDescriptor.DescriptorUuid = nativeLeUuidFromQtBluetoothUuid(
+ descriptorData.uuid);
+
+ gattDescriptor.DescriptorType = nativeLeGattDescriptorTypeFromUuid
+ (descriptorData.uuid);
+
+ return gattDescriptor;
+}
+
+void QLowEnergyControllerPrivateWin32::customEvent(QEvent *e)
+{
+ if (e->type() != CharactericticValueEventType)
+ return;
+
+ const CharactericticValueEvent *characteristicEvent
+ = static_cast<CharactericticValueEvent *>(e);
+
+ updateValueOfCharacteristic(characteristicEvent->m_handle,
+ characteristicEvent->m_value, false);
+
+ const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(
+ characteristicEvent->m_handle);
+ if (service.isNull())
+ return;
+
+ const QLowEnergyCharacteristic ch(service, characteristicEvent->m_handle);
+ emit service->characteristicChanged(ch, characteristicEvent->m_value);
+}
+
+QLowEnergyControllerPrivateWin32::QLowEnergyControllerPrivateWin32()
+ : QLowEnergyControllerPrivate()
+{
+ QMutexLocker locker(&controllersGuard);
+ qControllers()->append(this);
+
+ gattFunctionsResolved = resolveFunctions(bluetoothapis());
+ if (!gattFunctionsResolved) {
+ qCWarning(QT_BT_WINDOWS) << "LE is not supported on this OS";
+ return;
+ }
+}
+
+QLowEnergyControllerPrivateWin32::~QLowEnergyControllerPrivateWin32()
+{
+ QMutexLocker locker(&controllersGuard);
+ qControllers()->removeAll(this);
+}
+
+void QLowEnergyControllerPrivateWin32::init()
+{
+}
+
+void QLowEnergyControllerPrivateWin32::connectToDevice()
+{
+ // required to pass unit test on default backend
+ if (remoteDevice.isNull()) {
+ qWarning() << "Invalid/null remote device address";
+ setError(QLowEnergyController::UnknownRemoteDeviceError);
+ return;
+ }
+
+ if (!deviceSystemPath.isEmpty()) {
+ qCDebug(QT_BT_WINDOWS) << "Already is connected";
+ return;
+ }
+
+ setState(QLowEnergyController::ConnectingState);
+
+ deviceSystemPath =
+ QBluetoothDeviceDiscoveryAgentPrivate::discoveredLeDeviceSystemPath(
+ remoteDevice);
+
+ if (deviceSystemPath.isEmpty()) {
+ qCWarning(QT_BT_WINDOWS) << qt_error_string(ERROR_PATH_NOT_FOUND);
+ setError(QLowEnergyController::UnknownRemoteDeviceError);
+ setState(QLowEnergyController::UnconnectedState);
+ return;
+ }
+
+ setState(QLowEnergyController::ConnectedState);
+
+ thread = new QThread;
+ threadWorker = new ThreadWorker;
+ threadWorker->moveToThread(thread);
+ connect(threadWorker, &ThreadWorker::jobFinished, this, &QLowEnergyControllerPrivateWin32::jobFinished);
+ connect(thread, &QThread::finished, threadWorker, &ThreadWorker::deleteLater);
+ connect(thread, &QThread::finished, thread, &QThread::deleteLater);
+ thread->start();
+
+ Q_Q(QLowEnergyController);
+ emit q->connected();
+}
+
+void QLowEnergyControllerPrivateWin32::disconnectFromDevice()
+{
+ if (deviceSystemPath.isEmpty()) {
+ qCDebug(QT_BT_WINDOWS) << "Already is disconnected";
+ return;
+ }
+
+ setState(QLowEnergyController::ClosingState);
+ deviceSystemPath.clear();
+ setState(QLowEnergyController::UnconnectedState);
+
+ if (thread) {
+ disconnect(threadWorker, &ThreadWorker::jobFinished, this, &QLowEnergyControllerPrivateWin32::jobFinished);
+ thread->quit();
+ thread = nullptr;
+ }
+
+ for (const auto &servicePrivate: serviceList)
+ closeSystemDevice(servicePrivate->hService);
+
+ Q_Q(QLowEnergyController);
+ emit q->disconnected();
+}
+
+void QLowEnergyControllerPrivateWin32::discoverServices()
+{
+ int systemErrorCode = NO_ERROR;
+
+ const HANDLE hDevice = openSystemDevice(
+ deviceSystemPath, QIODevice::ReadOnly, &systemErrorCode);
+
+ if (systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << qt_error_string(systemErrorCode);
+ setError(QLowEnergyController::NetworkError);
+ setState(QLowEnergyController::ConnectedState);
+ return;
+ }
+
+ const QVector<BTH_LE_GATT_SERVICE> foundServices =
+ enumeratePrimaryGattServices(hDevice, &systemErrorCode);
+
+ closeSystemDevice(hDevice);
+
+ if (systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << qt_error_string(systemErrorCode);
+ setError(QLowEnergyController::NetworkError);
+ setState(QLowEnergyController::ConnectedState);
+ return;
+ }
+
+ setState(QLowEnergyController::DiscoveringState);
+
+ Q_Q(QLowEnergyController);
+
+ for (const BTH_LE_GATT_SERVICE &service : foundServices) {
+ const QBluetoothUuid uuid = qtBluetoothUuidFromNativeLeUuid(
+ service.ServiceUuid);
+ qCDebug(QT_BT_WINDOWS) << "Found uuid:" << uuid;
+
+ QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate();
+ priv->uuid = uuid;
+ priv->type = QLowEnergyService::PrimaryService;
+ priv->startHandle = service.AttributeHandle;
+ priv->setController(this);
+
+ QSharedPointer<QLowEnergyServicePrivate> pointer(priv);
+ serviceList.insert(uuid, pointer);
+
+ emit q->serviceDiscovered(uuid);
+ }
+
+ setState(QLowEnergyController::DiscoveredState);
+ emit q->discoveryFinished();
+}
+
+void QLowEnergyControllerPrivateWin32::discoverServiceDetails(
+ const QBluetoothUuid &service)
+{
+ if (!serviceList.contains(service)) {
+ qCWarning(QT_BT_WINDOWS) << "Discovery of unknown service" << service.toString()
+ << "not possible";
+ return;
+ }
+
+ const QSharedPointer<QLowEnergyServicePrivate> servicePrivate =
+ serviceList.value(service);
+
+ int systemErrorCode = NO_ERROR;
+
+ // Only open a service once and close it in the QLowEnergyServicePrivate destructor
+ if (!servicePrivate->hService || servicePrivate->hService == INVALID_HANDLE_VALUE) {
+ servicePrivate->hService = openSystemService(remoteDevice, service,
+ QIODevice::ReadOnly | QIODevice::WriteOnly,
+ &systemErrorCode);
+ if (systemErrorCode != NO_ERROR) {
+ servicePrivate->hService = openSystemService(remoteDevice, service,
+ QIODevice::ReadOnly,
+ &systemErrorCode);
+ }
+ }
+
+ if (systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service.toString()
+ << ":" << qt_error_string(systemErrorCode);
+ servicePrivate->setError(QLowEnergyService::UnknownError);
+ servicePrivate->setState(QLowEnergyService::DiscoveryRequired);
+ return;
+ }
+
+ // We assume that the service does not have any characteristics with descriptors.
+ servicePrivate->endHandle = servicePrivate->startHandle;
+
+ const QVector<BTH_LE_GATT_CHARACTERISTIC> foundCharacteristics =
+ enumerateGattCharacteristics(servicePrivate->hService, nullptr, &systemErrorCode);
+
+ if (systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << "Unable to get characteristics for service" << service.toString()
+ << ":" << qt_error_string(systemErrorCode);
+ servicePrivate->setError(QLowEnergyService::CharacteristicReadError);
+ servicePrivate->setState(QLowEnergyService::DiscoveryRequired);
+ return;
+ }
+
+ for (const BTH_LE_GATT_CHARACTERISTIC &gattCharacteristic : foundCharacteristics) {
+ const QLowEnergyHandle characteristicHandle = gattCharacteristic.AttributeHandle;
+
+ QLowEnergyServicePrivate::CharData detailsData;
+
+ detailsData.hValueChangeEvent = nullptr;
+
+ detailsData.uuid = qtBluetoothUuidFromNativeLeUuid(
+ gattCharacteristic.CharacteristicUuid);
+ detailsData.valueHandle = gattCharacteristic.CharacteristicValueHandle;
+
+ QLowEnergyCharacteristic::PropertyTypes properties = QLowEnergyCharacteristic::Unknown;
+ if (gattCharacteristic.HasExtendedProperties)
+ properties |= QLowEnergyCharacteristic::ExtendedProperty;
+ if (gattCharacteristic.IsBroadcastable)
+ properties |= QLowEnergyCharacteristic::Broadcasting;
+ if (gattCharacteristic.IsIndicatable)
+ properties |= QLowEnergyCharacteristic::Indicate;
+ if (gattCharacteristic.IsNotifiable)
+ properties |= QLowEnergyCharacteristic::Notify;
+ if (gattCharacteristic.IsReadable)
+ properties |= QLowEnergyCharacteristic::Read;
+ if (gattCharacteristic.IsSignedWritable)
+ properties |= QLowEnergyCharacteristic::WriteSigned;
+ if (gattCharacteristic.IsWritable)
+ properties |= QLowEnergyCharacteristic::Write;
+ if (gattCharacteristic.IsWritableWithoutResponse)
+ properties |= QLowEnergyCharacteristic::WriteNoResponse;
+
+ detailsData.properties = properties;
+ detailsData.value = getGattCharacteristicValue(
+ servicePrivate->hService, const_cast<PBTH_LE_GATT_CHARACTERISTIC>(
+ &gattCharacteristic), &systemErrorCode);
+
+ if (systemErrorCode != NO_ERROR) {
+ // We do not interrupt enumerating of characteristics
+ // if value can not be read
+ qCWarning(QT_BT_WINDOWS) << "Unable to get value for characteristic"
+ << detailsData.uuid.toString()
+ << "of the service" << service.toString()
+ << ":" << qt_error_string(systemErrorCode);
+ }
+
+ // We assume that the characteristic has no any descriptors. So, the
+ // biggest characteristic + 1 will indicate an end handle of service.
+ servicePrivate->endHandle = std::max(
+ servicePrivate->endHandle,
+ QLowEnergyHandle(gattCharacteristic.AttributeHandle + 1));
+
+ const QVector<BTH_LE_GATT_DESCRIPTOR> foundDescriptors = enumerateGattDescriptors(
+ servicePrivate->hService, const_cast<PBTH_LE_GATT_CHARACTERISTIC>(
+ &gattCharacteristic), &systemErrorCode);
+
+ if (systemErrorCode != NO_ERROR) {
+ if (systemErrorCode != ERROR_NOT_FOUND) {
+ qCWarning(QT_BT_WINDOWS) << "Unable to get descriptor for characteristic"
+ << detailsData.uuid.toString()
+ << "of the service" << service.toString()
+ << ":" << qt_error_string(systemErrorCode);
+ servicePrivate->setError(QLowEnergyService::DescriptorReadError);
+ servicePrivate->setState(QLowEnergyService::DiscoveryRequired);
+ return;
+ }
+ }
+
+ for (const BTH_LE_GATT_DESCRIPTOR &gattDescriptor : foundDescriptors) {
+ const QLowEnergyHandle descriptorHandle = gattDescriptor.AttributeHandle;
+
+ QLowEnergyServicePrivate::DescData data;
+ data.uuid = qtBluetoothUuidFromNativeLeUuid(
+ gattDescriptor.DescriptorUuid);
+
+ data.value = getGattDescriptorValue(servicePrivate->hService, const_cast<PBTH_LE_GATT_DESCRIPTOR>(
+ &gattDescriptor), &systemErrorCode);
+
+ if (systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << "Unable to get value for descriptor"
+ << data.uuid.toString()
+ << "for characteristic"
+ << detailsData.uuid.toString()
+ << "of the service" << service.toString()
+ << ":" << qt_error_string(systemErrorCode);
+ servicePrivate->setError(QLowEnergyService::DescriptorReadError);
+ servicePrivate->setState(QLowEnergyService::DiscoveryRequired);
+ return;
+ }
+
+ // Biggest descriptor will contain an end handle of service.
+ servicePrivate->endHandle = std::max(
+ servicePrivate->endHandle,
+ QLowEnergyHandle(gattDescriptor.AttributeHandle));
+
+ detailsData.descriptorList.insert(descriptorHandle, data);
+ }
+
+ servicePrivate->characteristicList.insert(characteristicHandle, detailsData);
+ }
+
+ servicePrivate->setState(QLowEnergyService::ServiceDiscovered);
+}
+
+void QLowEnergyControllerPrivateWin32::startAdvertising(const QLowEnergyAdvertisingParameters &, const QLowEnergyAdvertisingData &, const QLowEnergyAdvertisingData &)
+{
+ Q_UNIMPLEMENTED();
+}
+
+void QLowEnergyControllerPrivateWin32::stopAdvertising()
+{
+ Q_UNIMPLEMENTED();
+}
+
+void QLowEnergyControllerPrivateWin32::requestConnectionUpdate(const QLowEnergyConnectionParameters &)
+{
+ Q_UNIMPLEMENTED();
+}
+
+void QLowEnergyControllerPrivateWin32::readCharacteristic(
+ const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle)
+{
+ Q_ASSERT(!service.isNull());
+ if (!service->characteristicList.contains(charHandle))
+ return;
+
+ const QLowEnergyServicePrivate::CharData &charDetails
+ = service->characteristicList[charHandle];
+ if (!(charDetails.properties & QLowEnergyCharacteristic::Read)) {
+ // if this succeeds the device has a bug, char is advertised as
+ // non-readable. We try to be permissive and let the remote
+ // device answer to the read attempt
+ qCWarning(QT_BT_WINDOWS) << "Reading non-readable char" << charHandle;
+ }
+
+ ReadCharData data;
+ data.systemErrorCode = NO_ERROR;
+ data.hService = service->hService;
+
+ if (data.systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString()
+ << ":" << qt_error_string(data.systemErrorCode);
+ service->setError(QLowEnergyService::CharacteristicReadError);
+ return;
+ }
+
+ data.gattCharacteristic = recoverNativeLeGattCharacteristic(
+ service->startHandle, charHandle, charDetails);
+
+ ThreadWorkerJob job;
+ job.operation = ThreadWorkerJob::ReadChar;
+ job.data = QVariant::fromValue(data);
+
+ QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection,
+ Q_ARG(ThreadWorkerJob, job));
+}
+
+void QLowEnergyControllerPrivateWin32::writeCharacteristic(
+ const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QByteArray &newValue,
+ QLowEnergyService::WriteMode mode)
+{
+ Q_ASSERT(!service.isNull());
+
+ if (!service->characteristicList.contains(charHandle)) {
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return;
+ }
+
+ WriteCharData data;
+ data.systemErrorCode = NO_ERROR;
+ data.hService = service->hService;
+
+ if (data.systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString()
+ << ":" << qt_error_string(data.systemErrorCode);
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return;
+ }
+
+ const QLowEnergyServicePrivate::CharData &charDetails
+ = service->characteristicList[charHandle];
+
+ data.gattCharacteristic = recoverNativeLeGattCharacteristic(
+ service->startHandle, charHandle, charDetails);
+
+ data.flags = (mode == QLowEnergyService::WriteWithResponse)
+ ? BLUETOOTH_GATT_FLAG_NONE
+ : BLUETOOTH_GATT_FLAG_WRITE_WITHOUT_RESPONSE;
+
+ ThreadWorkerJob job;
+ job.operation = ThreadWorkerJob::WriteChar;
+ data.newValue = newValue;
+ data.mode = mode;
+ job.data = QVariant::fromValue(data);
+
+ QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection,
+ Q_ARG(ThreadWorkerJob, job));
+}
+
+void QLowEnergyControllerPrivateWin32::jobFinished(const ThreadWorkerJob &job)
+{
+ switch (job.operation) {
+ case ThreadWorkerJob::WriteChar:
+ {
+ const WriteCharData data = job.data.value<WriteCharData>();
+ const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattCharacteristic.AttributeHandle);
+ const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
+
+ if (data.systemErrorCode != NO_ERROR) {
+ const QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle];
+ qCWarning(QT_BT_WINDOWS) << "Unable to set value for characteristic"
+ << charDetails.uuid.toString()
+ << "of the service" << service->uuid.toString()
+ << ":" << qt_error_string(data.systemErrorCode);
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return;
+ }
+
+ updateValueOfCharacteristic(charHandle, data.newValue, false);
+
+ if (data.mode == QLowEnergyService::WriteWithResponse) {
+ const QLowEnergyCharacteristic ch = characteristicForHandle(charHandle);
+ emit service->characteristicWritten(ch, data.newValue);
+ }
+ }
+ break;
+ case ThreadWorkerJob::ReadChar:
+ {
+ const ReadCharData data = job.data.value<ReadCharData>();
+ const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattCharacteristic.AttributeHandle);
+ const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
+
+ if (data.systemErrorCode != NO_ERROR) {
+ const QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle];
+ qCWarning(QT_BT_WINDOWS) << "Unable to get value for characteristic"
+ << charDetails.uuid.toString()
+ << "of the service" << service->uuid.toString()
+ << ":" << qt_error_string(data.systemErrorCode);
+ service->setError(QLowEnergyService::CharacteristicReadError);
+ return;
+ }
+
+ updateValueOfCharacteristic(charHandle, data.value, false);
+
+ const QLowEnergyCharacteristic ch(service, charHandle);
+ emit service->characteristicRead(ch, data.value);
+ }
+ break;
+ case ThreadWorkerJob::WriteDescr:
+ {
+ WriteDescData data = job.data.value<WriteDescData>();
+ const QLowEnergyHandle descriptorHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.AttributeHandle);
+ const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.CharacteristicHandle);
+ const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
+ QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle];
+ const QLowEnergyServicePrivate::DescData &dscrDetails = charDetails.descriptorList[descriptorHandle];
+
+ if (data.systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << "Unable to set value for descriptor"
+ << dscrDetails.uuid.toString()
+ << "for characteristic"
+ << charDetails.uuid.toString()
+ << "of the service" << service->uuid.toString()
+ << ":" << qt_error_string(data.systemErrorCode);
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return;
+ }
+
+ if (data.gattDescriptor.DescriptorType == ClientCharacteristicConfiguration) {
+
+ QDataStream in(data.newValue);
+ quint8 u;
+ in >> u;
+
+ if (u & ClientCharacteristicConfigurationValue::UseNotifications
+ || u & ClientCharacteristicConfigurationValue::UseIndications) {
+ if (!charDetails.hValueChangeEvent) {
+ BTH_LE_GATT_CHARACTERISTIC gattCharacteristic = recoverNativeLeGattCharacteristic(
+ service->startHandle, charHandle, charDetails);
+
+ // note: if the service handle is closed the event registration is no longer valid.
+ charDetails.hValueChangeEvent = registerEvent(
+ data.hService, gattCharacteristic, this, &data.systemErrorCode);
+ }
+ } else {
+ if (charDetails.hValueChangeEvent) {
+ unregisterEvent(charDetails.hValueChangeEvent, &data.systemErrorCode);
+ charDetails.hValueChangeEvent = nullptr;
+ }
+ }
+
+ if (data.systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << "Unable to subscribe events for descriptor"
+ << dscrDetails.uuid.toString()
+ << "for characteristic"
+ << charDetails.uuid.toString()
+ << "of the service" << service->uuid.toString()
+ << ":" << qt_error_string(data.systemErrorCode);
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return;
+ }
+ }
+
+ updateValueOfDescriptor(charHandle, descriptorHandle, data.newValue, false);
+
+ const QLowEnergyDescriptor dscr(service, charHandle, descriptorHandle);
+ emit service->descriptorWritten(dscr, data.newValue);
+ }
+ break;
+ case ThreadWorkerJob::ReadDescr:
+ {
+ ReadDescData data = job.data.value<ReadDescData>();
+ const QLowEnergyHandle descriptorHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.AttributeHandle);
+ const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.CharacteristicHandle);
+ const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
+ QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle];
+ const QLowEnergyServicePrivate::DescData &dscrDetails = charDetails.descriptorList[descriptorHandle];
+
+ if (data.systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << "Unable to get value for descriptor"
+ << dscrDetails.uuid.toString()
+ << "for characteristic"
+ << charDetails.uuid.toString()
+ << "of the service" << service->uuid.toString()
+ << ":" << qt_error_string(data.systemErrorCode);
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return;
+ }
+
+ updateValueOfDescriptor(charHandle, descriptorHandle, data.value, false);
+
+ QLowEnergyDescriptor dscr(service, charHandle, descriptorHandle);
+ emit service->descriptorRead(dscr, data.value);
+ }
+ break;
+ }
+
+ QMetaObject::invokeMethod(threadWorker, "runPendingJob", Qt::QueuedConnection);
+}
+
+void QLowEnergyControllerPrivateWin32::readDescriptor(
+ const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle)
+{
+ Q_ASSERT(!service.isNull());
+ if (!service->characteristicList.contains(charHandle))
+ return;
+
+ const QLowEnergyServicePrivate::CharData &charDetails
+ = service->characteristicList[charHandle];
+ if (!charDetails.descriptorList.contains(descriptorHandle))
+ return;
+
+ ReadDescData data;
+ data.systemErrorCode = NO_ERROR;
+ data.hService = service->hService;
+
+ if (data.systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString()
+ << ":" << qt_error_string(data.systemErrorCode);
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return;
+ }
+
+ const QLowEnergyServicePrivate::DescData &dscrDetails
+ = charDetails.descriptorList[descriptorHandle];
+
+ data.gattDescriptor = recoverNativeLeGattDescriptor(
+ service->startHandle, charHandle, descriptorHandle, dscrDetails);
+
+ ThreadWorkerJob job;
+ job.operation = ThreadWorkerJob::ReadDescr;
+ job.data = QVariant::fromValue(data);
+
+ QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection,
+ Q_ARG(ThreadWorkerJob, job));
+}
+
+void QLowEnergyControllerPrivateWin32::writeDescriptor(
+ const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle,
+ const QByteArray &newValue)
+{
+ Q_ASSERT(!service.isNull());
+ if (!service->characteristicList.contains(charHandle))
+ return;
+
+ QLowEnergyServicePrivate::CharData &charDetails
+ = service->characteristicList[charHandle];
+ if (!charDetails.descriptorList.contains(descriptorHandle))
+ return;
+
+ WriteDescData data;
+ data.systemErrorCode = NO_ERROR;
+ data.newValue = newValue;
+ data.hService = service->hService;
+
+ if (data.systemErrorCode != NO_ERROR) {
+ qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString()
+ << ":" << qt_error_string(data.systemErrorCode);
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return;
+ }
+
+ const QLowEnergyServicePrivate::DescData &dscrDetails
+ = charDetails.descriptorList[descriptorHandle];
+
+ data.gattDescriptor = recoverNativeLeGattDescriptor(
+ service->startHandle, charHandle, descriptorHandle, dscrDetails);
+
+ ThreadWorkerJob job;
+ job.operation = ThreadWorkerJob::WriteDescr;
+ job.data = QVariant::fromValue(data);
+
+ QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection,
+ Q_ARG(ThreadWorkerJob, job));
+}
+
+void QLowEnergyControllerPrivateWin32::addToGenericAttributeList(const QLowEnergyServiceData &, QLowEnergyHandle)
+{
+ Q_UNIMPLEMENTED();
+}
+
+void ThreadWorker::putJob(const ThreadWorkerJob &job)
+{
+ m_jobs.append(job);
+ if (m_jobs.count() == 1)
+ runPendingJob();
+}
+
+void ThreadWorker::runPendingJob()
+{
+ if (!m_jobs.count())
+ return;
+
+ ThreadWorkerJob job = m_jobs.first();
+
+ switch (job.operation) {
+ case ThreadWorkerJob::WriteChar:
+ {
+ WriteCharData data = job.data.value<WriteCharData>();
+ setGattCharacteristicValue(data.hService, &data.gattCharacteristic,
+ data.newValue, data.flags, &data.systemErrorCode);
+ job.data = QVariant::fromValue(data);
+ }
+ break;
+ case ThreadWorkerJob::ReadChar:
+ {
+ ReadCharData data = job.data.value<ReadCharData>();
+ data.value = getGattCharacteristicValue(
+ data.hService, &data.gattCharacteristic, &data.systemErrorCode);
+ job.data = QVariant::fromValue(data);
+ }
+ break;
+ case ThreadWorkerJob::WriteDescr:
+ {
+ WriteDescData data = job.data.value<WriteDescData>();
+ setGattDescriptorValue(data.hService, &data.gattDescriptor,
+ data.newValue, &data.systemErrorCode);
+ job.data = QVariant::fromValue(data);
+ }
+ break;
+ case ThreadWorkerJob::ReadDescr:
+ {
+ ReadDescData data = job.data.value<ReadDescData>();
+ data.value = getGattDescriptorValue(
+ data.hService,
+ const_cast<PBTH_LE_GATT_DESCRIPTOR>(&data.gattDescriptor),
+ &data.systemErrorCode);
+ job.data = QVariant::fromValue(data);
+ }
+ break;
+ }
+
+ m_jobs.removeFirst();
+ emit jobFinished(job);
+}
+
+QT_END_NAMESPACE