7#include "bluez/bluez_data_p.h"
8#include "bluez/hcimanager_p.h"
11#include <QtCore/qloggingcategory.h>
17Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
19QLeAdvertiser::~QLeAdvertiser()
31}
__attribute__ ((packed));
47 return QByteArray(
reinterpret_cast<
const char *>(&data),
sizeof data);
51 const QLowEnergyAdvertisingData &advertisingData,
52 const QLowEnergyAdvertisingData &scanResponseData,
53 std::shared_ptr<HciManager> hciManager, QObject *parent)
54 :
QLeAdvertiser(params, advertisingData, scanResponseData, parent), m_hciManager(hciManager)
56 Q_ASSERT(m_hciManager);
57 connect(m_hciManager.get(), &HciManager::commandCompleted,
this,
58 &QLeAdvertiserBluez::handleCommandCompleted);
63 disconnect(m_hciManager.get(), &HciManager::commandCompleted,
this,
64 &QLeAdvertiserBluez::handleCommandCompleted);
70 if (!m_hciManager->monitorEvent(HciManager::HciEvent::EVT_CMD_COMPLETE)) {
75 m_sendPowerLevel = advertisingData().includePowerLevel()
76 || scanResponseData().includePowerLevel();
78 queueReadTxPowerLevelCommand();
80 queueAdvertisingCommands();
86 toggleAdvertising(
false);
92 m_pendingCommands << Command(ocf, data);
97 if (m_pendingCommands.isEmpty()) {
101 const Command &c = m_pendingCommands.first();
102 if (!m_hciManager->sendCommand(QBluezConst::OgfLinkControl, c.ocf, c.data)) {
110 toggleAdvertising(
false);
112 setAdvertisingParams();
113 setAdvertisingData();
114 setScanResponseData();
115 toggleAdvertising(
true);
121 queueCommand(QBluezConst::OcfLeReadTxPowerLevel, QByteArray());
127 queueCommand(QBluezConst::OcfLeSetAdvEnable, QByteArray(1, enable));
135 static_assert(
sizeof params == 15,
"unexpected struct size");
137 memset(¶ms, 0,
sizeof params);
138 setAdvertisingInterval(params);
139 params.type = parameters().mode();
140 params.filterPolicy = parameters().filterPolicy();
141 if (params.filterPolicy != QLowEnergyAdvertisingParameters::IgnoreWhiteList
142 && advertisingData().discoverability() == QLowEnergyAdvertisingData::DiscoverabilityLimited) {
143 qCWarning(QT_BT_BLUEZ) <<
"limited discoverability is incompatible with "
144 "using a white list; disabling filtering";
145 params.filterPolicy = QLowEnergyAdvertisingParameters::IgnoreWhiteList;
147 params.ownAddrType = QLowEnergyController::PublicAddress;
153 params.channelMap = 0x7;
155 const QByteArray paramsData = byteArrayFromStruct(params);
156 qCDebug(QT_BT_BLUEZ) <<
"advertising parameters:" << paramsData.toHex();
157 queueCommand(QBluezConst::OcfLeSetAdvParams, paramsData);
162 return qMin(qMax(val, min), max);
167 const double multiplier = 0.625;
168 const quint16 minVal = parameters().minimumInterval() / multiplier;
169 const quint16 maxVal = parameters().maximumInterval() / multiplier;
170 Q_ASSERT(minVal <= maxVal);
171 const quint16 specMinimum =
172 parameters().mode() == QLowEnergyAdvertisingParameters::AdvScanInd
173 || parameters().mode() == QLowEnergyAdvertisingParameters::AdvNonConnInd ? 0xa0 : 0x20;
174 const quint16 specMaximum = 0x4000;
175 params.minInterval = qToLittleEndian(forceIntoRange(minVal, specMinimum, specMaximum));
176 params.maxInterval = qToLittleEndian(forceIntoRange(maxVal, specMinimum, specMaximum));
177 Q_ASSERT(params.minInterval <= params.maxInterval);
182 if (m_sendPowerLevel) {
183 advData.data[advData.length++] = 2;
184 advData.data[advData.length++]= 0xa;
185 advData.data[advData.length++] = m_powerLevel;
193 if (advertisingData().discoverability() == QLowEnergyAdvertisingData::DiscoverabilityLimited)
195 else if (advertisingData().discoverability() == QLowEnergyAdvertisingData::DiscoverabilityGeneral)
199 advData.data[advData.length++] = 2;
200 advData.data[advData.length++] = 0x1;
201 advData.data[advData.length++] = flags;
206template<> quint8 servicesType<quint16>(
bool dataComplete)
208 return dataComplete ? 0x3 : 0x2;
210template<> quint8 servicesType<quint32>(
bool dataComplete)
212 return dataComplete ? 0x5 : 0x4;
214template<> quint8 servicesType<QUuid::Id128Bytes>(
bool dataComplete)
216 return dataComplete ? 0x7 : 0x6;
222 if (services.isEmpty())
224 constexpr auto sizeofT =
static_cast<
int>(
sizeof(T));
225 const qsizetype spaceAvailable =
sizeof data.data - data.length;
227 const qsizetype maxServices = (std::min)((spaceAvailable - 2) / sizeofT, services.size());
228 if (maxServices <= 0) {
229 qCWarning(QT_BT_BLUEZ) <<
"services data does not fit into advertising data packet";
232 const bool dataComplete = maxServices == services.size();
234 qCWarning(QT_BT_BLUEZ) <<
"only" << maxServices <<
"out of" << services.size()
235 <<
"services fit into the advertising data";
237 data.data[data.length++] = 1 + maxServices * sizeofT;
238 data.data[data.length++] = servicesType<T>(dataComplete);
239 for (qsizetype i = 0; i < maxServices; ++i) {
240 memcpy(data.data + data.length, &services.at(i), sizeofT);
241 data.length += sizeofT;
247 QList<quint16> services16;
248 QList<quint32> services32;
249 QList<QUuid::Id128Bytes> services128;
250 const QList<QBluetoothUuid> services = src.services();
251 for (
const QBluetoothUuid &service : services) {
253 const quint16 service16 = service.toUInt16(&ok);
255 services16 << qToLittleEndian(service16);
258 const quint32 service32 = service.toUInt32(&ok);
260 services32 << qToLittleEndian(service32);
265 services128 << service.toBytes(QSysInfo::LittleEndian);
267 addServicesData(dest, services16);
268 addServicesData(dest, services32);
269 addServicesData(dest, services128);
274 if (src.manufacturerId() == QLowEnergyAdvertisingData::invalidManufacturerId())
277 const QByteArray manufacturerData = src.manufacturerData();
278 if (dest.length >=
sizeof dest.data - 1 - 1 - 2 - manufacturerData.size()) {
279 qCWarning(QT_BT_BLUEZ) <<
"manufacturer data does not fit into advertising data packet";
283 dest.data[dest.length++] = manufacturerData.size() + 1 + 2;
284 dest.data[dest.length++] = 0xff;
285 putBtData(src.manufacturerId(), dest.data + dest.length);
286 dest.length +=
sizeof(quint16);
287 std::memcpy(dest.data + dest.length, manufacturerData.data(), manufacturerData.size());
288 dest.length += manufacturerData.size();
293 if (src.localName().isEmpty())
295 if (dest.length >=
sizeof dest.data - 3) {
296 qCWarning(QT_BT_BLUEZ) <<
"local name does not fit into advertising data";
300 const QByteArray localNameUtf8 = src.localName().toUtf8();
301 const qsizetype fullSize = localNameUtf8.size() + 1 + 1;
302 const qsizetype size = (std::min)(fullSize, qsizetype(
sizeof dest.data - dest.length));
303 const bool isComplete = size == fullSize;
304 dest.data[dest.length++] = size - 1;
305 const int dataType = isComplete ? 0x9 : 0x8;
306 dest.data[dest.length++] = dataType;
307 std::memcpy(dest.data + dest.length, localNameUtf8, size - 2);
308 dest.length += size - 2;
315 static_assert(
sizeof theData == 32,
"unexpected struct size");
318 const QLowEnergyAdvertisingData &sourceData = isScanResponseData
319 ? scanResponseData() : advertisingData();
321 if (
const QByteArray rawData = sourceData.rawData(); !rawData.isEmpty()) {
322 theData.length = (std::min)(qsizetype(
sizeof theData.data), rawData.size());
323 std::memcpy(theData.data, rawData.data(), theData.length);
325 if (sourceData.includePowerLevel())
326 setPowerLevel(theData);
327 if (!isScanResponseData)
332 setLocalNameData(sourceData, theData);
333 setServicesData(sourceData, theData);
334 setManufacturerData(sourceData, theData);
337 std::memset(theData.data + theData.length, 0,
sizeof theData.data - theData.length);
338 const QByteArray dataToSend = byteArrayFromStruct(theData);
340 if (!isScanResponseData) {
341 qCDebug(QT_BT_BLUEZ) <<
"advertising data:" << dataToSend.toHex();
342 queueCommand(QBluezConst::OcfLeSetAdvData, dataToSend);
343 }
else if ((parameters().mode() == QLowEnergyAdvertisingParameters::AdvScanInd
344 || parameters().mode() == QLowEnergyAdvertisingParameters::AdvInd)
345 && theData.length > 0) {
346 qCDebug(QT_BT_BLUEZ) <<
"scan response data:" << dataToSend.toHex();
347 queueCommand(QBluezConst::OcfLeSetScanResponseData, dataToSend);
366 if (parameters().filterPolicy() == QLowEnergyAdvertisingParameters::IgnoreWhiteList)
368 queueCommand(QBluezConst::OcfLeClearWhiteList, QByteArray());
369 const QList<QLowEnergyAdvertisingParameters::AddressInfo> whiteListInfos
370 = parameters().whiteList();
371 for (
const auto &addressInfo : whiteListInfos) {
372 WhiteListParams commandParam;
373 static_assert(
sizeof commandParam == 7,
"unexpected struct size");
374 commandParam.addrType = addressInfo.type;
375 convertAddress(addressInfo.address.toUInt64(), commandParam.addr.b);
376 queueCommand(QBluezConst::OcfLeAddToWhiteList, byteArrayFromStruct(commandParam));
381 const QByteArray &data)
383 if (m_pendingCommands.isEmpty())
386 const Command currentCmd = m_pendingCommands.first();
387 if (currentCmd.ocf != ocf)
389 m_pendingCommands.takeFirst();
391 qCDebug(QT_BT_BLUEZ) <<
"command" << ocf
392 <<
"failed with status" << (HciManager::HciError)status
393 <<
"status code" << status;
398 qCDebug(QT_BT_BLUEZ) <<
"Advertising disable failed, ignoring";
403 qCDebug(QT_BT_BLUEZ) <<
"reading power level failed, leaving it out of the "
405 m_sendPowerLevel =
false;
411 qCDebug(QT_BT_BLUEZ) <<
"command" << ocf <<
"executed successfully";
416 if (m_sendPowerLevel) {
418 m_powerLevel = data.at(0);
421 qCDebug(QT_BT_BLUEZ) <<
"TX power level is" << m_powerLevel;
423 queueAdvertisingCommands();
434 m_pendingCommands.clear();
436 emit errorOccurred();
441#include "moc_qleadvertiser_bluez_p.cpp"
#define ocfFromOpCode(op)
void doStartAdvertising() override
void doStopAdvertising() override
~QLeAdvertiserBluez() override
static quint16 forceIntoRange(quint16 val, quint16 min, quint16 max)
static quint8 servicesType(bool dataComplete)
static void addServicesData(AdvData &data, const QList< T > &services)
static QByteArray byteArrayFromStruct(const T &data)