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
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
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 + 2);
77 } else if (dataType == 0x20) {
78 Q_ASSERT(bufferData.size() >= 4);
79 ret.insert(QBluetoothUuid(qFromLittleEndian<quint32>(bufferData.constData())),
80 bufferData + 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 + 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
107 public std::enable_shared_from_this<AdvertisementWatcherWrapper>
108{
110public:
113 {
114 stop();
115 }
116 void init()
117 {
118 m_watcher.ScanningMode(BluetoothLEScanningMode::Active);
119 }
120 void start() {
121 subscribeToEvents();
122 m_watcher.Start();
123 }
124 void stop()
125 {
126 if (canStop()) {
127 unsubscribeFromEvents();
128 m_watcher.Stop();
129 }
130 }
131
132signals:
133 // The signal will be emitted from a separate thread,
134 // so we need to use Qt::QueuedConnection
136 const ManufacturerData &manufacturerData,
137 const ServiceData &serviceData,
138 const QList<QBluetoothUuid> &uuids);
139private:
140 void subscribeToEvents()
141 {
142 // The callbacks are triggered from separate threads. So we capture
143 // thisPtr to make sure that the object is valid.
144 auto thisPtr = shared_from_this();
145 m_receivedToken = m_watcher.Received(
146 [thisPtr](BluetoothLEAdvertisementWatcher,
147 BluetoothLEAdvertisementReceivedEventArgs args) {
148 const uint64_t address = args.BluetoothAddress();
149 const short rssi = args.RawSignalStrengthInDBm();
150 const BluetoothLEAdvertisement ad = args.Advertisement();
151
152 const ManufacturerData manufacturerData = extractManufacturerData(ad);
153 const ServiceData serviceData = extractServiceData(ad);
154
155 QList<QBluetoothUuid> serviceUuids;
156 const auto guids = ad.ServiceUuids();
157 for (const auto &guid : guids) {
158 const GUID uuid = fromWinRtGuid(guid);
159 serviceUuids.append(QBluetoothUuid(uuid));
160 }
161
162 emit thisPtr->advertisementDataReceived(address, rssi, manufacturerData,
163 serviceData, serviceUuids);
164 });
165 }
166 void unsubscribeFromEvents()
167 {
168 m_watcher.Received(m_receivedToken);
169 }
170 bool canStop() const
171 {
172 const auto status = m_watcher.Status();
173 return status == BluetoothLEAdvertisementWatcherStatus::Started
174 || status == BluetoothLEAdvertisementWatcherStatus::Aborted;
175 }
176
177 BluetoothLEAdvertisementWatcher m_watcher;
178 winrt::event_token m_receivedToken;
179};
180
181// Both constants are taken from Microsoft's docs:
182// https://docs.microsoft.com/en-us/windows/uwp/devices-sensors/aep-service-class-ids
183// Alternatively we could create separate watchers for paired and unpaired devices.
184static const winrt::hstring ClassicDeviceSelector =
185 L"System.Devices.Aep.ProtocolId:=\"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}\"";
186// Do not use it for now, so comment out. Do not delete in case we want to reuse it.
187//static const winrt::hstring LowEnergyDeviceSelector =
188// L"System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\"";
189
191 public std::enable_shared_from_this<QWinRTBluetoothDeviceDiscoveryWorker>
192{
194public:
195 QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods,
196 int interval);
198 void start();
199 void stop();
200
201private:
202 void startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode);
203
204 std::shared_ptr<QBluetoothDeviceWatcherWinRT> createDeviceWatcher(winrt::hstring selector,
205 int watcherId);
206 void generateError(QBluetoothDeviceDiscoveryAgent::Error error, const char *msg = nullptr);
207 void invokeDeviceFoundWithDebug(const QBluetoothDeviceInfo &info);
208 void finishDiscovery();
209 bool isFinished() const;
210
211 // Bluetooth Classic handlers
212 void getClassicDeviceFromId(const winrt::hstring &id);
213 void handleClassicDevice(const BluetoothDevice &device);
214 void handleRfcommServices(const RfcommDeviceServicesResult &servicesResult,
215 uint64_t address, const QString &name, uint32_t classOfDeviceInt);
216
217 // Bluetooth Low Energy handlers
218 void getLowEnergyDeviceFromId(const winrt::hstring &id);
219 void handleLowEnergyDevice(const BluetoothLEDevice &device);
220 void handleGattServices(const GattDeviceServicesResult &servicesResult,
222
223 // Bluetooth Low Energy Advertising handlers
224 std::shared_ptr<AdvertisementWatcherWrapper> createAdvertisementWatcher();
225
226 // invokable methods for handling finish conditions
227 Q_INVOKABLE void decrementPendingDevicesCountAndCheckFinished(
228 std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker);
229
232 void deviceDataChanged(const QBluetoothAddress &address, QBluetoothDeviceInfo::Fields,
233 qint16 rssi, ManufacturerData manufacturerData, ServiceData serviceData);
236
237private slots:
238 void onBluetoothDeviceFound(winrt::hstring deviceId, int watcherId);
239 void onDeviceEnumerationCompleted(int watcherId);
240
241 void onAdvertisementDataReceived(quint64 address, qint16 rssi,
242 const ManufacturerData &manufacturerData,
243 const ServiceData &serviceData,
244 const QList<QBluetoothUuid> &uuids);
245
246 void stopAdvertisementWatcher();
247
248private:
249 struct LEAdvertisingInfo {
250 QList<QBluetoothUuid> services;
251 ManufacturerData manufacturerData;
252 ServiceData serviceData;
253 qint16 rssi = 0;
254 };
255
256 quint8 requestedModes = 0;
257 QMutex m_leDevicesMutex;
258 QMap<quint64, LEAdvertisingInfo> m_foundLEDevicesMap;
259 int m_pendingDevices = 0;
260
261 static constexpr int ClassicWatcherId = 1;
262 static constexpr int LowEnergyWatcherId = 2;
263
264 std::shared_ptr<QBluetoothDeviceWatcherWinRT> m_classicWatcher;
265 std::shared_ptr<QBluetoothDeviceWatcherWinRT> m_lowEnergyWatcher;
266 std::shared_ptr<AdvertisementWatcherWrapper> m_advertisementWatcher;
267 bool m_classicScanStarted = false;
268 bool m_lowEnergyScanStarted = false;
269 QTimer *m_leScanTimer = nullptr;
270};
271
273 std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker)
274{
275 QMetaObject::invokeMethod(worker.get(), "decrementPendingDevicesCountAndCheckFinished",
277 Q_ARG(std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker>,
278 worker));
279}
280
282 QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods, int interval)
283 : requestedModes(methods)
284{
285 qRegisterMetaType<QBluetoothDeviceInfo>();
286 qRegisterMetaType<QBluetoothDeviceInfo::Fields>();
287 qRegisterMetaType<ManufacturerData>();
288 qRegisterMetaType<std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker>>();
289
290 m_classicWatcher = createDeviceWatcher(ClassicDeviceSelector, ClassicWatcherId);
291 // For LE scan use DeviceWatcher to handle only paired devices.
292 // Non-paired devices will be found using BluetoothLEAdvertisementWatcher.
293 const auto leSelector = BluetoothLEDevice::GetDeviceSelectorFromPairingState(true);
294 m_lowEnergyWatcher = createDeviceWatcher(leSelector, LowEnergyWatcherId);
295 m_advertisementWatcher = createAdvertisementWatcher();
296
297 // Docs claim that a negative interval means that the backend handles it on its own
298 if (interval < 0)
299 interval = 40000;
300
301 if (interval != 0) {
302 m_leScanTimer = new QTimer(this);
303 m_leScanTimer->setSingleShot(true);
304 m_leScanTimer->setInterval(interval);
305 connect(m_leScanTimer, &QTimer::timeout, this,
306 &QWinRTBluetoothDeviceDiscoveryWorker::stopAdvertisementWatcher);
307 }
308}
309
314
316{
318 if (m_classicWatcher && m_classicWatcher->init()) {
319 m_classicWatcher->start();
320 m_classicScanStarted = true;
321 } else {
323 "Could not start classic device watcher");
324 }
325 }
327 if (m_lowEnergyWatcher && m_lowEnergyWatcher->init()) {
328 m_lowEnergyWatcher->start();
329 m_lowEnergyScanStarted = true;
330 } else {
332 "Could not start low energy device watcher");
333 }
334 if (m_advertisementWatcher) {
335 m_advertisementWatcher->init();
336 m_advertisementWatcher->start();
337 if (m_leScanTimer)
338 m_leScanTimer->start();
339 } else {
341 "Could not start low energy advertisement watcher");
342 }
343 }
344
345 qCDebug(QT_BT_WINDOWS) << "Worker started";
346}
347
349{
350 if (m_leScanTimer && m_leScanTimer->isActive())
351 m_leScanTimer->stop();
352 m_classicWatcher->stop();
353 m_lowEnergyWatcher->stop();
354 m_advertisementWatcher->stop();
355}
356
357void QWinRTBluetoothDeviceDiscoveryWorker::finishDiscovery()
358{
359 stop();
361}
362
363bool QWinRTBluetoothDeviceDiscoveryWorker::isFinished() const
364{
365 // If the interval is set to 0, we do not start a timer, and that means
366 // that we need to wait for the user to explicitly call stop()
367 return (m_pendingDevices == 0) && !m_lowEnergyScanStarted && !m_classicScanStarted
368 && (m_leScanTimer && !m_leScanTimer->isActive());
369}
370
371void QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothDeviceFound(winrt::hstring deviceId, int watcherId)
372{
373 if (watcherId == ClassicWatcherId)
374 getClassicDeviceFromId(deviceId);
375 else if (watcherId == LowEnergyWatcherId)
376 getLowEnergyDeviceFromId(deviceId);
377}
378
379void QWinRTBluetoothDeviceDiscoveryWorker::onDeviceEnumerationCompleted(int watcherId)
380{
381 qCDebug(QT_BT_WINDOWS) << (watcherId == ClassicWatcherId ? "BT" : "BTLE")
382 << "enumeration completed";
383 if (watcherId == ClassicWatcherId) {
384 m_classicWatcher->stop();
385 m_classicScanStarted = false;
386 } else if (watcherId == LowEnergyWatcherId) {
387 m_lowEnergyWatcher->stop();
388 m_lowEnergyScanStarted = false;
389 }
390 if (isFinished())
391 finishDiscovery();
392}
393
394// this function executes in main worker thread
395void QWinRTBluetoothDeviceDiscoveryWorker::onAdvertisementDataReceived(
396 quint64 address, qint16 rssi, const ManufacturerData &manufacturerData,
397 const ServiceData &serviceData, const QList<QBluetoothUuid> &uuids)
398{
399 // Merge newly found services with list of currently found ones
400 bool needDiscoverServices = false;
401 {
402 QMutexLocker locker(&m_leDevicesMutex);
403 if (m_foundLEDevicesMap.contains(address)) {
404 QBluetoothDeviceInfo::Fields changedFields = QBluetoothDeviceInfo::Field::None;
405 const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address);
406 QList<QBluetoothUuid> foundServices = adInfo.services;
407 if (adInfo.rssi != rssi) {
408 m_foundLEDevicesMap[address].rssi = rssi;
409 changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI);
410 }
411 if (adInfo.manufacturerData != manufacturerData) {
412 m_foundLEDevicesMap[address].manufacturerData.insert(manufacturerData);
413 if (adInfo.manufacturerData != m_foundLEDevicesMap[address].manufacturerData)
415 }
416 if (adInfo.serviceData != serviceData) {
417 m_foundLEDevicesMap[address].serviceData.insert(serviceData);
418 if (adInfo.serviceData != m_foundLEDevicesMap[address].serviceData)
419 changedFields.setFlag((QBluetoothDeviceInfo::Field::ServiceData));
420 }
421 for (const QBluetoothUuid &uuid : std::as_const(uuids)) {
422 if (!foundServices.contains(uuid)) {
423 foundServices.append(uuid);
424 needDiscoverServices = true;
425 }
426 }
427 if (!needDiscoverServices) {
428 if (!changedFields.testFlag(QBluetoothDeviceInfo::Field::None)) {
429 QMetaObject::invokeMethod(this, "deviceDataChanged", Qt::AutoConnection,
431 Q_ARG(QBluetoothDeviceInfo::Fields, changedFields),
432 Q_ARG(qint16, rssi),
433 Q_ARG(ManufacturerData, manufacturerData),
434 Q_ARG(ServiceData, serviceData));
435 }
436 }
437 m_foundLEDevicesMap[address].services = foundServices;
438 } else {
439 needDiscoverServices = true;
440 LEAdvertisingInfo info;
441 info.services = std::move(uuids);
442 info.manufacturerData = std::move(manufacturerData);
443 info.serviceData = std::move(serviceData);
444 info.rssi = rssi;
445 m_foundLEDevicesMap.insert(address, info);
446 }
447 }
448 if (needDiscoverServices) {
449 ++m_pendingDevices; // as if we discovered a new LE device
450 auto thisPtr = shared_from_this();
451 auto asyncOp = BluetoothLEDevice::FromBluetoothAddressAsync(address);
452 asyncOp.Completed([thisPtr, address](auto &&op, AsyncStatus status) {
453 if (thisPtr) {
454 if (status == AsyncStatus::Completed) {
455 BluetoothLEDevice device = op.GetResults();
456 if (device) {
457 thisPtr->handleLowEnergyDevice(device);
458 return;
459 }
460 }
461 // status != Completed or failed to extract result
462 qCDebug(QT_BT_WINDOWS) << "Failed to get LE device from address"
465 }
466 });
467 }
468}
469
470void QWinRTBluetoothDeviceDiscoveryWorker::stopAdvertisementWatcher()
471{
472 m_advertisementWatcher->stop();
473 if (isFinished())
474 finishDiscovery();
475}
476
477std::shared_ptr<QBluetoothDeviceWatcherWinRT>
478QWinRTBluetoothDeviceDiscoveryWorker::createDeviceWatcher(winrt::hstring selector, int watcherId)
479{
480 auto watcher = std::make_shared<QBluetoothDeviceWatcherWinRT>(
481 watcherId, selector, DeviceInformationKind::AssociationEndpoint);
482 if (watcher) {
484 this, &QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothDeviceFound,
487 this, &QWinRTBluetoothDeviceDiscoveryWorker::onDeviceEnumerationCompleted,
489 }
490 return watcher;
491}
492
493void QWinRTBluetoothDeviceDiscoveryWorker::generateError(
495{
497 qCWarning(QT_BT_WINDOWS) << msg;
498}
499
500void QWinRTBluetoothDeviceDiscoveryWorker::invokeDeviceFoundWithDebug(const QBluetoothDeviceInfo &info)
501{
502 qCDebug(QT_BT_WINDOWS) << "Discovered BTLE device: " << info.address() << info.name()
503 << "Num UUIDs" << info.serviceUuids().size() << "RSSI:" << info.rssi()
504 << "Num manufacturer data" << info.manufacturerData().size()
505 << "Num service data" << info.serviceData().size();
506
509}
510
511// this function executes in main worker thread
512void QWinRTBluetoothDeviceDiscoveryWorker::getClassicDeviceFromId(const winrt::hstring &id)
513{
514 ++m_pendingDevices;
515 auto thisPtr = shared_from_this();
516 auto asyncOp = BluetoothDevice::FromIdAsync(id);
517 asyncOp.Completed([thisPtr](auto &&op, AsyncStatus status) {
518 if (thisPtr) {
519 if (status == AsyncStatus::Completed) {
520 BluetoothDevice device = op.GetResults();
521 if (device) {
522 thisPtr->handleClassicDevice(device);
523 return;
524 }
525 }
526 // status != Completed or failed to extract result
527 qCDebug(QT_BT_WINDOWS) << "Failed to get Classic device from id";
529 }
530 });
531}
532
533// this is a callback - executes in a new thread
534void QWinRTBluetoothDeviceDiscoveryWorker::handleClassicDevice(const BluetoothDevice &device)
535{
536 const uint64_t address = device.BluetoothAddress();
537 const std::wstring name { device.Name() }; // via operator std::wstring_view()
538 const QString btName = QString::fromStdWString(name);
539 const uint32_t deviceClass = device.ClassOfDevice().RawValue();
540 auto thisPtr = shared_from_this();
541 auto asyncOp = device.GetRfcommServicesAsync();
542 asyncOp.Completed([thisPtr, address, btName, deviceClass](auto &&op, AsyncStatus status) {
543 if (thisPtr) {
544 if (status == AsyncStatus::Completed) {
545 auto servicesResult = op.GetResults();
546 if (servicesResult) {
547 thisPtr->handleRfcommServices(servicesResult, address, btName, deviceClass);
548 return;
549 }
550 }
551 // Failed to get services
552 qCDebug(QT_BT_WINDOWS) << "Failed to get RFCOMM services for device" << btName;
554 }
555 });
556}
557
558// this is a callback - executes in a new thread
559void QWinRTBluetoothDeviceDiscoveryWorker::handleRfcommServices(
560 const RfcommDeviceServicesResult &servicesResult, uint64_t address,
561 const QString &name, uint32_t classOfDeviceInt)
562{
563 // need to perform the check even if some of the operations fails
564 auto shared = shared_from_this();
565 auto guard = qScopeGuard([shared]() {
567 });
568 Q_UNUSED(guard); // to suppress warning
569
570 const auto error = servicesResult.Error();
571 if (error != BluetoothError::Success) {
572 qCWarning(QT_BT_WINDOWS) << "Obtain device services completed with BluetoothError"
573 << static_cast<int>(error);
574 return;
575 }
576
577 const auto services = servicesResult.Services();
578 QList<QBluetoothUuid> uuids;
579 for (const auto &service : services) {
580 const auto serviceId = service.ServiceId();
581 const GUID uuid = fromWinRtGuid(serviceId.Uuid());
582 uuids.append(QBluetoothUuid(uuid));
583 }
584
585 const QBluetoothAddress btAddress(address);
586
587 qCDebug(QT_BT_WINDOWS) << "Discovered BT device: " << btAddress << name
588 << "Num UUIDs" << uuids.size();
589
590 QBluetoothDeviceInfo info(btAddress, name, classOfDeviceInt);
592 info.setServiceUuids(uuids);
593 info.setCached(true);
594
597}
598
599void QWinRTBluetoothDeviceDiscoveryWorker::decrementPendingDevicesCountAndCheckFinished(
600 std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker)
601{
602 --m_pendingDevices;
603 if (isFinished())
604 finishDiscovery();
605 // Worker is passed here simply to make sure that the object is still alive
606 // when we call this method via QObject::invoke().
607 Q_UNUSED(worker)
608}
609
610// this function executes in main worker thread
611void QWinRTBluetoothDeviceDiscoveryWorker::getLowEnergyDeviceFromId(const winrt::hstring &id)
612{
613 ++m_pendingDevices;
614 auto asyncOp = BluetoothLEDevice::FromIdAsync(id);
615 auto thisPtr = shared_from_this();
616 asyncOp.Completed([thisPtr](auto &&op, AsyncStatus status) {
617 if (thisPtr) {
618 if (status == AsyncStatus::Completed) {
619 BluetoothLEDevice device = op.GetResults();
620 if (device) {
621 thisPtr->handleLowEnergyDevice(device);
622 return;
623 }
624 }
625 // status != Completed or failed to extract result
626 qCDebug(QT_BT_WINDOWS) << "Failed to get LE device from id";
628 }
629 });
630}
631
632// this is a callback - executes in a new thread
633void QWinRTBluetoothDeviceDiscoveryWorker::handleLowEnergyDevice(const BluetoothLEDevice &device)
634{
635 const uint64_t address = device.BluetoothAddress();
636 const std::wstring name { device.Name() }; // via operator std::wstring_view()
637 const QString btName = QString::fromStdWString(name);
638 const bool isPaired = device.DeviceInformation().Pairing().IsPaired();
639
640 m_leDevicesMutex.lock();
641 const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address);
642 m_leDevicesMutex.unlock();
643 const ManufacturerData manufacturerData = adInfo.manufacturerData;
644 const ServiceData serviceData = adInfo.serviceData;
645 const qint16 rssi = adInfo.rssi;
646
649 info.setRssi(rssi);
650 for (quint16 key : manufacturerData.keys())
651 info.setManufacturerData(key, manufacturerData.value(key));
652 for (QBluetoothUuid key : serviceData.keys())
653 info.setServiceData(key, serviceData.value(key));
654 info.setCached(true);
655
656 // Use the services obtained from the advertisement data if the device is not paired
657 if (!isPaired) {
658 info.setServiceUuids(adInfo.services);
660 invokeDeviceFoundWithDebug(info);
661 } else {
662 auto asyncOp = device.GetGattServicesAsync();
663 auto thisPtr = shared_from_this();
664 asyncOp.Completed([thisPtr, info](auto &&op, AsyncStatus status) mutable {
665 if (status == AsyncStatus::Completed) {
666 auto servicesResult = op.GetResults();
667 if (servicesResult) {
668 thisPtr->handleGattServices(servicesResult, info);
669 return;
670 }
671 }
672 // Failed to get services
673 qCDebug(QT_BT_WINDOWS) << "Failed to get GATT services for device" << info.name();
675 });
676 }
677}
678
679// this is a callback - executes in a new thread
680void QWinRTBluetoothDeviceDiscoveryWorker::handleGattServices(
681 const GattDeviceServicesResult &servicesResult, QBluetoothDeviceInfo &info)
682{
683 // need to perform the check even if some of the operations fails
684 auto shared = shared_from_this();
685 auto guard = qScopeGuard([shared]() {
687 });
688 Q_UNUSED(guard); // to suppress warning
689
690 const auto status = servicesResult.Status();
691 if (status == GattCommunicationStatus::Success) {
692 const auto services = servicesResult.Services();
693 QList<QBluetoothUuid> uuids;
694 for (const auto &service : services) {
695 const GUID uuid = fromWinRtGuid(service.Uuid());
696 uuids.append(QBluetoothUuid(uuid));
697 }
698 info.setServiceUuids(uuids);
699 } else {
700 qCWarning(QT_BT_WINDOWS) << "Obtaining LE services finished with status"
701 << static_cast<int>(status);
702 }
703 invokeDeviceFoundWithDebug(info);
704}
705
706std::shared_ptr<AdvertisementWatcherWrapper>
707QWinRTBluetoothDeviceDiscoveryWorker::createAdvertisementWatcher()
708{
709 auto watcher = std::make_shared<AdvertisementWatcherWrapper>();
710 if (watcher) {
712 this, &QWinRTBluetoothDeviceDiscoveryWorker::onAdvertisementDataReceived,
714 }
715 return watcher;
716}
717
719 const QBluetoothAddress &deviceAdapter, QBluetoothDeviceDiscoveryAgent *parent)
720 : q_ptr(parent), adapterAddress(deviceAdapter)
721{
722 mainThreadCoInit(this);
723}
724
726{
727 disconnectAndClearWorker();
728 mainThreadCoUninit(this);
729}
730
732{
733 return worker != nullptr;
734}
735
736QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
737{
739}
740
741void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
742{
743 QBluetoothLocalDevice adapter(adapterAddress);
744 if (!adapter.isValid()) {
745 qCWarning(QT_BT_WINDOWS) << "Cannot find Bluetooth adapter for device search";
747 errorString = QBluetoothDeviceDiscoveryAgent::tr("Cannot find valid Bluetooth adapter.");
748 emit q_ptr->errorOccurred(lastError);
749 return;
750 } else if (adapter.hostMode() == QBluetoothLocalDevice::HostPoweredOff) {
751 qCWarning(QT_BT_WINDOWS) << "Bluetooth adapter powered off";
753 errorString = QBluetoothDeviceDiscoveryAgent::tr("Bluetooth adapter powered off.");
754 emit q_ptr->errorOccurred(lastError);
755 return;
756 }
757
758 if (worker)
759 return;
760
761 worker = std::make_shared<QWinRTBluetoothDeviceDiscoveryWorker>(methods,
762 lowEnergySearchTimeout);
764 errorString.clear();
765 discoveredDevices.clear();
767 this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice);
769 this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData);
771 this, &QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured);
773 this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished);
774 worker->start();
775}
776
778{
780 if (worker) {
781 worker->stop();
782 disconnectAndClearWorker();
783 emit q->canceled();
784 }
785}
786
787void QBluetoothDeviceDiscoveryAgentPrivate::registerDevice(const QBluetoothDeviceInfo &info)
788{
790
791 for (QList<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin();
792 iter != discoveredDevices.end(); ++iter) {
793 if (iter->address() == info.address()) {
794 qCDebug(QT_BT_WINDOWS) << "Updating device" << iter->name() << iter->address();
795 // merge service uuids
796 QList<QBluetoothUuid> uuids = iter->serviceUuids();
797 uuids.append(info.serviceUuids());
798 const QSet<QBluetoothUuid> uuidSet(uuids.begin(), uuids.end());
799 if (iter->serviceUuids().size() != uuidSet.size())
800 iter->setServiceUuids(uuidSet.values().toVector());
801 if (iter->coreConfigurations() != info.coreConfigurations())
803 return;
804 }
805 }
806
807 discoveredDevices << info;
808 emit q->deviceDiscovered(info);
809}
810
811void QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData(const QBluetoothAddress &address,
812 QBluetoothDeviceInfo::Fields fields,
813 qint16 rssi,
814 ManufacturerData manufacturerData,
815 ServiceData serviceData)
816{
817 if (fields.testFlag(QBluetoothDeviceInfo::Field::None))
818 return;
819
821 for (QList<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin();
822 iter != discoveredDevices.end(); ++iter) {
823 if (iter->address() == address) {
824 qCDebug(QT_BT_WINDOWS) << "Updating data for device" << iter->name() << iter->address();
825 if (fields.testFlag(QBluetoothDeviceInfo::Field::RSSI))
826 iter->setRssi(rssi);
828 for (quint16 key : manufacturerData.keys())
829 iter->setManufacturerData(key, manufacturerData.value(key));
830 if (fields.testFlag(QBluetoothDeviceInfo::Field::ServiceData))
831 for (QBluetoothUuid key : serviceData.keys())
832 iter->setServiceData(key, serviceData.value(key));
833 emit q->deviceUpdated(*iter, fields);
834 return;
835 }
836 }
837}
838
839void QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured(QBluetoothDeviceDiscoveryAgent::Error e)
840{
842 lastError = e;
843 emit q->errorOccurred(e);
844}
845
846void QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished()
847{
849 disconnectAndClearWorker();
850 emit q->finished();
851}
852
853void QBluetoothDeviceDiscoveryAgentPrivate::disconnectAndClearWorker()
854{
855 if (!worker)
856 return;
857
859 this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished);
861 this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice);
863 this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData);
865 this, &QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured);
866
867 worker = nullptr;
868}
869
871
872#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
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
iterator end()
Definition qlist.h:626
iterator begin()
Definition qlist.h:625
void clear()
Definition qlist.h:434
iterator insert(const Key &key, const T &value)
Definition qmap.h:688
T value(const Key &key, const T &defaultValue=T()) const
Definition qmap.h:357
bool contains(const Key &key) const
Definition qmap.h:341
\inmodule QtCore
Definition qmutex.h:313
\inmodule QtCore
Definition qmutex.h:281
void unlock() noexcept
Unlocks the mutex.
Definition qmutex.h:289
void lock() noexcept
Locks the mutex.
Definition qmutex.h:286
\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
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1252
static QString fromStdWString(const std::wstring &s)
Returns a copy of the str string.
Definition qstring.h:1458
\inmodule QtCore
Definition qtimer.h:20
void setSingleShot(bool singleShot)
Definition qtimer.cpp:552
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void setInterval(int msec)
Definition qtimer.cpp:579
bool isActive() const
Returns true if the timer is running (pending); otherwise returns false.
Definition qtimer.cpp:167
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
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)
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 QT_BEGIN_NAMESPACE QByteArray byteArrayFromBuffer(const IBuffer &buffer)
static ServiceData extractServiceData(const BluetoothLEAdvertisement &ad)
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
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
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:1390
#define Q_ARG(Type, data)
Definition qobjectdefs.h:63
GLenum mode
GLuint64 key
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
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:47
unsigned short quint16
Definition qtypes.h:48
unsigned long long quint64
Definition qtypes.h:61
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned char quint8
Definition qtypes.h:46
QFutureWatcher< int > watcher
QFileSelector selector
[1]
QStringList keys
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
myObject disconnect()
[26]
QGraphicsItem * item
QHostInfo info
[0]
QJSValueList args
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...