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