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
qleadvertiser_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
9
10#include <QtCore/qloggingcategory.h>
11
12#include <cstring>
13
15
17
31
36
39 bdaddr_t addr;
40};
41
42
43template <typename T>
45{
46 return QByteArray(reinterpret_cast<const char *>(&data), sizeof data);
47}
48
50 const QLowEnergyAdvertisingData &advertisingData,
51 const QLowEnergyAdvertisingData &scanResponseData,
52 std::shared_ptr<HciManager> hciManager, QObject *parent)
53 : QLeAdvertiser(params, advertisingData, scanResponseData, parent), m_hciManager(hciManager)
54{
55 Q_ASSERT(m_hciManager);
56 connect(m_hciManager.get(), &HciManager::commandCompleted, this,
57 &QLeAdvertiserBluez::handleCommandCompleted);
58}
59
61{
62 disconnect(m_hciManager.get(), &HciManager::commandCompleted, this,
63 &QLeAdvertiserBluez::handleCommandCompleted);
65}
66
68{
69 if (!m_hciManager->monitorEvent(HciManager::HciEvent::EVT_CMD_COMPLETE)) {
70 handleError();
71 return;
72 }
73
74 m_sendPowerLevel = advertisingData().includePowerLevel()
76 if (m_sendPowerLevel)
77 queueReadTxPowerLevelCommand();
78 else
79 queueAdvertisingCommands();
80 sendNextCommand();
81}
82
84{
85 toggleAdvertising(false);
86 sendNextCommand();
87}
88
89void QLeAdvertiserBluez::queueCommand(QBluezConst::OpCodeCommandField ocf, const QByteArray &data)
90{
91 m_pendingCommands << Command(ocf, data);
92}
93
94void QLeAdvertiserBluez::sendNextCommand()
95{
96 if (m_pendingCommands.isEmpty()) {
97 // TODO: Unmonitor event.
98 return;
99 }
100 const Command &c = m_pendingCommands.first();
101 if (!m_hciManager->sendCommand(QBluezConst::OgfLinkControl, c.ocf, c.data)) {
102 handleError();
103 return;
104 }
105}
106
107void QLeAdvertiserBluez::queueAdvertisingCommands()
108{
109 toggleAdvertising(false); // Stop advertising first, in case it's currently active.
110 setWhiteList();
111 setAdvertisingParams();
112 setAdvertisingData();
113 setScanResponseData();
114 toggleAdvertising(true);
115}
116
117void QLeAdvertiserBluez::queueReadTxPowerLevelCommand()
118{
119 // Spec v4.2, Vol 2, Part E, 7.8.6
121}
122
123void QLeAdvertiserBluez::toggleAdvertising(bool enable)
124{
125 // Spec v4.2, Vol 2, Part E, 7.8.9
127}
128
129void QLeAdvertiserBluez::setAdvertisingParams()
130{
131 // Spec v4.2, Vol 2, Part E, 7.8.5
132 // or Spec v5.3, Vol 4, Part E, 7.8.5
134 static_assert(sizeof params == 15, "unexpected struct size");
135 using namespace std;
136 memset(&params, 0, sizeof params);
137 setAdvertisingInterval(params);
138 params.type = parameters().mode();
139 params.filterPolicy = parameters().filterPolicy();
142 qCWarning(QT_BT_BLUEZ) << "limited discoverability is incompatible with "
143 "using a white list; disabling filtering";
145 }
146 params.ownAddrType = QLowEnergyController::PublicAddress; // TODO: Make configurable.
147
148 // TODO: For ADV_DIRECT_IND.
149 // params.directAddrType = xxx;
150 // params.direct_bdaddr = xxx;
151
152 params.channelMap = 0x7; // All channels.
153
154 const QByteArray paramsData = byteArrayFromStruct(params);
155 qCDebug(QT_BT_BLUEZ) << "advertising parameters:" << paramsData.toHex();
156 queueCommand(QBluezConst::OcfLeSetAdvParams, paramsData);
157}
158
160{
161 return qMin(qMax(val, min), max);
162}
163
164void QLeAdvertiserBluez::setAdvertisingInterval(AdvParams &params)
165{
166 const double multiplier = 0.625;
167 const quint16 minVal = parameters().minimumInterval() / multiplier;
168 const quint16 maxVal = parameters().maximumInterval() / multiplier;
169 Q_ASSERT(minVal <= maxVal);
170 const quint16 specMinimum =
173 const quint16 specMaximum = 0x4000;
174 params.minInterval = qToLittleEndian(forceIntoRange(minVal, specMinimum, specMaximum));
175 params.maxInterval = qToLittleEndian(forceIntoRange(maxVal, specMinimum, specMaximum));
176 Q_ASSERT(params.minInterval <= params.maxInterval);
177}
178
179void QLeAdvertiserBluez::setPowerLevel(AdvData &advData)
180{
181 if (m_sendPowerLevel) {
182 advData.data[advData.length++] = 2;
183 advData.data[advData.length++]= 0xa;
184 advData.data[advData.length++] = m_powerLevel;
185 }
186}
187
188void QLeAdvertiserBluez::setFlags(AdvData &advData)
189{
190 // TODO: Discoverability flags are incompatible with ADV_DIRECT_IND
191 quint8 flags = 0;
193 flags |= 0x1;
195 flags |= 0x2;
196 flags |= 0x4; // "BR/EDR not supported". Otherwise clients might try to connect over Bluetooth classic.
197 if (flags) {
198 advData.data[advData.length++] = 2;
199 advData.data[advData.length++] = 0x1;
200 advData.data[advData.length++] = flags;
201 }
202}
203
204template<typename T> static quint8 servicesType(bool dataComplete);
205template<> quint8 servicesType<quint16>(bool dataComplete)
206{
207 return dataComplete ? 0x3 : 0x2;
208}
209template<> quint8 servicesType<quint32>(bool dataComplete)
210{
211 return dataComplete ? 0x5 : 0x4;
212}
213template<> quint8 servicesType<QUuid::Id128Bytes>(bool dataComplete)
214{
215 return dataComplete ? 0x7 : 0x6;
216}
217
218template<typename T>
219static void addServicesData(AdvData &data, const QList<T> &services)
220{
221 if (services.isEmpty())
222 return;
223 constexpr auto sizeofT = static_cast<int>(sizeof(T)); // signed is more convenient
224 const qsizetype spaceAvailable = sizeof data.data - data.length;
225 // Determine how many services will be set, space may limit the number
226 const qsizetype maxServices = (std::min)((spaceAvailable - 2) / sizeofT, services.size());
227 if (maxServices <= 0) {
228 qCWarning(QT_BT_BLUEZ) << "services data does not fit into advertising data packet";
229 return;
230 }
231 const bool dataComplete = maxServices == services.size();
232 if (!dataComplete) {
233 qCWarning(QT_BT_BLUEZ) << "only" << maxServices << "out of" << services.size()
234 << "services fit into the advertising data";
235 }
236 data.data[data.length++] = 1 + maxServices * sizeofT;
237 data.data[data.length++] = servicesType<T>(dataComplete);
238 for (qsizetype i = 0; i < maxServices; ++i) {
239 memcpy(data.data + data.length, &services.at(i), sizeofT);
240 data.length += sizeofT;
241 }
242}
243
244void QLeAdvertiserBluez::setServicesData(const QLowEnergyAdvertisingData &src, AdvData &dest)
245{
246 QList<quint16> services16;
247 QList<quint32> services32;
248 QList<QUuid::Id128Bytes> services128;
249 const QList<QBluetoothUuid> services = src.services();
250 for (const QBluetoothUuid &service : services) {
251 bool ok;
252 const quint16 service16 = service.toUInt16(&ok);
253 if (ok) {
254 services16 << qToLittleEndian(service16);
255 continue;
256 }
257 const quint32 service32 = service.toUInt32(&ok);
258 if (ok) {
259 services32 << qToLittleEndian(service32);
260 continue;
261 }
262
263 // QUuid::toBytes() is defaults to Big-Endian
264 services128 << service.toBytes(QSysInfo::LittleEndian);
265 }
266 addServicesData(dest, services16);
267 addServicesData(dest, services32);
268 addServicesData(dest, services128);
269}
270
271void QLeAdvertiserBluez::setManufacturerData(const QLowEnergyAdvertisingData &src, AdvData &dest)
272{
274 return;
275
276 const QByteArray manufacturerData = src.manufacturerData();
277 if (dest.length >= sizeof dest.data - 1 - 1 - 2 - manufacturerData.size()) {
278 qCWarning(QT_BT_BLUEZ) << "manufacturer data does not fit into advertising data packet";
279 return;
280 }
281
282 dest.data[dest.length++] = manufacturerData.size() + 1 + 2;
283 dest.data[dest.length++] = 0xff;
284 putBtData(src.manufacturerId(), dest.data + dest.length);
285 dest.length += sizeof(quint16);
286 std::memcpy(dest.data + dest.length, manufacturerData.data(), manufacturerData.size());
287 dest.length += manufacturerData.size();
288}
289
290void QLeAdvertiserBluez::setLocalNameData(const QLowEnergyAdvertisingData &src, AdvData &dest)
291{
292 if (src.localName().isEmpty())
293 return;
294 if (dest.length >= sizeof dest.data - 3) {
295 qCWarning(QT_BT_BLUEZ) << "local name does not fit into advertising data";
296 return;
297 }
298
299 const QByteArray localNameUtf8 = src.localName().toUtf8();
300 const qsizetype fullSize = localNameUtf8.size() + 1 + 1;
301 const qsizetype size = (std::min)(fullSize, qsizetype(sizeof dest.data - dest.length));
302 const bool isComplete = size == fullSize;
303 dest.data[dest.length++] = size - 1;
304 const int dataType = isComplete ? 0x9 : 0x8;
305 dest.data[dest.length++] = dataType;
306 std::memcpy(dest.data + dest.length, localNameUtf8, size - 2);
307 dest.length += size - 2;
308}
309
310void QLeAdvertiserBluez::setData(bool isScanResponseData)
311{
312 // Spec v4.2, Vol 3, Part C, 11 and Supplement, Part 1
313 AdvData theData;
314 static_assert(sizeof theData == 32, "unexpected struct size");
315 theData.length = 0;
316
317 const QLowEnergyAdvertisingData &sourceData = isScanResponseData
318 ? scanResponseData() : advertisingData();
319
320 if (const QByteArray rawData = sourceData.rawData(); !rawData.isEmpty()) {
321 theData.length = (std::min)(qsizetype(sizeof theData.data), rawData.size());
322 std::memcpy(theData.data, rawData.data(), theData.length);
323 } else {
324 if (sourceData.includePowerLevel())
325 setPowerLevel(theData);
326 if (!isScanResponseData)
327 setFlags(theData);
328
329 // Insert new constant-length data here.
330
331 setLocalNameData(sourceData, theData);
332 setServicesData(sourceData, theData);
333 setManufacturerData(sourceData, theData);
334 }
335
336 std::memset(theData.data + theData.length, 0, sizeof theData.data - theData.length);
337 const QByteArray dataToSend = byteArrayFromStruct(theData);
338
339 if (!isScanResponseData) {
340 qCDebug(QT_BT_BLUEZ) << "advertising data:" << dataToSend.toHex();
341 queueCommand(QBluezConst::OcfLeSetAdvData, dataToSend);
342 } else if ((parameters().mode() == QLowEnergyAdvertisingParameters::AdvScanInd
344 && theData.length > 0) {
345 qCDebug(QT_BT_BLUEZ) << "scan response data:" << dataToSend.toHex();
346 queueCommand(QBluezConst::OcfLeSetScanResponseData, dataToSend);
347 }
348}
349
350void QLeAdvertiserBluez::setAdvertisingData()
351{
352 // Spec v4.2, Vol 2, Part E, 7.8.7
353 setData(false);
354}
355
356void QLeAdvertiserBluez::setScanResponseData()
357{
358 // Spec v4.2, Vol 2, Part E, 7.8.8
359 setData(true);
360}
361
362void QLeAdvertiserBluez::setWhiteList()
363{
364 // Spec v4.2, Vol 2, Part E, 7.8.15-16
366 return;
368 const QList<QLowEnergyAdvertisingParameters::AddressInfo> whiteListInfos
369 = parameters().whiteList();
370 for (const auto &addressInfo : whiteListInfos) {
371 WhiteListParams commandParam;
372 static_assert(sizeof commandParam == 7, "unexpected struct size");
373 commandParam.addrType = addressInfo.type;
374 convertAddress(addressInfo.address.toUInt64(), commandParam.addr.b);
375 queueCommand(QBluezConst::OcfLeAddToWhiteList, byteArrayFromStruct(commandParam));
376 }
377}
378
379void QLeAdvertiserBluez::handleCommandCompleted(quint16 opCode, quint8 status,
380 const QByteArray &data)
381{
382 if (m_pendingCommands.isEmpty())
383 return;
385 const Command currentCmd = m_pendingCommands.first();
386 if (currentCmd.ocf != ocf)
387 return; // Not one of our commands.
388 m_pendingCommands.takeFirst();
389 if (status != 0) {
390 qCDebug(QT_BT_BLUEZ) << "command" << ocf
391 << "failed with status" << (HciManager::HciError)status
392 << "status code" << status;
393 if (ocf == QBluezConst::OcfLeSetAdvEnable && status == 0xc && currentCmd.data == QByteArray(1, '\0')) {
394 // we ignore OcfLeSetAdvEnable if it tries to disable an active advertisement
395 // it seems the platform often automatically turns off advertisements
396 // subsequently the explicit stopAdvertisement call fails when re-issued
397 qCDebug(QT_BT_BLUEZ) << "Advertising disable failed, ignoring";
398 sendNextCommand();
399 return;
400 }
402 qCDebug(QT_BT_BLUEZ) << "reading power level failed, leaving it out of the "
403 "advertising data";
404 m_sendPowerLevel = false;
405 } else {
406 handleError();
407 return;
408 }
409 } else {
410 qCDebug(QT_BT_BLUEZ) << "command" << ocf << "executed successfully";
411 }
412
413 switch (ocf) {
415 if (m_sendPowerLevel) {
416 m_powerLevel = data.at(0);
417 qCDebug(QT_BT_BLUEZ) << "TX power level is" << m_powerLevel;
418 }
419 queueAdvertisingCommands();
420 break;
421 default:
422 break;
423 }
424
425 sendNextCommand();
426}
427
428void QLeAdvertiserBluez::handleError()
429{
430 m_pendingCommands.clear();
431 // TODO: Unmonitor event
432 emit errorOccurred();
433}
434
436
437#include "moc_qleadvertiser_bluez_p.cpp"
void putBtData(T src, void *dst)
#define ocfFromOpCode(op)
std::vector< ObjCStrongReference< CBMutableService > > services
void commandCompleted(quint16 opCode, quint8 status, const QByteArray &data)
\inmodule QtBluetooth
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
void doStartAdvertising() override
void doStopAdvertising() override
QLeAdvertiserBluez(const QLowEnergyAdvertisingParameters &params, const QLowEnergyAdvertisingData &advertisingData, const QLowEnergyAdvertisingData &scanResponseData, std::shared_ptr< HciManager > hciManager, QObject *parent=nullptr)
const QLowEnergyAdvertisingData & scanResponseData() const
const QLowEnergyAdvertisingParameters & parameters() const
const QLowEnergyAdvertisingData & advertisingData() const
bool isEmpty() const noexcept
Definition qlist.h:401
T & first()
Definition qlist.h:645
The QLowEnergyAdvertisingData class represents the data to be broadcast during Bluetooth Low Energy a...
bool includePowerLevel() const
Returns whether to include the device's transmit power level in the advertising data.
static quint16 invalidManufacturerId()
Returns an invalid manufacturer id.
The QLowEnergyAdvertisingParameters class represents the parameters used for Bluetooth Low Energy adv...
FilterPolicy filterPolicy() const
Returns the filter policy that determines how the white list is used.
Mode mode() const
Returns the advertising mode.
int minimumInterval() const
Returns the minimum advertising interval in milliseconds.
int maximumInterval() const
Returns the maximum advertising interval in milliseconds.
\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
@ LittleEndian
Definition qsysinfo.h:30
@ OcfLeSetScanResponseData
Combined button and popup list for selecting options.
Q_CORE_EXPORT QtJniTypes::Service service()
static void convertAddress(const quint64 from, quint8(&to)[6])
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
constexpr T qToLittleEndian(T source)
Definition qendian.h:176
quint8 servicesType< quint32 >(bool dataComplete)
static quint16 forceIntoRange(quint16 val, quint16 min, quint16 max)
struct AdvData __attribute__
quint8 servicesType< QUuid::Id128Bytes >(bool dataComplete)
quint8 filterPolicy
static quint8 servicesType(bool dataComplete)
static void addServicesData(AdvData &data, const QList< T > &services)
static QByteArray byteArrayFromStruct(const T &data)
quint8 servicesType< quint16 >(bool dataComplete)
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLenum mode
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum src
GLbitfield flags
GLboolean enable
void ** params
const GLubyte * c
GLuint GLfloat * val
GLuint GLenum GLsizei GLsizei GLint GLint GLboolean packed
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
unsigned int quint32
Definition qtypes.h:50
unsigned short quint16
Definition qtypes.h:48
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned char quint8
Definition qtypes.h:46
mimeData setData("text/csv", csvData)
myObject disconnect()
[26]
quint8 data[31]