Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qlowenergycontroller_winrt.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6
7#include <QtBluetooth/qbluetoothlocaldevice.h>
8#include <QtBluetooth/QLowEnergyCharacteristicData>
9#include <QtBluetooth/QLowEnergyDescriptorData>
10#include <QtBluetooth/private/qbluetoothutils_winrt_p.h>
11#include <QtBluetooth/QLowEnergyService>
12
13#include <QtCore/QtEndian>
14#include <QtCore/QLoggingCategory>
15#include <QtCore/private/qfunctions_winrt_p.h>
16#include <QtCore/QDeadlineTimer>
17#include <QtCore/qpointer.h>
18
19#include <functional>
20
21#include <robuffer.h>
22#include <windows.devices.enumeration.h>
23#include <windows.devices.bluetooth.h>
24#include <windows.devices.bluetooth.genericattributeprofile.h>
25#include <windows.foundation.collections.h>
26#include <windows.foundation.metadata.h>
27#include <windows.storage.streams.h>
28
29using namespace Microsoft::WRL;
30using namespace Microsoft::WRL::Wrappers;
31using namespace ABI::Windows::Foundation;
32using namespace ABI::Windows::Foundation::Collections;
33using namespace ABI::Windows::Foundation::Metadata;
34using namespace ABI::Windows::Devices;
36using namespace ABI::Windows::Devices::Bluetooth::GenericAttributeProfile;
37using namespace ABI::Windows::Devices::Enumeration;
38using namespace ABI::Windows::Storage::Streams;
39
41
42typedef ITypedEventHandler<BluetoothLEDevice *, IInspectable *> StatusHandler;
43typedef ITypedEventHandler<GattSession *, IInspectable *> MtuHandler;
44typedef ITypedEventHandler<GattCharacteristic *, GattValueChangedEventArgs *> ValueChangedHandler;
45typedef GattReadClientCharacteristicConfigurationDescriptorResult ClientCharConfigDescriptorResult;
46typedef IGattReadClientCharacteristicConfigurationDescriptorResult IClientCharConfigDescriptorResult;
47
48#define EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr) \
49 if (FAILED(hr)) { \
50 emitErrorAndQuitThread(hr); \
51 return; \
52 }
53
54#define EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, message) \
55 if (FAILED(hr)) { \
56 emitErrorAndQuitThread(message); \
57 return; \
58 }
59
60#define WARN_AND_CONTINUE_IF_FAILED(hr, msg) \
61 if (FAILED(hr)) { \
62 qCWarning(QT_BT_WINDOWS) << msg; \
63 continue; \
64 }
65
66#define DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, msg) \
67 if (FAILED(hr)) { \
68 qCWarning(QT_BT_WINDOWS) << msg; \
69 --mCharacteristicsCountToBeDiscovered; \
70 continue; \
71 }
72
73#define CHECK_FOR_DEVICE_CONNECTION_ERROR_IMPL(this, hr, msg, ret) \
74 if (FAILED(hr)) { \
75 this->handleConnectionError(msg); \
76 ret; \
77 }
78
79#define CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, msg, ret) \
80 CHECK_FOR_DEVICE_CONNECTION_ERROR_IMPL(this, hr, msg, ret)
81
82#define CHECK_HR_AND_SET_SERVICE_ERROR(hr, msg, service, error, ret) \
83 if (FAILED(hr)) { \
84 qCDebug(QT_BT_WINDOWS) << msg; \
85 service->setError(error); \
86 ret; \
87 }
88
89Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS)
90Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS_SERVICE_THREAD)
91
92static constexpr qint64 kMaxConnectTimeout = 20000; // 20 sec
93
94static QByteArray byteArrayFromGattResult(const ComPtr<IGattReadResult> &gattResult,
95 bool isWCharString = false)
96{
97 ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
98 HRESULT hr;
99 hr = gattResult->get_Value(&buffer);
100 if (FAILED(hr) || !buffer) {
101 qCWarning(QT_BT_WINDOWS) << "Could not obtain buffer from GattReadResult";
102 return QByteArray();
103 }
104 return byteArrayFromBuffer(buffer, isWCharString);
105}
106
107template <typename T>
108static void closeDeviceService(ComPtr<T> service)
109{
110 ComPtr<IClosable> closableService;
111 HRESULT hr = service.As(&closableService);
112 RETURN_IF_FAILED("Could not cast type to closable", return);
113 hr = closableService->Close();
114 RETURN_IF_FAILED("Close() call failed", return);
115}
116
118{
120public:
122 const ComPtr<IGattDeviceService3> &deviceService,
124 : mService(service), mMode(mode), mDeviceService(deviceService)
125 {
126 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
127 }
128
130 {
131 if (mAbortRequested)
133 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
134 }
135
136public slots:
138 {
139 auto exitCondition = [this]() { return mAbortRequested; };
140
142 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
143 ComPtr<IAsyncOperation<GattCharacteristicsResult *>> characteristicsOp;
144 ComPtr<IGattCharacteristicsResult> characteristicsResult;
145 HRESULT hr = mDeviceService->GetCharacteristicsAsync(&characteristicsOp);
147 hr = QWinRTFunctions::await(characteristicsOp, characteristicsResult.GetAddressOf(),
148 QWinRTFunctions::ProcessMainThreadEvents, 5000,
149 exitCondition);
151 GattCommunicationStatus status;
152 hr = characteristicsResult->get_Status(&status);
154 if (status != GattCommunicationStatus_Success) {
155 emitErrorAndQuitThread(QLatin1String("Could not obtain char list"));
156 return;
157 }
158 ComPtr<IVectorView<GattCharacteristic *>> characteristics;
159 hr = characteristicsResult->get_Characteristics(&characteristics);
161
162 uint characteristicsCount;
163 hr = characteristics->get_Size(&characteristicsCount);
165
166 mCharacteristicsCountToBeDiscovered = characteristicsCount;
167 for (uint i = 0; !mAbortRequested && (i < characteristicsCount); ++i) {
168 ComPtr<IGattCharacteristic> characteristic;
169 hr = characteristics->GetAt(i, &characteristic);
170 if (FAILED(hr)) {
171 qCWarning(QT_BT_WINDOWS) << "Could not obtain characteristic at" << i;
173 continue;
174 }
175
176 ComPtr<IGattCharacteristic3> characteristic3;
177 hr = characteristic.As(&characteristic3);
178 if (FAILED(hr)) {
179 qCWarning(QT_BT_WINDOWS) << "Could not cast characteristic";
181 continue;
182 }
183
184 // For some strange reason, Windows doesn't discover descriptors of characteristics (if not paired).
185 // Qt API assumes that all characteristics and their descriptors are discovered in one go.
186 // So we start 'GetDescriptorsAsync' for each discovered characteristic and finish only
187 // when GetDescriptorsAsync for all characteristics return.
188 ComPtr<IAsyncOperation<GattDescriptorsResult *>> descAsyncOp;
189 hr = characteristic3->GetDescriptorsAsync(&descAsyncOp);
190 DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not obtain list of descriptors")
191
192 ComPtr<IGattDescriptorsResult> descResult;
193 hr = QWinRTFunctions::await(descAsyncOp, descResult.GetAddressOf(),
194 QWinRTFunctions::ProcessMainThreadEvents, 5000,
195 exitCondition);
196 DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor read result")
197
199 hr = characteristic->get_AttributeHandle(&handle);
201 hr, "Could not obtain characteristic's attribute handle")
203 charData.valueHandle = handle + 1;
204 if (mStartHandle == 0 || mStartHandle > handle)
206 if (mEndHandle == 0 || mEndHandle < handle)
208 GUID guuid;
209 hr = characteristic->get_Uuid(&guuid);
210 DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not obtain characteristic's Uuid")
211 charData.uuid = QBluetoothUuid(guuid);
212 GattCharacteristicProperties properties;
213 hr = characteristic->get_CharacteristicProperties(&properties);
215 "Could not obtain characteristic's properties")
216 charData.properties = QLowEnergyCharacteristic::PropertyTypes(properties & 0xff);
217 if (charData.properties & QLowEnergyCharacteristic::Read
219 ComPtr<IAsyncOperation<GattReadResult *>> readOp;
220 hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached,
221 &readOp);
222 DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not read characteristic")
223 ComPtr<IGattReadResult> readResult;
224 hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf(),
225 QWinRTFunctions::ProcessMainThreadEvents, 5000,
226 exitCondition);
228 "Could not obtain characteristic read result")
229 if (!readResult)
230 qCWarning(QT_BT_WINDOWS) << "Characteristic read result is null";
231 else
232 charData.value = byteArrayFromGattResult(readResult);
233 }
235
236 ComPtr<IVectorView<GattDescriptor *>> descriptors;
237
238 GattCommunicationStatus commStatus;
239 hr = descResult->get_Status(&commStatus);
240 if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) {
241 qCWarning(QT_BT_WINDOWS) << "Descriptor operation failed";
243 continue;
244 }
245
246 hr = descResult->get_Descriptors(&descriptors);
247 DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not obtain list of descriptors")
248
249 uint descriptorCount;
250 hr = descriptors->get_Size(&descriptorCount);
251 DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, "Could not obtain list of descriptors' size")
252 for (uint j = 0; !mAbortRequested && (j < descriptorCount); ++j) {
254 ComPtr<IGattDescriptor> descriptor;
255 hr = descriptors->GetAt(j, &descriptor);
256 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor")
257 quint16 descHandle;
258 hr = descriptor->get_AttributeHandle(&descHandle);
259 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor's attribute handle")
260 GUID descriptorUuid;
261 hr = descriptor->get_Uuid(&descriptorUuid);
262 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor's Uuid")
263 descData.uuid = QBluetoothUuid(descriptorUuid);
264 charData.descriptorList.insert(descHandle, descData);
267 ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp;
268 hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(
269 &readOp);
270 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value")
271 ComPtr<IClientCharConfigDescriptorResult> readResult;
272 hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf(),
273 QWinRTFunctions::ProcessMainThreadEvents, 5000,
274 exitCondition);
275 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not await descriptor read result")
276 GattClientCharacteristicConfigurationDescriptorValue value;
277 hr = readResult->get_ClientCharacteristicConfigurationDescriptor(&value);
279 "Could not get descriptor value from result")
280 quint16 result = 0;
281 bool correct = false;
282 if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) {
283 result |= GattClientCharacteristicConfigurationDescriptorValue_Indicate;
284 correct = true;
285 }
286 if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
287 result |= GattClientCharacteristicConfigurationDescriptorValue_Notify;
288 correct = true;
289 }
290 if (value == GattClientCharacteristicConfigurationDescriptorValue_None)
291 correct = true;
292 if (!correct)
293 continue;
294
295 descData.value = QByteArray(2, Qt::Uninitialized);
296 qToLittleEndian(result, descData.value.data());
297 }
298 mIndicateChars << charData.uuid;
299 } else {
301 ComPtr<IAsyncOperation<GattReadResult *>> readOp;
302 hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached,
303 &readOp);
304 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value")
305 ComPtr<IGattReadResult> readResult;
306 hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf(),
307 QWinRTFunctions::ProcessMainThreadEvents, 5000,
308 exitCondition);
309 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not await descriptor read result")
311 descData.value = byteArrayFromGattResult(readResult, true);
312 else
313 descData.value = byteArrayFromGattResult(readResult);
314 }
315 }
316 charData.descriptorList.insert(descHandle, descData);
317 }
318
321 }
322 checkAllCharacteristicsDiscovered();
323 }
324
326 {
327 mAbortRequested = true;
328 }
329
330private:
331 void checkAllCharacteristicsDiscovered();
332 void emitErrorAndQuitThread(HRESULT hr);
333 void emitErrorAndQuitThread(const QString &error);
334
335public:
338 ComPtr<IGattDeviceService3> mDeviceService;
339 QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> mCharacteristicList;
343 QList<QBluetoothUuid> mIndicateChars;
344 bool mAbortRequested = false;
345
346signals:
347 void charListObtained(const QBluetoothUuid &service,
348 QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList,
349 QList<QBluetoothUuid> indicateChars, QLowEnergyHandle startHandle,
350 QLowEnergyHandle endHandle);
352};
353
354void QWinRTLowEnergyServiceHandler::checkAllCharacteristicsDiscovered()
355{
359 }
360 QThread::currentThread()->quit();
361}
362
363void QWinRTLowEnergyServiceHandler::emitErrorAndQuitThread(HRESULT hr)
364{
365 emitErrorAndQuitThread(qt_error_string(hr));
366}
367
368void QWinRTLowEnergyServiceHandler::emitErrorAndQuitThread(const QString &error)
369{
370 mAbortRequested = true; // so that the service is closed during cleanup
372 QThread::currentThread()->quit();
373}
374
376{
378public:
380 {
381 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
382 // This should be checked before the handler is created
383 Q_ASSERT(!mAddress.isNull());
384 }
386 {
387 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
388 mDevice.Reset();
389 mGattSession.Reset();
390 // To close the COM library gracefully, each successful call to
391 // CoInitialize, including those that return S_FALSE, must be balanced
392 // by a corresponding call to CoUninitialize.
393 if (mInitialized == S_OK || mInitialized == S_FALSE)
394 CoUninitialize();
395 }
396
397public slots:
398 void connectToDevice();
400
401signals:
402 void deviceConnected(ComPtr<IBluetoothLEDevice> device, ComPtr<IGattSession> session);
404
405private:
406 void connectToPairedDevice();
407 void connectToUnpairedDevice();
408 void emitErrorAndQuitThread(const QString &error);
409 void emitErrorAndQuitThread(const char *error);
410 void emitConnectedAndQuitThread();
411
412 ComPtr<IBluetoothLEDevice> mDevice = nullptr;
413 ComPtr<IGattSession> mGattSession = nullptr;
414 const QBluetoothAddress mAddress;
415 bool mAbortConnection = false;
416 HRESULT mInitialized = E_UNEXPECTED; // some error code
417};
418
420{
421 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
422 mInitialized = CoInitializeEx(NULL, COINIT_MULTITHREADED);
423 qCDebug(QT_BT_WINDOWS) << qt_error_string(mInitialized);
424
425 auto earlyExit = [this]() { return mAbortConnection; };
426 ComPtr<IBluetoothLEDeviceStatics> deviceStatics;
427 HRESULT hr = GetActivationFactory(
428 HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(),
429 &deviceStatics);
430 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain device factory");
431 ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation;
432 hr = deviceStatics->FromBluetoothAddressAsync(mAddress.toUInt64(), &deviceFromIdOperation);
433 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not find LE device from address");
434 hr = QWinRTFunctions::await(deviceFromIdOperation, mDevice.GetAddressOf(),
435 QWinRTFunctions::ProcessMainThreadEvents, 5000, earlyExit);
436 if (FAILED(hr) || !mDevice) {
437 emitErrorAndQuitThread("Could not find LE device");
438 return;
439 }
440
441 // get GattSession: 1. get device id
442 ComPtr<IBluetoothLEDevice4> device4;
443 hr = mDevice.As(&device4);
444 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not cast device");
445
446 ComPtr<IBluetoothDeviceId> deviceId;
447 hr = device4->get_BluetoothDeviceId(&deviceId);
448 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not get bluetooth device id");
449
450 // get GattSession: 2. get session statics
451 ComPtr<IGattSessionStatics> sessionStatics;
452 hr = GetActivationFactory(
453 HString::MakeReference(
454 RuntimeClass_Windows_Devices_Bluetooth_GenericAttributeProfile_GattSession)
455 .Get(),
456 &sessionStatics);
457 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain GattSession statics");
458
459 // get GattSession: 3. get session
460 ComPtr<IAsyncOperation<GattSession *>> gattSessionFromIdOperation;
461 hr = sessionStatics->FromDeviceIdAsync(deviceId.Get(), &gattSessionFromIdOperation);
462 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not get GattSession from id");
463 hr = QWinRTFunctions::await(gattSessionFromIdOperation, mGattSession.GetAddressOf(),
464 QWinRTFunctions::ProcessMainThreadEvents, 5000, earlyExit);
465 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not complete Gatt session acquire");
466
467 BluetoothConnectionStatus status;
468 hr = mDevice->get_ConnectionStatus(&status);
469 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain device's connection status");
470 if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) {
471 emitConnectedAndQuitThread();
472 return;
473 }
474
475 QBluetoothLocalDevice localDevice;
476 QBluetoothLocalDevice::Pairing pairing = localDevice.pairingStatus(mAddress);
477 if (pairing == QBluetoothLocalDevice::Unpaired)
478 connectToUnpairedDevice();
479 else
480 connectToPairedDevice();
481}
482
484{
485 mAbortConnection = true;
486 // Disconnect from the QLowEnergyControllerPrivateWinRT, so that it does
487 // not get notifications. It's absolutely fine to keep doing smth in
488 // background, as multiple connections to the same device should be handled
489 // correctly by OS.
492}
493
494void QWinRTLowEnergyConnectionHandler::connectToPairedDevice()
495{
496 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
497 ComPtr<IBluetoothLEDevice3> device3;
498 HRESULT hr = mDevice.As(&device3);
499 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not cast device");
500 ComPtr<IAsyncOperation<GattDeviceServicesResult *>> deviceServicesOp;
501 auto earlyExit = [this]() { return mAbortConnection; };
503 while (!mAbortConnection && !deadline.hasExpired()) {
504 hr = device3->GetGattServicesAsync(&deviceServicesOp);
505 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain services");
506 ComPtr<IGattDeviceServicesResult> deviceServicesResult;
507 hr = QWinRTFunctions::await(deviceServicesOp, deviceServicesResult.GetAddressOf(),
508 QWinRTFunctions::ProcessThreadEvents, 5000, earlyExit);
509 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not await services operation");
510
511 GattCommunicationStatus commStatus;
512 hr = deviceServicesResult->get_Status(&commStatus);
513 if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) {
514 emitErrorAndQuitThread("Service operation failed");
515 return;
516 }
517
518 ComPtr<IVectorView<GattDeviceService *>> deviceServices;
519 hr = deviceServicesResult->get_Services(&deviceServices);
520 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain list of services");
521 uint serviceCount;
522 hr = deviceServices->get_Size(&serviceCount);
523 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain service count");
524
525 if (serviceCount == 0) {
526 emitErrorAndQuitThread("Found devices without services");
527 return;
528 }
529
530 // Windows automatically connects to the device as soon as a service value is read/written.
531 // Thus we read one value in order to establish the connection.
532 for (uint i = 0; i < serviceCount; ++i) {
533 ComPtr<IGattDeviceService> service;
534 hr = deviceServices->GetAt(i, &service);
535 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain service");
536 ComPtr<IGattDeviceService3> service3;
537 hr = service.As(&service3);
538 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not cast service");
539 ComPtr<IAsyncOperation<GattCharacteristicsResult *>> characteristicsOp;
540 hr = service3->GetCharacteristicsAsync(&characteristicsOp);
541 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain characteristic");
542 ComPtr<IGattCharacteristicsResult> characteristicsResult;
543 hr = QWinRTFunctions::await(characteristicsOp, characteristicsResult.GetAddressOf(),
544 QWinRTFunctions::ProcessThreadEvents, 5000, earlyExit);
545 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not await characteristic operation");
546 GattCommunicationStatus commStatus;
547 hr = characteristicsResult->get_Status(&commStatus);
548 if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) {
549 qCWarning(QT_BT_WINDOWS) << "Characteristic operation failed";
550 break;
551 }
552 ComPtr<IVectorView<GattCharacteristic *>> characteristics;
553 hr = characteristicsResult->get_Characteristics(&characteristics);
554 if (hr == E_ACCESSDENIED) {
555 // Everything will work as expected up until this point if the
556 // manifest capabilties for bluetooth LE are not set.
557 emitErrorAndQuitThread("Could not obtain characteristic list. "
558 "Please check your manifest capabilities");
559 return;
560 }
561 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain characteristic list");
562 uint characteristicsCount;
563 hr = characteristics->get_Size(&characteristicsCount);
565 "Could not obtain characteristic list's size");
566 for (uint j = 0; j < characteristicsCount; ++j) {
567 ComPtr<IGattCharacteristic> characteristic;
568 hr = characteristics->GetAt(j, &characteristic);
569 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain characteristic");
570 ComPtr<IAsyncOperation<GattReadResult *>> op;
571 GattCharacteristicProperties props;
572 hr = characteristic->get_CharacteristicProperties(&props);
574 hr, "Could not obtain characteristic's properties");
575 if (!(props & GattCharacteristicProperties_Read))
576 continue;
577 hr = characteristic->ReadValueWithCacheModeAsync(
578 BluetoothCacheMode::BluetoothCacheMode_Uncached, &op);
579 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not read characteristic value");
580 ComPtr<IGattReadResult> result;
581 // Reading characteristics can take surprisingly long at the
582 // first time, so we need to have a large the timeout here.
583 hr = QWinRTFunctions::await(op, result.GetAddressOf(),
584 QWinRTFunctions::ProcessThreadEvents, 5000, earlyExit);
585 // E_ILLEGAL_METHOD_CALL will be the result for a device, that is not reachable at
586 // the moment. In this case we should jump back into the outer loop and keep trying.
587 if (hr == E_ILLEGAL_METHOD_CALL)
588 break;
589 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not await characteristic read");
590 ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
591 hr = result->get_Value(&buffer);
592 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain characteristic value");
593 if (!buffer) {
594 qCDebug(QT_BT_WINDOWS) << "Problem reading value";
595 break;
596 }
597
598 emitConnectedAndQuitThread();
599 return;
600 }
601 }
602 }
603 // If we got here because of mAbortConnection == true, the error message
604 // will not be delivered, so it does not matter. But we need to terminate
605 // the thread anyway!
606 emitErrorAndQuitThread("Connect to device failed due to timeout!");
607}
608
609void QWinRTLowEnergyConnectionHandler::connectToUnpairedDevice()
610{
611 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
612 ComPtr<IBluetoothLEDevice3> device3;
613 HRESULT hr = mDevice.As(&device3);
614 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not cast device");
615 ComPtr<IGattDeviceServicesResult> deviceServicesResult;
616 auto earlyExit = [this]() { return mAbortConnection; };
618 while (!mAbortConnection && !deadline.hasExpired()) {
619 ComPtr<IAsyncOperation<GattDeviceServicesResult *>> deviceServicesOp;
620 hr = device3->GetGattServicesAsync(&deviceServicesOp);
621 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not obtain services");
622 hr = QWinRTFunctions::await(deviceServicesOp, deviceServicesResult.GetAddressOf(),
623 QWinRTFunctions::ProcessMainThreadEvents, 0, earlyExit);
624 EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, "Could not await services operation");
625
626 GattCommunicationStatus commStatus;
627 hr = deviceServicesResult->get_Status(&commStatus);
628 if (commStatus == GattCommunicationStatus_Unreachable)
629 continue;
630
631 if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) {
632 emitErrorAndQuitThread("Service operation failed");
633 return;
634 }
635
636 emitConnectedAndQuitThread();
637 return;
638 }
639 // If we got here because of mAbortConnection == true, the error message
640 // will not be delivered, so it does not matter. But we need to terminate
641 // the thread anyway!
642 emitErrorAndQuitThread("Connect to device failed due to timeout!");
643}
644
645void QWinRTLowEnergyConnectionHandler::emitErrorAndQuitThread(const QString &error)
646{
648 QThread::currentThread()->quit();
649}
650
651void QWinRTLowEnergyConnectionHandler::emitErrorAndQuitThread(const char *error)
652{
653 emitErrorAndQuitThread(QString::fromUtf8(error));
654}
655
656void QWinRTLowEnergyConnectionHandler::emitConnectedAndQuitThread()
657{
658 emit deviceConnected(mDevice, mGattSession);
659 QThread::currentThread()->quit();
660}
661
670
672{
673 unregisterFromStatusChanges();
674 unregisterFromValueChanges();
675}
676
680
682{
683 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
684 if (remoteDevice.isNull()) {
685 qCWarning(QT_BT_WINDOWS) << "Invalid/null remote device address";
687 return;
688 }
690
692 QThread *thread = new QThread;
693 worker->moveToThread(thread);
700 [this](const QString &msg) { handleConnectionError(msg.toUtf8().constData()); });
702 [this](ComPtr<IBluetoothLEDevice> device, ComPtr<IGattSession> session) {
703 if (!device || !session) {
704 handleConnectionError("Failed to get device or gatt service");
705 return;
706 }
707 mDevice = device;
708 mGattSession = session;
709
710 if (!registerForStatusChanges() || !registerForMtuChanges()) {
711 handleConnectionError("Failed to register for changes");
712 return;
713 }
714
717 emit q->connected();
718 });
719 thread->start();
720}
721
723{
724 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
728 unregisterFromValueChanges();
729 unregisterFromStatusChanges();
730 unregisterFromMtuChanges();
731 clearAllServices();
732 mGattSession = nullptr;
733 mDevice = nullptr;
735 emit q->disconnected();
736}
737
738HRESULT QLowEnergyControllerPrivateWinRT::getNativeService(const QBluetoothUuid &serviceUuid,
739 NativeServiceCallback callback)
740{
741 if (m_openedServices.contains(serviceUuid)) {
742 callback(m_openedServices.value(serviceUuid));
743 return S_OK;
744 }
745
746 ComPtr<IBluetoothLEDevice3> device3;
747 HRESULT hr = mDevice.As(&device3);
748 RETURN_IF_FAILED("Could not convert to IBluetoothDevice3", return hr);
749
750 ComPtr<IAsyncOperation<GattDeviceServicesResult *>> servicesResultOperation;
751 hr = device3->GetGattServicesForUuidAsync(serviceUuid, &servicesResultOperation);
752 RETURN_IF_FAILED("Could not start async services request", return hr);
753
754 QPointer<QLowEnergyControllerPrivateWinRT> thisPtr(this);
755 hr = servicesResultOperation->put_Completed(
756 Callback<IAsyncOperationCompletedHandler<
757 GenericAttributeProfile::GattDeviceServicesResult *>>(
758 [thisPtr, callback, &serviceUuid](
759 IAsyncOperation<GattDeviceServicesResult *> *op, AsyncStatus status)
760 {
761 if (thisPtr) {
762 if (status != AsyncStatus::Completed) {
763 qCDebug(QT_BT_WINDOWS) << "Failed to get result of async service request";
764 return S_OK;
765 }
766 ComPtr<IGattDeviceServicesResult> result;
767 ComPtr<IVectorView<GattDeviceService *>> deviceServices;
768 HRESULT hr = op->GetResults(&result);
769 RETURN_IF_FAILED("Failed to get result of async service request", return hr);
770
771 ComPtr<IVectorView<GattDeviceService *>> services;
772 hr = result->get_Services(&services);
773 RETURN_IF_FAILED("Failed to extract services from the result", return hr);
774
775 uint servicesCount = 0;
776 hr = services->get_Size(&servicesCount);
777 RETURN_IF_FAILED("Failed to extract services count", return hr);
778
779 if (servicesCount > 0) {
780 if (servicesCount > 1) {
781 qWarning() << "getNativeService: more than one service detected for UUID"
782 << serviceUuid << "The first service will be used.";
783 }
784 ComPtr<IGattDeviceService> service;
785 hr = services->GetAt(0, &service);
786 if (FAILED(hr)) {
787 qCDebug(QT_BT_WINDOWS) << "Could not obtain native service for Uuid"
788 << serviceUuid;
789 }
790 if (service) {
791 thisPtr->m_openedServices[serviceUuid] = service;
792 callback(service); // Use the service in a custom callback
793 }
794 } else {
795 qCWarning(QT_BT_WINDOWS) << "No services found for Uuid" << serviceUuid;
796 }
797 } else {
798 qCWarning(QT_BT_WINDOWS) << "LE controller was removed while getting native service";
799 }
800 return S_OK;
801 }).Get());
802
803 return hr;
804}
805
806HRESULT QLowEnergyControllerPrivateWinRT::getNativeCharacteristic(
807 const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid,
808 NativeCharacteristicCallback callback)
809{
810 QPointer<QLowEnergyControllerPrivateWinRT> thisPtr(this);
811 auto serviceCallback = [thisPtr, callback, charUuid](ComPtr<IGattDeviceService> service) {
812 ComPtr<IGattDeviceService3> service3;
813 HRESULT hr = service.As(&service3);
814 RETURN_IF_FAILED("Could not cast service to service3", return);
815
816 ComPtr<IAsyncOperation<GattCharacteristicsResult *>> characteristicRequestOp;
817 hr = service3->GetCharacteristicsForUuidAsync(charUuid, &characteristicRequestOp);
818
819 hr = characteristicRequestOp->put_Completed(
820 Callback<IAsyncOperationCompletedHandler<GattCharacteristicsResult *>>(
821 [thisPtr, callback](
822 IAsyncOperation<GattCharacteristicsResult *> *op, AsyncStatus status)
823 {
824 if (thisPtr) {
825 if (status != AsyncStatus::Completed) {
826 qCDebug(QT_BT_WINDOWS) << "Failed to get result of async characteristic "
827 "operation";
828 return S_OK;
829 }
830 ComPtr<IGattCharacteristicsResult> result;
831 HRESULT hr = op->GetResults(&result);
832 RETURN_IF_FAILED("Failed to get result of async characteristic operation",
833 return hr);
834 GattCommunicationStatus status;
835 hr = result->get_Status(&status);
836 if (FAILED(hr) || status != GattCommunicationStatus_Success) {
837 qErrnoWarning(hr, "Native characteristic operation failed.");
838 return S_OK;
839 }
840 ComPtr<IVectorView<GattCharacteristic *>> characteristics;
841 hr = result->get_Characteristics(&characteristics);
842 RETURN_IF_FAILED("Could not obtain characteristic list.", return S_OK);
843 uint size;
844 hr = characteristics->get_Size(&size);
845 RETURN_IF_FAILED("Could not obtain characteristic list's size.", return S_OK);
846 if (size != 1)
847 qErrnoWarning("More than 1 characteristic found.");
848 ComPtr<IGattCharacteristic> characteristic;
849 hr = characteristics->GetAt(0, &characteristic);
850 RETURN_IF_FAILED("Could not obtain first characteristic for service", return S_OK);
851 if (characteristic)
852 callback(characteristic); // use the characteristic in a custom callback
853 }
854 return S_OK;
855 }).Get());
856 };
857
858 HRESULT hr = getNativeService(serviceUuid, serviceCallback);
859 if (FAILED(hr))
860 qCDebug(QT_BT_WINDOWS) << "Failed to get native service for" << serviceUuid;
861
862 return hr;
863}
864
865void QLowEnergyControllerPrivateWinRT::registerForValueChanges(const QBluetoothUuid &serviceUuid,
866 const QBluetoothUuid &charUuid)
867{
868 qCDebug(QT_BT_WINDOWS) << "Registering characteristic" << charUuid << "in service"
869 << serviceUuid << "for value changes";
870 for (const ValueChangedEntry &entry : std::as_const(mValueChangedTokens)) {
871 GUID guuid;
872 HRESULT hr;
873 hr = entry.characteristic->get_Uuid(&guuid);
874 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain characteristic's Uuid")
875 if (QBluetoothUuid(guuid) == charUuid)
876 return;
877 }
878
879 auto callback = [this, charUuid, serviceUuid](ComPtr<IGattCharacteristic> characteristic) {
880 EventRegistrationToken token;
881 HRESULT hr;
882 hr = characteristic->add_ValueChanged(
883 Callback<ValueChangedHandler>(
884 this, &QLowEnergyControllerPrivateWinRT::onValueChange).Get(),
885 &token);
886 RETURN_IF_FAILED("Could not register characteristic for value changes", return)
887 mValueChangedTokens.append(ValueChangedEntry(characteristic, token));
888 qCDebug(QT_BT_WINDOWS) << "Characteristic" << charUuid << "in service"
889 << serviceUuid << "registered for value changes";
890 };
891
892 HRESULT hr = getNativeCharacteristic(serviceUuid, charUuid, callback);
893 if (FAILED(hr)) {
894 qCDebug(QT_BT_WINDOWS).nospace() << "Could not obtain native characteristic "
895 << charUuid << " from service " << serviceUuid
896 << ". Qt will not be able to signal"
897 << " changes for this characteristic.";
898 }
899}
900
901void QLowEnergyControllerPrivateWinRT::unregisterFromValueChanges()
902{
903 qCDebug(QT_BT_WINDOWS) << "Unregistering " << mValueChangedTokens.size() << " value change tokens";
904 HRESULT hr;
905 for (const ValueChangedEntry &entry : std::as_const(mValueChangedTokens)) {
906 if (!entry.characteristic) {
907 qCWarning(QT_BT_WINDOWS) << "Unregistering from value changes for characteristic failed."
908 << "Characteristic has been deleted";
909 continue;
910 }
911 hr = entry.characteristic->remove_ValueChanged(entry.token);
912 if (FAILED(hr))
913 qCWarning(QT_BT_WINDOWS) << "Unregistering from value changes for characteristic failed.";
914 }
915 mValueChangedTokens.clear();
916}
917
918HRESULT QLowEnergyControllerPrivateWinRT::onValueChange(IGattCharacteristic *characteristic, IGattValueChangedEventArgs *args)
919{
920 HRESULT hr;
922 hr = characteristic->get_AttributeHandle(&handle);
923 RETURN_IF_FAILED("Could not obtain characteristic's handle", return S_OK)
924 ComPtr<IBuffer> buffer;
925 hr = args->get_CharacteristicValue(&buffer);
926 RETURN_IF_FAILED("Could not obtain characteristic's value", return S_OK)
928 return S_OK;
929}
930HRESULT QLowEnergyControllerPrivateWinRT::onMtuChange(IGattSession *session, IInspectable *args)
931{
932 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
933 if (session != mGattSession.Get()) {
934 qCWarning(QT_BT_WINDOWS) << "Got MTU changed event for wrong or outdated GattSession.";
935 return S_OK;
936 }
937
939 emit q->mtuChanged(mtu());
940 return S_OK;
941}
942
943bool QLowEnergyControllerPrivateWinRT::registerForStatusChanges()
944{
945 if (!mDevice)
946 return false;
947
948 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
949
950 HRESULT hr;
951 hr = mDevice->add_ConnectionStatusChanged(
952 Callback<StatusHandler>(this, &QLowEnergyControllerPrivateWinRT::onStatusChange).Get(),
953 &mStatusChangedToken);
954 RETURN_IF_FAILED("Could not add status callback", return false)
955 return true;
956}
957
958void QLowEnergyControllerPrivateWinRT::unregisterFromStatusChanges()
959{
960 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
961 if (mDevice && mStatusChangedToken.value) {
962 mDevice->remove_ConnectionStatusChanged(mStatusChangedToken);
963 mStatusChangedToken.value = 0;
964 }
965}
966
967bool QLowEnergyControllerPrivateWinRT::registerForMtuChanges()
968{
969 if (!mDevice || !mGattSession)
970 return false;
971
972 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
973
974 HRESULT hr;
975 hr = mGattSession->add_MaxPduSizeChanged(
976 Callback<MtuHandler>(this, &QLowEnergyControllerPrivateWinRT::onMtuChange).Get(),
977 &mMtuChangedToken);
978 RETURN_IF_FAILED("Could not add MTU callback", return false)
979 return true;
980}
981
982void QLowEnergyControllerPrivateWinRT::unregisterFromMtuChanges()
983{
984 qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
985 if (mDevice && mGattSession && mMtuChangedToken.value) {
986 mGattSession->remove_MaxPduSizeChanged(mMtuChangedToken);
987 mMtuChangedToken.value = 0;
988 }
989}
990
991HRESULT QLowEnergyControllerPrivateWinRT::onStatusChange(IBluetoothLEDevice *dev, IInspectable *)
992{
994 BluetoothConnectionStatus status;
995 HRESULT hr;
996 hr = dev->get_ConnectionStatus(&status);
997 RETURN_IF_FAILED("Could not obtain connection status", return S_OK)
999 && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) {
1001 emit q->connected();
1003 && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Disconnected) {
1005 unregisterFromValueChanges();
1006 unregisterFromStatusChanges();
1007 unregisterFromMtuChanges();
1008 mGattSession = nullptr;
1009 mDevice = nullptr;
1012 emit q->disconnected();
1013 }
1014 return S_OK;
1015}
1016
1017void QLowEnergyControllerPrivateWinRT::obtainIncludedServices(
1018 QSharedPointer<QLowEnergyServicePrivate> servicePointer,
1019 ComPtr<IGattDeviceService> service)
1020{
1022 ComPtr<IGattDeviceService3> service3;
1023 HRESULT hr = service.As(&service3);
1024 RETURN_IF_FAILED("Could not cast service", return);
1025 ComPtr<IAsyncOperation<GattDeviceServicesResult *>> op;
1026 hr = service3->GetIncludedServicesAsync(&op);
1027 // Some devices return ERROR_ACCESS_DISABLED_BY_POLICY
1028 RETURN_IF_FAILED("Could not obtain included services", return);
1029 ComPtr<IGattDeviceServicesResult> result;
1030 hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessMainThreadEvents, 5000);
1031 RETURN_IF_FAILED("Could not await service operation", return);
1032 // The device can be disconnected by the time we return from await()
1034 return;
1035 GattCommunicationStatus status;
1036 hr = result->get_Status(&status);
1037 if (FAILED(hr) || status != GattCommunicationStatus_Success) {
1038 qErrnoWarning("Could not obtain list of included services");
1039 return;
1040 }
1041 ComPtr<IVectorView<GattDeviceService *>> includedServices;
1042 hr = result->get_Services(&includedServices);
1043 RETURN_IF_FAILED("Could not obtain service list", return);
1044
1045 uint count;
1046 hr = includedServices->get_Size(&count);
1047 RETURN_IF_FAILED("Could not obtain service list's size", return);
1048 for (uint i = 0; i < count; ++i) {
1049 ComPtr<IGattDeviceService> includedService;
1050 hr = includedServices->GetAt(i, &includedService);
1051 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service from list");
1052 GUID guuid;
1053 hr = includedService->get_Uuid(&guuid);
1054 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain included service's Uuid");
1055 const QBluetoothUuid includedUuid(guuid);
1056 QSharedPointer<QLowEnergyServicePrivate> includedPointer;
1057 qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__
1058 << "Changing service pointer from thread"
1060 if (serviceList.contains(includedUuid)) {
1061 includedPointer = serviceList.value(includedUuid);
1062 } else {
1064 priv->uuid = includedUuid;
1065 priv->setController(this);
1066
1067 includedPointer = QSharedPointer<QLowEnergyServicePrivate>(priv);
1068 serviceList.insert(includedUuid, includedPointer);
1069 }
1070 includedPointer->type |= QLowEnergyService::IncludedService;
1071 servicePointer->includedServices.append(includedUuid);
1072
1073 obtainIncludedServices(includedPointer, includedService);
1074
1075 emit q->serviceDiscovered(includedUuid);
1076 }
1077}
1078
1079HRESULT QLowEnergyControllerPrivateWinRT::onServiceDiscoveryFinished(ABI::Windows::Foundation::IAsyncOperation<GattDeviceServicesResult *> *op, AsyncStatus status)
1080{
1081 // Check if the device is in the proper state, because it can already be
1082 // disconnected when the callback arrives.
1083 // Also the callback can theoretically come when the connection is
1084 // reestablisheed again (for example, if the user quickly clicks
1085 // "Disconnect" and then "Connect" again in some UI). But we can probably
1086 // omit such details, as we are connecting to the same device anyway.
1088 return S_OK;
1089
1091 if (status != AsyncStatus::Completed) {
1092 qCDebug(QT_BT_WINDOWS) << "Could not obtain services";
1093 return S_OK;
1094 }
1095 ComPtr<IGattDeviceServicesResult> result;
1096 ComPtr<IVectorView<GattDeviceService *>> deviceServices;
1097 HRESULT hr = op->GetResults(&result);
1098 CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service discovery result",
1099 return S_OK);
1100 GattCommunicationStatus commStatus;
1101 hr = result->get_Status(&commStatus);
1102 CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service discovery status",
1103 return S_OK);
1104 if (commStatus != GattCommunicationStatus_Success)
1105 return S_OK;
1106
1107 hr = result->get_Services(&deviceServices);
1108 CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service list",
1109 return S_OK);
1110
1111 uint serviceCount;
1112 hr = deviceServices->get_Size(&serviceCount);
1113 CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service list size",
1114 return S_OK);
1115 for (uint i = 0; i < serviceCount; ++i) {
1116 ComPtr<IGattDeviceService> deviceService;
1117 hr = deviceServices->GetAt(i, &deviceService);
1118 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service");
1119 GUID guuid;
1120 hr = deviceService->get_Uuid(&guuid);
1121 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service's Uuid");
1122 const QBluetoothUuid service(guuid);
1123 m_openedServices[service] = deviceService;
1124
1125 qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__
1126 << "Changing service pointer from thread"
1128 QSharedPointer<QLowEnergyServicePrivate> pointer;
1129 if (serviceList.contains(service)) {
1130 pointer = serviceList.value(service);
1131 } else {
1133 priv->uuid = service;
1134 priv->setController(this);
1135
1136 pointer = QSharedPointer<QLowEnergyServicePrivate>(priv);
1137 serviceList.insert(service, pointer);
1138 }
1140
1141 obtainIncludedServices(pointer, deviceService);
1142 // The obtainIncludedServices method calls await(), so the device can be
1143 // disconnected by the time we return from it. TODO - rewrite in an
1144 // async way!
1146 emit q->discoveryFinished(); // Probably not needed when the device
1147 // is already disconnected?
1148 return S_OK;
1149 }
1150
1151 emit q->serviceDiscovered(service);
1152 }
1153
1155 emit q->discoveryFinished();
1156
1157 return S_OK;
1158}
1159
1160void QLowEnergyControllerPrivateWinRT::clearAllServices()
1161{
1162 // These services will be closed in the respective
1163 // QWinRTLowEnergyServiceHandler workers (in background threads).
1164 for (auto &uuid : m_requestDetailsServiceUuids)
1165 m_openedServices.remove(uuid);
1166 m_requestDetailsServiceUuids.clear();
1167
1168 for (auto service : m_openedServices) {
1169 closeDeviceService(service);
1170 }
1171 m_openedServices.clear();
1172}
1173
1174void QLowEnergyControllerPrivateWinRT::closeAndRemoveService(const QBluetoothUuid &uuid)
1175{
1176 auto service = m_openedServices.take(uuid);
1177 if (service)
1178 closeDeviceService(service);
1179}
1180
1182{
1183 qCDebug(QT_BT_WINDOWS) << "Service discovery initiated";
1184 // clear the previous services cache, as we request the services again
1185 clearAllServices();
1186 ComPtr<IBluetoothLEDevice3> device3;
1187 HRESULT hr = mDevice.As(&device3);
1188 CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return);
1189 ComPtr<IAsyncOperation<GenericAttributeProfile::GattDeviceServicesResult *>> asyncResult;
1190 hr = device3->GetGattServicesAsync(&asyncResult);
1191 CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain services", return);
1192 hr = asyncResult->put_Completed(
1193 Callback<IAsyncOperationCompletedHandler<GenericAttributeProfile::GattDeviceServicesResult *>>(
1194 this, &QLowEnergyControllerPrivateWinRT::onServiceDiscoveryFinished).Get());
1195 CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not register services discovery callback",
1196 return);
1197}
1198
1201{
1202 qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << service;
1203 if (!serviceList.contains(service)) {
1204 qCWarning(QT_BT_WINDOWS) << "Discovery done of unknown service:"
1205 << service.toString();
1206 return;
1207 }
1208
1209 // clear the cache to rediscover service details
1210 closeAndRemoveService(service);
1211
1212 auto serviceCallback = [service, mode, this](ComPtr<IGattDeviceService> deviceService) {
1213 discoverServiceDetailsHelper(service, mode, deviceService);
1214 };
1215
1216 HRESULT hr = getNativeService(service, serviceCallback);
1217 if (FAILED(hr))
1218 qCDebug(QT_BT_WINDOWS) << "Could not obtain native service for uuid " << service;
1219}
1220
1221void QLowEnergyControllerPrivateWinRT::discoverServiceDetailsHelper(
1223 GattDeviceServiceComPtr deviceService)
1224{
1225 auto reactOnDiscoveryError = [](QSharedPointer<QLowEnergyServicePrivate> service,
1226 const QString &msg)
1227 {
1228 qCDebug(QT_BT_WINDOWS) << msg;
1229 service->setError(QLowEnergyService::UnknownError);
1230 service->setState(QLowEnergyService::RemoteService);
1231 };
1232 //update service data
1233 QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service);
1234 if (!pointer) {
1235 qCDebug(QT_BT_WINDOWS) << "Device was disconnected while doing service discovery";
1236 return;
1237 }
1238 qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
1241 ComPtr<IGattDeviceService3> deviceService3;
1242 HRESULT hr = deviceService.As(&deviceService3);
1243 if (FAILED(hr)) {
1244 reactOnDiscoveryError(pointer, QStringLiteral("Could not cast service: %1").arg(hr));
1245 return;
1246 }
1247 ComPtr<IAsyncOperation<GattDeviceServicesResult *>> op;
1248 hr = deviceService3->GetIncludedServicesAsync(&op);
1249 if (FAILED(hr)) {
1250 reactOnDiscoveryError(pointer,
1251 QStringLiteral("Could not obtain included service list: %1").arg(hr));
1252 return;
1253 }
1254 ComPtr<IGattDeviceServicesResult> result;
1255 hr = QWinRTFunctions::await(op, result.GetAddressOf());
1256 if (FAILED(hr)) {
1257 reactOnDiscoveryError(pointer,
1258 QStringLiteral("Could not await service operation: %1").arg(hr));
1259 return;
1260 }
1261 GattCommunicationStatus status;
1262 hr = result->get_Status(&status);
1263 if (FAILED(hr) || status != GattCommunicationStatus_Success) {
1264 reactOnDiscoveryError(pointer,
1265 QStringLiteral("Obtaining list of included services failed: %1").
1266 arg(hr));
1267 return;
1268 }
1269 ComPtr<IVectorView<GattDeviceService *>> deviceServices;
1270 hr = result->get_Services(&deviceServices);
1271 if (FAILED(hr)) {
1272 reactOnDiscoveryError(pointer,
1273 QStringLiteral("Could not obtain service list from result: %1").
1274 arg(hr));
1275 return;
1276 }
1277 uint serviceCount;
1278 hr = deviceServices->get_Size(&serviceCount);
1279 if (FAILED(hr)) {
1280 reactOnDiscoveryError(pointer,
1281 QStringLiteral("Could not obtain included service list's size: %1").
1282 arg(hr));
1283 return;
1284 }
1285 for (uint i = 0; i < serviceCount; ++i) {
1286 ComPtr<IGattDeviceService> includedService;
1287 hr = deviceServices->GetAt(i, &includedService);
1288 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service from list")
1289 GUID guuid;
1290 hr = includedService->get_Uuid(&guuid);
1291 WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service Uuid")
1292
1293 const QBluetoothUuid service(guuid);
1294 if (service.isNull()) {
1295 qCDebug(QT_BT_WINDOWS) << "Could not find service";
1296 continue;
1297 }
1298
1299 pointer->includedServices.append(service);
1300
1301 // update the type of the included service
1302 QSharedPointer<QLowEnergyServicePrivate> otherService = serviceList.value(service);
1303 if (!otherService.isNull())
1304 otherService->type |= QLowEnergyService::IncludedService;
1305 }
1306
1308 new QWinRTLowEnergyServiceHandler(service, deviceService3, mode);
1309 m_requestDetailsServiceUuids.insert(service);
1310 QThread *thread = new QThread;
1311 worker->moveToThread(thread);
1318 this, &QLowEnergyControllerPrivateWinRT::handleServiceHandlerError);
1320 [this](const QBluetoothUuid &service, QHash<QLowEnergyHandle,
1321 QLowEnergyServicePrivate::CharData> charList, QList<QBluetoothUuid> indicateChars,
1322 QLowEnergyHandle startHandle, QLowEnergyHandle endHandle) {
1323 if (!serviceList.contains(service)) {
1324 qCWarning(QT_BT_WINDOWS)
1325 << "Discovery complete for unknown service:" << service.toString();
1326 return;
1327 }
1328 m_requestDetailsServiceUuids.remove(service);
1329
1330 QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service);
1331 pointer->startHandle = startHandle;
1332 pointer->endHandle = endHandle;
1333 pointer->characteristicList = charList;
1334
1335 for (const QBluetoothUuid &indicateChar : std::as_const(indicateChars))
1336 registerForValueChanges(service, indicateChar);
1337
1339 });
1340 thread->start();
1341}
1342
1351
1356
1361
1363 const QSharedPointer<QLowEnergyServicePrivate> service,
1364 const QLowEnergyHandle charHandle)
1365{
1366 qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << service << charHandle;
1367 qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
1369 Q_ASSERT(!service.isNull());
1373 return;
1374 }
1375
1376 if (!service->characteristicList.contains(charHandle)) {
1377 qCDebug(QT_BT_WINDOWS) << charHandle << "could not be found in service" << service->uuid;
1379 return;
1380 }
1381
1382 const auto charData = service->characteristicList.value(charHandle);
1383 if (!(charData.properties & QLowEnergyCharacteristic::Read))
1384 qCDebug(QT_BT_WINDOWS) << "Read flag is not set for characteristic" << charData.uuid;
1385
1386 auto characteristicCallback = [charHandle, service, this](
1387 ComPtr<IGattCharacteristic> characteristic) {
1388 readCharacteristicHelper(service, charHandle, characteristic);
1389 };
1390
1391 HRESULT hr = getNativeCharacteristic(service->uuid, charData.uuid, characteristicCallback);
1392 if (FAILED(hr)) {
1393 qCDebug(QT_BT_WINDOWS) << "Could not obtain native characteristic" << charData.uuid
1394 << "from service" << service->uuid;
1396 }
1397}
1398
1399void QLowEnergyControllerPrivateWinRT::readCharacteristicHelper(
1400 const QSharedPointer<QLowEnergyServicePrivate> service,
1401 const QLowEnergyHandle charHandle,
1402 GattCharacteristicComPtr characteristic)
1403{
1404 ComPtr<IAsyncOperation<GattReadResult *>> readOp;
1405 HRESULT hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp);
1406 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not read characteristic",
1408 auto readCompletedLambda = [charHandle, service]
1409 (IAsyncOperation<GattReadResult*> *op, AsyncStatus status)
1410 {
1411 if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
1412 qCDebug(QT_BT_WINDOWS) << "Characteristic" << charHandle << "read operation failed.";
1414 return S_OK;
1415 }
1416 ComPtr<IGattReadResult> characteristicValue;
1417 HRESULT hr;
1418 hr = op->GetResults(&characteristicValue);
1419 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for characteristic", service,
1421
1422 const QByteArray value = byteArrayFromGattResult(characteristicValue);
1423 auto charData = service->characteristicList.value(charHandle);
1424 charData.value = value;
1425 service->characteristicList.insert(charHandle, charData);
1426 emit service->characteristicRead(QLowEnergyCharacteristic(service, charHandle), value);
1427 return S_OK;
1428 };
1429 hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattReadResult *>>(
1430 readCompletedLambda).Get());
1431 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register characteristic read callback",
1433}
1434
1436 const QSharedPointer<QLowEnergyServicePrivate> service,
1437 const QLowEnergyHandle charHandle,
1438 const QLowEnergyHandle descHandle)
1439{
1440 qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << service << charHandle << descHandle;
1441 qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
1443 Q_ASSERT(!service.isNull());
1445 service->setError(QLowEnergyService::DescriptorReadError);
1447 return;
1448 }
1449
1450 if (!service->characteristicList.contains(charHandle)) {
1451 qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "in characteristic" << charHandle
1452 << "cannot be found in service" << service->uuid;
1453 service->setError(QLowEnergyService::DescriptorReadError);
1454 return;
1455 }
1456
1457 const auto charData = service->characteristicList.value(charHandle);
1458
1459 auto characteristicCallback = [charHandle, descHandle, service, this](
1460 ComPtr<IGattCharacteristic> characteristic) {
1461 readDescriptorHelper(service, charHandle, descHandle, characteristic);
1462 };
1463
1464 HRESULT hr = getNativeCharacteristic(service->uuid, charData.uuid, characteristicCallback);
1465 if (FAILED(hr)) {
1466 qCDebug(QT_BT_WINDOWS) << "Could not obtain native characteristic" << charData.uuid
1467 << "from service" << service->uuid;
1468 service->setError(QLowEnergyService::DescriptorReadError);
1469 }
1470}
1471
1472void QLowEnergyControllerPrivateWinRT::readDescriptorHelper(
1473 const QSharedPointer<QLowEnergyServicePrivate> service,
1474 const QLowEnergyHandle charHandle,
1475 const QLowEnergyHandle descHandle,
1476 GattCharacteristicComPtr characteristic)
1477{
1478 // Get native descriptor
1479 const auto charData = service->characteristicList.value(charHandle);
1480 if (!charData.descriptorList.contains(descHandle)) {
1481 qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "cannot be found in characteristic"
1482 << charHandle;
1483 }
1484 const auto descData = charData.descriptorList.value(descHandle);
1485 const QBluetoothUuid descUuid = descData.uuid;
1486 if (descUuid ==
1488 ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp;
1489 HRESULT hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp);
1490 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not read client characteristic configuration",
1492 auto readCompletedLambda = [charHandle, descHandle, service]
1493 (IAsyncOperation<ClientCharConfigDescriptorResult *> *op, AsyncStatus status)
1494 {
1495 if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
1496 qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "read operation failed";
1498 return S_OK;
1499 }
1500 ComPtr<IClientCharConfigDescriptorResult> iValue;
1501 HRESULT hr;
1502 hr = op->GetResults(&iValue);
1503 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for descriptor", service,
1505 GattClientCharacteristicConfigurationDescriptorValue value;
1506 hr = iValue->get_ClientCharacteristicConfigurationDescriptor(&value);
1507 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain value for descriptor", service,
1509 quint16 result = 0;
1510 bool correct = false;
1511 if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) {
1513 correct = true;
1514 }
1515 if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
1517 correct = true;
1518 }
1519 if (value == GattClientCharacteristicConfigurationDescriptorValue_None)
1520 correct = true;
1521 if (!correct) {
1522 qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle
1523 << "read operation failed. Obtained unexpected value.";
1525 return S_OK;
1526 }
1529 descData.value = QByteArray(2, Qt::Uninitialized);
1530 qToLittleEndian(result, descData.value.data());
1531 service->characteristicList[charHandle].descriptorList[descHandle] = descData;
1532 emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle),
1533 descData.value);
1534 return S_OK;
1535 };
1536 hr = readOp->put_Completed(
1537 Callback<IAsyncOperationCompletedHandler<ClientCharConfigDescriptorResult *>>(
1538 readCompletedLambda).Get());
1539 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor read callback",
1541 return;
1542 }
1543
1544 ComPtr<IGattCharacteristic3> characteristic3;
1545 HRESULT hr = characteristic.As(&characteristic3);
1546 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast characteristic",
1548 ComPtr<IAsyncOperation<GattDescriptorsResult *>> op;
1549 hr = characteristic3->GetDescriptorsForUuidAsync(descData.uuid, &op);
1550 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor for uuid",
1552 ComPtr<IGattDescriptorsResult> result;
1553 hr = QWinRTFunctions::await(op, result.GetAddressOf(),
1554 QWinRTFunctions::ProcessMainThreadEvents, 5000);
1555 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not await descritpor read result",
1557
1558 GattCommunicationStatus commStatus;
1559 hr = result->get_Status(&commStatus);
1560 if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) {
1561 qErrnoWarning("Could not obtain list of descriptors");
1563 return;
1564 }
1565
1566 ComPtr<IVectorView<GattDescriptor *>> descriptors;
1567 hr = result->get_Descriptors(&descriptors);
1568 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor list",
1570 uint size;
1571 hr = descriptors->get_Size(&size);
1572 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not await descritpor list's size",
1574 if (size == 0) {
1575 qCWarning(QT_BT_WINDOWS) << "No descriptor with uuid" << descData.uuid << "was found.";
1577 return;
1578 } else if (size > 1) {
1579 qCWarning(QT_BT_WINDOWS) << "There is more than 1 descriptor with uuid" << descData.uuid;
1580 }
1581
1582 ComPtr<IGattDescriptor> descriptor;
1583 hr = descriptors->GetAt(0, &descriptor);
1584 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descritpor from list",
1586 ComPtr<IAsyncOperation<GattReadResult*>> readOp;
1587 hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp);
1588 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not read descriptor value",
1590 auto readCompletedLambda = [charHandle, descHandle, descUuid, service]
1591 (IAsyncOperation<GattReadResult*> *op, AsyncStatus status)
1592 {
1593 if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
1594 qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "read operation failed";
1596 return S_OK;
1597 }
1598 ComPtr<IGattReadResult> descriptorValue;
1599 HRESULT hr;
1600 hr = op->GetResults(&descriptorValue);
1601 if (FAILED(hr)) {
1602 qCDebug(QT_BT_WINDOWS) << "Could not obtain result for descriptor" << descHandle;
1604 return S_OK;
1605 }
1607 descData.uuid = descUuid;
1609 descData.value = byteArrayFromGattResult(descriptorValue, true);
1610 else
1611 descData.value = byteArrayFromGattResult(descriptorValue);
1612 service->characteristicList[charHandle].descriptorList[descHandle] = descData;
1613 emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle),
1614 descData.value);
1615 return S_OK;
1616 };
1617 hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattReadResult *>>(
1618 readCompletedLambda).Get());
1619 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor read callback",
1621}
1622
1624 const QSharedPointer<QLowEnergyServicePrivate> service,
1625 const QLowEnergyHandle charHandle,
1626 const QByteArray &newValue,
1628{
1629 qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << service << charHandle << newValue << mode;
1630 qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
1632 Q_ASSERT(!service.isNull());
1636 return;
1637 }
1638 if (!service->characteristicList.contains(charHandle)) {
1639 qCDebug(QT_BT_WINDOWS) << "Characteristic" << charHandle << "cannot be found in service"
1640 << service->uuid;
1642 return;
1643 }
1644
1645 QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle);
1646 const bool writeWithResponse = mode == QLowEnergyService::WriteWithResponse;
1647 if (!(charData.properties & (writeWithResponse ? QLowEnergyCharacteristic::Write
1649 qCDebug(QT_BT_WINDOWS) << "Write flag is not set for characteristic" << charHandle;
1650
1651 auto characteristicCallback = [charHandle, service, newValue, writeWithResponse, this](
1652 ComPtr<IGattCharacteristic> characteristic) {
1653 writeCharacteristicHelper(service, charHandle, newValue, writeWithResponse,
1654 characteristic);
1655 };
1656
1657 HRESULT hr = getNativeCharacteristic(service->uuid, charData.uuid, characteristicCallback);
1658 if (FAILED(hr)) {
1659 qCDebug(QT_BT_WINDOWS) << "Could not obtain native characteristic" << charData.uuid
1660 << "from service" << service->uuid;
1662 }
1663}
1664
1665void QLowEnergyControllerPrivateWinRT::writeCharacteristicHelper(
1666 const QSharedPointer<QLowEnergyServicePrivate> service,
1667 const QLowEnergyHandle charHandle, const QByteArray &newValue,
1668 bool writeWithResponse, GattCharacteristicComPtr characteristic)
1669{
1670 ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
1671 HRESULT hr = GetActivationFactory(
1672 HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(),
1673 &bufferFactory);
1674 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain buffer factory",
1676 ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
1677 const quint32 length = quint32(newValue.length());
1678 hr = bufferFactory->Create(length, &buffer);
1679 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not create buffer",
1681 hr = buffer->put_Length(length);
1682 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer length",
1684 ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess;
1685 hr = buffer.As(&byteAccess);
1686 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast buffer",
1688 byte *bytes;
1689 hr = byteAccess->Buffer(&bytes);
1690 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer",
1692 memcpy(bytes, newValue, length);
1693 ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp;
1694 GattWriteOption option = writeWithResponse ? GattWriteOption_WriteWithResponse
1695 : GattWriteOption_WriteWithoutResponse;
1696 hr = characteristic->WriteValueWithOptionAsync(buffer.Get(), option, &writeOp);
1697 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could write characteristic",
1699 const auto charData = service->characteristicList.value(charHandle);
1700 QPointer<QLowEnergyControllerPrivateWinRT> thisPtr(this);
1701 auto writeCompletedLambda =
1702 [charData, charHandle, newValue, service, writeWithResponse, thisPtr]
1703 (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status)
1704 {
1705 if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
1706 qCDebug(QT_BT_WINDOWS) << "Characteristic" << charHandle << "write operation failed";
1708 return S_OK;
1709 }
1710 GattCommunicationStatus result;
1711 HRESULT hr;
1712 hr = op->GetResults(&result);
1713 if (hr == E_BLUETOOTH_ATT_INVALID_ATTRIBUTE_VALUE_LENGTH) {
1714 qCDebug(QT_BT_WINDOWS) << "Characteristic" << charHandle
1715 << "write operation was tried with invalid value length";
1717 return S_OK;
1718 }
1719 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain characteristic write result", service,
1721 if (result != GattCommunicationStatus_Success) {
1722 qCDebug(QT_BT_WINDOWS) << "Characteristic" << charHandle << "write operation failed";
1724 return S_OK;
1725 }
1726 // only update cache when property is readable. Otherwise it remains
1727 // empty.
1728 if (thisPtr && charData.properties & QLowEnergyCharacteristic::Read)
1729 thisPtr->updateValueOfCharacteristic(charHandle, newValue, false);
1730 if (writeWithResponse) {
1731 emit service->characteristicWritten(QLowEnergyCharacteristic(service, charHandle),
1732 newValue);
1733 }
1734 return S_OK;
1735 };
1736 hr = writeOp->put_Completed(
1737 Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus>>(
1738 writeCompletedLambda).Get());
1739 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register characteristic write callback",
1741}
1742
1744 const QSharedPointer<QLowEnergyServicePrivate> service,
1745 const QLowEnergyHandle charHandle,
1746 const QLowEnergyHandle descHandle,
1747 const QByteArray &newValue)
1748{
1749 qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << service << charHandle << descHandle << newValue;
1750 qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
1752 Q_ASSERT(!service.isNull());
1754 service->setError(QLowEnergyService::DescriptorWriteError);
1756 return;
1757 }
1758
1759 if (!service->characteristicList.contains(charHandle)) {
1760 qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "in characteristic" << charHandle
1761 << "could not be found in service" << service->uuid;
1762 service->setError(QLowEnergyService::DescriptorWriteError);
1763 return;
1764 }
1765
1766 const auto charData = service->characteristicList.value(charHandle);
1767
1768 auto characteristicCallback = [descHandle, charHandle, service, newValue, this](
1769 ComPtr<IGattCharacteristic> characteristic) {
1770 writeDescriptorHelper(service, charHandle, descHandle, newValue, characteristic);
1771 };
1772
1773 HRESULT hr = getNativeCharacteristic(service->uuid, charData.uuid, characteristicCallback);
1774 if (FAILED(hr)) {
1775 qCDebug(QT_BT_WINDOWS) << "Could not obtain native characteristic" << charData.uuid
1776 << "from service" << service->uuid;
1777 service->setError(QLowEnergyService::DescriptorWriteError);
1778 }
1779}
1780
1781void QLowEnergyControllerPrivateWinRT::writeDescriptorHelper(
1782 const QSharedPointer<QLowEnergyServicePrivate> service,
1783 const QLowEnergyHandle charHandle,
1784 const QLowEnergyHandle descHandle,
1785 const QByteArray &newValue,
1786 GattCharacteristicComPtr characteristic)
1787{
1788 // Get native descriptor
1789 const auto charData = service->characteristicList.value(charHandle);
1790 if (!charData.descriptorList.contains(descHandle)) {
1791 qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle
1792 << "could not be found in Characteristic" << charHandle;
1793 }
1794
1795 QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle);
1796 if (descData.uuid ==
1798 GattClientCharacteristicConfigurationDescriptorValue value;
1799 quint16 intValue = qFromLittleEndian<quint16>(newValue);
1800 if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Indicate
1801 && intValue & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
1802 qCWarning(QT_BT_WINDOWS) << "Setting both Indicate and Notify "
1803 "is not supported on WinRT";
1804 value = GattClientCharacteristicConfigurationDescriptorValue(
1805 (GattClientCharacteristicConfigurationDescriptorValue_Indicate
1806 | GattClientCharacteristicConfigurationDescriptorValue_Notify));
1807 } else if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Indicate) {
1808 value = GattClientCharacteristicConfigurationDescriptorValue_Indicate;
1809 } else if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
1810 value = GattClientCharacteristicConfigurationDescriptorValue_Notify;
1811 } else if (intValue == 0) {
1812 value = GattClientCharacteristicConfigurationDescriptorValue_None;
1813 } else {
1814 qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle
1815 << "write operation failed: Invalid value";
1817 return;
1818 }
1819 ComPtr<IAsyncOperation<enum GattCommunicationStatus>> writeOp;
1820 HRESULT hr =
1821 characteristic->WriteClientCharacteristicConfigurationDescriptorAsync(value, &writeOp);
1822 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not write client characteristic configuration",
1824 QPointer<QLowEnergyControllerPrivateWinRT> thisPtr(this);
1825 auto writeCompletedLambda = [charHandle, descHandle, newValue, service, thisPtr]
1826 (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status)
1827 {
1828 if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
1829 qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "write operation failed";
1831 return S_OK;
1832 }
1833 GattCommunicationStatus result;
1834 HRESULT hr;
1835 hr = op->GetResults(&result);
1836 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for descriptor", service,
1838 if (result != GattCommunicationStatus_Success) {
1839 qCWarning(QT_BT_WINDOWS) << "Descriptor" << descHandle << "write operation failed";
1841 return S_OK;
1842 }
1843 if (thisPtr)
1844 thisPtr->updateValueOfDescriptor(charHandle, descHandle, newValue, false);
1845 emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle),
1846 newValue);
1847 return S_OK;
1848 };
1849 hr = writeOp->put_Completed(
1850 Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus >>(
1851 writeCompletedLambda).Get());
1852 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor write callback",
1854 return;
1855 }
1856
1857 ComPtr<IGattCharacteristic3> characteristic3;
1858 HRESULT hr = characteristic.As(&characteristic3);
1859 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast characteristic",
1861 ComPtr<IAsyncOperation<GattDescriptorsResult *>> op;
1862 hr = characteristic3->GetDescriptorsForUuidAsync(descData.uuid, &op);
1863 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor from Uuid",
1865 ComPtr<IGattDescriptorsResult> result;
1866 hr = QWinRTFunctions::await(op, result.GetAddressOf(),
1867 QWinRTFunctions::ProcessMainThreadEvents, 5000);
1868 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not await descriptor operation",
1870 GattCommunicationStatus commStatus;
1871 hr = result->get_Status(&commStatus);
1872 if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) {
1873 qCWarning(QT_BT_WINDOWS) << "Descriptor operation failed";
1875 return;
1876 }
1877 ComPtr<IVectorView<GattDescriptor *>> descriptors;
1878 hr = result->get_Descriptors(&descriptors);
1879 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain list of descriptors",
1881 uint size;
1882 hr = descriptors->get_Size(&size);
1883 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain list of descriptors' size",
1885 if (size == 0) {
1886 qCWarning(QT_BT_WINDOWS) << "No descriptor with uuid" << descData.uuid << "was found.";
1887 return;
1888 } else if (size > 1) {
1889 qCWarning(QT_BT_WINDOWS) << "There is more than 1 descriptor with uuid" << descData.uuid;
1890 }
1891 ComPtr<IGattDescriptor> descriptor;
1892 hr = descriptors->GetAt(0, &descriptor);
1893 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor",
1895 ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
1896 hr = GetActivationFactory(
1897 HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(),
1898 &bufferFactory);
1899 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain buffer factory",
1901 ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
1902 const quint32 length = quint32(newValue.length());
1903 hr = bufferFactory->Create(length, &buffer);
1904 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not create buffer",
1906 hr = buffer->put_Length(length);
1907 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer length",
1909 ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess;
1910 hr = buffer.As(&byteAccess);
1911 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast buffer",
1913 byte *bytes;
1914 hr = byteAccess->Buffer(&bytes);
1915 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer",
1917 memcpy(bytes, newValue, length);
1918 ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp;
1919 hr = descriptor->WriteValueAsync(buffer.Get(), &writeOp);
1920 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not write descriptor value",
1922 QPointer<QLowEnergyControllerPrivateWinRT> thisPtr(this);
1923 auto writeCompletedLambda = [charHandle, descHandle, newValue, service, thisPtr]
1924 (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status)
1925 {
1926 if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
1927 qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "write operation failed";
1929 return S_OK;
1930 }
1931 GattCommunicationStatus result;
1932 HRESULT hr;
1933 hr = op->GetResults(&result);
1934 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for descriptor", service,
1936 if (result != GattCommunicationStatus_Success) {
1937 qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "write operation failed";
1939 return S_OK;
1940 }
1941 if (thisPtr)
1942 thisPtr->updateValueOfDescriptor(charHandle, descHandle, newValue, false);
1943 emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle),
1944 newValue);
1945 return S_OK;
1946 };
1947 hr = writeOp->put_Completed(
1948 Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus>>(
1949 writeCompletedLambda).Get());
1950 CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor write callback",
1952}
1953
1959
1961{
1962 uint16_t mtu = 23;
1963 if (!mGattSession) {
1964 qCDebug(QT_BT_WINDOWS) << "mtu queried before GattSession available. Using default mtu.";
1965 return mtu;
1966 }
1967
1968 HRESULT hr = mGattSession->get_MaxPduSize(&mtu);
1969 RETURN_IF_FAILED("could not obtain MTU size", return mtu);
1970 qCDebug(QT_BT_WINDOWS) << "mtu determined to be" << mtu;
1971 return mtu;
1972}
1973
1974void QLowEnergyControllerPrivateWinRT::handleCharacteristicChanged(
1975 quint16 charHandle, const QByteArray &data)
1976{
1977 qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << charHandle << data;
1978 qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
1980 QSharedPointer<QLowEnergyServicePrivate> service =
1981 serviceForHandle(charHandle);
1982 if (service.isNull())
1983 return;
1984
1985 qCDebug(QT_BT_WINDOWS) << "Characteristic change notification" << service->uuid
1986 << charHandle << data.toHex();
1987
1988 QLowEnergyCharacteristic characteristic = characteristicForHandle(charHandle);
1989 if (!characteristic.isValid()) {
1990 qCWarning(QT_BT_WINDOWS) << "characteristicChanged: Cannot find characteristic";
1991 return;
1992 }
1993
1994 // only update cache when property is readable. Otherwise it remains
1995 // empty.
1996 if (characteristic.properties() & QLowEnergyCharacteristic::Read)
1997 updateValueOfCharacteristic(characteristic.attributeHandle(),
1998 data, false);
1999 emit service->characteristicChanged(characteristic, data);
2000}
2001
2002void QLowEnergyControllerPrivateWinRT::handleServiceHandlerError(const QString &error)
2003{
2005 return;
2006
2007 qCWarning(QT_BT_WINDOWS) << "Error while discovering services:" << error;
2010}
2011
2012void QLowEnergyControllerPrivateWinRT::handleConnectionError(const char *logMessage)
2013{
2014 qCWarning(QT_BT_WINDOWS) << logMessage;
2017 unregisterFromStatusChanges();
2018 unregisterFromMtuChanges();
2019}
2020
2022
2023#include "qlowenergycontroller_winrt.moc"
IOBluetoothDevice * device
std::vector< ObjCStrongReference< CBMutableService > > services
\inmodule QtBluetooth
\inmodule QtBluetooth
Pairing
This enum describes the pairing state between the two Bluetooth devices.
Pairing pairingStatus(const QBluetoothAddress &address) const
Returns the current bluetooth pairing status of address, if it's unpaired, paired,...
\inmodule QtBluetooth
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
qsizetype length() const noexcept
Same as size().
Definition qbytearray.h:499
\inmodule QtCore
bool hasExpired() const noexcept
Returns true if this QDeadlineTimer object has expired, false if there remains time left.
\inmodule QtCore
Definition qhash.h:820
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1303
qsizetype size() const noexcept
Definition qlist.h:397
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
The QLowEnergyAdvertisingData class represents the data to be broadcast during Bluetooth Low Energy a...
The QLowEnergyAdvertisingParameters class represents the parameters used for Bluetooth Low Energy adv...
QLowEnergyCharacteristic::PropertyTypes properties() const
Returns the properties of the characteristic.
bool isValid() const
Returns true if the QLowEnergyCharacteristic object is valid, otherwise returns false.
The QLowEnergyConnectionParameters class is used when requesting or reporting an update of the parame...
void discoverServiceDetails(const QBluetoothUuid &service, QLowEnergyService::DiscoveryMode mode) override
void characteristicChanged(quint16 charHandle, const QByteArray &data)
void addToGenericAttributeList(const QLowEnergyServiceData &service, QLowEnergyHandle startHandle) override
void readDescriptor(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle) override
void writeDescriptor(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle, const QByteArray &newValue) override
void startAdvertising(const QLowEnergyAdvertisingParameters &params, const QLowEnergyAdvertisingData &advertisingData, const QLowEnergyAdvertisingData &scanResponseData) override
void readCharacteristic(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle) override
void writeCharacteristic(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QByteArray &newValue, QLowEnergyService::WriteMode mode) override
void requestConnectionUpdate(const QLowEnergyConnectionParameters &params) override
QLowEnergyCharacteristic characteristicForHandle(QLowEnergyHandle handle)
Returns a valid characteristic if the given handle is the handle of the characteristic itself or one ...
QSharedPointer< QLowEnergyServicePrivate > serviceForHandle(QLowEnergyHandle handle)
QLowEnergyController::Error error
void setError(QLowEnergyController::Error newError)
quint16 updateValueOfCharacteristic(QLowEnergyHandle charHandle, const QByteArray &value, bool appendValue)
Returns the length of the updated characteristic value.
QLowEnergyController::ControllerState state
void setState(QLowEnergyController::ControllerState newState)
\inmodule QtBluetooth
\inmodule QtBluetooth
The QLowEnergyServiceData class is used to set up GATT service data. \inmodule QtBluetooth.
DiscoveryMode
This enum lists service discovery modes.
WriteMode
This enum describes the mode to be used when writing a characteristic value.
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
QThread * thread() const
Returns the thread in which the object lives.
Definition qobject.cpp:1598
bool moveToThread(QThread *thread QT6_DECL_NEW_OVERLOAD_TAIL)
Changes the thread affinity for this object and its children and returns true on success.
Definition qobject.cpp:1643
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
bool remove(const T &value)
Definition qset.h:63
void clear()
Definition qset.h:61
iterator insert(const T &value)
Definition qset.h:155
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
QByteArray toUtf8() const &
Definition qstring.h:634
void start(Priority=InheritPriority)
Definition qthread.cpp:996
static QThread * currentThread()
Definition qthread.cpp:1039
void finished(QPrivateSignal)
void started(QPrivateSignal)
void deviceConnected(ComPtr< IBluetoothLEDevice > device, ComPtr< IGattSession > session)
QWinRTLowEnergyConnectionHandler(const QBluetoothAddress &address)
void errorOccurred(const QString &error)
void errorOccured(const QString &error)
void charListObtained(const QBluetoothUuid &service, QHash< QLowEnergyHandle, QLowEnergyServicePrivate::CharData > charList, QList< QBluetoothUuid > indicateChars, QLowEnergyHandle startHandle, QLowEnergyHandle endHandle)
QLowEnergyService::DiscoveryMode mMode
QWinRTLowEnergyServiceHandler(const QBluetoothUuid &service, const ComPtr< IGattDeviceService3 > &deviceService, QLowEnergyService::DiscoveryMode mode)
ComPtr< IGattDeviceService3 > mDeviceService
QHash< QLowEnergyHandle, QLowEnergyServicePrivate::CharData > mCharacteristicList
#define this
Definition dialogs.cpp:9
void qErrnoWarning(const char *msg,...)
Token token
Definition keywords.cpp:444
Combined button and popup list for selecting options.
Q_CORE_EXPORT QtJniTypes::Service service()
@ QueuedConnection
constexpr Initialization Uninitialized
quint16 QLowEnergyHandle
Definition qbluetooth.h:42
static QT_BEGIN_NAMESPACE QByteArray byteArrayFromBuffer(const IBuffer &buffer)
static const QCssKnownValue properties[NumProperties - 1]
DBusConnection const char DBusError * error
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
constexpr T qToLittleEndian(T source)
Definition qendian.h:176
Q_DECL_COLD_FUNCTION Q_CORE_EXPORT QString qt_error_string(int errorCode=-1)
#define qWarning
Definition qlogging.h:166
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
IGattReadClientCharacteristicConfigurationDescriptorResult IClientCharConfigDescriptorResult
#define EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED_2(hr, message)
QT_BEGIN_NAMESPACE typedef ITypedEventHandler< BluetoothLEDevice *, IInspectable * > StatusHandler
#define CHECK_HR_AND_SET_SERVICE_ERROR(hr, msg, service, error, ret)
static QByteArray byteArrayFromGattResult(const ComPtr< IGattReadResult > &gattResult, bool isWCharString=false)
static void closeDeviceService(ComPtr< T > service)
#define DEC_CHAR_COUNT_AND_CONTINUE_IF_FAILED(hr, msg)
#define CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, msg, ret)
ITypedEventHandler< GattSession *, IInspectable * > MtuHandler
GattReadClientCharacteristicConfigurationDescriptorResult ClientCharConfigDescriptorResult
static constexpr qint64 kMaxConnectTimeout
ITypedEventHandler< GattCharacteristic *, GattValueChangedEventArgs * > ValueChangedHandler
#define WARN_AND_CONTINUE_IF_FAILED(hr, msg)
#define EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr)
void registerQLowEnergyControllerMetaType()
static const QMetaObjectPrivate * priv(const uint *data)
GLuint64 GLenum void * handle
GLenum mode
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint GLenum GLsizei length
GLenum GLenum GLsizei count
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLenum GLuint GLsizei const GLenum * props
GLuint entry
GLuint GLuint64EXT address
GLsizei const void * pointer
Definition qopenglext.h:384
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLuint64EXT * result
[6]
GLuint GLenum option
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
SSL_CTX int void * arg
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
#define Q_OBJECT
#define slots
#define signals
#define emit
#define Q_UNIMPLEMENTED()
unsigned int quint32
Definition qtypes.h:50
unsigned short quint16
Definition qtypes.h:48
unsigned int uint
Definition qtypes.h:34
long long qint64
Definition qtypes.h:60
long HRESULT
if(qFloatDistance(a, b)<(1<< 7))
[0]
settings remove("monkey")
QDeadlineTimer deadline(30s)
myObject disconnect()
[26]
QJSValueList args