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_bluez.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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
4#include <QtCore/QLoggingCategory>
5
6#include <QtCore/qcoreapplication.h>
7
10#include "qbluetoothaddress.h"
11#include "qbluetoothuuid.h"
12
17#include "bluez/properties_p.h"
19
21
23
25 const QBluetoothAddress &deviceAdapter, QBluetoothDeviceDiscoveryAgent *parent) :
26 adapterAddress(deviceAdapter),
27 q_ptr(parent)
28{
31 QStringLiteral("org.bluez"),
32 QStringLiteral("/"),
36 q_ptr,
37 [this](const QDBusObjectPath &objectPath, InterfaceList interfacesAndProperties) {
38 this->_q_InterfacesAdded(objectPath, interfacesAndProperties);
39 });
40
41 // start private address monitoring
43}
44
46{
47 delete adapter;
48}
49
50//TODO: Qt6 remove the pendingCancel/pendingStart logic as it is cumbersome.
51// It is a behavior change across all platforms and was initially done
52// for Bluez. The behavior should be similar to QBluetoothServiceDiscoveryAgent
53// PendingCancel creates issues whereby the agent is still shutting down
54// but isActive() below already returns false. This means the isActive() is
55// out of sync with the finished() and cancel() signal.
56
58{
59 if (pendingStart)
60 return true;
61 if (pendingCancel)
62 return false; //TODO Qt6: remove pending[Cancel|Start] logic (see comment above)
63
64 return adapter;
65}
66
67QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
68{
70}
71
72void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
73{
74 if (pendingCancel == true) {
75 pendingStart = true;
76 return;
77 }
78
80 errorString.clear();
81 discoveredDevices.clear();
82 devicesProperties.clear();
83
85
86 bool ok = false;
87 const QString adapterPath = findAdapterForAddress(adapterAddress, &ok);
88 if (!ok || adapterPath.isEmpty()) {
89 qCWarning(QT_BT_BLUEZ) << "Cannot find Bluez 5 adapter for device search" << ok;
91 errorString = QBluetoothDeviceDiscoveryAgent::tr("Cannot find valid Bluetooth adapter.");
92 q->errorOccurred(lastError);
93 return;
94 }
95
96 adapter = new OrgBluezAdapter1Interface(QStringLiteral("org.bluez"), adapterPath,
98
99 if (!adapter->powered()) {
100 qCDebug(QT_BT_BLUEZ) << "Aborting device discovery due to offline Bluetooth Adapter";
102 errorString = QBluetoothDeviceDiscoveryAgent::tr("Device is powered off");
103 delete adapter;
104 adapter = nullptr;
105 emit q->errorOccurred(lastError);
106 return;
107 }
108
111 map.insert(QStringLiteral("Transport"), QStringLiteral("auto"));
113 map.insert(QStringLiteral("Transport"), QStringLiteral("le"));
114 else
115 map.insert(QStringLiteral("Transport"), QStringLiteral("bredr"));
116
117 // older BlueZ 5.x versions don't have this function
118 // filterReply returns UnknownMethod which we ignore
119 QDBusPendingReply<> filterReply = adapter->SetDiscoveryFilter(map);
120 filterReply.waitForFinished();
121 if (filterReply.isError()) {
122 if (filterReply.error().type() == QDBusError::Other
123 && filterReply.error().name() == QStringLiteral("org.bluez.Error.Failed")) {
124 qCDebug(QT_BT_BLUEZ) << "Discovery method" << methods << "not supported";
126 errorString = QBluetoothDeviceDiscoveryAgent::tr("One or more device discovery methods "
127 "are not supported on this platform");
128 delete adapter;
129 adapter = nullptr;
130 emit q->errorOccurred(lastError);
131 return;
132 } else if (filterReply.error().type() != QDBusError::UnknownMethod) {
133 qCDebug(QT_BT_BLUEZ) << "SetDiscoveryFilter failed:" << filterReply.error();
134 }
135 }
136
137 QtBluezDiscoveryManager::instance()->registerDiscoveryInterest(adapter->path());
139 q, [this](const QString &path){
140 this->_q_discoveryInterrupted(path);
141 });
145 q, [this](const QString &interface, const QVariantMap &changedProperties,
146 const QStringList &invalidatedProperties,
147 const QDBusMessage &signal) {
148 this->_q_PropertiesChanged(interface, signal.path(), changedProperties, invalidatedProperties);
149 });
150
151 // remember what we have to cleanup
152 propertyMonitors.append(prop);
153
154 // collect initial set of information
155 QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects();
156 reply.waitForFinished();
157 if (!reply.isError()) {
158 ManagedObjectList managedObjectList = reply.value();
159 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
160 const QDBusObjectPath &path = it.key();
161 const InterfaceList &ifaceList = it.value();
162
163 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
164 const QString &iface = jt.key();
165
166 if (iface == QStringLiteral("org.bluez.Device1")) {
167
168 if (path.path().indexOf(adapter->path()) != 0)
169 continue; //devices whose path doesn't start with same path we skip
170
171 deviceFound(path.path(), jt.value());
172 if (!isActive()) // Can happen if stop() was called from a slot in user code.
173 return;
174 }
175 }
176 }
177 }
178
179 // wait interval and sum up what was found
180 if (!discoveryTimer) {
181 discoveryTimer = new QTimer(q);
182 discoveryTimer->setSingleShot(true);
183 QObject::connect(discoveryTimer, &QTimer::timeout,
184 q, [this]() {
185 this->_q_discoveryFinished();
186 });
187 }
188
189 if (lowEnergySearchTimeout > 0) { // otherwise no timeout and stop() required
190 discoveryTimer->setInterval(lowEnergySearchTimeout);
191 discoveryTimer->start();
192 }
193}
194
196{
197 if (!adapter)
198 return;
199
200 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO;
201 pendingCancel = true;
202 pendingStart = false;
203 _q_discoveryFinished();
204}
205
206// Returns invalid QBluetoothDeviceInfo in case of error
208{
209 const QBluetoothAddress btAddress(properties[QStringLiteral("Address")].toString());
210 if (btAddress.isNull())
211 return QBluetoothDeviceInfo();
212
213 const QString btName = properties[QStringLiteral("Alias")].toString();
214 quint32 btClass = properties[QStringLiteral("Class")].toUInt();
215
216 QBluetoothDeviceInfo deviceInfo(btAddress, btName, btClass);
217 deviceInfo.setRssi(qvariant_cast<short>(properties[QStringLiteral("RSSI")]));
218
219 QList<QBluetoothUuid> uuids;
220 bool foundLikelyLowEnergyUuid = false;
221 const QStringList foundUuids = qvariant_cast<QStringList>(properties[QStringLiteral("UUIDs")]);
222 for (const auto &u: foundUuids) {
223 const QBluetoothUuid id(u);
224 if (id.isNull())
225 continue;
226
227 if (!foundLikelyLowEnergyUuid) {
228 //once we found one BTLE service we are done
229 bool ok = false;
230 quint16 shortId = id.toUInt16(&ok);
231 quint16 genericAccessInt = static_cast<quint16>(QBluetoothUuid::ServiceClassUuid::GenericAccess);
232 if (ok && ((shortId & genericAccessInt) == genericAccessInt))
233 foundLikelyLowEnergyUuid = true;
234 }
235 uuids.append(id);
236 }
237 deviceInfo.setServiceUuids(uuids);
238
239 if (!btClass) {
241 } else {
243 if (foundLikelyLowEnergyUuid)
245 }
246
247 const ManufacturerDataList deviceManufacturerData = qdbus_cast<ManufacturerDataList>(properties[QStringLiteral("ManufacturerData")]);
248 const QList<quint16> keysManufacturer = deviceManufacturerData.keys();
249 for (quint16 key : keysManufacturer)
250 deviceInfo.setManufacturerData(
251 key, deviceManufacturerData.value(key).variant().toByteArray());
252
253 const ServiceDataList deviceServiceData =
254 qdbus_cast<ServiceDataList>(properties[QStringLiteral("ServiceData")]);
255 const QList<QString> keysService = deviceServiceData.keys();
256 for (QString key : keysService)
258 deviceServiceData.value(key).variant().toByteArray());
259
260 return deviceInfo;
261}
262
263void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(const QString &devicePath,
264 const QVariantMap &properties)
265{
267
268 if (!q->isActive())
269 return;
270
271 auto deviceAdapter = qvariant_cast<QDBusObjectPath>(properties[QStringLiteral("Adapter")]);
272 if (deviceAdapter.path() != adapter->path())
273 return;
274
275 // read information
277 if (!deviceInfo.isValid()) // no point reporting an empty address
278 return;
279
280 qCDebug(QT_BT_BLUEZ) << "Discovered: " << deviceInfo.name() << deviceInfo.address()
281 << "Num UUIDs" << deviceInfo.serviceUuids().size()
282 << "total device" << discoveredDevices.size() << "cached"
283 << "RSSI" << deviceInfo.rssi()
284 << "Num ManufacturerData" << deviceInfo.manufacturerData().size()
285 << "Num ServiceData" << deviceInfo.serviceData().size();
286
287 // Cache the properties so we do not have to access dbus every time to get a value
288 devicesProperties[devicePath] = properties;
289
290 for (qsizetype i = 0; i < discoveredDevices.size(); ++i) {
291 if (discoveredDevices[i].address() == deviceInfo.address()) {
292 if (lowEnergySearchTimeout > 0 && discoveredDevices[i] == deviceInfo) {
293 qCDebug(QT_BT_BLUEZ) << "Duplicate: " << deviceInfo.address();
294 return;
295 }
296 discoveredDevices.replace(i, deviceInfo);
297
298 emit q->deviceDiscovered(deviceInfo);
299 return; // this works if the list doesn't contain duplicates. Don't let it.
300 }
301 }
302
303 discoveredDevices.append(deviceInfo);
304 emit q->deviceDiscovered(deviceInfo);
305}
306
307void QBluetoothDeviceDiscoveryAgentPrivate::_q_InterfacesAdded(const QDBusObjectPath &object_path,
308 InterfaceList interfaces_and_properties)
309{
311
312 if (!q->isActive())
313 return;
314
315 if (interfaces_and_properties.contains(QStringLiteral("org.bluez.Device1"))) {
316 // device interfaces belonging to different adapter
317 // will be filtered out by deviceFound();
318 deviceFound(object_path.path(),
319 interfaces_and_properties[QStringLiteral("org.bluez.Device1")]);
320 }
321}
322
323void QBluetoothDeviceDiscoveryAgentPrivate::_q_discoveryFinished()
324{
326
327 if (discoveryTimer)
328 discoveryTimer->stop();
329
331 QtBluezDiscoveryManager::instance()->unregisterDiscoveryInterest(adapter->path());
332
333 qDeleteAll(propertyMonitors);
334 propertyMonitors.clear();
335
336 delete adapter;
337 adapter = nullptr;
338
339 if (pendingCancel && !pendingStart) {
340 pendingCancel = false;
341 emit q->canceled();
342 } else if (pendingStart) {
343 pendingStart = false;
344 pendingCancel = false;
347 } else {
348 emit q->finished();
349 }
350}
351
352void QBluetoothDeviceDiscoveryAgentPrivate::_q_discoveryInterrupted(const QString &path)
353{
355
356 if (!q->isActive())
357 return;
358
359 if (path == adapter->path()) {
360 qCWarning(QT_BT_BLUEZ) << "Device discovery aborted due to unexpected adapter changes from another process.";
361
362 if (discoveryTimer)
363 discoveryTimer->stop();
364
366 // no need to call unregisterDiscoveryInterest since QtBluezDiscoveryManager
367 // does this automatically when emitting discoveryInterrupted(QString) signal
368
369 delete adapter;
370 adapter = nullptr;
371
372 errorString = QBluetoothDeviceDiscoveryAgent::tr("Bluetooth adapter error");
374 emit q->errorOccurred(lastError);
375 }
376}
377
378void QBluetoothDeviceDiscoveryAgentPrivate::_q_PropertiesChanged(const QString &interface,
379 const QString &path,
380 const QVariantMap &changed_properties,
381 const QStringList &invalidated_properties)
382{
384 if (interface != QStringLiteral("org.bluez.Device1"))
385 return;
386
387 if (!devicesProperties.contains(path))
388 return;
389
390 // Update the cached properties before checking changed_properties for RSSI and ManufacturerData
391 // so the cached properties are always up to date.
392 QVariantMap & properties = devicesProperties[path];
393 for (QVariantMap::const_iterator it = changed_properties.constBegin();
394 it != changed_properties.constEnd(); ++it) {
395 properties[it.key()] = it.value();
396 }
397
398 for (const QString & property : invalidated_properties)
400
402 if (!info.isValid())
403 return;
404
405 if (changed_properties.contains(QStringLiteral("RSSI"))
406 || changed_properties.contains(QStringLiteral("ManufacturerData"))) {
407
408 for (qsizetype i = 0; i < discoveredDevices.size(); ++i) {
409 if (discoveredDevices[i].address() == info.address()) {
410 QBluetoothDeviceInfo::Fields updatedFields = QBluetoothDeviceInfo::Field::None;
411 if (changed_properties.contains(QStringLiteral("RSSI"))) {
412 qCDebug(QT_BT_BLUEZ) << "Updating RSSI for" << info.address()
413 << changed_properties.value(QStringLiteral("RSSI"));
414 discoveredDevices[i].setRssi(
415 changed_properties.value(QStringLiteral("RSSI")).toInt());
416 updatedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI);
417 }
418 if (changed_properties.contains(QStringLiteral("ManufacturerData"))) {
419 qCDebug(QT_BT_BLUEZ) << "Updating ManufacturerData for" << info.address();
420 ManufacturerDataList changedManufacturerData =
421 qdbus_cast< ManufacturerDataList >(changed_properties.value(QStringLiteral("ManufacturerData")));
422
423 const QList<quint16> keys = changedManufacturerData.keys();
424 bool wasNewValue = false;
425 for (quint16 key : keys) {
426 bool added = discoveredDevices[i].setManufacturerData(key, changedManufacturerData.value(key).variant().toByteArray());
427 wasNewValue = (wasNewValue || added);
428 }
429
430 if (wasNewValue)
432 }
433
434 if (lowEnergySearchTimeout > 0) {
435 if (discoveredDevices[i] != info) { // field other than manufacturer or rssi changed
436 if (discoveredDevices.at(i).name() == info.name()) {
437 qCDebug(QT_BT_BLUEZ) << "Almost Duplicate " << info.address()
438 << info.name() << "- replacing in place";
439 discoveredDevices.replace(i, info);
440 emit q->deviceDiscovered(info);
441 }
442 } else {
443 if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None))
444 emit q->deviceUpdated(discoveredDevices[i], updatedFields);
445 }
446
447 return;
448 }
449
450 discoveredDevices.replace(i, info);
451 emit q_ptr->deviceDiscovered(discoveredDevices[i]);
452
453 if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None))
454 emit q->deviceUpdated(discoveredDevices[i], updatedFields);
455 return;
456 }
457 }
458 }
459}
static JNINativeMethod methods[]
QString findAdapterForAddress(const QBluetoothAddress &wantedAddress, bool *ok=nullptr)
QMap< QDBusObjectPath, InterfaceList > ManagedObjectList
QMap< QString, QDBusVariant > ServiceDataList
QT_BEGIN_NAMESPACE void initializeBluez5()
static BluetoothManagement * instance()
void InterfacesAdded(const QDBusObjectPath &object_path, InterfaceList interfaces_and_properties)
void PropertiesChanged(const QString &interface, const QVariantMap &changed_properties, const QStringList &invalidated_properties, const QDBusMessage &msg)
\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 deviceDiscovered(const QBluetoothDeviceInfo &info)
This signal is emitted when the Bluetooth device described by info is discovered.
\inmodule QtBluetooth
QBluetoothAddress address() const
Returns the address of the device.
QByteArray manufacturerData(quint16 manufacturerId) const
Returns the data associated with the given manufacturerId.
bool isValid() const
Returns true if the QBluetoothDeviceInfo object is valid, otherwise returns false.
void setServiceUuids(const QList< QBluetoothUuid > &uuids)
Sets the list of service UUIDs to uuids.
void setRssi(qint16 signal)
Set the signal strength value, used internally.
QByteArray serviceData(const QBluetoothUuid &serviceId) const
Returns the data associated with the given serviceId.
QList< QBluetoothUuid > serviceUuids() const
Returns the list of service UUIDs supported by the device.
bool setServiceData(const QBluetoothUuid &serviceId, const QByteArray &data)
Sets the advertised service data for the given serviceId.
QString name() const
Returns the name assigned to the device.
qint16 rssi() const
Returns the signal strength when the device was last scanned.
bool setManufacturerData(quint16 manufacturerId, const QByteArray &data)
Sets the advertised manufacturer data for the given manufacturerId.
void setCoreConfigurations(QBluetoothDeviceInfo::CoreConfigurations coreConfigs)
Sets the CoreConfigurations of the device to coreConfigs.
\inmodule QtBluetooth
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
static QDBusConnection systemBus()
Returns a QDBusConnection object opened with the system bus.
\inmodule QtDBus
\inmodule QtDBus
qsizetype size() const noexcept
Definition qlist.h:397
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
void replace(qsizetype i, parameter_type t)
Definition qlist.h:543
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
Definition qmap.h:187
iterator insert(const Key &key, const T &value)
Definition qmap.h:688
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
const_iterator constBegin() const noexcept
Definition qset.h:139
const_iterator constEnd() const noexcept
Definition qset.h:143
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1252
\inmodule QtCore
Definition qtimer.h:20
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
QUuid::Variant variant() const noexcept
Returns the value in the \l{Variant field} {variant field} of the UUID.
Definition quuid.cpp:867
void discoveryInterrupted(const QString &adapterPath)
static QtBluezDiscoveryManager * instance()
QMap< QString, QString > map
[6]
qDeleteAll(list.begin(), list.end())
QSet< QString >::iterator it
auto signal
Combined button and popup list for selecting options.
constexpr QBindableInterface iface
Definition qproperty.h:666
static QBluetoothDeviceInfo createDeviceInfoFromBluez5Device(const QVariantMap &properties)
#define Q_FUNC_INFO
static const QCssKnownValue properties[NumProperties - 1]
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 int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char * interface
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
GLuint64 key
GLenum GLuint id
[7]
GLuint start
GLuint GLuint64EXT address
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLsizei const GLchar *const * path
#define QStringLiteral(str)
#define emit
unsigned int quint32
Definition qtypes.h:50
unsigned short quint16
Definition qtypes.h:48
ptrdiff_t qsizetype
Definition qtypes.h:165
static int toInt(const QChar &qc, int R)
const char property[13]
Definition qwizard.cpp:101
QStringList keys
settings remove("monkey")
QNetworkAccessManager manager
QNetworkReply * reply
QHostInfo info
[0]
char * toString(const MyType &t)
[31]