Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qbluetoothdevicediscoveryagent_winrt.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 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#include "qbluetoothaddress.h"
7#include "qbluetoothuuid.h"
8
9#include <QtBluetooth/private/qbluetoothdevicewatcher_winrt_p.h>
10#include <QtBluetooth/private/qbluetoothutils_winrt_p.h>
11#include <QtBluetooth/private/qtbluetoothglobal_p.h>
12
13#include <QtCore/QLoggingCategory>
14#include <QtCore/QMutex>
15#include <QtCore/qendian.h>
16
17#include <winrt/Windows.Devices.Bluetooth.h>
18#include <winrt/Windows.Devices.Bluetooth.Advertisement.h>
19#include <winrt/Windows.Devices.Bluetooth.Rfcomm.h>
20#include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h>
21#include <winrt/Windows.Devices.Enumeration.h>
22#include <winrt/Windows.Foundation.h>
23#include <winrt/Windows.Foundation.Collections.h>
24#include <winrt/Windows.Storage.Streams.h>
25
26using namespace winrt::Windows::Devices::Bluetooth;
27using namespace winrt::Windows::Devices::Bluetooth::Advertisement;
28using namespace winrt::Windows::Devices::Bluetooth::GenericAttributeProfile;
29using namespace winrt::Windows::Devices::Bluetooth::Rfcomm;
30using namespace winrt::Windows::Devices::Enumeration;
31using namespace winrt::Windows::Foundation;
32using namespace winrt::Windows::Storage::Streams;
33
35
36QT_IMPL_METATYPE_EXTERN(ManufacturerData)
37QT_IMPL_METATYPE_EXTERN(ServiceData)
38
39Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS)
40
41// Endianness conversion for QUuid::Id128Bytes doesn't exist in qtendian.h
42inline QUuid::Id128Bytes qbswap(const QUuid::Id128Bytes src)
43{
45 for (int i = 0; i < 16; i++)
46 dst.data[i] = src.data[15 - i];
47 return dst;
48}
49
51{
52 const uint8_t *data = buffer.data();
53 return QByteArray(reinterpret_cast<const char *>(data),
54 static_cast<qsizetype>(buffer.Length()));
55}
56
57static ManufacturerData extractManufacturerData(const BluetoothLEAdvertisement &ad)
58{
59 ManufacturerData ret;
60 const auto data = ad.ManufacturerData();
61 for (const auto &item : data) {
62 const uint16_t id = item.CompanyId();
63 const QByteArray bufferData = byteArrayFromBuffer(item.Data());
64 if (ret.contains(id))
65 qCWarning(QT_BT_WINDOWS) << "Company ID already present in manufacturer data.";
66 ret.insert(id, bufferData);
67 }
68 return ret;
69}
70
71static ServiceData extractServiceData(const BluetoothLEAdvertisement &ad)
72{
73 static constexpr int serviceDataTypes[3] = { 0x16, 0x20, 0x21 };
74
75 ServiceData ret;
76
77 for (const auto &serviceDataType : serviceDataTypes) {
78 const auto dataSections = ad.GetSectionsByType(serviceDataType);
79 for (const auto &section : dataSections) {
80 const unsigned char dataType = section.DataType();
81 const QByteArray bufferData = byteArrayFromBuffer(section.Data());
82 if (dataType == 0x16) {
83 Q_ASSERT(bufferData.size() >= 2);
84 ret.insert(QBluetoothUuid(qFromLittleEndian<quint16>(bufferData.constData())),
85 bufferData + 2);
86 } else if (dataType == 0x20) {
87 Q_ASSERT(bufferData.size() >= 4);
88 ret.insert(QBluetoothUuid(qFromLittleEndian<quint32>(bufferData.constData())),
89 bufferData + 4);
90 } else if (dataType == 0x21) {
91 Q_ASSERT(bufferData.size() >= 16);
92 ret.insert(QBluetoothUuid(qToBigEndian<QUuid::Id128Bytes>(
93 qFromLittleEndian<QUuid::Id128Bytes>(bufferData.constData()))),
94 bufferData + 16);
95 }
96 }
97 }
98
99 return ret;
100}
101
102// Needed because there is no explicit conversion
103static GUID fromWinRtGuid(const winrt::guid &guid)
104{
105 const GUID uuid {
106 guid.Data1,
107 guid.Data2,
108 guid.Data3,
109 { guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
110 guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7] }
111 };
112 return uuid;
113}
114
116 public std::enable_shared_from_this<AdvertisementWatcherWrapper>
117{
119public:
122 {
123 stop();
124 }
125 void init()
126 {
127 m_watcher.ScanningMode(BluetoothLEScanningMode::Active);
128 }
129 void start() {
130 subscribeToEvents();
131 m_watcher.Start();
132 }
133 void stop()
134 {
135 if (canStop()) {
136 unsubscribeFromEvents();
137 m_watcher.Stop();
138 }
139 }
140
141signals:
142 // The signal will be emitted from a separate thread,
143 // so we need to use Qt::QueuedConnection
145 const ManufacturerData &manufacturerData,
146 const ServiceData &serviceData,
147 const QList<QBluetoothUuid> &uuids);
148private:
149 void subscribeToEvents()
150 {
151 // The callbacks are triggered from separate threads. So we capture
152 // thisPtr to make sure that the object is valid.
153 auto thisPtr = shared_from_this();
154 m_receivedToken = m_watcher.Received(
155 [thisPtr](BluetoothLEAdvertisementWatcher,
156 BluetoothLEAdvertisementReceivedEventArgs args) {
157 const uint64_t address = args.BluetoothAddress();
158 const short rssi = args.RawSignalStrengthInDBm();
159 const BluetoothLEAdvertisement ad = args.Advertisement();
160
161 const ManufacturerData manufacturerData = extractManufacturerData(ad);
162 const ServiceData serviceData = extractServiceData(ad);
163
164 QList<QBluetoothUuid> serviceUuids;
165 const auto guids = ad.ServiceUuids();
166 for (const auto &guid : guids) {
167 const GUID uuid = fromWinRtGuid(guid);
168 serviceUuids.append(QBluetoothUuid(uuid));
169 }
170
171 emit thisPtr->advertisementDataReceived(address, rssi, manufacturerData,
172 serviceData, serviceUuids);
173 });
174 }
175 void unsubscribeFromEvents()
176 {
177 m_watcher.Received(m_receivedToken);
178 }
179 bool canStop() const
180 {
181 const auto status = m_watcher.Status();
182 return status == BluetoothLEAdvertisementWatcherStatus::Started
183 || status == BluetoothLEAdvertisementWatcherStatus::Aborted;
184 }
185
186 BluetoothLEAdvertisementWatcher m_watcher;
187 winrt::event_token m_receivedToken;
188};
189
190// Both constants are taken from Microsoft's docs:
191// https://docs.microsoft.com/en-us/windows/uwp/devices-sensors/aep-service-class-ids
192// Alternatively we could create separate watchers for paired and unpaired devices.
193static const winrt::hstring ClassicDeviceSelector =
194 L"System.Devices.Aep.ProtocolId:=\"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}\"";
195// Do not use it for now, so comment out. Do not delete in case we want to reuse it.
196//static const winrt::hstring LowEnergyDeviceSelector =
197// L"System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\"";
198
200 public std::enable_shared_from_this<QWinRTBluetoothDeviceDiscoveryWorker>
201{
203public:
204 QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods,
205 int interval);
207 void start();
208 void stop();
209
210private:
211 void startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode);
212
213 std::shared_ptr<QBluetoothDeviceWatcherWinRT> createDeviceWatcher(winrt::hstring selector,
214 int watcherId);
215 void generateError(QBluetoothDeviceDiscoveryAgent::Error error, const char *msg = nullptr);
216 void invokeDeviceFoundWithDebug(const QBluetoothDeviceInfo &info);
217 void finishDiscovery();
218 bool isFinished() const;
219
220 // Bluetooth Classic handlers
221 void getClassicDeviceFromId(const winrt::hstring &id);
222 void handleClassicDevice(const BluetoothDevice &device);
223 void handleRfcommServices(const RfcommDeviceServicesResult &servicesResult,
224 uint64_t address, const QString &name, uint32_t classOfDeviceInt);
225
226 // Bluetooth Low Energy handlers
227 void getLowEnergyDeviceFromId(const winrt::hstring &id);
228 void handleLowEnergyDevice(const BluetoothLEDevice &device);
229 void handleGattServices(const GattDeviceServicesResult &servicesResult,
231
232 // Bluetooth Low Energy Advertising handlers
233 std::shared_ptr<AdvertisementWatcherWrapper> createAdvertisementWatcher();
234
235 // invokable methods for handling finish conditions
236 Q_INVOKABLE void decrementPendingDevicesCountAndCheckFinished(
237 std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker);
238
241 void deviceDataChanged(const QBluetoothAddress &address, QBluetoothDeviceInfo::Fields,
242 qint16 rssi, ManufacturerData manufacturerData, ServiceData serviceData);
245
246private slots:
247 void onBluetoothDeviceFound(winrt::hstring deviceId, int watcherId);
248 void onDeviceEnumerationCompleted(int watcherId);
249
250 void onAdvertisementDataReceived(quint64 address, qint16 rssi,
251 const ManufacturerData &manufacturerData,
252 const ServiceData &serviceData,
253 const QList<QBluetoothUuid> &uuids);
254
255 void stopAdvertisementWatcher();
256
257private:
258 struct LEAdvertisingInfo {
260 ManufacturerData manufacturerData;
261 ServiceData serviceData;
262 qint16 rssi = 0;
263 };
264
265 quint8 requestedModes = 0;
266 QMutex m_leDevicesMutex;
267 QMap<quint64, LEAdvertisingInfo> m_foundLEDevicesMap;
268 int m_pendingDevices = 0;
269
270 static constexpr int ClassicWatcherId = 1;
271 static constexpr int LowEnergyWatcherId = 2;
272
273 std::shared_ptr<QBluetoothDeviceWatcherWinRT> m_classicWatcher;
274 std::shared_ptr<QBluetoothDeviceWatcherWinRT> m_lowEnergyWatcher;
275 std::shared_ptr<AdvertisementWatcherWrapper> m_advertisementWatcher;
276 bool m_classicScanStarted = false;
277 bool m_lowEnergyScanStarted = false;
278 QTimer *m_leScanTimer = nullptr;
279};
280
282 std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker)
283{
284 QMetaObject::invokeMethod(worker.get(), "decrementPendingDevicesCountAndCheckFinished",
286 Q_ARG(std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker>,
287 worker));
288}
289
291 QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods, int interval)
292 : requestedModes(methods)
293{
294 qRegisterMetaType<QBluetoothDeviceInfo>();
295 qRegisterMetaType<QBluetoothDeviceInfo::Fields>();
296 qRegisterMetaType<ManufacturerData>();
297 qRegisterMetaType<std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker>>();
298
299 m_classicWatcher = createDeviceWatcher(ClassicDeviceSelector, ClassicWatcherId);
300 // For LE scan use DeviceWatcher to handle only paired devices.
301 // Non-paired devices will be found using BluetoothLEAdvertisementWatcher.
302 const auto leSelector = BluetoothLEDevice::GetDeviceSelectorFromPairingState(true);
303 m_lowEnergyWatcher = createDeviceWatcher(leSelector, LowEnergyWatcherId);
304 m_advertisementWatcher = createAdvertisementWatcher();
305
306 // Docs claim that a negative interval means that the backend handles it on its own
307 if (interval < 0)
308 interval = 40000;
309
310 if (interval != 0) {
311 m_leScanTimer = new QTimer(this);
312 m_leScanTimer->setSingleShot(true);
313 m_leScanTimer->setInterval(interval);
314 connect(m_leScanTimer, &QTimer::timeout, this,
315 &QWinRTBluetoothDeviceDiscoveryWorker::stopAdvertisementWatcher);
316 }
317}
318
320{
321 stop();
322}
323
325{
327 if (m_classicWatcher && m_classicWatcher->init()) {
328 m_classicWatcher->start();
329 m_classicScanStarted = true;
330 } else {
332 "Could not start classic device watcher");
333 }
334 }
336 if (m_lowEnergyWatcher && m_lowEnergyWatcher->init()) {
337 m_lowEnergyWatcher->start();
338 m_lowEnergyScanStarted = true;
339 } else {
341 "Could not start low energy device watcher");
342 }
343 if (m_advertisementWatcher) {
344 m_advertisementWatcher->init();
345 m_advertisementWatcher->start();
346 if (m_leScanTimer)
347 m_leScanTimer->start();
348 } else {
350 "Could not start low energy advertisement watcher");
351 }
352 }
353
354 qCDebug(QT_BT_WINDOWS) << "Worker started";
355}
356
358{
359 if (m_leScanTimer && m_leScanTimer->isActive())
360 m_leScanTimer->stop();
361 m_classicWatcher->stop();
362 m_lowEnergyWatcher->stop();
363 m_advertisementWatcher->stop();
364}
365
366void QWinRTBluetoothDeviceDiscoveryWorker::finishDiscovery()
367{
368 stop();
370}
371
372bool QWinRTBluetoothDeviceDiscoveryWorker::isFinished() const
373{
374 // If the interval is set to 0, we do not start a timer, and that means
375 // that we need to wait for the user to explicitly call stop()
376 return (m_pendingDevices == 0) && !m_lowEnergyScanStarted && !m_classicScanStarted
377 && (m_leScanTimer && !m_leScanTimer->isActive());
378}
379
380void QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothDeviceFound(winrt::hstring deviceId, int watcherId)
381{
382 if (watcherId == ClassicWatcherId)
383 getClassicDeviceFromId(deviceId);
384 else if (watcherId == LowEnergyWatcherId)
385 getLowEnergyDeviceFromId(deviceId);
386}
387
388void QWinRTBluetoothDeviceDiscoveryWorker::onDeviceEnumerationCompleted(int watcherId)
389{
390 qCDebug(QT_BT_WINDOWS) << (watcherId == ClassicWatcherId ? "BT" : "BTLE")
391 << "enumeration completed";
392 if (watcherId == ClassicWatcherId) {
393 m_classicWatcher->stop();
394 m_classicScanStarted = false;
395 } else if (watcherId == LowEnergyWatcherId) {
396 m_lowEnergyWatcher->stop();
397 m_lowEnergyScanStarted = false;
398 }
399 if (isFinished())
400 finishDiscovery();
401}
402
403// this function executes in main worker thread
404void QWinRTBluetoothDeviceDiscoveryWorker::onAdvertisementDataReceived(
405 quint64 address, qint16 rssi, const ManufacturerData &manufacturerData,
406 const ServiceData &serviceData, const QList<QBluetoothUuid> &uuids)
407{
408 // Merge newly found services with list of currently found ones
409 bool needDiscoverServices = false;
410 {
411 QMutexLocker locker(&m_leDevicesMutex);
412 if (m_foundLEDevicesMap.contains(address)) {
413 QBluetoothDeviceInfo::Fields changedFields = QBluetoothDeviceInfo::Field::None;
414 const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address);
415 QList<QBluetoothUuid> foundServices = adInfo.services;
416 if (adInfo.rssi != rssi) {
417 m_foundLEDevicesMap[address].rssi = rssi;
418 changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI);
419 }
420 if (adInfo.manufacturerData != manufacturerData) {
421 m_foundLEDevicesMap[address].manufacturerData.insert(manufacturerData);
422 if (adInfo.manufacturerData != m_foundLEDevicesMap[address].manufacturerData)
424 }
425 if (adInfo.serviceData != serviceData) {
426 m_foundLEDevicesMap[address].serviceData.insert(serviceData);
427 if (adInfo.serviceData != m_foundLEDevicesMap[address].serviceData)
428 changedFields.setFlag((QBluetoothDeviceInfo::Field::ServiceData));
429 }
430 for (const QBluetoothUuid &uuid : std::as_const(uuids)) {
431 if (!foundServices.contains(uuid)) {
432 foundServices.append(uuid);
433 needDiscoverServices = true;
434 }
435 }
436 if (!needDiscoverServices) {
437 if (!changedFields.testFlag(QBluetoothDeviceInfo::Field::None)) {
438 QMetaObject::invokeMethod(this, "deviceDataChanged", Qt::AutoConnection,
440 Q_ARG(QBluetoothDeviceInfo::Fields, changedFields),
441 Q_ARG(qint16, rssi),
442 Q_ARG(ManufacturerData, manufacturerData),
443 Q_ARG(ServiceData, serviceData));
444 }
445 }
446 m_foundLEDevicesMap[address].services = foundServices;
447 } else {
448 needDiscoverServices = true;
449 LEAdvertisingInfo info;
450 info.services = std::move(uuids);
451 info.manufacturerData = std::move(manufacturerData);
452 info.serviceData = std::move(serviceData);
453 info.rssi = rssi;
454 m_foundLEDevicesMap.insert(address, info);
455 }
456 }
457 if (needDiscoverServices) {
458 ++m_pendingDevices; // as if we discovered a new LE device
459 auto thisPtr = shared_from_this();
460 auto asyncOp = BluetoothLEDevice::FromBluetoothAddressAsync(address);
461 asyncOp.Completed([thisPtr, address](auto &&op, AsyncStatus status) {
462 if (thisPtr) {
463 if (status == AsyncStatus::Completed) {
464 BluetoothLEDevice device = op.GetResults();
465 if (device) {
466 thisPtr->handleLowEnergyDevice(device);
467 return;
468 }
469 }
470 // status != Completed or failed to extract result
471 qCDebug(QT_BT_WINDOWS) << "Failed to get LE device from address"
474 }
475 });
476 }
477}
478
479void QWinRTBluetoothDeviceDiscoveryWorker::stopAdvertisementWatcher()
480{
481 m_advertisementWatcher->stop();
482 if (isFinished())
483 finishDiscovery();
484}
485
486std::shared_ptr<QBluetoothDeviceWatcherWinRT>
487QWinRTBluetoothDeviceDiscoveryWorker::createDeviceWatcher(winrt::hstring selector, int watcherId)
488{
489 auto watcher = std::make_shared<QBluetoothDeviceWatcherWinRT>(
490 watcherId, selector, DeviceInformationKind::AssociationEndpoint);
491 if (watcher) {
493 this, &QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothDeviceFound,
496 this, &QWinRTBluetoothDeviceDiscoveryWorker::onDeviceEnumerationCompleted,
498 }
499 return watcher;
500}
501
502void QWinRTBluetoothDeviceDiscoveryWorker::generateError(
504{
506 qCWarning(QT_BT_WINDOWS) << msg;
507}
508
509void QWinRTBluetoothDeviceDiscoveryWorker::invokeDeviceFoundWithDebug(const QBluetoothDeviceInfo &info)
510{
511 qCDebug(QT_BT_WINDOWS) << "Discovered BTLE device: " << info.address() << info.name()
512 << "Num UUIDs" << info.serviceUuids().size() << "RSSI:" << info.rssi()
513 << "Num manufacturer data" << info.manufacturerData().size()
514 << "Num service data" << info.serviceData().size();
515
518}
519
520// this function executes in main worker thread
521void QWinRTBluetoothDeviceDiscoveryWorker::getClassicDeviceFromId(const winrt::hstring &id)
522{
523 ++m_pendingDevices;
524 auto thisPtr = shared_from_this();
525 auto asyncOp = BluetoothDevice::FromIdAsync(id);
526 asyncOp.Completed([thisPtr](auto &&op, AsyncStatus status) {
527 if (thisPtr) {
528 if (status == AsyncStatus::Completed) {
529 BluetoothDevice device = op.GetResults();
530 if (device) {
531 thisPtr->handleClassicDevice(device);
532 return;
533 }
534 }
535 // status != Completed or failed to extract result
536 qCDebug(QT_BT_WINDOWS) << "Failed to get Classic device from id";
538 }
539 });
540}
541
542// this is a callback - executes in a new thread
543void QWinRTBluetoothDeviceDiscoveryWorker::handleClassicDevice(const BluetoothDevice &device)
544{
545 const uint64_t address = device.BluetoothAddress();
546 const std::wstring name { device.Name() }; // via operator std::wstring_view()
547 const QString btName = QString::fromStdWString(name);
548 const uint32_t deviceClass = device.ClassOfDevice().RawValue();
549 auto thisPtr = shared_from_this();
550 auto asyncOp = device.GetRfcommServicesAsync();
551 asyncOp.Completed([thisPtr, address, btName, deviceClass](auto &&op, AsyncStatus status) {
552 if (thisPtr) {
553 if (status == AsyncStatus::Completed) {
554 auto servicesResult = op.GetResults();
555 if (servicesResult) {
556 thisPtr->handleRfcommServices(servicesResult, address, btName, deviceClass);
557 return;
558 }
559 }
560 // Failed to get services
561 qCDebug(QT_BT_WINDOWS) << "Failed to get RFCOMM services for device" << btName;
563 }
564 });
565}
566
567// this is a callback - executes in a new thread
568void QWinRTBluetoothDeviceDiscoveryWorker::handleRfcommServices(
569 const RfcommDeviceServicesResult &servicesResult, uint64_t address,
570 const QString &name, uint32_t classOfDeviceInt)
571{
572 // need to perform the check even if some of the operations fails
573 auto shared = shared_from_this();
574 auto guard = qScopeGuard([shared]() {
576 });
577 Q_UNUSED(guard); // to suppress warning
578
579 const auto error = servicesResult.Error();
580 if (error != BluetoothError::Success) {
581 qCWarning(QT_BT_WINDOWS) << "Obtain device services completed with BluetoothError"
582 << static_cast<int>(error);
583 return;
584 }
585
586 const auto services = servicesResult.Services();
588 for (const auto &service : services) {
589 const auto serviceId = service.ServiceId();
590 const GUID uuid = fromWinRtGuid(serviceId.Uuid());
591 uuids.append(QBluetoothUuid(uuid));
592 }
593
594 const QBluetoothAddress btAddress(address);
595
596 qCDebug(QT_BT_WINDOWS) << "Discovered BT device: " << btAddress << name
597 << "Num UUIDs" << uuids.size();
598
599 QBluetoothDeviceInfo info(btAddress, name, classOfDeviceInt);
601 info.setServiceUuids(uuids);
602 info.setCached(true);
603
606}
607
608void QWinRTBluetoothDeviceDiscoveryWorker::decrementPendingDevicesCountAndCheckFinished(
609 std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker)
610{
611 --m_pendingDevices;
612 if (isFinished())
613 finishDiscovery();
614 // Worker is passed here simply to make sure that the object is still alive
615 // when we call this method via QObject::invoke().
616 Q_UNUSED(worker)
617}
618
619// this function executes in main worker thread
620void QWinRTBluetoothDeviceDiscoveryWorker::getLowEnergyDeviceFromId(const winrt::hstring &id)
621{
622 ++m_pendingDevices;
623 auto asyncOp = BluetoothLEDevice::FromIdAsync(id);
624 auto thisPtr = shared_from_this();
625 asyncOp.Completed([thisPtr](auto &&op, AsyncStatus status) {
626 if (thisPtr) {
627 if (status == AsyncStatus::Completed) {
628 BluetoothLEDevice device = op.GetResults();
629 if (device) {
630 thisPtr->handleLowEnergyDevice(device);
631 return;
632 }
633 }
634 // status != Completed or failed to extract result
635 qCDebug(QT_BT_WINDOWS) << "Failed to get LE device from id";
637 }
638 });
639}
640
641// this is a callback - executes in a new thread
642void QWinRTBluetoothDeviceDiscoveryWorker::handleLowEnergyDevice(const BluetoothLEDevice &device)
643{
644 const uint64_t address = device.BluetoothAddress();
645 const std::wstring name { device.Name() }; // via operator std::wstring_view()
646 const QString btName = QString::fromStdWString(name);
647 const bool isPaired = device.DeviceInformation().Pairing().IsPaired();
648
649 m_leDevicesMutex.lock();
650 const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address);
651 m_leDevicesMutex.unlock();
652 const ManufacturerData manufacturerData = adInfo.manufacturerData;
653 const ServiceData serviceData = adInfo.serviceData;
654 const qint16 rssi = adInfo.rssi;
655
658 info.setRssi(rssi);
659 for (quint16 key : manufacturerData.keys())
660 info.setManufacturerData(key, manufacturerData.value(key));
661 for (QBluetoothUuid key : serviceData.keys())
662 info.setServiceData(key, serviceData.value(key));
663 info.setCached(true);
664
665 // Use the services obtained from the advertisement data if the device is not paired
666 if (!isPaired) {
667 info.setServiceUuids(adInfo.services);
669 invokeDeviceFoundWithDebug(info);
670 } else {
671 auto asyncOp = device.GetGattServicesAsync();
672 auto thisPtr = shared_from_this();
673 asyncOp.Completed([thisPtr, info](auto &&op, AsyncStatus status) mutable {
674 if (status == AsyncStatus::Completed) {
675 auto servicesResult = op.GetResults();
676 if (servicesResult) {
677 thisPtr->handleGattServices(servicesResult, info);
678 return;
679 }
680 }
681 // Failed to get services
682 qCDebug(QT_BT_WINDOWS) << "Failed to get GATT services for device" << info.name();
684 });
685 }
686}
687
688// this is a callback - executes in a new thread
689void QWinRTBluetoothDeviceDiscoveryWorker::handleGattServices(
690 const GattDeviceServicesResult &servicesResult, QBluetoothDeviceInfo &info)
691{
692 // need to perform the check even if some of the operations fails
693 auto shared = shared_from_this();
694 auto guard = qScopeGuard([shared]() {
696 });
697 Q_UNUSED(guard); // to suppress warning
698
699 const auto status = servicesResult.Status();
700 if (status == GattCommunicationStatus::Success) {
701 const auto services = servicesResult.Services();
703 for (const auto &service : services) {
704 const GUID uuid = fromWinRtGuid(service.Uuid());
705 uuids.append(QBluetoothUuid(uuid));
706 }
707 info.setServiceUuids(uuids);
708 } else {
709 qCWarning(QT_BT_WINDOWS) << "Obtaining LE services finished with status"
710 << static_cast<int>(status);
711 }
712 invokeDeviceFoundWithDebug(info);
713}
714
715std::shared_ptr<AdvertisementWatcherWrapper>
716QWinRTBluetoothDeviceDiscoveryWorker::createAdvertisementWatcher()
717{
718 auto watcher = std::make_shared<AdvertisementWatcherWrapper>();
719 if (watcher) {
721 this, &QWinRTBluetoothDeviceDiscoveryWorker::onAdvertisementDataReceived,
723 }
724 return watcher;
725}
726
729 : q_ptr(parent), adapterAddress(deviceAdapter)
730{
731 mainThreadCoInit(this);
732}
733
735{
736 disconnectAndClearWorker();
737 mainThreadCoUninit(this);
738}
739
741{
742 return worker != nullptr;
743}
744
745QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
746{
748}
749
750void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
751{
752 QBluetoothLocalDevice adapter(adapterAddress);
753 if (!adapter.isValid()) {
754 qCWarning(QT_BT_WINDOWS) << "Cannot find Bluetooth adapter for device search";
756 errorString = QBluetoothDeviceDiscoveryAgent::tr("Cannot find valid Bluetooth adapter.");
757 emit q_ptr->errorOccurred(lastError);
758 return;
759 } else if (adapter.hostMode() == QBluetoothLocalDevice::HostPoweredOff) {
760 qCWarning(QT_BT_WINDOWS) << "Bluetooth adapter powered off";
762 errorString = QBluetoothDeviceDiscoveryAgent::tr("Bluetooth adapter powered off.");
763 emit q_ptr->errorOccurred(lastError);
764 return;
765 }
766
767 if (worker)
768 return;
769
770 worker = std::make_shared<QWinRTBluetoothDeviceDiscoveryWorker>(methods,
771 lowEnergySearchTimeout);
773 errorString.clear();
774 discoveredDevices.clear();
776 this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice);
778 this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData);
780 this, &QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured);
782 this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished);
783 worker->start();
784}
785
787{
789 if (worker) {
790 worker->stop();
791 disconnectAndClearWorker();
792 emit q->canceled();
793 }
794}
795
796void QBluetoothDeviceDiscoveryAgentPrivate::registerDevice(const QBluetoothDeviceInfo &info)
797{
799
800 for (QList<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin();
801 iter != discoveredDevices.end(); ++iter) {
802 if (iter->address() == info.address()) {
803 qCDebug(QT_BT_WINDOWS) << "Updating device" << iter->name() << iter->address();
804 // merge service uuids
805 QList<QBluetoothUuid> uuids = iter->serviceUuids();
806 uuids.append(info.serviceUuids());
807 const QSet<QBluetoothUuid> uuidSet(uuids.begin(), uuids.end());
808 if (iter->serviceUuids().size() != uuidSet.size())
809 iter->setServiceUuids(uuidSet.values().toVector());
810 if (iter->coreConfigurations() != info.coreConfigurations())
812 return;
813 }
814 }
815
816 discoveredDevices << info;
817 emit q->deviceDiscovered(info);
818}
819
820void QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData(const QBluetoothAddress &address,
821 QBluetoothDeviceInfo::Fields fields,
822 qint16 rssi,
823 ManufacturerData manufacturerData,
824 ServiceData serviceData)
825{
826 if (fields.testFlag(QBluetoothDeviceInfo::Field::None))
827 return;
828
830 for (QList<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin();
831 iter != discoveredDevices.end(); ++iter) {
832 if (iter->address() == address) {
833 qCDebug(QT_BT_WINDOWS) << "Updating data for device" << iter->name() << iter->address();
834 if (fields.testFlag(QBluetoothDeviceInfo::Field::RSSI))
835 iter->setRssi(rssi);
837 for (quint16 key : manufacturerData.keys())
838 iter->setManufacturerData(key, manufacturerData.value(key));
839 if (fields.testFlag(QBluetoothDeviceInfo::Field::ServiceData))
840 for (QBluetoothUuid key : serviceData.keys())
841 iter->setServiceData(key, serviceData.value(key));
842 emit q->deviceUpdated(*iter, fields);
843 return;
844 }
845 }
846}
847
848void QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured(QBluetoothDeviceDiscoveryAgent::Error e)
849{
851 lastError = e;
852 emit q->errorOccurred(e);
853}
854
855void QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished()
856{
858 disconnectAndClearWorker();
859 emit q->finished();
860}
861
862void QBluetoothDeviceDiscoveryAgentPrivate::disconnectAndClearWorker()
863{
864 if (!worker)
865 return;
866
868 this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished);
870 this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice);
872 this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData);
874 this, &QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured);
875
876 worker = nullptr;
877}
878
880
881#include <qbluetoothdevicediscoveryagent_winrt.moc>
static JNINativeMethod methods[]
quint8 rssi
IOBluetoothDevice * device
std::vector< ObjCStrongReference< CBMutableService > > services
void advertisementDataReceived(quint64 address, qint16 rssi, const ManufacturerData &manufacturerData, const ServiceData &serviceData, const QList< QBluetoothUuid > &uuids)
\inmodule QtBluetooth
void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &deviceAdapter, QBluetoothDeviceDiscoveryAgent *parent)
static DiscoveryMethods supportedDiscoveryMethods()
This function returns the discovery methods supported by the current platform.
void errorOccurred(QBluetoothDeviceDiscoveryAgent::Error error)
This signal is emitted when an error occurs during Bluetooth device discovery.
DiscoveryMethod
This enum descibes the type of discovery method employed by the QBluetoothDeviceDiscoveryAgent.
Error
Indicates all possible error conditions found during Bluetooth device discovery.
\inmodule QtBluetooth
void deviceAdded(winrt::hstring deviceId, int id)
\inmodule QtBluetooth
\inmodule QtBluetooth
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
qint64 size() const
Returns the file size in bytes.
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
iterator end()
Definition qlist.h:609
iterator begin()
Definition qlist.h:608
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
Definition qmap.h:186
iterator insert(const Key &key, const T &value)
Definition qmap.h:687
T value(const Key &key, const T &defaultValue=T()) const
Definition qmap.h:356
bool contains(const Key &key) const
Definition qmap.h:340
\inmodule QtCore
Definition qmutex.h:317
\inmodule QtCore
Definition qmutex.h:285
void unlock() noexcept
Unlocks the mutex.
Definition qmutex.h:293
void lock() noexcept
Locks the mutex.
Definition qmutex.h:290
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
Definition qset.h:18
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1107
static QString fromStdWString(const std::wstring &s)
Returns a copy of the str string.
Definition qstring.h:1333
\inmodule QtCore
Definition qtimer.h:20
void setSingleShot(bool singleShot)
Definition qtimer.cpp:580
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:208
void setInterval(int msec)
Definition qtimer.cpp:607
bool isActive() const
Returns true if the timer is running (pending); otherwise returns false.
Definition qtimer.cpp:156
void stop()
Stops the timer.
Definition qtimer.cpp:226
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
\inmodule QtCore
Definition quuid.h:31
void deviceDataChanged(const QBluetoothAddress &address, QBluetoothDeviceInfo::Fields, qint16 rssi, ManufacturerData manufacturerData, ServiceData serviceData)
void deviceFound(const QBluetoothDeviceInfo &info)
void errorOccured(QBluetoothDeviceDiscoveryAgent::Error error)
QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods, int interval)
double e
@ BluetoothDevice
Combined button and popup list for selecting options.
Q_CORE_EXPORT QtJniTypes::Service service()
@ AutoConnection
@ QueuedConnection
static ManufacturerData extractManufacturerData(const BluetoothLEAdvertisement &ad)
static void invokeDecrementPendingDevicesCountAndCheckFinished(std::shared_ptr< QWinRTBluetoothDeviceDiscoveryWorker > worker)
static const winrt::hstring ClassicDeviceSelector
static GUID fromWinRtGuid(const winrt::guid &guid)
static QByteArray byteArrayFromBuffer(const IBuffer &buffer)
static ServiceData extractServiceData(const BluetoothLEAdvertisement &ad)
QT_BEGIN_NAMESPACE QUuid::Id128Bytes qbswap(const QUuid::Id128Bytes src)
void mainThreadCoInit(void *caller)
void mainThreadCoUninit(void *caller)
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter * iter
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
return ret
#define QT_IMPL_METATYPE_EXTERN(TYPE)
Definition qmetatype.h:1369
#define Q_ARG(Type, data)
Definition qobjectdefs.h:62
GLenum mode
GLuint64 key
GLenum src
GLenum GLuint buffer
GLenum GLenum dst
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint name
GLuint GLuint64EXT address
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QScopeGuard< typename std::decay< F >::type > qScopeGuard(F &&f)
[qScopeGuard]
Definition qscopeguard.h:60
#define Q_OBJECT
#define Q_INVOKABLE
#define slots
#define signals
#define Q_SIGNALS
#define emit
#define Q_UNUSED(x)
short qint16
Definition qtypes.h:42
unsigned short quint16
Definition qtypes.h:43
unsigned long long quint64
Definition qtypes.h:56
ptrdiff_t qsizetype
Definition qtypes.h:70
unsigned char quint8
Definition qtypes.h:41
QFutureWatcher< int > watcher
QFileInfo info(fileName)
[8]
QFileSelector selector
[1]
QStringList keys
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
myObject disconnect()
[26]
QGraphicsItem * item
QJSValueList args
bool contains(const AT &t) const noexcept
Definition qlist.h:44
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent
\inmodule QtCore
Definition quuid.h:58