diff options
author | Christian Kandeler <christian.kandeler@theqtcompany.com> | 2015-10-14 16:54:47 +0200 |
---|---|---|
committer | Alex Blasche <alexander.blasche@theqtcompany.com> | 2015-11-17 15:40:02 +0000 |
commit | a5f362af452555b5aaa4585be82053029e4b25c0 (patch) | |
tree | eae7edea0537c8fe55226628fc3d5618741cf04f /src | |
parent | eb59027d32c7904a129b16c786df1dc2097ab9c9 (diff) |
Bluetooth: Introduce API for LE advertising.
And provide an implementation for BlueZ.
Change-Id: I302aee7c43b77016d9e1e7a0d5bcbf00096abf76
Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/bluetooth/bluetooth.pro | 8 | ||||
-rw-r--r-- | src/bluetooth/bluez/bluez_data_p.h | 32 | ||||
-rw-r--r-- | src/bluetooth/bluez/hcimanager.cpp | 46 | ||||
-rw-r--r-- | src/bluetooth/bluez/hcimanager_p.h | 4 | ||||
-rw-r--r-- | src/bluetooth/qleadvertiser_bluez.cpp | 443 | ||||
-rw-r--r-- | src/bluetooth/qleadvertiser_p.h | 137 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyadvertisingdata.cpp | 280 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyadvertisingdata.h | 99 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyadvertisingparameters.cpp | 259 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyadvertisingparameters.h | 111 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller.cpp | 176 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller.h | 27 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_android.cpp | 15 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_bluez.cpp | 31 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_osx.mm | 45 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_osx_p.h | 2 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_p.cpp | 10 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_p.h | 9 |
18 files changed, 1713 insertions, 21 deletions
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index e37ad7aa..99067c74 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -27,6 +27,8 @@ PUBLIC_HEADERS += \ qlowenergycharacteristic.h \ qlowenergydescriptor.h \ qbluetoothtransferreply.h \ + qlowenergyadvertisingdata.h \ + qlowenergyadvertisingparameters.h \ qlowenergycontroller.h PRIVATE_HEADERS += \ @@ -43,7 +45,8 @@ PRIVATE_HEADERS += \ qprivatelinearbuffer_p.h \ qbluetoothlocaldevice_p.h \ qlowenergycontroller_p.h \ - qlowenergyserviceprivate_p.h + qlowenergyserviceprivate_p.h \ + qleadvertiser_p.h \ SOURCES += \ qbluetoothaddress.cpp\ @@ -60,6 +63,8 @@ SOURCES += \ qbluetoothtransfermanager.cpp \ qbluetoothtransferrequest.cpp \ qbluetoothtransferreply.cpp \ + qlowenergyadvertisingdata.cpp \ + qlowenergyadvertisingparameters.cpp \ qlowenergyservice.cpp \ qlowenergycharacteristic.cpp \ qlowenergydescriptor.cpp \ @@ -88,6 +93,7 @@ config_bluez:qtHaveModule(dbus) { # old versions of Bluez do not have the required BTLE symbols config_bluez_le { SOURCES += \ + qleadvertiser_bluez.cpp \ qlowenergycontroller_bluez.cpp } else { message("Bluez version is too old to support Bluetooth Low Energy.") diff --git a/src/bluetooth/bluez/bluez_data_p.h b/src/bluetooth/bluez/bluez_data_p.h index 5d6a90ef..456a9374 100644 --- a/src/bluetooth/bluez/bluez_data_p.h +++ b/src/bluetooth/bluez/bluez_data_p.h @@ -217,12 +217,14 @@ template<> inline void putBtData(quint128 src, void *dst) #define HCI_FILTER 2 // HCI packet types +#define HCI_COMMAND_PKT 0x01 #define HCI_EVENT_PKT 0x04 #define HCI_VENDOR_PKT 0xff #define HCI_FLT_TYPE_BITS 31 #define HCI_FLT_EVENT_BITS 63 + struct sockaddr_hci { sa_family_t hci_family; unsigned short hci_dev; @@ -347,6 +349,36 @@ typedef struct { } __attribute__ ((packed)) evt_encrypt_change; #define EVT_ENCRYPT_CHANGE_SIZE 4 +#define EVT_CMD_COMPLETE 0x0E +struct evt_cmd_complete { + quint8 ncmd; + quint16 opcode; +} __attribute__ ((packed)); + +struct hci_command_hdr { + quint16 opcode; /* OCF & OGF */ + quint8 plen; +} __attribute__ ((packed)); + +enum OpCodeGroupField { + OgfLinkControl = 0x8, +}; + +enum OpCodeCommandField { + OcfLeSetAdvParams = 0x6, + OcfLeReadTxPowerLevel = 0x7, + OcfLeSetAdvData = 0x8, + OcfLeSetScanResponseData = 0x9, + OcfLeSetAdvEnable = 0xa, + OcfLeClearWhiteList = 0x10, + OcfLeAddToWhiteList = 0x11, +}; + +/* Command opcode pack/unpack */ +#define opCodePack(ogf, ocf) (quint16(((ocf) & 0x03ff)|((ogf) << 10))) +#define ogfFromOpCode(op) ((op) >> 10) +#define ocfFromOpCode(op) ((op) & 0x03ff) + QT_END_NAMESPACE #endif // BLUEZ_DATA_P_H diff --git a/src/bluetooth/bluez/hcimanager.cpp b/src/bluetooth/bluez/hcimanager.cpp index 30511ae5..388f3e0c 100644 --- a/src/bluetooth/bluez/hcimanager.cpp +++ b/src/bluetooth/bluez/hcimanager.cpp @@ -36,12 +36,14 @@ #include "qbluetoothsocket_p.h" -#include <QtCore/QLoggingCategory> +#include <QtCore/qloggingcategory.h> +#include <cstring> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> +#include <sys/uio.h> #include <unistd.h> #define HCIGETCONNLIST _IOR('H', 212, int) @@ -174,6 +176,36 @@ bool HciManager::monitorEvent(HciManager::HciEvent event) return true; } +bool HciManager::sendCommand(OpCodeGroupField ogf, OpCodeCommandField ocf, const QByteArray ¶meters) +{ + qCDebug(QT_BT_BLUEZ) << "sending command; ogf:" << ogf << "ocf:" << ocf; + quint8 packetType = HCI_COMMAND_PKT; + hci_command_hdr command = { + opCodePack(ogf, ocf), + static_cast<uint8_t>(parameters.count()) + }; + static_assert(sizeof command == 3, "unexpected struct size"); + struct iovec iv[3]; + iv[0].iov_base = &packetType; + iv[0].iov_len = 1; + iv[1].iov_base = &command; + iv[1].iov_len = sizeof command; + int ivn = 2; + if (!parameters.isEmpty()) { + iv[2].iov_base = const_cast<char *>(parameters.constData()); // const_cast is safe, since iov_base will not get modified. + iv[2].iov_len = parameters.count(); + ++ivn; + } + while (writev(hciSocket, iv, ivn) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + qCDebug(QT_BT_BLUEZ()) << "hci command failure:" << strerror(errno); + return false; + } + qCDebug(QT_BT_BLUEZ) << "command sent successfully"; + return true; +} + /* * Unsubscribe from all events */ @@ -275,6 +307,18 @@ void HciManager::_q_readNotify() emit encryptionChangedEvent(remoteDevice, event->status == 0); } break; + case EVT_CMD_COMPLETE: { + auto * const event = reinterpret_cast<const evt_cmd_complete *>(data); + static_assert(sizeof *event == 3, "unexpected struct size"); + + // There is always a status byte right after the generic structure. + Q_ASSERT(size > static_cast<int>(sizeof *event)); + const quint8 status = data[sizeof *event]; + const auto additionalData = QByteArray(reinterpret_cast<const char *>(data) + + sizeof *event + 1, size - sizeof *event - 1); + emit commandCompleted(event->opcode, status, additionalData); + } + break; default: break; } diff --git a/src/bluetooth/bluez/hcimanager_p.h b/src/bluetooth/bluez/hcimanager_p.h index 9dd2ceee..c8f2fe56 100644 --- a/src/bluetooth/bluez/hcimanager_p.h +++ b/src/bluetooth/bluez/hcimanager_p.h @@ -59,6 +59,7 @@ class HciManager : public QObject public: enum HciEvent { EncryptChangeEvent = EVT_ENCRYPT_CHANGE, + CommandCompleteEvent = EVT_CMD_COMPLETE, }; explicit HciManager(const QBluetoothAddress &deviceAdapter, QObject *parent = 0); @@ -66,12 +67,13 @@ public: bool isValid() const; bool monitorEvent(HciManager::HciEvent event); + bool sendCommand(OpCodeGroupField ogf, OpCodeCommandField ocf, const QByteArray ¶meters); void stopEvents(); QBluetoothAddress addressForConnectionHandle(quint16 handle) const; - signals: void encryptionChangedEvent(const QBluetoothAddress &address, bool wasSuccess); + void commandCompleted(quint16 opCode, quint8 status, const QByteArray &data); private slots: void _q_readNotify(); diff --git a/src/bluetooth/qleadvertiser_bluez.cpp b/src/bluetooth/qleadvertiser_bluez.cpp new file mode 100644 index 00000000..4c231dca --- /dev/null +++ b/src/bluetooth/qleadvertiser_bluez.cpp @@ -0,0 +1,443 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qleadvertiser_p.h" + +#include "bluez/bluez_data_p.h" +#include "bluez/hcimanager_p.h" +#include "qbluetoothsocket_p.h" + +#include <QtCore/qloggingcategory.h> + +#include <cstring> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) + +struct AdvParams { + quint16 minInterval; + quint16 maxInterval; + quint8 type; + quint8 ownAddrType; + quint8 directAddrType; + bdaddr_t directAddr; + quint8 channelMap; + quint8 filterPolicy; +} __attribute__ ((packed)); + +struct AdvData { + quint8 length; + quint8 data[31]; +}; + +struct WhiteListParams { + quint8 addrType; + bdaddr_t addr; +}; + + +template<typename T> QByteArray byteArrayFromStruct(const T &data, int maxSize = -1) +{ + return QByteArray(reinterpret_cast<const char *>(&data), maxSize != -1 ? maxSize : sizeof data); +} + +QLeAdvertiserBluez::QLeAdvertiserBluez(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData, + HciManager &hciManager, QObject *parent) + : QLeAdvertiser(params, advertisingData, scanResponseData, parent), m_hciManager(hciManager) +{ + connect(&m_hciManager, &HciManager::commandCompleted, this, + &QLeAdvertiserBluez::handleCommandCompleted); +} + +QLeAdvertiserBluez::~QLeAdvertiserBluez() +{ + disconnect(&m_hciManager, &HciManager::commandCompleted, this, + &QLeAdvertiserBluez::handleCommandCompleted); + doStopAdvertising(); +} + +void QLeAdvertiserBluez::doStartAdvertising() +{ + if (!m_hciManager.monitorEvent(HciManager::CommandCompleteEvent)) { + handleError(); + return; + } + + m_disableCommandFinished = false; + m_sendPowerLevel = advertisingData().includePowerLevel() + || scanResponseData().includePowerLevel(); + if (m_sendPowerLevel) + queueReadTxPowerLevelCommand(); + else + queueAdvertisingCommands(); + sendNextCommand(); +} + +void QLeAdvertiserBluez::doStopAdvertising() +{ + toggleAdvertising(false); +} + +void QLeAdvertiserBluez::queueCommand(OpCodeCommandField ocf, const QByteArray &data) +{ + m_pendingCommands << Command(ocf, data); +} + +void QLeAdvertiserBluez::sendNextCommand() +{ + if (m_pendingCommands.isEmpty()) { + // TODO: Unmonitor event. + return; + } + const Command &c = m_pendingCommands.first(); + if (!m_hciManager.sendCommand(OgfLinkControl, c.ocf, c.data)) { + handleError(); + return; + } +} + +void QLeAdvertiserBluez::queueAdvertisingCommands() +{ + toggleAdvertising(false); // Stop advertising first, in case it's currently active. + setWhiteList(); + setAdvertisingParams(); + setAdvertisingData(); + setScanResponseData(); + toggleAdvertising(true); +} + +void QLeAdvertiserBluez::queueReadTxPowerLevelCommand() +{ + // Spec v4.2, Vol 2, Part E, 7.8.6 + queueCommand(OcfLeReadTxPowerLevel, QByteArray()); +} + +void QLeAdvertiserBluez::toggleAdvertising(bool enable) +{ + // Spec v4.2, Vol 2, Part E, 7.8.9 + queueCommand(OcfLeSetAdvEnable, QByteArray(1, enable)); +} + +void QLeAdvertiserBluez::setAdvertisingParams() +{ + // Spec v4.2, Vol 2, Part E, 7.8.5 + AdvParams params; + static_assert(sizeof params == 15, "unexpected struct size"); + setAdvertisingInterval(params); + params.type = parameters().mode(); + params.filterPolicy = parameters().filterPolicy(); + if (params.filterPolicy != QLowEnergyAdvertisingParameters::IgnoreWhiteList + && advertisingData().discoverability() == QLowEnergyAdvertisingData::DiscoverabilityLimited) { + qCWarning(QT_BT_BLUEZ) << "limited discoverability is incompatible with " + "using a white list; disabling filtering"; + params.filterPolicy = QLowEnergyAdvertisingParameters::IgnoreWhiteList; + } + params.ownAddrType = QLowEnergyController::PublicAddress; // TODO: Make configurable. + + // TODO: For ADV_DIRECT_IND. + // params.directAddrType = xxx; + // params.direct_bdaddr = xxx; + + params.channelMap = 0x7; // All channels. + + const QByteArray paramsData = byteArrayFromStruct(params); + qCDebug(QT_BT_BLUEZ) << "advertising parameters:" << paramsData.toHex(); + queueCommand(OcfLeSetAdvParams, paramsData); +} + +static quint16 forceIntoRange(quint16 val, quint16 min, quint16 max) +{ + return qMin(qMax(val, min), max); +} + +void QLeAdvertiserBluez::setAdvertisingInterval(AdvParams ¶ms) +{ + const double multiplier = 0.625; + const quint16 minVal = parameters().minimumInterval() / multiplier; + const quint16 maxVal = parameters().maximumInterval() / multiplier; + Q_ASSERT(minVal <= maxVal); + const quint16 specMinimum = + parameters().mode() == QLowEnergyAdvertisingParameters::AdvScanInd + || parameters().mode() == QLowEnergyAdvertisingParameters::AdvNonConnInd ? 0xa0 : 0x20; + const quint16 specMaximum = 0x4000; + params.minInterval = forceIntoRange(minVal, specMinimum, specMaximum); + params.maxInterval = forceIntoRange(maxVal, specMinimum, specMaximum); + Q_ASSERT(params.minInterval <= params.maxInterval); +} + +void QLeAdvertiserBluez::setPowerLevel(AdvData &advData) +{ + if (m_sendPowerLevel) { + advData.data[advData.length++] = 2; + advData.data[advData.length++]= 0xa; + advData.data[advData.length++] = m_powerLevel; + } +} + +void QLeAdvertiserBluez::setFlags(AdvData &advData) +{ + // TODO: Discoverability flags are incompatible with ADV_DIRECT_IND + quint8 flags = 0; + if (advertisingData().discoverability() == QLowEnergyAdvertisingData::DiscoverabilityLimited) + flags |= 0x1; + else if (advertisingData().discoverability() == QLowEnergyAdvertisingData::DiscoverabilityLimited) + flags |= 0x2; + if (flags) { + advData.data[advData.length++] = 2; + advData.data[advData.length++] = 0x1; + advData.data[advData.length++] = flags; + } +} + +template<typename T> static quint8 servicesType(bool dataComplete); +template<> quint8 servicesType<quint16>(bool dataComplete) +{ + return dataComplete ? 0x3 : 0x2; +} +template<> quint8 servicesType<quint32>(bool dataComplete) +{ + return dataComplete ? 0x5 : 0x4; +} +template<> quint8 servicesType<quint128>(bool dataComplete) +{ + return dataComplete ? 0x7 : 0x6; +} + +template<typename T> static void addServicesData(AdvData &data, const QVector<T> &services) +{ + if (services.isEmpty()) + return; + const int spaceAvailable = sizeof data.data - data.length; + const int maxServices = qMin<int>((spaceAvailable - 2) / sizeof(T), services.count()); + if (maxServices == 0) { + qCWarning(QT_BT_BLUEZ) << "services data does not fit into advertising data packet"; + return; + } + const bool dataComplete = maxServices == services.count(); + if (!dataComplete) { + qCWarning(QT_BT_BLUEZ) << "only" << maxServices << "out of" << services.count() + << "services fit into the advertising data"; + } + data.data[data.length++] = 1 + maxServices * sizeof(T); + data.data[data.length++] = servicesType<T>(dataComplete); + for (int i = 0; i < maxServices; ++i) { + putBtData(services.at(i), data.data + data.length); + data.length += sizeof(T); + } +} + +void QLeAdvertiserBluez::setServicesData(const QLowEnergyAdvertisingData &src, AdvData &dest) +{ + QVector<quint16> services16; + QVector<quint32> services32; + QVector<quint128> services128; + foreach (const QBluetoothUuid &service, src.services()) { + bool ok; + const quint16 service16 = service.toUInt16(&ok); + if (ok) { + services16 << service16; + continue; + } + const quint32 service32 = service.toUInt32(&ok); + if (ok) { + services32 << service32; + continue; + } + services128 << service.toUInt128(); + } + addServicesData(dest, services16); + addServicesData(dest, services32); + addServicesData(dest, services128); +} + +void QLeAdvertiserBluez::setManufacturerData(const QLowEnergyAdvertisingData &src, AdvData &dest) +{ + if (src.manufacturerId() == QLowEnergyAdvertisingData::invalidManufacturerId()) + return; + if (dest.length >= sizeof dest.data - 1 - 1 - 2 - src.manufacturerData().count()) { + qCWarning(QT_BT_BLUEZ) << "manufacturer data does not fit into advertising data packet"; + return; + } + + dest.data[dest.length++] = src.manufacturerData().count() + 1 + 2; + dest.data[dest.length++] = 0xff; + putBtData(src.manufacturerId(), dest.data + dest.length); + dest.length += sizeof(quint16); + std::memcpy(dest.data + dest.length, src.manufacturerData(), src.manufacturerData().count()); + dest.length += src.manufacturerData().count(); +} + +void QLeAdvertiserBluez::setLocalNameData(const QLowEnergyAdvertisingData &src, AdvData &dest) +{ + if (src.localName().isEmpty()) + return; + if (dest.length >= sizeof dest.data - 3) { + qCWarning(QT_BT_BLUEZ) << "local name does not fit into advertising data"; + return; + } + + const QByteArray localNameUtf8 = src.localName().toUtf8(); + const int fullSize = localNameUtf8.count() + 1 + 1; + const int size = qMin<int>(fullSize, sizeof dest.data - dest.length); + const bool isComplete = size == fullSize; + dest.data[dest.length++] = size - 1; + const int dataType = isComplete ? 0x9 : 0x8; + dest.data[dest.length++] = dataType; + std::memcpy(dest.data + dest.length, localNameUtf8, size - 2); + dest.length += size - 2; +} + +void QLeAdvertiserBluez::setData(bool isScanResponseData) +{ + // Spec v4.2, Vol 3, Part C, 11 and Supplement, Part 1 + AdvData theData; + static_assert(sizeof theData == 32, "unexpected struct size"); + theData.length = 0; + + const QLowEnergyAdvertisingData &sourceData = isScanResponseData + ? scanResponseData() : advertisingData(); + + if (!sourceData.rawData().isEmpty()) { + theData.length = qMin<int>(sizeof theData.data, sourceData.rawData().count()); + std::memcpy(theData.data, sourceData.rawData().constData(), theData.length); + } else { + if (sourceData.includePowerLevel()) + setPowerLevel(theData); + if (!isScanResponseData) + setFlags(theData); + + // Insert new constant-length data here. + + setLocalNameData(sourceData, theData); + setServicesData(sourceData, theData); + setManufacturerData(sourceData, theData); + } + + const QByteArray dataToSend = byteArrayFromStruct(theData, 1 + theData.length); + if (!isScanResponseData) { + qCDebug(QT_BT_BLUEZ) << "advertising data:" << dataToSend.toHex(); + queueCommand(OcfLeSetAdvData, dataToSend); + } else if ((parameters().mode() == QLowEnergyAdvertisingParameters::AdvScanInd + || parameters().mode() == QLowEnergyAdvertisingParameters::AdvInd) + && theData.length > 0) { + qCDebug(QT_BT_BLUEZ) << "scan response data:" << dataToSend.toHex(); + queueCommand(OcfLeSetScanResponseData, dataToSend); + } +} + +void QLeAdvertiserBluez::setAdvertisingData() +{ + // Spec v4.2, Vol 2, Part E, 7.8.7 + setData(false); +} + +void QLeAdvertiserBluez::setScanResponseData() +{ + // Spec v4.2, Vol 2, Part E, 7.8.8 + setData(true); +} + +void QLeAdvertiserBluez::setWhiteList() +{ + // Spec v4.2, Vol 2, Part E, 7.8.15-16 + if (parameters().filterPolicy() == QLowEnergyAdvertisingParameters::IgnoreWhiteList) + return; + queueCommand(OcfLeClearWhiteList, QByteArray()); + foreach (const auto &addressInfo, parameters().whiteList()) { + WhiteListParams commandParam; + static_assert(sizeof commandParam == 7, "unexpected struct size"); + commandParam.addrType = addressInfo.type; + convertAddress(addressInfo.address.toUInt64(), commandParam.addr.b); + queueCommand(OcfLeAddToWhiteList, byteArrayFromStruct(commandParam)); + } +} + +void QLeAdvertiserBluez::handleCommandCompleted(quint16 opCode, quint8 status, + const QByteArray &data) +{ + if (m_pendingCommands.isEmpty()) + return; + const quint16 ocf = ocfFromOpCode(opCode); + if (m_pendingCommands.first().ocf != ocf) + return; // Not one of our commands. + m_pendingCommands.takeFirst(); + if (status != 0) { + qCDebug(QT_BT_BLUEZ) << "command" << ocf << "failed with status" << status; + if (ocf == OcfLeSetAdvEnable && !m_disableCommandFinished && status == 0xc) { + qCDebug(QT_BT_BLUEZ) << "initial advertising disable failed, ignoring"; + m_disableCommandFinished = true; + sendNextCommand(); + return; + } + if (ocf == OcfLeReadTxPowerLevel) { + qCDebug(QT_BT_BLUEZ) << "reading power level failed, leaving it out of the " + "advertising data"; + m_sendPowerLevel = false; + } else { + handleError(); + return; + } + } else { + qCDebug(QT_BT_BLUEZ) << "command" << ocf << "executed successfully"; + } + + switch (ocf) { + case OcfLeReadTxPowerLevel: + if (m_sendPowerLevel) { + m_powerLevel = data.at(0); + qCDebug(QT_BT_BLUEZ) << "TX power level is" << m_powerLevel; + } + queueAdvertisingCommands(); + break; + case OcfLeSetAdvEnable: + if (!m_disableCommandFinished) + m_disableCommandFinished = true; + break; + default: + break; + } + + sendNextCommand(); +} + +void QLeAdvertiserBluez::handleError() +{ + m_pendingCommands.clear(); + // TODO: Unmonitor event + emit errorOccurred(); +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qleadvertiser_p.h b/src/bluetooth/qleadvertiser_p.h new file mode 100644 index 00000000..bdcfcf1c --- /dev/null +++ b/src/bluetooth/qleadvertiser_p.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLEADVERTISER_P_H +#define QLEADVERTISER_P_H + +#include "qlowenergyadvertisingdata.h" +#include "qlowenergyadvertisingparameters.h" + +#ifdef QT_BLUEZ_BLUETOOTH +#include "bluez/bluez_data_p.h" +#endif + +#include <QtCore/qobject.h> +#include <QtCore/qvector.h> + +QT_BEGIN_NAMESPACE + +class QLeAdvertiser : public QObject +{ + Q_OBJECT +public: + void startAdvertising() { doStartAdvertising(); } + void stopAdvertising() { doStopAdvertising(); } + +signals: + void errorOccurred(); + +protected: + QLeAdvertiser(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advData, + const QLowEnergyAdvertisingData &responseData, QObject *parent) + : QObject(parent), m_params(params), m_advData(advData), m_responseData(responseData) {} + virtual ~QLeAdvertiser() { } + + const QLowEnergyAdvertisingParameters ¶meters() const { return m_params; } + const QLowEnergyAdvertisingData &advertisingData() const { return m_advData; } + const QLowEnergyAdvertisingData &scanResponseData() const { return m_responseData; } + +private: + virtual void doStartAdvertising() = 0; + virtual void doStopAdvertising() = 0; + + const QLowEnergyAdvertisingParameters m_params; + const QLowEnergyAdvertisingData m_advData; + const QLowEnergyAdvertisingData m_responseData; +}; + + +#ifdef QT_BLUEZ_BLUETOOTH +struct AdvData; +struct AdvParams; +class HciManager; + +class QLeAdvertiserBluez : public QLeAdvertiser +{ +public: + QLeAdvertiserBluez(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData, HciManager &hciManager, + QObject *parent = nullptr); + ~QLeAdvertiserBluez(); + +private: + void doStartAdvertising() override; + void doStopAdvertising() override; + + void setPowerLevel(AdvData &advData); + void setFlags(AdvData &advData); + void setServicesData(const QLowEnergyAdvertisingData &src, AdvData &dest); + void setManufacturerData(const QLowEnergyAdvertisingData &src, AdvData &dest); + void setLocalNameData(const QLowEnergyAdvertisingData &src, AdvData &dest); + + void queueCommand(OpCodeCommandField ocf, const QByteArray &advertisingData); + void sendNextCommand(); + void queueAdvertisingCommands(); + void queueReadTxPowerLevelCommand(); + void toggleAdvertising(bool enable); + void setAdvertisingParams(); + void setAdvertisingInterval(AdvParams ¶ms); + void setData(bool isScanResponseData); + void setAdvertisingData(); + void setScanResponseData(); + void setWhiteList(); + + void handleCommandCompleted(quint16 opCode, quint8 status, const QByteArray &advertisingData); + void handleError(); + + HciManager &m_hciManager; + + struct Command { + Command() {} + Command(OpCodeCommandField ocf, const QByteArray &data) : ocf(ocf), data(data) { } + OpCodeCommandField ocf; + QByteArray data; + }; + QVector<Command> m_pendingCommands; + + quint8 m_powerLevel; + bool m_sendPowerLevel; + bool m_disableCommandFinished; +}; +#endif // QT_BLUEZ_BLUETOOTH + +QT_END_NAMESPACE + +#endif // Include guard. diff --git a/src/bluetooth/qlowenergyadvertisingdata.cpp b/src/bluetooth/qlowenergyadvertisingdata.cpp new file mode 100644 index 00000000..e68a1b35 --- /dev/null +++ b/src/bluetooth/qlowenergyadvertisingdata.cpp @@ -0,0 +1,280 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlowenergyadvertisingdata.h" + +#include <cstring> + +QT_BEGIN_NAMESPACE + +class QLowEnergyAdvertisingDataPrivate : public QSharedData +{ +public: + QLowEnergyAdvertisingDataPrivate() + : manufacturerId(QLowEnergyAdvertisingData::invalidManufacturerId()) + , discoverability(QLowEnergyAdvertisingData::DiscoverabilityNone) + , includePowerLevel(false) + { + } + + QString localName; + QByteArray manufacturerData; + QByteArray rawData; + QList<QBluetoothUuid> services; + quint16 manufacturerId; + QLowEnergyAdvertisingData::Discoverability discoverability; + bool includePowerLevel; +}; + +/*! + \since 5.7 + \class QLowEnergyAdvertisingData + \brief The QLowEnergyAdvertisingData class represents the data to be broadcast during + Bluetooth Low Energy advertising. + \inmodule QtBluetooth + \ingroup shared + + This data can include the device name, GATT services offered by the device, and so on. + The data set via this class will be used when advertising is started by calling + \l QLowEnergyController::startAdvertising(). Objects of this class can represent an + Advertising Data packet or a Scan Response packet. + \note The actual data packets sent over the advertising channel cannot contain more than 31 + bytes. If the variable-length data set via this class exceeds that limit, it will + be left out of the packet or truncated, depending on the type. + + \sa QLowEnergyAdvertisingParameters + \sa QLowEnergyController::startAdvertising() +*/ + +/*! + \enum QLowEnergyAdvertisingData::Discoverability + + The discoverability of the advertising device as defined by the Generic Access Profile. + + \value DiscoverabilityNone + The advertising device does not wish to be discoverable by scanning devices. + \value DiscoverabilityLimited + The advertising device wishes to be discoverable with a high priority. Note that this mode + is not compatible with using a white list. The value of + \l QLowEnergyAdvertisingParameters::filterPolicy() is always assumed to be + \l QLowEnergyAdvertisingParameters::IgnoreWhiteList when limited discoverability + is used. + \value DiscoverabilityGeneral + The advertising device wishes to be discoverable by scanning devices. + */ + +/*! + Creates a new object of this class. All values are initialized to their defaults + according to the Bluetooth Low Energy specification. + */ +QLowEnergyAdvertisingData::QLowEnergyAdvertisingData() : d(new QLowEnergyAdvertisingDataPrivate) +{ +} + +/*! Constructs a new object of this class that is a copy of \a other. */ +QLowEnergyAdvertisingData::QLowEnergyAdvertisingData(const QLowEnergyAdvertisingData &other) + : d(other.d) +{ +} + +/*! Destroys this object. */ +QLowEnergyAdvertisingData::~QLowEnergyAdvertisingData() +{ +} + +/*! Makes this object a copy of \a other and returns the new value of this object. */ +QLowEnergyAdvertisingData &QLowEnergyAdvertisingData::operator=(const QLowEnergyAdvertisingData &other) +{ + d = other.d; + return *this; +} + +/*! + Specifies that \a name should be broadcast as the name of the device. If the full name does not + fit into the advertising data packet, an abbreviated name is sent, as described by the + Bluetooth Low Energy specification. + */ +void QLowEnergyAdvertisingData::setLocalName(const QString &name) +{ + d->localName = name; +} + +/*! + Returns the name of the local device that is to be advertised. + */ +QString QLowEnergyAdvertisingData::localName() const +{ + return d->localName; +} + +/*! + Sets the manufacturer id and data. The \a id parameter is a company identifier as assigned + by the Bluetooth SIG. The \a data parameter is an arbitrary value. + */ +void QLowEnergyAdvertisingData::setManufacturerData(quint16 id, const QByteArray &data) +{ + d->manufacturerId = id; + d->manufacturerData = data; +} + +/*! + Returns the manufacturer id. + The default is \l QLowEnergyAdvertisingData::invalidManufacturerId(), which means + the data will not be advertised. + */ +quint16 QLowEnergyAdvertisingData::manufacturerId() const +{ + return d->manufacturerId; +} + +/*! + Returns the manufacturer data. The default is an empty byte array. + */ +QByteArray QLowEnergyAdvertisingData::manufacturerData() const +{ + return d->manufacturerData; +} + +/*! + Specifies whether to include the device's transmit power level in the advertising data. If + \a doInclude is \c true, the data will be included, otherwise it will not. + */ +void QLowEnergyAdvertisingData::setIncludePowerLevel(bool doInclude) +{ + d->includePowerLevel = doInclude; +} + +/*! + Returns whether to include the device's transmit power level in the advertising data. + The default is \c false. + */ +bool QLowEnergyAdvertisingData::includePowerLevel() const +{ + return d->includePowerLevel; +} + +/*! + Sets the discoverability type of the advertising device to \a mode. + \note Discoverability information can only appear in an actual advertising data packet. If + this object acts as scan response data, a call to this function will have no effect + on the scan response sent. + */ +void QLowEnergyAdvertisingData::setDiscoverability(QLowEnergyAdvertisingData::Discoverability mode) +{ + d->discoverability = mode; +} + +/*! + Returns the discoverability mode of the advertising device. + The default is \l DiscoverabilityNone. + */ +QLowEnergyAdvertisingData::Discoverability QLowEnergyAdvertisingData::discoverability() const +{ + return d->discoverability; +} + +/*! + Specifies that the service UUIDs in \a services should be advertised. + If the entire list does not fit into the packet, an incomplete list is sent as specified + by the Bluetooth Low Energy specification. + */ +void QLowEnergyAdvertisingData::setServices(const QList<QBluetoothUuid> &services) +{ + d->services = services; +} + +/*! + Returns the list of service UUIDs to be advertised. + By default, this list is empty. + */ +QList<QBluetoothUuid> QLowEnergyAdvertisingData::services() const +{ + return d->services; +} + +/*! + Sets the data to be advertised to \a data. If the value is not an empty byte array, it will + be sent as-is as the advertising data and all other data in this object will be ignored. + This can be used to send non-standard data. + \note If \a data is longer than 31 bytes, it will be truncated. It is the caller's responsibility + to ensure that \a data is well-formed. + */ +void QLowEnergyAdvertisingData::setRawData(const QByteArray &data) +{ + d->rawData = data; +} + +/*! + Returns the user-supplied raw data to be advertised. The default is an empty byte array. + */ +QByteArray QLowEnergyAdvertisingData::rawData() const +{ + return d->rawData; +} + +/*! + \fn void QLowEnergyAdvertisingData::swap(QLowEnergyAdvertisingData &other) + Swaps this object with \a other. + */ + +/*! + Returns \c true if \a data1 and \a data2 are equal with respect to their public state, + otherwise returns \c false. + */ +bool operator==(const QLowEnergyAdvertisingData &data1, const QLowEnergyAdvertisingData &data2) +{ + if (data1.d == data2.d) + return true; + return data1.discoverability() == data2.discoverability() + && data1.includePowerLevel() == data2.includePowerLevel() + && data1.localName() == data2.localName() + && data1.manufacturerData() == data2.manufacturerData() + && data1.manufacturerId() == data2.manufacturerId() + && data1.services() == data2.services() + && data1.rawData() == data2.rawData(); +} + +/*! + \fn bool operator!=(const QLowEnergyAdvertisingData &data1, + const QLowEnergyAdvertisingData &data2) + Returns \c true if \a data1 and \a data2 are not equal with respect to their public state, + otherwise returns \c false. + */ + +/*! + \fn static quint16 QLowEnergyAdvertisingData::invalidManufacturerId(); + Returns an invalid manufacturer id. If this value is set as the manufacturer id + (which it is by default), no manufacturer data will be present in the advertising data. + */ + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergyadvertisingdata.h b/src/bluetooth/qlowenergyadvertisingdata.h new file mode 100644 index 00000000..314df3f0 --- /dev/null +++ b/src/bluetooth/qlowenergyadvertisingdata.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOWENERGYADVERTISINGDATA_H +#define QLOWENERGYADVERTISINGDATA_H + +#include <QtBluetooth/qbluetoothglobal.h> +#include <QtBluetooth/qbluetoothuuid.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_NAMESPACE + +class QLowEnergyAdvertisingDataPrivate; + +class Q_BLUETOOTH_EXPORT QLowEnergyAdvertisingData +{ + friend Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyAdvertisingData &data1, + const QLowEnergyAdvertisingData &data2); +public: + QLowEnergyAdvertisingData(); + QLowEnergyAdvertisingData(const QLowEnergyAdvertisingData &other); + ~QLowEnergyAdvertisingData(); + + QLowEnergyAdvertisingData &operator=(const QLowEnergyAdvertisingData &other); + + void setLocalName(const QString &name); + QString localName() const; + + static quint16 invalidManufacturerId() { return 0xffff; } + void setManufacturerData(quint16 id, const QByteArray &data); + quint16 manufacturerId() const; + QByteArray manufacturerData() const; + + void setIncludePowerLevel(bool doInclude); + bool includePowerLevel() const; + + enum Discoverability { + DiscoverabilityNone, DiscoverabilityLimited, DiscoverabilityGeneral + }; + void setDiscoverability(Discoverability mode); + Discoverability discoverability() const; + + void setServices(const QList<QBluetoothUuid> &services); + QList<QBluetoothUuid> services() const; + + // TODO: BR/EDR capability flag? + + void setRawData(const QByteArray &data); + QByteArray rawData() const; + + void swap(QLowEnergyAdvertisingData &other) Q_DECL_NOTHROW { qSwap(d, other.d); } + +private: + QSharedDataPointer<QLowEnergyAdvertisingDataPrivate> d; +}; + +Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyAdvertisingData &data1, + const QLowEnergyAdvertisingData &data2); +inline bool operator!=(const QLowEnergyAdvertisingData &data1, + const QLowEnergyAdvertisingData &data2) +{ + return !(data1 == data2); +} + +Q_DECLARE_SHARED(QLowEnergyAdvertisingData) + +QT_END_NAMESPACE + +#endif // Include guard diff --git a/src/bluetooth/qlowenergyadvertisingparameters.cpp b/src/bluetooth/qlowenergyadvertisingparameters.cpp new file mode 100644 index 00000000..bf1e7083 --- /dev/null +++ b/src/bluetooth/qlowenergyadvertisingparameters.cpp @@ -0,0 +1,259 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlowenergyadvertisingparameters.h" + +QT_BEGIN_NAMESPACE + +class QLowEnergyAdvertisingParametersPrivate : public QSharedData +{ +public: + QLowEnergyAdvertisingParametersPrivate() + : filterPolicy(QLowEnergyAdvertisingParameters::IgnoreWhiteList) + , mode(QLowEnergyAdvertisingParameters::AdvInd) + , minInterval(1280) + , maxInterval(1280) + { + } + + QList<QLowEnergyAdvertisingParameters::AddressInfo> whiteList; + QLowEnergyAdvertisingParameters::FilterPolicy filterPolicy; + QLowEnergyAdvertisingParameters::Mode mode; + int minInterval; + int maxInterval; +}; + +/*! + \since 5.7 + \class QLowEnergyAdvertisingParameters + \brief The QLowEnergyAdvertisingParameters class represents the parameters used for + Bluetooth Low Energy advertising. + \inmodule QtBluetooth + \ingroup shared + + When running the advertising procedure, a number of parameters can be configured, such as + how fast to advertise or which clients, if any, can connect to the advertising device. + These parameters are set via this class, and their values will be used when advertising + is started by calling \l QLowEnergyController::startAdvertising(). + + \sa QLowEnergyAdvertisingData + \sa QLowEnergyController::startAdvertising() +*/ + +/*! + \enum QLowEnergyAdvertisingParameters::Mode + + Specifies in which way to advertise. + \value AdvInd + For non-directed, connectable advertising. Advertising is not directed to + one specific device and a device seeing the advertisement can connect to the + advertising device or send scan requests. + \value AdvScanInd + For non-directed, scannable advertising. Advertising is not directed to + one specific device and a device seeing the advertisement can send a scan + request to the advertising device, but cannot connect to it. + \value AdvNonConnInd + For non-directed, non-connectable advertising. Advertising is not directed to + one specific device. A device seeing the advertisement cannot connect to the + advertising device, nor can it send a scan request. This mode thus implies + pure broadcasting. +*/ + +/*! + \enum QLowEnergyAdvertisingParameters::FilterPolicy + + Specifies the semantics of the white list. + \value IgnoreWhiteList + The value of the white list is ignored, that is, no filtering takes place for + either scan or connection requests when using undirected advertising. + \value UseWhiteListForScanning + The white list is used when handling scan requests, but is ignored for connection + requests. + \value UseWhiteListForConnecting + The white list is used when handling connection requests, but is ignored for scan + requests. + \value UseWhiteListForScanningAndConnecting + The white list is used for both connection and scan requests. + + \sa QLowEnergyAdvertisingParameters::whiteList() +*/ + +/*! + \struct QLowEnergyAdvertisingParameters::AddressInfo + + Objects of this type form the elements of a white list. + \sa QLowEnergyAdvertisingParameters::whiteList() +*/ + +/*! + \variable QLowEnergyAdvertisingParameters::AddressInfo::address + The Bluetooth address of a remote address. +*/ + +/*! + \variable QLowEnergyAdvertisingParameters::AddressInfo::type + The type of the address (public or private). +*/ + + +/*! + Constructs a new object of this class. All values are initialized to their defaults + according to the Bluetooth Low Energy specification. + */ +QLowEnergyAdvertisingParameters::QLowEnergyAdvertisingParameters() + : d(new QLowEnergyAdvertisingParametersPrivate) +{ +} + +/*! Constructs a new object of this class that is a copy of \a other. */ +QLowEnergyAdvertisingParameters::QLowEnergyAdvertisingParameters(const QLowEnergyAdvertisingParameters &other) + : d(other.d) +{ +} + +/*! Destroys this object. */ +QLowEnergyAdvertisingParameters::~QLowEnergyAdvertisingParameters() +{ +} + +/*! Makes this object a copy of \a other and returns the new value of this object. */ +QLowEnergyAdvertisingParameters &QLowEnergyAdvertisingParameters::operator=(const QLowEnergyAdvertisingParameters &other) +{ + d = other.d; + return *this; +} + +/*! Sets the advertising mode to \a mode. */ +void QLowEnergyAdvertisingParameters::setMode(QLowEnergyAdvertisingParameters::Mode mode) +{ + d->mode = mode; +} + +/*! + Returns the advertising mode. The default is \l QLowEnergyAdvertisingParameters::AdvInd. + */ +QLowEnergyAdvertisingParameters::Mode QLowEnergyAdvertisingParameters::mode() const +{ + return d->mode; +} + +/*! + Sets the white list that is potentially used for filtering scan and connection requests. + The \a whiteList parameter is the list of addresses to use for filtering, and \a policy + specifies how exactly to use \a whiteList. + */ +void QLowEnergyAdvertisingParameters::setWhiteList(const QList<AddressInfo> &whiteList, + FilterPolicy policy) +{ + d->whiteList = whiteList; + d->filterPolicy = policy; +} + +/*! + Returns the white list used for filtering scan and connection requests. + By default, this list is empty. + */ +QList<QLowEnergyAdvertisingParameters::AddressInfo> QLowEnergyAdvertisingParameters::whiteList() const +{ + return d->whiteList; +} + +/*! + Returns the filter policy that determines how the white list is used. The default + is \l QLowEnergyAdvertisingParameters::IgnoreWhiteList. + */ +QLowEnergyAdvertisingParameters::FilterPolicy QLowEnergyAdvertisingParameters::filterPolicy() const +{ + return d->filterPolicy; +} + +/*! + Sets the advertising interval. This is a range that gives the controller an upper and a lower + bound for how often to send the advertising data. Both \a minimum and \a maximum are given + in milliseconds. + If \a maximum is smaller than \a minimum, it will be set to the value of \a minimum. + \note There are limits for the minimum and maximum interval; the exact values depend on + the mode. If they are exceeded, the lowest or highest possible value will be used, + respectively. + */ +void QLowEnergyAdvertisingParameters::setInterval(quint16 minimum, quint16 maximum) +{ + d->minInterval = minimum; + d->maxInterval = qMax(minimum, maximum); +} + +/*! + Returns the minimum advertising interval in milliseconds. The default is 1280. + */ +int QLowEnergyAdvertisingParameters::minimumInterval() const +{ + return d->minInterval; +} + +/*! + Returns the maximum advertising interval in milliseconds. The default is 1280. + */ +int QLowEnergyAdvertisingParameters::maximumInterval() const +{ + return d->maxInterval; +} + +/*! + \fn void QLowEnergyAdvertisingParameters::swap(QLowEnergyAdvertisingParameters &other) + Swaps this object with \a other. + */ + +/*! + Returns \a true if \a p1 and \a p2 are equal with respect to their public state, + otherwise returns false. + */ +bool operator==(const QLowEnergyAdvertisingParameters &p1, + const QLowEnergyAdvertisingParameters &p2) +{ + if (p1.d == p2.d) + return true; + return p1.filterPolicy() == p2.filterPolicy() + && p1.minimumInterval() == p2.minimumInterval() + && p1.maximumInterval() == p2.maximumInterval() + && p1.mode() == p2.mode() + && p1.whiteList() == p2.whiteList(); +} + +/*! + \fn bool operator!=(const QLowEnergyAdvertisingParameters &p1, + const QLowEnergyAdvertisingParameters &p2) + Returns \a true if \a p1 and \a p2 are not equal with respect to their public state, + otherwise returns false. + */ + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergyadvertisingparameters.h b/src/bluetooth/qlowenergyadvertisingparameters.h new file mode 100644 index 00000000..8d98a10b --- /dev/null +++ b/src/bluetooth/qlowenergyadvertisingparameters.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOWENERGYADVERTISINGPARAMETERS_H +#define QLOWENERGYADVERTISINGPARAMETERS_H + +#include <QtBluetooth/qbluetoothglobal.h> +#include <QtBluetooth/qbluetoothaddress.h> +#include <QtBluetooth/qlowenergycontroller.h> +#include <QtCore/qlist.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_NAMESPACE + +class QLowEnergyAdvertisingParametersPrivate; + +class Q_BLUETOOTH_EXPORT QLowEnergyAdvertisingParameters +{ + friend Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyAdvertisingParameters &p1, + const QLowEnergyAdvertisingParameters &p2); +public: + QLowEnergyAdvertisingParameters(); + QLowEnergyAdvertisingParameters(const QLowEnergyAdvertisingParameters &other); + ~QLowEnergyAdvertisingParameters(); + + QLowEnergyAdvertisingParameters &operator=(const QLowEnergyAdvertisingParameters &other); + + enum Mode { AdvInd = 0x0, AdvScanInd = 0x2, AdvNonConnInd = 0x3 }; + void setMode(Mode mode); + Mode mode() const; + + struct AddressInfo { + AddressInfo(const QBluetoothAddress &addr, QLowEnergyController::RemoteAddressType t) + : address(addr), type(t) {} + AddressInfo() {} + + QBluetoothAddress address; + QLowEnergyController::RemoteAddressType type; + }; + enum FilterPolicy { + IgnoreWhiteList = 0x00, + UseWhiteListForScanning = 0x01, + UseWhiteListForConnecting = 0x02, + UseWhiteListForScanningAndConnecting = 0x03, + }; + void setWhiteList(const QList<AddressInfo> &whiteList, FilterPolicy policy); + QList<AddressInfo> whiteList() const; + FilterPolicy filterPolicy() const; + + void setInterval(quint16 minimum, quint16 maximum); + int minimumInterval() const; + int maximumInterval() const; + + // TODO: own address type + // TODO: For ADV_DIRECT_IND: peer address + peer address type + + void swap(QLowEnergyAdvertisingParameters &other) Q_DECL_NOTHROW { qSwap(d, other.d); } + +private: + QSharedDataPointer<QLowEnergyAdvertisingParametersPrivate> d; +}; + +inline bool operator==(const QLowEnergyAdvertisingParameters::AddressInfo &ai1, + const QLowEnergyAdvertisingParameters::AddressInfo &ai2) +{ + return ai1.address == ai2.address && ai1.type == ai2.type; +} + +Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyAdvertisingParameters &p1, + const QLowEnergyAdvertisingParameters &p2); +inline bool operator!=(const QLowEnergyAdvertisingParameters &p1, + const QLowEnergyAdvertisingParameters &p2) +{ + return !(p1 == p2); +} + +Q_DECLARE_SHARED(QLowEnergyAdvertisingParameters) + +QT_END_NAMESPACE + +#endif // Include guard diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp index 62e8f7e3..9a9327a5 100644 --- a/src/bluetooth/qlowenergycontroller.cpp +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -35,11 +35,14 @@ #include "qlowenergycontroller_p.h" #include <QtBluetooth/QBluetoothLocalDevice> +#include <QtCore/QLoggingCategory> #include <algorithm> QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(QT_BT) + /*! \class QLowEnergyController \inmodule QtBluetooth @@ -49,9 +52,7 @@ QT_BEGIN_NAMESPACE \since 5.4 QLowEnergyController acts as the entry point for Bluetooth Low Energy - development. Each QLowEnergyController instance acts as placeholder - towards a remote Low Energy device enabling connection control, - service discovery and state tracking. + development. Bluetooth Low Energy defines two types of devices; the peripheral and the central. Each role performs a different task. The peripheral device @@ -62,12 +63,12 @@ QT_BEGIN_NAMESPACE the sensor is the peripheral device and the mobile phone acts as the central device. - At the moment Qt only supports the central role and therefore the remote - device can only be a device acting as a peripheral. This implies that the local - device acts within the boundaries of the central role as per the Bluetooth 4.0 - specification. + A controller in the central role is created via the \l createCentral() factory method. + Such an object essentially acts as a placeholder towards a remote Low Energy peripheral + device, enabling features such as service discovery and state tracking. - The first step is to establish a connection via \l connectToDevice(). + After having created a controller object in the central role, the first step is to establish + a connection via \l connectToDevice(). Once the connection has been established, the controller's \l state() changes to \l QLowEnergyController::ConnectedState and the \l connected() signal is emitted. It is important to mention that some platforms such as @@ -93,7 +94,20 @@ QT_BEGIN_NAMESPACE connection becomes invalid as soon as the controller disconnects from the remote Bluetooth Low Energy device. + A controller in the peripheral role is created via the \l createPeripheral() factory method. + Such an object acts as a peripheral device itself, enabling features such as advertising + services and allowing clients to get notified about changes to characteristic values. + + \omit + After having created a controller object in the peripheral role, the first step is to + populate the set of GATT services offered to client devices [not yet implemented]. + Afterwards, one would call \l startAdvertising() to let the device broadcast some data + and, depending on the type of advertising being done, also listen for incoming connections + from GATT clients. + \endomit + \sa QLowEnergyService, QLowEnergyCharacteristic, QLowEnergyDescriptor + \sa QLowEnergyAdvertisingParameters, QLowEnergyAdvertisingData */ /*! @@ -113,6 +127,8 @@ QT_BEGIN_NAMESPACE there is no local Bluetooth device. \value ConnectionError The attempt to connect to the remote device failed. This value was introduced by Qt 5.5. + \value AdvertisingError The attempt to start advertising failed. + This value was introduced by Qt 5.7. */ /*! @@ -128,6 +144,7 @@ QT_BEGIN_NAMESPACE \value DiscoveredState The controller has discovered all services offered by the remote device. \value ClosingState The controller is about to be disconnected from the remote device. + \value AdvertisingState The controller is currently advertising data. */ /*! @@ -135,26 +152,47 @@ QT_BEGIN_NAMESPACE Indicates what type of Bluetooth address the remote device uses. - \value PublicAddress The peripheral uses a public Bluetooth address. + \value PublicAddress The remote device uses a public Bluetooth address. \value RandomAddress A random address is a Bluetooth Low Energy security feature. Peripherals using such addresses may frequently change their Bluetooth address. This information is needed when trying to connect to a peripheral. */ +/*! + \enum QLowEnergyController::Role + + Indicates the role of the controller object. + + \value CentralRole + The controller acts as a client interacting with a remote device which is in the peripheral + role. The controller can initiate connections, discover services and + read and write characteristics. + \value PeripheralRole + The controller can be used to advertise services and handle incoming + connections and client requests, acting as a GATT server. A remote device connected to + the controller is in the central role. + + \sa QLowEnergyController::createCentral() + \sa QLowEnergyController::createPeripheral() + \since 5.7 + \note The peripheral role is currently only supported on Linux. + */ + /*! \fn void QLowEnergyController::connected() This signal is emitted when the controller successfully connects to the remote - Low Energy device. + Low Energy device (if the controller is in the \l CentralRole) or if a remote Low Energy + device connected to the controller (if the controller is in the \l PeripheralRole). */ /*! \fn void QLowEnergyController::disconnected() This signal is emitted when the controller disconnects from the remote - Low Energy device. + Low Energy device or vice versa. */ /*! @@ -181,6 +219,8 @@ QT_BEGIN_NAMESPACE This signal is emitted each time a new service is discovered. The \a newService parameter contains the UUID of the found service. + This signal can only be emitted if the controller is in the \c CentralRole. + \sa discoverServices(), discoveryFinished() */ @@ -191,6 +231,8 @@ QT_BEGIN_NAMESPACE The signal is not emitted if the discovery process finishes with an error. + This signal can only be emitted if the controller is in the \l CentralRole. + \sa discoverServices(), error() */ @@ -225,11 +267,14 @@ void QLowEnergyControllerPrivate::setError( case QLowEnergyController::ConnectionError: errorString = QLowEnergyController::tr("Error occurred trying to connect to remote device."); break; - case QLowEnergyController::NoError: - return; + case QLowEnergyController::AdvertisingError: + errorString = QLowEnergyController::tr("Error occurred trying to start advertising"); + break; case QLowEnergyController::UnknownError: errorString = QLowEnergyController::tr("Unknown Error"); break; + case QLowEnergyController::NoError: + return; } emit q->error(newError); @@ -409,6 +454,7 @@ QLowEnergyController::QLowEnergyController( { Q_D(QLowEnergyController); d->q_ptr = this; + d->role = CentralRole; d->remoteDevice = remoteDevice; d->localAdapter = QBluetoothLocalDevice().address(); d->addressType = QLowEnergyController::PublicAddress; @@ -425,6 +471,7 @@ QLowEnergyController::QLowEnergyController( the connection management. \since 5.5 + \obsolete */ QLowEnergyController::QLowEnergyController( const QBluetoothDeviceInfo &remoteDeviceInfo, @@ -433,6 +480,7 @@ QLowEnergyController::QLowEnergyController( { Q_D(QLowEnergyController); d->q_ptr = this; + d->role = CentralRole; d->remoteDevice = remoteDeviceInfo.address(); d->localAdapter = QBluetoothLocalDevice().address(); d->addressType = QLowEnergyController::PublicAddress; @@ -462,11 +510,47 @@ QLowEnergyController::QLowEnergyController( { Q_D(QLowEnergyController); d->q_ptr = this; + d->role = CentralRole; d->remoteDevice = remoteDevice; d->localAdapter = localDevice; } /*! + Returns a new object of this class that is in the \l CentralRole. + The \a remoteDevice refers to the device that a connection will be established to later. + * + The controller uses the local default Bluetooth adapter for the connection management. + \sa QLowEnergyController::CentralRole + */ +QLowEnergyController *QLowEnergyController::createCentral(const QBluetoothDeviceInfo &remoteDevice, + QObject *parent) +{ + return new QLowEnergyController(remoteDevice, parent); +} + + +/*! + Returns a new object of this class that is in the \l PeripheralRole. + Typically, the next step is to call \l startAdvertising() on the returned object. + * + The controller uses the local default Bluetooth adapter for the connection management. + \sa QLowEnergyController::PeripheralRole + */ +QLowEnergyController *QLowEnergyController::createPeripheral(QObject *parent) +{ + return new QLowEnergyController(parent); +} + +QLowEnergyController::QLowEnergyController(QObject *parent) + : QObject(parent), d_ptr(new QLowEnergyControllerPrivate()) +{ + Q_D(QLowEnergyController); + d->q_ptr = this; + d->role = PeripheralRole; + d->localAdapter = QBluetoothLocalDevice().address(); +} + +/*! Destroys the QLowEnergyController instance. */ QLowEnergyController::~QLowEnergyController() @@ -492,6 +576,11 @@ QBluetoothAddress QLowEnergyController::localAddress() const /*! Returns the address of the remote Bluetooth Low Energy device. + + For a controller in the \l CentralRole, this value will always be the one passed in when + the controller object was created. For a controller in the \l PeripheralRole, this value + is the address of the currently connected client device. In particular, this address will + be invalid if the controller is not currently in the \l ConnectedState. */ QBluetoothAddress QLowEnergyController::remoteAddress() const { @@ -499,7 +588,8 @@ QBluetoothAddress QLowEnergyController::remoteAddress() const } /*! - Returns the name of the remote Bluetooth Low Energy device. + Returns the name of the remote Bluetooth Low Energy device, if the controller is in the + \l CentralRole. Otherwise the result is unspecified. \since 5.5 */ @@ -611,6 +701,10 @@ void QLowEnergyController::discoverServices() { Q_D(QLowEnergyController); + if (d->role != PeripheralRole) { + qCWarning(QT_BT) << "Cannot discover services in peripheral role"; + return; + } if (d->state != QLowEnergyController::ConnectedState) return; @@ -619,7 +713,8 @@ void QLowEnergyController::discoverServices() } /*! - Returns the list of services offered by the remote device. + Returns the list of services offered by the remote device, if the controller is in + the \l CentralRole. Otherwise, the result is unspecified. The list contains all primary and secondary services. @@ -672,6 +767,49 @@ QLowEnergyService *QLowEnergyController::createServiceObject( } /*! + Starts advertising the data given in \a advertisingData and \a scanResponseData, using + the parameters set in \a parameters. The controller has to be in the \l PeripheralRole. + \omit + If \a parameters indicates that the advertisement should be connectable, then this call + also starts listening for incoming client connections. + \endomit + + Providing \a scanResponseData is not required, as it is not applicable for certain + configurations of \c parameters. + + If this object is currently not in the \l UnconnectedState, nothing happens. + \note Advertising will stop automatically once a client connects to the local device. + */ +void QLowEnergyController::startAdvertising(const QLowEnergyAdvertisingParameters ¶meters, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData) +{ + Q_D(QLowEnergyController); + if (role() != PeripheralRole) { + qCWarning(QT_BT) << "Cannot start advertising in central role" << state(); + return; + } + if (state() != UnconnectedState) { + qCWarning(QT_BT) << "Cannot start advertising in state" << state(); + return; + } + d->startAdvertising(parameters, advertisingData, scanResponseData); +} + +/*! + Stops advertising, if this object is currently in the advertising state. + */ +void QLowEnergyController::stopAdvertising() +{ + Q_D(QLowEnergyController); + if (state() != AdvertisingState) { + qCDebug(QT_BT) << "stopAdvertising called in state" << state(); + return; + } + d->stopAdvertising(); +} + +/*! Returns the last occurred error or \l NoError. */ QLowEnergyController::Error QLowEnergyController::error() const @@ -688,4 +826,12 @@ QString QLowEnergyController::errorString() const return d_ptr->errorString; } +/*! + Returns the role that this controller object is in. + */ +QLowEnergyController::Role QLowEnergyController::role() const +{ + return d_ptr->role; +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller.h b/src/bluetooth/qlowenergycontroller.h index be729dda..66928da8 100644 --- a/src/bluetooth/qlowenergycontroller.h +++ b/src/bluetooth/qlowenergycontroller.h @@ -38,11 +38,14 @@ #include <QtBluetooth/QBluetoothAddress> #include <QtBluetooth/QBluetoothDeviceInfo> #include <QtBluetooth/QBluetoothUuid> +#include <QtBluetooth/QLowEnergyAdvertisingData> #include <QtBluetooth/QLowEnergyService> QT_BEGIN_NAMESPACE +class QLowEnergyAdvertisingParameters; class QLowEnergyControllerPrivate; + class Q_BLUETOOTH_EXPORT QLowEnergyController : public QObject { Q_OBJECT @@ -53,7 +56,8 @@ public: UnknownRemoteDeviceError, NetworkError, InvalidBluetoothAdapterError, - ConnectionError + ConnectionError, + AdvertisingError, }; Q_ENUM(Error) @@ -63,7 +67,8 @@ public: ConnectedState, DiscoveringState, DiscoveredState, - ClosingState + ClosingState, + AdvertisingState, }; Q_ENUM(ControllerState) @@ -73,6 +78,9 @@ public: }; Q_ENUM(RemoteAddressType) + enum Role { CentralRole, PeripheralRole }; + Q_ENUM(Role) + explicit QLowEnergyController(const QBluetoothAddress &remoteDevice, QObject *parent = 0); // TODO Qt 6 remove ctor explicit QLowEnergyController(const QBluetoothDeviceInfo &remoteDevice, @@ -80,6 +88,11 @@ public: explicit QLowEnergyController(const QBluetoothAddress &remoteDevice, const QBluetoothAddress &localDevice, QObject *parent = 0); // TODO Qt 6 remove ctor + + static QLowEnergyController *createCentral(const QBluetoothDeviceInfo &remoteDevice, + QObject *parent = 0); + static QLowEnergyController *createPeripheral(QObject *parent = 0); + ~QLowEnergyController(); QBluetoothAddress localAddress() const; @@ -100,9 +113,16 @@ public: QLowEnergyService *createServiceObject( const QBluetoothUuid &service, QObject *parent = 0); + void startAdvertising(const QLowEnergyAdvertisingParameters ¶meters, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData = QLowEnergyAdvertisingData()); + void stopAdvertising(); + Error error() const; QString errorString() const; + Role role() const; + Q_SIGNALS: void connected(); void disconnected(); @@ -113,6 +133,8 @@ Q_SIGNALS: void discoveryFinished(); private: + explicit QLowEnergyController(QObject *parent = 0); // For the peripheral role. + Q_DECLARE_PRIVATE(QLowEnergyController) QLowEnergyControllerPrivate *d_ptr; }; @@ -122,5 +144,6 @@ QT_END_NAMESPACE Q_DECLARE_METATYPE(QLowEnergyController::Error) Q_DECLARE_METATYPE(QLowEnergyController::ControllerState) Q_DECLARE_METATYPE(QLowEnergyController::RemoteAddressType) +Q_DECLARE_METATYPE(QLowEnergyController::Role) #endif // QLOWENERGYCONTROLLER_H diff --git a/src/bluetooth/qlowenergycontroller_android.cpp b/src/bluetooth/qlowenergycontroller_android.cpp index b04ddedd..437eb9a4 100644 --- a/src/bluetooth/qlowenergycontroller_android.cpp +++ b/src/bluetooth/qlowenergycontroller_android.cpp @@ -580,4 +580,19 @@ void QLowEnergyControllerPrivate::serviceError( service->setError(errorCode); } +void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData) +{ + Q_UNUSED(params); + Q_UNUSED(advertisingData); + Q_UNUSED(scanResponseData); + qCWarning(QT_BT_ANDROID) << "LE advertising not implemented for Android"; +} + +void QLowEnergyControllerPrivate::stopAdvertising() +{ + qCWarning(QT_BT_ANDROID) << "LE advertising not implemented for Android"; +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index 18d0f5a1..45d392d8 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -34,6 +34,7 @@ #include "qlowenergycontroller_p.h" #include "qbluetoothsocket_p.h" +#include "qleadvertiser_p.h" #include "bluez/bluez_data_p.h" #include "bluez/hcimanager_p.h" @@ -203,7 +204,8 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() mtuSize(ATT_DEFAULT_LE_MTU), securityLevelValue(-1), encryptionChangePending(false), - hciManager(0) + hciManager(0), + advertiser(0) { qRegisterMetaType<QList<QLowEnergyHandle> >(); @@ -219,6 +221,26 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() { } +void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData) +{ + qCDebug(QT_BT_BLUEZ) << "Starting to advertise"; + if (!advertiser) { + advertiser = new QLeAdvertiserBluez(params, advertisingData, scanResponseData, *hciManager, + this); + connect(advertiser, &QLeAdvertiser::errorOccurred, this, + &QLowEnergyControllerPrivate::handleAdvertisingError); + } + setState(QLowEnergyController::AdvertisingState); + advertiser->startAdvertising(); +} + +void QLowEnergyControllerPrivate::stopAdvertising() +{ + setState(QLowEnergyController::UnconnectedState); + advertiser->stopAdvertising(); +} void QLowEnergyControllerPrivate::connectToDevice() { @@ -1743,4 +1765,11 @@ bool QLowEnergyControllerPrivate::increaseEncryptLevelfRequired(quint8 errorCode return false; } +void QLowEnergyControllerPrivate::handleAdvertisingError() +{ + qCWarning(QT_BT_BLUEZ) << "received advertising error"; + setError(QLowEnergyController::AdvertisingError); + setState(QLowEnergyController::UnconnectedState); +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm index d0d65248..dca245a8 100644 --- a/src/bluetooth/qlowenergycontroller_osx.mm +++ b/src/bluetooth/qlowenergycontroller_osx.mm @@ -817,6 +817,7 @@ QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddres { OSX_D_PTR; + osx_d_ptr->role = CentralRole; osx_d_ptr->remoteAddress = remoteAddress; osx_d_ptr->localAddress = QBluetoothLocalDevice().address(); @@ -831,6 +832,7 @@ QLowEnergyController::QLowEnergyController(const QBluetoothDeviceInfo &remoteDev { OSX_D_PTR; + osx_d_ptr->role = CentralRole; osx_d_ptr->localAddress = QBluetoothLocalDevice().address(); // That's the only "real" ctor - with Core Bluetooth we need a _valid_ deviceUuid // from 'remoteDevice'. @@ -844,6 +846,7 @@ QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddres { OSX_D_PTR; + osx_d_ptr->role = CentralRole; osx_d_ptr->remoteAddress = remoteAddress; osx_d_ptr->localAddress = localAddress; @@ -851,12 +854,39 @@ QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddres "addresses is not supported!"; } +QLowEnergyController::QLowEnergyController(QObject *parent) + : QObject(parent), d_ptr(new QLowEnergyControllerPrivateOSX(this)) +{ + OSX_D_PTR; + + osx_d_ptr->role = PeripheralRole; + osx_d_ptr->localAddress = QBluetoothLocalDevice().address(); +} + +QLowEnergyController *QLowEnergyController::createCentral(const QBluetoothDeviceInfo &remoteDevice, + QObject *parent) +{ + return new QLowEnergyController(remoteDevice, parent); +} + +QLowEnergyController *QLowEnergyController::createPeripheral(QObject *parent) +{ + return new QLowEnergyController(parent); +} + QLowEnergyController::~QLowEnergyController() { // Deleting a peripheral will also disconnect. delete d_ptr; } +QLowEnergyController::Role QLowEnergyController::role() const +{ + OSX_D_PTR; + + return osx_d_ptr->role; +} + QBluetoothAddress QLowEnergyController::localAddress() const { OSX_D_PTR; @@ -993,4 +1023,19 @@ QString QLowEnergyController::errorString() const return osx_d_ptr->errorString; } +void QLowEnergyController::startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData) +{ + Q_UNUSED(params); + Q_UNUSED(advertisingData); + Q_UNUSED(scanResponseData); + qCWarning(QT_BT_OSX) << "LE advertising not implemented for OS X"; +} + +void QLowEnergyController::stopAdvertising() +{ + qCWarning(QT_BT_OSX) << "LE advertising not implemented for OS X"; +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h index 1c1cb1cd..6f6619db 100644 --- a/src/bluetooth/qlowenergycontroller_osx_p.h +++ b/src/bluetooth/qlowenergycontroller_osx_p.h @@ -156,6 +156,8 @@ private: QBluetoothAddress localAddress; QBluetoothAddress remoteAddress; + QLowEnergyController::Role role; + QLowEnergyController::ControllerState controllerState; QLowEnergyController::RemoteAddressType addressType; diff --git a/src/bluetooth/qlowenergycontroller_p.cpp b/src/bluetooth/qlowenergycontroller_p.cpp index b3c718b5..4ee1e50f 100644 --- a/src/bluetooth/qlowenergycontroller_p.cpp +++ b/src/bluetooth/qlowenergycontroller_p.cpp @@ -104,4 +104,14 @@ void QLowEnergyControllerPrivate::writeDescriptor( } +void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters &/* params */, + const QLowEnergyAdvertisingData &/* advertisingData */, + const QLowEnergyAdvertisingData &/* scanResponseData */) +{ +} + +void QLowEnergyControllerPrivate::stopAdvertising() +{ +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index f587d3f9..722de692 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -85,6 +85,7 @@ class LowEnergyNotificationHub; #endif typedef QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate> > ServiceDataMap; +class QLeAdvertiser; class QLowEnergyControllerPrivate : public QObject { @@ -107,6 +108,11 @@ public: void discoverServiceDetails(const QBluetoothUuid &service); + void startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData); + void stopAdvertising(); + // misc helpers QSharedPointer<QLowEnergyServicePrivate> serviceForHandle( QLowEnergyHandle handle); @@ -142,6 +148,7 @@ public: QBluetoothAddress remoteDevice; QBluetoothAddress localAdapter; + QLowEnergyController::Role role; QString remoteName; @@ -172,6 +179,7 @@ private: bool encryptionChangePending; HciManager *hciManager; + QLeAdvertiser *advertiser; void sendCommand(const QByteArray &packet); void sendNextPendingRequest(); @@ -204,6 +212,7 @@ private: void resetController(); + void handleAdvertisingError(); private slots: void l2cpConnected(); void l2cpDisconnected(); |