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
qleadvertiser_bluezdbus.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
5#include "bluez/leadvertisement1adaptor_p.h"
6#include "bluez/leadvertisingmanager1_p.h"
7#include "bluez/bluez5_helper_p.h"
8
9#include <QtCore/QtMinMax>
10#include <QtCore/QLoggingCategory>
11
12QT_BEGIN_NAMESPACE
13
14Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
15
16using namespace Qt::StringLiterals;
17using namespace QtBluetoothPrivate; // for D-Bus wrappers
18
19// The advertisement dbus object path is freely definable, use as prefix
20static constexpr auto advObjectPathTemplate{"/qt/btle/advertisement/%1%2/%3"_L1};
21static constexpr auto bluezService{"org.bluez"_L1};
22static constexpr auto bluezErrorFailed{"org.bluez.Error.Failed"_L1};
23
24// From bluez API documentation
25static constexpr auto advDataTXPower{"tx-power"_L1};
26static constexpr auto advDataTypePeripheral{"peripheral"_L1};
27static constexpr auto advDataTypeBroadcast{"broadcast"_L1};
28static constexpr quint16 advDataMinIntervalMs{20};
29static constexpr quint16 advDataMaxIntervalMs{10485};
30
31
32QLeDBusAdvertiser::QLeDBusAdvertiser(const QLowEnergyAdvertisingParameters &params,
33 const QLowEnergyAdvertisingData &advertisingData,
34 const QLowEnergyAdvertisingData &scanResponseData,
35 const QString &hostAdapterPath,
36 QObject* parent)
37 : QObject(parent),
38 m_advParams(params),
39 m_advData(advertisingData),
40 m_advObjectPath(QString(advObjectPathTemplate).
41 arg(sanitizeNameForDBus(QCoreApplication::applicationName())).
42 arg(QCoreApplication::applicationPid()).
43 arg(QRandomGenerator::global()->generate())),
44 m_advDataDBus(new OrgBluezLEAdvertisement1Adaptor(this)),
45 m_advManager(new OrgBluezLEAdvertisingManager1Interface(bluezService, hostAdapterPath,
46 QDBusConnection::systemBus(), this))
47{
48 // Bluez DBus API doesn't allow distinguishing between advertisement and scan response data;
49 // consolidate the two if they differ.
50 // Union of service UUIDs:
51 if (scanResponseData.services() != advertisingData.services()) {
52 QList<QBluetoothUuid> services = advertisingData.services();
53 const auto scanResponseServices = scanResponseData.services();
54 for (const auto &service : scanResponseServices) {
55 if (!services.contains(service))
56 services.append(service);
57 }
58 m_advData.setServices(services);
59 }
60 // Scan response is given precedence with rest of the data
61 if (!scanResponseData.localName().isEmpty())
62 m_advData.setLocalName(scanResponseData.localName());
63 if (scanResponseData.manufacturerId() != QLowEnergyAdvertisingData::invalidManufacturerId()) {
64 m_advData.setManufacturerData(scanResponseData.manufacturerId(),
65 scanResponseData.manufacturerData());
66 }
67 if (scanResponseData.includePowerLevel())
68 m_advData.setIncludePowerLevel(true);
69
70 setDataForDBus();
71}
72
73QLeDBusAdvertiser::~QLeDBusAdvertiser()
74{
75 stopAdvertising();
76}
77
78// This function parses the advertising data provided by the application and
79// populates the dbus adaptor with it. DBus will ask the data from the adaptor when
80// the advertisement is later registered (started)
81void QLeDBusAdvertiser::setDataForDBus()
82{
83 setAdvertisingParamsForDBus();
84 setAdvertisementDataForDBus();
85}
86
87void QLeDBusAdvertiser::setAdvertisingParamsForDBus()
88{
89 // Whitelist and filter policy
90 if (!m_advParams.whiteList().isEmpty())
91 qCWarning(QT_BT_BLUEZ) << "White lists and filter policies not supported, ignoring";
92
93 // Legacy advertising mode mapped to GAP role (peripheral vs broadcast)
94 switch (m_advParams.mode())
95 {
96 case QLowEnergyAdvertisingParameters::AdvScanInd:
97 case QLowEnergyAdvertisingParameters::AdvNonConnInd:
98 m_advDataDBus->setType(advDataTypeBroadcast);
99 break;
100 case QLowEnergyAdvertisingParameters::AdvInd:
101 default:
102 m_advDataDBus->setType(advDataTypePeripheral);
103 }
104
105 // Advertisement interval (min max in milliseconds). Ensure the values fit the range bluez
106 // allows. The max >= min is guaranteed by QLowEnergyAdvertisingParameters::setInterval().
107 // Note: Bluez reads these values but at the time of this writing it marks this feature
108 // as 'experimental'
109 m_advDataDBus->setMinInterval(qBound(advDataMinIntervalMs,
110 quint16(m_advParams.minimumInterval()),
111 advDataMaxIntervalMs));
112 m_advDataDBus->setMaxInterval(qBound(advDataMinIntervalMs,
113 quint16(m_advParams.maximumInterval()),
114 advDataMaxIntervalMs));
115}
116
117void QLeDBusAdvertiser::setAdvertisementDataForDBus()
118{
119 // We don't calculate the advertisement length to guard for too long advertisements.
120 // There isn't adequate control and visibility on the advertisement for that.
121 // - We don't know the max length (legacy or extended advertising)
122 // - Bluez may truncate some of the fields on its own, making calculus here imprecise
123 // - Scan response may or may not be used to offload some of the data
124
125 // Include the power level if requested and dbus supports it
126 const auto supportedIncludes = m_advManager->supportedIncludes();
127 if (m_advData.includePowerLevel() && supportedIncludes.contains(advDataTXPower))
128 m_advDataDBus->setIncludes({advDataTXPower});
129
130 // Set the application provided name (valid to be empty).
131 // For clarity: bluez also has "local-name" system include that could be set if no local
132 // name is provided. However that would require that the LocalName DBus property would
133 // not exist. Existing LocalName property when 'local-name' is included leads to an
134 // advertisement error.
135 m_advDataDBus->setLocalName(m_advData.localName());
136
137 // Service UUIDs
138 if (!m_advData.services().isEmpty()) {
139 QStringList serviceUUIDList;
140 const auto services = m_advData.services();
141 for (const auto &service : services)
142 serviceUUIDList << service.toString(QUuid::StringFormat::WithoutBraces);
143 m_advDataDBus->setServiceUUIDs(serviceUUIDList);
144 }
145
146 // Manufacturer data
147 if (m_advData.manufacturerId() != QLowEnergyAdvertisingData::invalidManufacturerId()) {
148 m_advDataDBus->setManufacturerData({
149 {m_advData.manufacturerId(), QDBusVariant(m_advData.manufacturerData())}});
150 }
151
152 // Discoverability
153 if (m_advDataDBus->type() == advDataTypePeripheral) {
154 m_advDataDBus->setDiscoverable(m_advData.discoverability()
155 != QLowEnergyAdvertisingData::DiscoverabilityNone);
156 } else {
157 qCDebug(QT_BT_BLUEZ) << "Ignoring advertisement discoverability in broadcast mode";
158 }
159
160 // Raw data
161 if (!m_advData.rawData().isEmpty())
162 qCWarning(QT_BT_BLUEZ) << "Raw advertisement data not supported, ignoring";
163}
164
165void QLeDBusAdvertiser::startAdvertising()
166{
167 qCDebug(QT_BT_BLUEZ) << "Start advertising" << m_advObjectPath << "on" << m_advManager->path();
168 if (m_advertising) {
169 qCWarning(QT_BT_BLUEZ) << "Start tried while already advertising";
170 return;
171 }
172
173 if (!QDBusConnection::systemBus().registerObject(m_advObjectPath, m_advDataDBus,
174 QDBusConnection::ExportAllContents)) {
175 qCWarning(QT_BT_BLUEZ) << "Advertisement dbus object registration failed";
176 emit errorOccurred();
177 return;
178 }
179
180 // Register the advertisement which starts the actual advertising.
181 // We use call watcher here instead of waitForFinished() because DBus will
182 // call back our advertisement object (to read data) in this same thread => would block
183 auto reply = m_advManager->RegisterAdvertisement(QDBusObjectPath(m_advObjectPath), {});
184 QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
185
186 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this,
187 [this](QDBusPendingCallWatcher* watcher){
188 QDBusPendingReply<> reply = *watcher;
189 if (reply.isError()) {
190 qCWarning(QT_BT_BLUEZ) << "Advertisement registration failed" << reply.error();
191 if (reply.error().name() == bluezErrorFailed)
192 qCDebug(QT_BT_BLUEZ) << "Advertisement could've been too large";
193 QDBusConnection::systemBus().unregisterObject(m_advObjectPath);
194 emit errorOccurred();
195 } else {
196 qCDebug(QT_BT_BLUEZ) << "Advertisement started successfully";
197 m_advertising = true;
198 }
199 watcher->deleteLater();
200 });
201}
202
203void QLeDBusAdvertiser::stopAdvertising()
204{
205 if (!m_advertising)
206 return;
207
208 m_advertising = false;
209 auto reply = m_advManager->UnregisterAdvertisement(QDBusObjectPath(m_advObjectPath));
210 reply.waitForFinished();
211 if (reply.isError())
212 qCWarning(QT_BT_BLUEZ) << "Error in unregistering advertisement" << reply.error();
213 else
214 qCDebug(QT_BT_BLUEZ) << "Advertisement unregistered successfully";
215 QDBusConnection::systemBus().unregisterObject(m_advObjectPath);
216}
217
218// Called by Bluez when the advertisement has been removed (org.bluez.LEAdvertisement1.Release)
219void QLeDBusAdvertiser::Release()
220{
221 qCDebug(QT_BT_BLUEZ) << "Advertisement" << m_advObjectPath << "released"
222 << (m_advertising ? "unexpectedly" : "");
223 if (m_advertising) {
224 // If we are advertising, it means the Release is unsolicited
225 // and handled as an advertisement error. No need to call UnregisterAdvertisement
226 m_advertising = false;
227 QDBusConnection::systemBus().unregisterObject(m_advObjectPath);
228 emit errorOccurred();
229 }
230}
231
232QT_END_NAMESPACE
static constexpr quint16 advDataMinIntervalMs
static constexpr auto advObjectPathTemplate
static constexpr auto advDataTypePeripheral
static constexpr auto advDataTypeBroadcast
static constexpr auto bluezErrorFailed
static constexpr quint16 advDataMaxIntervalMs
static constexpr auto bluezService
static constexpr auto advDataTXPower