Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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
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
34QT_BEGIN_NAMESPACE
35
36QT_IMPL_METATYPE_EXTERN(ManufacturerData)
37QT_IMPL_METATYPE_EXTERN(ServiceData)
38
39Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS)
40
41static QByteArray byteArrayFromBuffer(const IBuffer &buffer)
42{
43 const uint8_t *data = buffer.data();
44 return QByteArray(reinterpret_cast<const char *>(data),
45 static_cast<qsizetype>(buffer.Length()));
46}
47
48static ManufacturerData extractManufacturerData(const BluetoothLEAdvertisement &ad)
49{
50 ManufacturerData ret;
51 const auto data = ad.ManufacturerData();
52 for (const auto &item : data) {
53 const uint16_t id = item.CompanyId();
54 const QByteArray bufferData = byteArrayFromBuffer(item.Data());
55 if (ret.contains(id))
56 qCWarning(QT_BT_WINDOWS) << "Company ID already present in manufacturer data.";
57 ret.insert(id, bufferData);
58 }
59 return ret;
60}
61
62static ServiceData extractServiceData(const BluetoothLEAdvertisement &ad)
63{
64 static constexpr int serviceDataTypes[3] = { 0x16, 0x20, 0x21 };
65
66 ServiceData ret;
67
68 for (const auto &serviceDataType : serviceDataTypes) {
69 const auto dataSections = ad.GetSectionsByType(serviceDataType);
70 for (const auto &section : dataSections) {
71 const unsigned char dataType = section.DataType();
72 const QByteArray bufferData = byteArrayFromBuffer(section.Data());
73 if (dataType == 0x16) {
74 Q_ASSERT(bufferData.size() >= 2);
75 ret.insert(QBluetoothUuid(qFromLittleEndian<quint16>(bufferData.constData())),
76 bufferData.right(bufferData.length() - 2));
77 } else if (dataType == 0x20) {
78 Q_ASSERT(bufferData.size() >= 4);
79 ret.insert(QBluetoothUuid(qFromLittleEndian<quint32>(bufferData.constData())),
80 bufferData.right(bufferData.length() - 4));
81 } else if (dataType == 0x21) {
82 Q_ASSERT(bufferData.size() >= 16);
83 ret.insert(QBluetoothUuid(qToBigEndian<QUuid::Id128Bytes>(
84 qFromLittleEndian<QUuid::Id128Bytes>(bufferData.constData()))),
85 bufferData.right(bufferData.length() - 16));
86 }
87 }
88 }
89
90 return ret;
91}
92
93// Needed because there is no explicit conversion
94static GUID fromWinRtGuid(const winrt::guid &guid)
95{
96 const GUID uuid {
97 guid.Data1,
98 guid.Data2,
99 guid.Data3,
100 { guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
101 guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7] }
102 };
103 return uuid;
104}
105
106class AdvertisementWatcherWrapper : public QObject,
107 public std::enable_shared_from_this<AdvertisementWatcherWrapper>
108{
109 Q_OBJECT
110public:
111 AdvertisementWatcherWrapper() {}
112 ~AdvertisementWatcherWrapper()
113 {
114 stop();
115 }
116 void init()
117 {
118 m_watcher.AllowExtendedAdvertisements(true);
119 m_watcher.ScanningMode(BluetoothLEScanningMode::Active);
120 }
121 void start() {
122 subscribeToEvents();
123 m_watcher.Start();
124 }
125 void stop()
126 {
127 if (canStop()) {
128 unsubscribeFromEvents();
129 m_watcher.Stop();
130 }
131 }
132
133signals:
134 // The signal will be emitted from a separate thread,
135 // so we need to use Qt::QueuedConnection
136 void advertisementDataReceived(quint64 address, qint16 rssi,
137 const ManufacturerData &manufacturerData,
138 const ServiceData &serviceData,
139 const QList<QBluetoothUuid> &uuids);
140private:
141 void subscribeToEvents()
142 {
143 // The callbacks are triggered from separate threads. So we capture
144 // thisPtr to make sure that the object is valid.
145 auto thisPtr = shared_from_this();
146 m_receivedToken = m_watcher.Received(
147 [thisPtr](BluetoothLEAdvertisementWatcher,
148 BluetoothLEAdvertisementReceivedEventArgs args) {
149 const uint64_t address = args.BluetoothAddress();
150 const short rssi = args.RawSignalStrengthInDBm();
151 const BluetoothLEAdvertisement ad = args.Advertisement();
152
153 const ManufacturerData manufacturerData = extractManufacturerData(ad);
154 const ServiceData serviceData = extractServiceData(ad);
155
156 QList<QBluetoothUuid> serviceUuids;
157 const auto guids = ad.ServiceUuids();
158 for (const auto &guid : guids) {
159 const GUID uuid = fromWinRtGuid(guid);
160 serviceUuids.append(QBluetoothUuid(uuid));
161 }
162
163 emit thisPtr->advertisementDataReceived(address, rssi, manufacturerData,
164 serviceData, serviceUuids);
165 });
166 }
167 void unsubscribeFromEvents()
168 {
169 m_watcher.Received(m_receivedToken);
170 }
171 bool canStop() const
172 {
173 const auto status = m_watcher.Status();
174 return status == BluetoothLEAdvertisementWatcherStatus::Started
175 || status == BluetoothLEAdvertisementWatcherStatus::Aborted;
176 }
177
178 BluetoothLEAdvertisementWatcher m_watcher;
179 winrt::event_token m_receivedToken;
180};
181
182// Both constants are taken from Microsoft's docs:
183// https://docs.microsoft.com/en-us/windows/uwp/devices-sensors/aep-service-class-ids
184// Alternatively we could create separate watchers for paired and unpaired devices.
186 L"System.Devices.Aep.ProtocolId:=\"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}\"";
187// Do not use it for now, so comment out. Do not delete in case we want to reuse it.
188//static const winrt::hstring LowEnergyDeviceSelector =
189// L"System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\"";
190
193{
195public:
197 int interval);
199 void start();
200 void stop();
201
202private:
203 void startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode);
204
205 std::shared_ptr<QBluetoothDeviceWatcherWinRT> createDeviceWatcher(winrt::hstring selector,
206 int watcherId);
207 void generateError(QBluetoothDeviceDiscoveryAgent::Error error, const char *msg = nullptr);
208 void invokeDeviceFoundWithDebug(const QBluetoothDeviceInfo &info);
209 void finishDiscovery();
210 bool isFinished() const;
211
212 // Bluetooth Classic handlers
213 void getClassicDeviceFromId(const winrt::hstring &id);
214 void handleClassicDevice(const BluetoothDevice &device);
215 void handleRfcommServices(const RfcommDeviceServicesResult &servicesResult,
216 uint64_t address, const QString &name, uint32_t classOfDeviceInt,
217 bool isCached);
218
219 // Bluetooth Low Energy handlers
220 void getLowEnergyDeviceFromId(const winrt::hstring &id);
221 void handleLowEnergyDevice(const BluetoothLEDevice &device);
222 void handleGattServices(const GattDeviceServicesResult &servicesResult,
223 QBluetoothDeviceInfo &info);
224
225 // Bluetooth Low Energy Advertising handlers
226 std::shared_ptr<AdvertisementWatcherWrapper> createAdvertisementWatcher();
227
228 // invokable methods for handling finish conditions
229 Q_INVOKABLE void decrementPendingDevicesCountAndCheckFinished(
230 std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker);
231
234 void deviceDataChanged(const QBluetoothAddress &address, QBluetoothDeviceInfo::Fields,
235 qint16 rssi, ManufacturerData manufacturerData, ServiceData serviceData);
236 void errorOccured(QBluetoothDeviceDiscoveryAgent::Error error);
238
239private slots:
241 void onDeviceEnumerationCompleted(int watcherId);
242
243 void onAdvertisementDataReceived(quint64 address, qint16 rssi,
244 const ManufacturerData &manufacturerData,
245 const ServiceData &serviceData,
246 const QList<QBluetoothUuid> &uuids);
247
248 void stopAdvertisementWatcher();
249
250private:
251 struct LEAdvertisingInfo {
252 QList<QBluetoothUuid> services;
253 ManufacturerData manufacturerData;
254 ServiceData serviceData;
255 qint16 rssi = 0;
256 };
257
258 quint8 requestedModes = 0;
259 QMutex m_leDevicesMutex;
260 QMap<quint64, LEAdvertisingInfo> m_foundLEDevicesMap;
261 int m_pendingDevices = 0;
262
263 static constexpr int ClassicWatcherId = 1;
264 static constexpr int LowEnergyWatcherId = 2;
265
266 std::shared_ptr<QBluetoothDeviceWatcherWinRT> m_classicWatcher;
267 std::shared_ptr<QBluetoothDeviceWatcherWinRT> m_lowEnergyWatcher;
268 std::shared_ptr<AdvertisementWatcherWrapper> m_advertisementWatcher;
269 bool m_classicScanStarted = false;
270 bool m_lowEnergyScanStarted = false;
271 QTimer *m_leScanTimer = nullptr;
272};
273
275 std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker)
276{
277 QMetaObject::invokeMethod(worker.get(), "decrementPendingDevicesCountAndCheckFinished",
278 Qt::QueuedConnection,
279 Q_ARG(std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker>,
280 worker));
281}
282
283QWinRTBluetoothDeviceDiscoveryWorker::QWinRTBluetoothDeviceDiscoveryWorker(
284 QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods, int interval)
285 : requestedModes(methods)
286{
287 qRegisterMetaType<QBluetoothDeviceInfo>();
288 qRegisterMetaType<QBluetoothDeviceInfo::Fields>();
289 qRegisterMetaType<ManufacturerData>();
290 qRegisterMetaType<std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker>>();
291
292 m_classicWatcher = createDeviceWatcher(ClassicDeviceSelector, ClassicWatcherId);
293 // For LE scan use DeviceWatcher to handle only paired devices.
294 // Non-paired devices will be found using BluetoothLEAdvertisementWatcher.
295 const auto leSelector = BluetoothLEDevice::GetDeviceSelectorFromPairingState(true);
296 m_lowEnergyWatcher = createDeviceWatcher(leSelector, LowEnergyWatcherId);
297 m_advertisementWatcher = createAdvertisementWatcher();
298
299 // Docs claim that a negative interval means that the backend handles it on its own
300 if (interval < 0)
301 interval = 40000;
302
303 if (interval != 0) {
304 m_leScanTimer = new QTimer(this);
305 m_leScanTimer->setSingleShot(true);
306 m_leScanTimer->setInterval(interval);
307 connect(m_leScanTimer, &QTimer::timeout, this,
308 &QWinRTBluetoothDeviceDiscoveryWorker::stopAdvertisementWatcher);
309 }
310}
311
316
318{
319 if (requestedModes & QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
320 if (m_classicWatcher && m_classicWatcher->init()) {
321 m_classicWatcher->start();
322 m_classicScanStarted = true;
323 } else {
324 generateError(QBluetoothDeviceDiscoveryAgent::Error::UnknownError,
325 "Could not start classic device watcher");
326 }
327 }
328 if (requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) {
329 if (m_lowEnergyWatcher && m_lowEnergyWatcher->init()) {
330 m_lowEnergyWatcher->start();
331 m_lowEnergyScanStarted = true;
332 } else {
333 generateError(QBluetoothDeviceDiscoveryAgent::Error::UnknownError,
334 "Could not start low energy device watcher");
335 }
336 if (m_advertisementWatcher) {
337 m_advertisementWatcher->init();
338 m_advertisementWatcher->start();
339 if (m_leScanTimer)
340 m_leScanTimer->start();
341 } else {
342 generateError(QBluetoothDeviceDiscoveryAgent::Error::UnknownError,
343 "Could not start low energy advertisement watcher");
344 }
345 }
346
347 qCDebug(QT_BT_WINDOWS) << "Worker started";
348}
349
351{
352 if (m_leScanTimer && m_leScanTimer->isActive())
353 m_leScanTimer->stop();
354 m_classicWatcher->stop();
355 m_lowEnergyWatcher->stop();
356 m_advertisementWatcher->stop();
357}
358
359void QWinRTBluetoothDeviceDiscoveryWorker::finishDiscovery()
360{
361 stop();
362 emit scanFinished();
363}
364
365bool QWinRTBluetoothDeviceDiscoveryWorker::isFinished() const
366{
367 // If the interval is set to 0, we do not start a timer, and that means
368 // that we need to wait for the user to explicitly call stop()
369 return (m_pendingDevices == 0) && !m_lowEnergyScanStarted && !m_classicScanStarted
370 && (m_leScanTimer && !m_leScanTimer->isActive());
371}
372
373void QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothDeviceFound(winrt::hstring deviceId, int watcherId)
374{
375 if (watcherId == ClassicWatcherId)
376 getClassicDeviceFromId(deviceId);
377 else if (watcherId == LowEnergyWatcherId)
378 getLowEnergyDeviceFromId(deviceId);
379}
380
381void QWinRTBluetoothDeviceDiscoveryWorker::onDeviceEnumerationCompleted(int watcherId)
382{
383 qCDebug(QT_BT_WINDOWS) << (watcherId == ClassicWatcherId ? "BT" : "BTLE")
384 << "enumeration completed";
385 if (watcherId == ClassicWatcherId) {
386 m_classicWatcher->stop();
387 m_classicScanStarted = false;
388 } else if (watcherId == LowEnergyWatcherId) {
389 m_lowEnergyWatcher->stop();
390 m_lowEnergyScanStarted = false;
391 }
392 if (isFinished())
393 finishDiscovery();
394}
395
396// this function executes in main worker thread
397void QWinRTBluetoothDeviceDiscoveryWorker::onAdvertisementDataReceived(
398 quint64 address, qint16 rssi, const ManufacturerData &manufacturerData,
399 const ServiceData &serviceData, const QList<QBluetoothUuid> &uuids)
400{
401 // Merge newly found services with list of currently found ones
402 bool needDiscoverServices = false;
403 {
404 QMutexLocker locker(&m_leDevicesMutex);
405 if (m_foundLEDevicesMap.contains(address)) {
406 QBluetoothDeviceInfo::Fields changedFields = QBluetoothDeviceInfo::Field::None;
407 const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address);
408 QList<QBluetoothUuid> foundServices = adInfo.services;
409 if (adInfo.rssi != rssi) {
410 m_foundLEDevicesMap[address].rssi = rssi;
411 changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI);
412 }
413 if (adInfo.manufacturerData != manufacturerData) {
414 m_foundLEDevicesMap[address].manufacturerData.insert(manufacturerData);
415 if (adInfo.manufacturerData != m_foundLEDevicesMap[address].manufacturerData)
416 changedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData);
417 }
418 if (adInfo.serviceData != serviceData) {
419 m_foundLEDevicesMap[address].serviceData.insert(serviceData);
420 if (adInfo.serviceData != m_foundLEDevicesMap[address].serviceData)
421 changedFields.setFlag((QBluetoothDeviceInfo::Field::ServiceData));
422 }
423 for (const QBluetoothUuid &uuid : std::as_const(uuids)) {
424 if (!foundServices.contains(uuid)) {
425 foundServices.append(uuid);
426 needDiscoverServices = true;
427 }
428 }
429 if (!needDiscoverServices) {
430 if (!changedFields.testFlag(QBluetoothDeviceInfo::Field::None)) {
431 QMetaObject::invokeMethod(this, "deviceDataChanged", Qt::AutoConnection,
432 Q_ARG(QBluetoothAddress, QBluetoothAddress(address)),
433 Q_ARG(QBluetoothDeviceInfo::Fields, changedFields),
434 Q_ARG(qint16, rssi),
435 Q_ARG(ManufacturerData, manufacturerData),
436 Q_ARG(ServiceData, serviceData));
437 }
438 }
439 m_foundLEDevicesMap[address].services = foundServices;
440 } else {
441 needDiscoverServices = true;
442 LEAdvertisingInfo info;
443 info.services = std::move(uuids);
444 info.manufacturerData = std::move(manufacturerData);
445 info.serviceData = std::move(serviceData);
446 info.rssi = rssi;
447 m_foundLEDevicesMap.insert(address, info);
448 }
449 }
450 if (needDiscoverServices) {
451 ++m_pendingDevices; // as if we discovered a new LE device
452 auto thisPtr = shared_from_this();
453 auto asyncOp = BluetoothLEDevice::FromBluetoothAddressAsync(address);
454 asyncOp.Completed([thisPtr, address](auto &&op, AsyncStatus status) {
455 if (thisPtr) {
456 if (status == AsyncStatus::Completed) {
457 BluetoothLEDevice device = op.GetResults();
458 if (device) {
459 thisPtr->handleLowEnergyDevice(device);
460 return;
461 }
462 }
463 // status != Completed or failed to extract result
464 qCDebug(QT_BT_WINDOWS) << "Failed to get LE device from address"
465 << QBluetoothAddress(address);
467 }
468 });
469 }
470}
471
472void QWinRTBluetoothDeviceDiscoveryWorker::stopAdvertisementWatcher()
473{
474 m_advertisementWatcher->stop();
475 if (isFinished())
476 finishDiscovery();
477}
478
479std::shared_ptr<QBluetoothDeviceWatcherWinRT>
480QWinRTBluetoothDeviceDiscoveryWorker::createDeviceWatcher(winrt::hstring selector, int watcherId)
481{
482 auto watcher = std::make_shared<QBluetoothDeviceWatcherWinRT>(
483 watcherId, selector, DeviceInformationKind::AssociationEndpoint);
484 if (watcher) {
485 connect(watcher.get(), &QBluetoothDeviceWatcherWinRT::deviceAdded,
486 this, &QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothDeviceFound,
487 Qt::QueuedConnection);
488 connect(watcher.get(), &QBluetoothDeviceWatcherWinRT::enumerationCompleted,
489 this, &QWinRTBluetoothDeviceDiscoveryWorker::onDeviceEnumerationCompleted,
490 Qt::QueuedConnection);
491 }
492 return watcher;
493}
494
495void QWinRTBluetoothDeviceDiscoveryWorker::generateError(
496 QBluetoothDeviceDiscoveryAgent::Error error, const char *msg)
497{
498 emit errorOccured(error);
499 qCWarning(QT_BT_WINDOWS) << msg;
500}
501
502void QWinRTBluetoothDeviceDiscoveryWorker::invokeDeviceFoundWithDebug(const QBluetoothDeviceInfo &info)
503{
504 qCDebug(QT_BT_WINDOWS) << "Discovered BTLE device: " << info.address() << info.name()
505 << "Num UUIDs" << info.serviceUuids().size() << "RSSI:" << info.rssi()
506 << "Num manufacturer data" << info.manufacturerData().size()
507 << "Num service data" << info.serviceData().size();
508
509 QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection,
510 Q_ARG(QBluetoothDeviceInfo, info));
511}
512
513// this function executes in main worker thread
514void QWinRTBluetoothDeviceDiscoveryWorker::getClassicDeviceFromId(const winrt::hstring &id)
515{
516 ++m_pendingDevices;
517 auto thisPtr = shared_from_this();
518 auto asyncOp = BluetoothDevice::FromIdAsync(id);
519 asyncOp.Completed([thisPtr](auto &&op, AsyncStatus status) {
520 if (thisPtr) {
521 if (status == AsyncStatus::Completed) {
522 BluetoothDevice device = op.GetResults();
523 if (device) {
524 thisPtr->handleClassicDevice(device);
525 return;
526 }
527 }
528 // status != Completed or failed to extract result
529 qCDebug(QT_BT_WINDOWS) << "Failed to get Classic device from id";
531 }
532 });
533}
534
535// this is a callback - executes in a new thread
536void QWinRTBluetoothDeviceDiscoveryWorker::handleClassicDevice(const BluetoothDevice &device)
537{
538 const uint64_t address = device.BluetoothAddress();
539 const std::wstring name { device.Name() }; // via operator std::wstring_view()
540 const QString btName = QString::fromStdWString(name);
541 const uint32_t deviceClass = device.ClassOfDevice().RawValue();
542
543 // Use IsPaired() to determine if the device is cached or not.
544 // That is because Windows will always report paired devices, even if
545 // they are not physically available. This is a sub-optimal approach,
546 // but looks like we cannot do better...
547 const auto deviceInfo = SAFE(device.DeviceInformation());
548 if (!deviceInfo) {
549 qCDebug(QT_BT_WINDOWS) << "Failed to obtain DeviceInformation for Classic device";
551 return;
552 }
553 const bool isCached = SAFE(deviceInfo.Pairing().IsPaired());
554
555 auto thisPtr = shared_from_this();
556 auto asyncOp = device.GetRfcommServicesAsync();
557 asyncOp.Completed([thisPtr, address, btName, deviceClass, isCached]
558 (auto &&op, AsyncStatus status) {
559 if (thisPtr) {
560 if (status == AsyncStatus::Completed) {
561 auto servicesResult = op.GetResults();
562 if (servicesResult) {
563 thisPtr->handleRfcommServices(servicesResult, address, btName,
564 deviceClass, isCached);
565 return;
566 }
567 }
568 // Failed to get services
569 qCDebug(QT_BT_WINDOWS) << "Failed to get RFCOMM services for device" << btName;
571 }
572 });
573}
574
575// this is a callback - executes in a new thread
576void QWinRTBluetoothDeviceDiscoveryWorker::handleRfcommServices(
577 const RfcommDeviceServicesResult &servicesResult, uint64_t address,
578 const QString &name, uint32_t classOfDeviceInt, bool isCached)
579{
580 // need to perform the check even if some of the operations fails
581 auto shared = shared_from_this();
582 auto guard = qScopeGuard([shared]() {
584 });
585 Q_UNUSED(guard); // to suppress warning
586
587 const auto error = servicesResult.Error();
588 if (error != BluetoothError::Success) {
589 qCWarning(QT_BT_WINDOWS) << "Obtain device services completed with BluetoothError"
590 << static_cast<int>(error);
591 return;
592 }
593
594 const auto services = servicesResult.Services();
595 QList<QBluetoothUuid> uuids;
596 for (const auto &service : services) {
597 const auto serviceId = service.ServiceId();
598 const GUID uuid = fromWinRtGuid(serviceId.Uuid());
599 uuids.append(QBluetoothUuid(uuid));
600 }
601
602 const QBluetoothAddress btAddress(address);
603
604 qCDebug(QT_BT_WINDOWS) << "Discovered BT device: " << btAddress << name
605 << "Num UUIDs" << uuids.size();
606
607 QBluetoothDeviceInfo info(btAddress, name, classOfDeviceInt);
608 info.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
609 info.setServiceUuids(uuids);
610 info.setCached(isCached);
611
612 QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection,
613 Q_ARG(QBluetoothDeviceInfo, info));
614}
615
616void QWinRTBluetoothDeviceDiscoveryWorker::decrementPendingDevicesCountAndCheckFinished(
617 std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker)
618{
619 --m_pendingDevices;
620 if (isFinished())
621 finishDiscovery();
622 // Worker is passed here simply to make sure that the object is still alive
623 // when we call this method via QObject::invoke().
624 Q_UNUSED(worker)
625}
626
627// this function executes in main worker thread
628void QWinRTBluetoothDeviceDiscoveryWorker::getLowEnergyDeviceFromId(const winrt::hstring &id)
629{
630 ++m_pendingDevices;
631 auto asyncOp = BluetoothLEDevice::FromIdAsync(id);
632 auto thisPtr = shared_from_this();
633 asyncOp.Completed([thisPtr](auto &&op, AsyncStatus status) {
634 if (thisPtr) {
635 if (status == AsyncStatus::Completed) {
636 BluetoothLEDevice device = op.GetResults();
637 if (device) {
638 thisPtr->handleLowEnergyDevice(device);
639 return;
640 }
641 }
642 // status != Completed or failed to extract result
643 qCDebug(QT_BT_WINDOWS) << "Failed to get LE device from id";
645 }
646 });
647}
648
649// this is a callback - executes in a new thread
650void QWinRTBluetoothDeviceDiscoveryWorker::handleLowEnergyDevice(const BluetoothLEDevice &device)
651{
652 uint64_t address = 0;
653 if (!TRY(address = device.BluetoothAddress())) {
654 qCDebug(QT_BT_WINDOWS) << "Failed to read BluetoothAddress() for LE device";
656 return;
657 }
658 const std::wstring name { SAFE(device.Name()) }; // via operator std::wstring_view()
659 const QString btName = QString::fromStdWString(name);
660
661 const auto deviceInfo = SAFE(device.DeviceInformation());
662 if (!deviceInfo) {
663 qCDebug(QT_BT_WINDOWS) << "Failed to obtain DeviceInformation for LE device";
665 return;
666 }
667
668 const bool isPaired = SAFE(deviceInfo.Pairing().IsPaired());
669
670 m_leDevicesMutex.lock();
671 const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address);
672 m_leDevicesMutex.unlock();
673 const ManufacturerData manufacturerData = adInfo.manufacturerData;
674 const ServiceData serviceData = adInfo.serviceData;
675 const qint16 rssi = adInfo.rssi;
676
677 QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, 0);
678 info.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
679 info.setRssi(rssi);
680 for (quint16 key : manufacturerData.keys())
681 info.setManufacturerData(key, manufacturerData.value(key));
682 for (QBluetoothUuid key : serviceData.keys())
683 info.setServiceData(key, serviceData.value(key));
684
685 // Now we need to figure out if the device is cached or not.
686 // Theoretically, we could use the "System.Devices.Aep.IsPresent"
687 // device property. However, in practice it's not reported, so
688 // let's try to do our best and estimate the value.
689 // If the device is paired, then Windows will always report it,
690 // no matter if it's actually present or not.
691 // So, treat paired devices as cached at first. If the device is
692 // actually available, we'll get an update (at least with a new
693 // rssi), and update the cache state at that point.
694 info.setCached(isPaired);
695
696 // Use the services obtained from the advertisement data if the device is not paired
697 if (!isPaired) {
698 info.setServiceUuids(adInfo.services);
700 invokeDeviceFoundWithDebug(info);
701 } else {
702 auto asyncOp = SAFE(device.GetGattServicesAsync());
703 if (!asyncOp) {
705 return;
706 }
707 auto thisPtr = shared_from_this();
708 const bool ok = TRY(asyncOp.Completed([thisPtr, info](auto &&op, AsyncStatus status) mutable {
709 if (status == AsyncStatus::Completed) {
710 auto servicesResult = SAFE(op.GetResults());
711 if (servicesResult) {
712 thisPtr->handleGattServices(servicesResult, info);
713 return;
714 }
715 }
716 // Failed to get services
717 qCDebug(QT_BT_WINDOWS) << "Failed to get GATT services for device" << info.name();
719 }));
720 if (!ok) {
722 return;
723 }
724 }
725}
726
727// this is a callback - executes in a new thread
728void QWinRTBluetoothDeviceDiscoveryWorker::handleGattServices(
729 const GattDeviceServicesResult &servicesResult, QBluetoothDeviceInfo &info)
730{
731 // need to perform the check even if some of the operations fails
732 auto shared = shared_from_this();
733 auto guard = qScopeGuard([shared]() {
735 });
736 Q_UNUSED(guard); // to suppress warning
737
738 try {
739 const auto status = servicesResult.Status();
740 if (status == GattCommunicationStatus::Success) {
741 const auto services = servicesResult.Services();
742 QList<QBluetoothUuid> uuids;
743 for (const auto &service : services) {
744 const GUID uuid = fromWinRtGuid(service.Uuid());
745 uuids.append(QBluetoothUuid(uuid));
746 }
747 info.setServiceUuids(uuids);
748 } else {
749 qCWarning(QT_BT_WINDOWS) << "Obtaining LE services finished with status"
750 << static_cast<int>(status);
751 }
752 } catch (winrt::hresult_error const &e) {
753 LOG_HRESULT(e.code()) << "/* handleGattServices */";
754 }
755 invokeDeviceFoundWithDebug(info);
756}
757
758std::shared_ptr<AdvertisementWatcherWrapper>
759QWinRTBluetoothDeviceDiscoveryWorker::createAdvertisementWatcher()
760{
761 auto watcher = std::make_shared<AdvertisementWatcherWrapper>();
762 if (watcher) {
763 connect(watcher.get(), &AdvertisementWatcherWrapper::advertisementDataReceived,
764 this, &QWinRTBluetoothDeviceDiscoveryWorker::onAdvertisementDataReceived,
765 Qt::QueuedConnection);
766 }
767 return watcher;
768}
769
770QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
771 const QBluetoothAddress &deviceAdapter, QBluetoothDeviceDiscoveryAgent *parent)
772 : q_ptr(parent), adapterAddress(deviceAdapter)
773{
774 threadCoInit(this);
775}
776
777QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
778{
779 disconnectAndClearWorker();
780 threadCoUninit(this);
781}
782
783bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
784{
785 return worker != nullptr;
786}
787
788QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
789{
790 return (ClassicMethod | LowEnergyMethod);
791}
792
793void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
794{
795 QBluetoothLocalDevice adapter(adapterAddress);
796 if (!adapter.isValid()) {
797 qCWarning(QT_BT_WINDOWS) << "Cannot find Bluetooth adapter for device search";
798 lastError = QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError;
799 errorString = QBluetoothDeviceDiscoveryAgent::tr("Cannot find valid Bluetooth adapter.");
800 emit q_ptr->errorOccurred(lastError);
801 return;
802 } else if (adapter.hostMode() == QBluetoothLocalDevice::HostPoweredOff) {
803 qCWarning(QT_BT_WINDOWS) << "Bluetooth adapter powered off";
804 lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
805 errorString = QBluetoothDeviceDiscoveryAgent::tr("Bluetooth adapter powered off.");
806 emit q_ptr->errorOccurred(lastError);
807 return;
808 }
809
810 if (worker)
811 return;
812
813 worker = std::make_shared<QWinRTBluetoothDeviceDiscoveryWorker>(methods,
814 lowEnergySearchTimeout);
815 lastError = QBluetoothDeviceDiscoveryAgent::NoError;
816 errorString.clear();
817 discoveredDevices.clear();
818 connect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound,
819 this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice);
820 connect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::deviceDataChanged,
821 this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData);
822 connect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::errorOccured,
823 this, &QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured);
824 connect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished,
825 this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished);
826 worker->start();
827}
828
829void QBluetoothDeviceDiscoveryAgentPrivate::stop()
830{
831 Q_Q(QBluetoothDeviceDiscoveryAgent);
832 if (worker) {
833 worker->stop();
834 disconnectAndClearWorker();
835 emit q->canceled();
836 }
837}
838
839void QBluetoothDeviceDiscoveryAgentPrivate::registerDevice(const QBluetoothDeviceInfo &info)
840{
841 Q_Q(QBluetoothDeviceDiscoveryAgent);
842
843 for (QList<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin();
844 iter != discoveredDevices.end(); ++iter) {
845 if (iter->address() == info.address()) {
846 qCDebug(QT_BT_WINDOWS) << "Updating device" << iter->name() << iter->address();
847 // merge service uuids
848 QList<QBluetoothUuid> uuids = iter->serviceUuids();
849 uuids.append(info.serviceUuids());
850 const QSet<QBluetoothUuid> uuidSet(uuids.begin(), uuids.end());
851 if (iter->serviceUuids().size() != uuidSet.size())
852 iter->setServiceUuids(uuidSet.values().toVector());
853 if (iter->coreConfigurations() != info.coreConfigurations())
854 iter->setCoreConfigurations(QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration);
855 return;
856 }
857 }
858
859 discoveredDevices << info;
860 emit q->deviceDiscovered(info);
861}
862
863void QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData(const QBluetoothAddress &address,
864 QBluetoothDeviceInfo::Fields fields,
865 qint16 rssi,
866 ManufacturerData manufacturerData,
867 ServiceData serviceData)
868{
869 if (fields.testFlag(QBluetoothDeviceInfo::Field::None))
870 return;
871
872 Q_Q(QBluetoothDeviceDiscoveryAgent);
873 for (QList<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin();
874 iter != discoveredDevices.end(); ++iter) {
875 if (iter->address() == address) {
876 qCDebug(QT_BT_WINDOWS) << "Updating data for device" << iter->name() << iter->address();
877 if (fields.testFlag(QBluetoothDeviceInfo::Field::RSSI))
878 iter->setRssi(rssi);
879 if (fields.testFlag(QBluetoothDeviceInfo::Field::ManufacturerData))
880 for (quint16 key : manufacturerData.keys())
881 iter->setManufacturerData(key, manufacturerData.value(key));
882 if (fields.testFlag(QBluetoothDeviceInfo::Field::ServiceData))
883 for (QBluetoothUuid key : serviceData.keys())
884 iter->setServiceData(key, serviceData.value(key));
885 // We got some data, so the device is definitely available now.
886 // Update the cached state.
887 iter->setCached(false);
888 emit q->deviceUpdated(*iter, fields);
889 return;
890 }
891 }
892}
893
894void QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured(QBluetoothDeviceDiscoveryAgent::Error e)
895{
896 Q_Q(QBluetoothDeviceDiscoveryAgent);
897 lastError = e;
898 emit q->errorOccurred(e);
899}
900
901void QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished()
902{
903 Q_Q(QBluetoothDeviceDiscoveryAgent);
904 disconnectAndClearWorker();
905 emit q->finished();
906}
907
908void QBluetoothDeviceDiscoveryAgentPrivate::disconnectAndClearWorker()
909{
910 if (!worker)
911 return;
912
913 disconnect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished,
914 this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished);
915 disconnect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound,
916 this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice);
917 disconnect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::deviceDataChanged,
918 this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData);
919 disconnect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::errorOccured,
920 this, &QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured);
921
922 worker = nullptr;
923}
924
925QT_END_NAMESPACE
926
927#include <qbluetoothdevicediscoveryagent_winrt.moc>
void deviceDataChanged(const QBluetoothAddress &address, QBluetoothDeviceInfo::Fields, qint16 rssi, ManufacturerData manufacturerData, ServiceData serviceData)
void errorOccured(QBluetoothDeviceDiscoveryAgent::Error error)
static void invokeDecrementPendingDevicesCountAndCheckFinished(std::shared_ptr< QWinRTBluetoothDeviceDiscoveryWorker > worker)
static const winrt::hstring ClassicDeviceSelector