summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/bluetooth/bluetooth.pro14
-rw-r--r--src/bluetooth/bluez/bluez_data_p.h70
-rw-r--r--src/bluetooth/bluez/hcimanager.cpp46
-rw-r--r--src/bluetooth/bluez/hcimanager_p.h4
-rw-r--r--src/bluetooth/qbluetooth.cpp14
-rw-r--r--src/bluetooth/qbluetooth.h14
-rw-r--r--src/bluetooth/qbluetoothsocket_bluez.cpp4
-rw-r--r--src/bluetooth/qbluetoothuuid.cpp8
-rw-r--r--src/bluetooth/qbluetoothuuid.h1
-rw-r--r--src/bluetooth/qleadvertiser_bluez.cpp443
-rw-r--r--src/bluetooth/qleadvertiser_p.h137
-rw-r--r--src/bluetooth/qlowenergyadvertisingdata.cpp280
-rw-r--r--src/bluetooth/qlowenergyadvertisingdata.h99
-rw-r--r--src/bluetooth/qlowenergyadvertisingparameters.cpp259
-rw-r--r--src/bluetooth/qlowenergyadvertisingparameters.h111
-rw-r--r--src/bluetooth/qlowenergycharacteristicdata.cpp273
-rw-r--r--src/bluetooth/qlowenergycharacteristicdata.h97
-rw-r--r--src/bluetooth/qlowenergycontroller.cpp244
-rw-r--r--src/bluetooth/qlowenergycontroller.h32
-rw-r--r--src/bluetooth/qlowenergycontroller_android.cpp22
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp1430
-rw-r--r--src/bluetooth/qlowenergycontroller_osx.mm54
-rw-r--r--src/bluetooth/qlowenergycontroller_osx_p.h2
-rw-r--r--src/bluetooth/qlowenergycontroller_p.cpp18
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h153
-rw-r--r--src/bluetooth/qlowenergydescriptordata.cpp219
-rw-r--r--src/bluetooth/qlowenergydescriptordata.h95
-rw-r--r--src/bluetooth/qlowenergyservice.cpp73
-rw-r--r--src/bluetooth/qlowenergyservice.h4
-rw-r--r--src/bluetooth/qlowenergyservicedata.cpp209
-rw-r--r--src/bluetooth/qlowenergyservicedata.h92
-rw-r--r--src/bluetooth/qlowenergyserviceprivate.cpp2
-rw-r--r--src/bluetooth/qlowenergyserviceprivate_p.h4
-rw-r--r--tests/auto/auto.pro1
-rw-r--r--tests/auto/qlowenergycontroller-gattserver/README10
-rw-r--r--tests/auto/qlowenergycontroller-gattserver/qlowenergycontroller-gattserver.pro2
-rw-r--r--tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp220
-rw-r--r--tests/auto/qlowenergycontroller-gattserver/server/server.pro5
-rw-r--r--tests/auto/qlowenergycontroller-gattserver/test/test.pro6
-rw-r--r--tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp492
-rw-r--r--tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp1
41 files changed, 5086 insertions, 178 deletions
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro
index 67a001d2..920fbe9a 100644
--- a/src/bluetooth/bluetooth.pro
+++ b/src/bluetooth/bluetooth.pro
@@ -24,9 +24,14 @@ PUBLIC_HEADERS += \
qbluetoothtransfermanager.h \
qbluetoothtransferrequest.h \
qlowenergyservice.h \
+ qlowenergyservicedata.h \
qlowenergycharacteristic.h \
+ qlowenergycharacteristicdata.h \
qlowenergydescriptor.h \
+ qlowenergydescriptordata.h \
qbluetoothtransferreply.h \
+ qlowenergyadvertisingdata.h \
+ qlowenergyadvertisingparameters.h \
qlowenergycontroller.h
PRIVATE_HEADERS += \
@@ -43,7 +48,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,9 +66,14 @@ SOURCES += \
qbluetoothtransfermanager.cpp \
qbluetoothtransferrequest.cpp \
qbluetoothtransferreply.cpp \
+ qlowenergyadvertisingdata.cpp \
+ qlowenergyadvertisingparameters.cpp \
qlowenergyservice.cpp \
+ qlowenergyservicedata.cpp \
qlowenergycharacteristic.cpp \
+ qlowenergycharacteristicdata.cpp \
qlowenergydescriptor.cpp \
+ qlowenergydescriptordata.cpp \
qlowenergycontroller.cpp \
qlowenergyserviceprivate.cpp
@@ -88,6 +99,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 3722b80d..7db0e50b 100644
--- a/src/bluetooth/bluez/bluez_data_p.h
+++ b/src/bluetooth/bluez/bluez_data_p.h
@@ -134,22 +134,6 @@ struct sockaddr_rc {
// Bt Low Energy related
-#define bt_get_unaligned(ptr) \
-({ \
- struct __attribute__((packed)) { \
- __typeof__(*(ptr)) __v; \
- } *__p = (__typeof__(__p)) (ptr); \
- __p->__v; \
-})
-
-#define bt_put_unaligned(val, ptr) \
-do { \
- struct __attribute__((packed)) { \
- __typeof__(*(ptr)) __v; \
- } *__p = (__typeof__(__p)) (ptr); \
- __p->__v = (val); \
-} while (0)
-
#if __BYTE_ORDER == __LITTLE_ENDIAN
static inline void btoh128(const quint128 *src, quint128 *dst)
@@ -165,15 +149,7 @@ static inline void ntoh128(const quint128 *src, quint128 *dst)
dst->data[15 - i] = src->data[i];
}
-static inline quint16 bt_get_le16(const void *ptr)
-{
- return bt_get_unaligned((const quint16 *) ptr);
-}
#elif __BYTE_ORDER == __BIG_ENDIAN
-static inline quint16 bt_get_le16(const void *ptr)
-{
- return qbswap(bt_get_unaligned((const quint16 *) ptr));
-}
static inline void btoh128(const quint128 *src, quint128 *dst)
{
@@ -191,6 +167,20 @@ static inline void ntoh128(const quint128 *src, quint128 *dst)
#error "Unknown byte order"
#endif
+static inline quint16 bt_get_le16(const void *ptr)
+{
+ return qFromLittleEndian<quint16>(reinterpret_cast<const uchar *>(ptr));
+}
+
+template<typename T> inline void putBtData(T src, void *dst)
+{
+ qToLittleEndian(src, reinterpret_cast<uchar *>(dst));
+}
+template<> inline void putBtData(quint128 src, void *dst)
+{
+ btoh128(&src, reinterpret_cast<quint128 *>(dst));
+}
+
#define hton128(x, y) ntoh128(x, y)
// HCI related
@@ -203,12 +193,14 @@ static inline void ntoh128(const quint128 *src, quint128 *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;
@@ -333,6 +325,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 &parameters)
+{
+ 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 &parameters);
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/qbluetooth.cpp b/src/bluetooth/qbluetooth.cpp
index 7a1f42ea..03b08bb4 100644
--- a/src/bluetooth/qbluetooth.cpp
+++ b/src/bluetooth/qbluetooth.cpp
@@ -67,6 +67,20 @@ namespace QBluetooth {
Simple Pairing from Bluetooth 2.1 or greater is required.
Legacy pairing is not permitted.
*/
+
+/*!
+ \enum QBluetooth::AttAccessConstraint
+
+ This enum describes the possible requirements for reading or writing an ATT attribute.
+
+ \value AttAuthorizationRequired
+ The client needs authorization from the ATT server to access the attribute.
+ \value AttAuthenticationRequired
+ The client needs to be authenticated to access the attribute.
+ \value AttEncryptionRequired
+ The attribute can only be accessed if the connection is encrypted.
+*/
+
}
/*!
diff --git a/src/bluetooth/qbluetooth.h b/src/bluetooth/qbluetooth.h
index f448af02..1053b388 100644
--- a/src/bluetooth/qbluetooth.h
+++ b/src/bluetooth/qbluetooth.h
@@ -39,6 +39,10 @@
QT_BEGIN_NAMESPACE
namespace QBluetooth {
+
+// TODO Qt 6: Merge these two enums? But note that ATT Authorization has no equivalent
+// on the socket security level.
+
enum Security {
NoSecurity = 0x00,
Authorization = 0x01,
@@ -49,6 +53,16 @@ enum Security {
Q_DECLARE_FLAGS(SecurityFlags, Security)
Q_DECLARE_OPERATORS_FOR_FLAGS(SecurityFlags)
+
+enum AttAccessConstraint {
+ AttAuthorizationRequired = 0x1,
+ AttAuthenticationRequired = 0x2,
+ AttEncryptionRequired = 0x4,
+};
+
+Q_DECLARE_FLAGS(AttAccessConstraints, AttAccessConstraint)
+Q_DECLARE_OPERATORS_FOR_FLAGS(AttAccessConstraints)
+
}
typedef quint16 QLowEnergyHandle;
diff --git a/src/bluetooth/qbluetoothsocket_bluez.cpp b/src/bluetooth/qbluetoothsocket_bluez.cpp
index 43933f26..6e93f64c 100644
--- a/src/bluetooth/qbluetoothsocket_bluez.cpp
+++ b/src/bluetooth/qbluetoothsocket_bluez.cpp
@@ -267,9 +267,9 @@ void QBluetoothSocketPrivate::_q_readNotify()
connectWriteNotifier->setEnabled(false);
errorString = qt_error_string(errsv);
qCWarning(QT_BT_BLUEZ) << Q_FUNC_INFO << socket << "error:" << readFromDevice << errorString;
- if(errsv == EHOSTDOWN)
+ if (errsv == EHOSTDOWN)
q->setSocketError(QBluetoothSocket::HostNotFoundError);
- else
+ else if (errsv != ECONNRESET) // The other side closing the connection is not an error.
q->setSocketError(QBluetoothSocket::UnknownSocketError);
q->disconnectFromService();
diff --git a/src/bluetooth/qbluetoothuuid.cpp b/src/bluetooth/qbluetoothuuid.cpp
index 3c8dc786..0808bc98 100644
--- a/src/bluetooth/qbluetoothuuid.cpp
+++ b/src/bluetooth/qbluetoothuuid.cpp
@@ -1127,11 +1127,17 @@ QString QBluetoothUuid::descriptorToString(QBluetoothUuid::DescriptorType uuid)
}
/*!
- Returns true if \a other is equal to this Bluetooth UUID, otherwise false.
+ Returns \c true if \a other is equal to this Bluetooth UUID, otherwise \c false.
*/
bool QBluetoothUuid::operator==(const QBluetoothUuid &other) const
{
return QUuid::operator==(other);
}
+/*!
+ \fn bool QBluetoothUuid::operator!=(const QBluetoothUuid &other) const
+ Returns \c true if \a other is not equal to this Bluetooth UUID, otherwise \c false.
+ \since 5.7
+*/
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothuuid.h b/src/bluetooth/qbluetoothuuid.h
index 4646531c..1e8adbb1 100644
--- a/src/bluetooth/qbluetoothuuid.h
+++ b/src/bluetooth/qbluetoothuuid.h
@@ -373,6 +373,7 @@ public:
~QBluetoothUuid();
bool operator==(const QBluetoothUuid &other) const;
+ bool operator!=(const QBluetoothUuid &other) const { return !operator==(other); }
int minimumSize() const;
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 &params,
+ 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 &params)
+{
+ 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 &params,
+ const QLowEnergyAdvertisingData &advData,
+ const QLowEnergyAdvertisingData &responseData, QObject *parent)
+ : QObject(parent), m_params(params), m_advData(advData), m_responseData(responseData) {}
+ virtual ~QLeAdvertiser() { }
+
+ const QLowEnergyAdvertisingParameters &parameters() 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 &params,
+ 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 &params);
+ 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/qlowenergycharacteristicdata.cpp b/src/bluetooth/qlowenergycharacteristicdata.cpp
new file mode 100644
index 00000000..a200f1c0
--- /dev/null
+++ b/src/bluetooth/qlowenergycharacteristicdata.cpp
@@ -0,0 +1,273 @@
+/****************************************************************************
+**
+** 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 "qlowenergycharacteristicdata.h"
+
+#include "qlowenergydescriptordata.h"
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qdebug.h>
+
+#include <climits>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT)
+
+struct QLowEnergyCharacteristicDataPrivate : public QSharedData
+{
+ QLowEnergyCharacteristicDataPrivate()
+ : properties(QLowEnergyCharacteristic::Unknown)
+ , minimumValueLength(0)
+ , maximumValueLength(INT_MAX)
+ {}
+
+ QBluetoothUuid uuid;
+ QLowEnergyCharacteristic::PropertyTypes properties;
+ QList<QLowEnergyDescriptorData> descriptors;
+ QByteArray value;
+ QBluetooth::AttAccessConstraints readConstraints;
+ QBluetooth::AttAccessConstraints writeConstraints;
+ int minimumValueLength;
+ int maximumValueLength;
+};
+
+/*!
+ \since 5.7
+ \class QLowEnergyCharacteristicData
+ \brief The QLowEnergyCharacteristicData class is used to set up GATT service data.
+ \inmodule QtBluetooth
+ \ingroup shared
+
+ An Object of this class provides a characteristic to be added to a
+ \l QLowEnergyServiceData object via \l QLowEnergyServiceData::addCharacteristic().
+
+ \sa QLowEnergyServiceData
+ \sa QLowEnergyController::addService
+*/
+
+/*! Creates a new invalid object of this class. */
+QLowEnergyCharacteristicData::QLowEnergyCharacteristicData()
+ : d(new QLowEnergyCharacteristicDataPrivate)
+{
+}
+
+/*! Constructs a new object of this class that is a copy of \a other. */
+QLowEnergyCharacteristicData::QLowEnergyCharacteristicData(const QLowEnergyCharacteristicData &other)
+ : d(other.d)
+{
+}
+
+/*! Destroys this object. */
+QLowEnergyCharacteristicData::~QLowEnergyCharacteristicData()
+{
+}
+
+/*! Makes this object a copy of \a other and returns the new value of this object. */
+QLowEnergyCharacteristicData &QLowEnergyCharacteristicData::operator=(const QLowEnergyCharacteristicData &other)
+{
+ d = other.d;
+ return *this;
+}
+
+/*! Returns the UUID of this characteristic. */
+QBluetoothUuid QLowEnergyCharacteristicData::uuid() const
+{
+ return d->uuid;
+}
+
+/*! Sets the UUID of this characteristic to \a uuid. */
+void QLowEnergyCharacteristicData::setUuid(const QBluetoothUuid &uuid)
+{
+ d->uuid = uuid;
+}
+
+/*! Returns the value of this characteristic. */
+QByteArray QLowEnergyCharacteristicData::value() const
+{
+ return d->value;
+}
+
+/*! Sets the value of this characteristic to \a value. */
+void QLowEnergyCharacteristicData::setValue(const QByteArray &value)
+{
+ d->value = value;
+}
+
+/*! Returns the properties of this characteristic. */
+QLowEnergyCharacteristic::PropertyTypes QLowEnergyCharacteristicData::properties() const
+{
+ return d->properties;
+}
+
+/*! Sets the properties of this characteristic to \a properties. */
+void QLowEnergyCharacteristicData::setProperties(QLowEnergyCharacteristic::PropertyTypes properties)
+{
+ d->properties = properties;
+}
+
+/*! Returns the descriptors of this characteristic. */
+QList<QLowEnergyDescriptorData> QLowEnergyCharacteristicData::descriptors() const
+{
+ return d->descriptors;
+}
+
+/*!
+ Sets the descriptors of this characteristic to \a descriptors. Only valid descriptors
+ are considered.
+ \sa addDescriptor()
+ */
+void QLowEnergyCharacteristicData::setDescriptors(const QList<QLowEnergyDescriptorData> &descriptors)
+{
+ foreach (const QLowEnergyDescriptorData &desc, descriptors)
+ addDescriptor(desc);
+}
+
+/*!
+ Adds \a descriptor to the list of descriptors of this characteristic, if it is valid.
+ \sa setDescriptors()
+ */
+void QLowEnergyCharacteristicData::addDescriptor(const QLowEnergyDescriptorData &descriptor)
+{
+ if (descriptor.isValid())
+ d->descriptors << descriptor;
+ else
+ qCWarning(QT_BT) << "not adding invalid descriptor to characteristic";
+}
+
+/*!
+ Specifies that clients need to fulfill \a constraints to read the value of this characteristic.
+ */
+void QLowEnergyCharacteristicData::setReadConstraints(QBluetooth::AttAccessConstraints constraints)
+{
+ d->readConstraints = constraints;
+}
+
+/*!
+ Returns the constraints needed for a client to read the value of this characteristic.
+ If \l properties() does not include \l QLowEnergyCharacteristic::Read, this value is irrelevant.
+ By default, there are no read constraints.
+ */
+QBluetooth::AttAccessConstraints QLowEnergyCharacteristicData::readConstraints() const
+{
+ return d->readConstraints;
+}
+
+/*!
+ Specifies that clients need to fulfill \a constraints to write the value of this characteristic.
+ */
+void QLowEnergyCharacteristicData::setWriteConstraints(QBluetooth::AttAccessConstraints constraints)
+{
+ d->writeConstraints = constraints;
+}
+
+/*!
+ Returns the constraints needed for a client to write the value of this characteristic.
+ If \l properties() does not include either of \l QLowEnergyCharacteristic::Write,
+ \l QLowEnergyCharacteristic::WriteNoResponse and \l QLowEnergyCharacteristic::WriteSigned,
+ this value is irrelevant.
+ By default, there are no write constraints.
+ */
+QBluetooth::AttAccessConstraints QLowEnergyCharacteristicData::writeConstraints() const
+{
+ return d->writeConstraints;
+}
+
+/*!
+ Specifies \a minimum and \a maximum to be the smallest and largest length, respectively,
+ that the value of this characteristic can have. The unit is bytes. If \a minimum and
+ \a maximum are equal, the characteristic has a fixed-length value.
+ */
+void QLowEnergyCharacteristicData::setValueLength(int minimum, int maximum)
+{
+ d->minimumValueLength = minimum;
+ d->maximumValueLength = qMax(minimum, maximum);
+}
+
+/*!
+ Returns the minimum length in bytes that the value of this characteristic can have.
+ The default is zero.
+ */
+int QLowEnergyCharacteristicData::minimumValueLength() const
+{
+ return d->minimumValueLength;
+}
+
+/*!
+ Returns the maximum length in bytes that the value of this characteristic can have.
+ By default, there is no limit beyond the constraints of the data type.
+ */
+int QLowEnergyCharacteristicData::maximumValueLength() const
+{
+ return d->maximumValueLength;
+}
+
+/*!
+ Returns true if and only if this characteristic is valid, that is, it has a non-null UUID.
+ */
+bool QLowEnergyCharacteristicData::isValid() const
+{
+ return !uuid().isNull();
+}
+
+/*!
+ \fn void QLowEnergyCharacteristicData::swap(QLowEnergyCharacteristicData &other)
+ Swaps this object with \a other.
+ */
+
+/*!
+ Returns \c true if \a cd1 and \a cd2 are equal with respect to their public state,
+ otherwise returns \c false.
+ */
+bool operator==(const QLowEnergyCharacteristicData &cd1, const QLowEnergyCharacteristicData &cd2)
+{
+ return cd1.d == cd2.d || (
+ cd1.uuid() == cd2.uuid()
+ && cd1.properties() == cd2.properties()
+ && cd1.descriptors() == cd2.descriptors()
+ && cd1.value() == cd2.value()
+ && cd1.readConstraints() == cd2.readConstraints()
+ && cd1.writeConstraints() == cd2.writeConstraints()
+ && cd1.minimumValueLength() == cd2.maximumValueLength()
+ && cd1.maximumValueLength() == cd2.maximumValueLength());
+}
+
+/*!
+ \fn bool operator!=(const QLowEnergyCharacteristicData &cd1,
+ const QLowEnergyCharacteristicData &cd2)
+ Returns \c true if \a cd1 and \a cd2 are not equal with respect to their public state,
+ otherwise returns \c false.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergycharacteristicdata.h b/src/bluetooth/qlowenergycharacteristicdata.h
new file mode 100644
index 00000000..56493198
--- /dev/null
+++ b/src/bluetooth/qlowenergycharacteristicdata.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** 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 QLOWENERGYCHARACTERISTICDATA_H
+#define QLOWENERGYCHARACTERISTICDATA_H
+
+#include <QtBluetooth/qlowenergycharacteristic.h>
+#include <QtCore/qshareddata.h>
+
+QT_BEGIN_NAMESPACE
+
+class QLowEnergyDescriptorData;
+struct QLowEnergyCharacteristicDataPrivate;
+class Q_BLUETOOTH_EXPORT QLowEnergyCharacteristicData
+{
+ friend Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyCharacteristicData &cd1,
+ const QLowEnergyCharacteristicData &cd2);
+public:
+ QLowEnergyCharacteristicData();
+ QLowEnergyCharacteristicData(const QLowEnergyCharacteristicData &other);
+ ~QLowEnergyCharacteristicData();
+
+ QLowEnergyCharacteristicData &operator=(const QLowEnergyCharacteristicData &other);
+
+ QBluetoothUuid uuid() const;
+ void setUuid(const QBluetoothUuid &uuid);
+
+ QByteArray value() const;
+ void setValue(const QByteArray &value);
+
+ QLowEnergyCharacteristic::PropertyTypes properties() const;
+ void setProperties(QLowEnergyCharacteristic::PropertyTypes properties);
+
+ QList<QLowEnergyDescriptorData> descriptors() const;
+ void setDescriptors(const QList<QLowEnergyDescriptorData> &descriptors);
+ void addDescriptor(const QLowEnergyDescriptorData &descriptor);
+
+ void setReadConstraints(QBluetooth::AttAccessConstraints constraints);
+ QBluetooth::AttAccessConstraints readConstraints() const;
+
+ void setWriteConstraints(QBluetooth::AttAccessConstraints constraints);
+ QBluetooth::AttAccessConstraints writeConstraints() const;
+
+ void setValueLength(int minimum, int maximum);
+ int minimumValueLength() const;
+ int maximumValueLength() const;
+
+ bool isValid() const;
+
+ void swap(QLowEnergyCharacteristicData &other) Q_DECL_NOTHROW { qSwap(d, other.d); }
+
+private:
+ QSharedDataPointer<QLowEnergyCharacteristicDataPrivate> d;
+};
+
+Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyCharacteristicData &cd1,
+ const QLowEnergyCharacteristicData &cd2);
+inline bool operator!=(const QLowEnergyCharacteristicData &cd1,
+ const QLowEnergyCharacteristicData &cd2)
+{
+ return !(cd1 == cd2);
+}
+
+Q_DECLARE_SHARED(QLowEnergyCharacteristicData)
+
+QT_END_NAMESPACE
+
+#endif // Include guard.
diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp
index 3fb68e19..8a6a2e1d 100644
--- a/src/bluetooth/qlowenergycontroller.cpp
+++ b/src/bluetooth/qlowenergycontroller.cpp
@@ -34,12 +34,19 @@
#include "qlowenergycontroller.h"
#include "qlowenergycontroller_p.h"
+#include "qlowenergycharacteristicdata.h"
+#include "qlowenergydescriptordata.h"
+#include "qlowenergyservicedata.h"
+
#include <QtBluetooth/QBluetoothLocalDevice>
+#include <QtCore/QLoggingCategory>
#include <algorithm>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(QT_BT)
+
/*!
\class QLowEnergyController
\inmodule QtBluetooth
@@ -49,9 +56,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 +67,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 +98,18 @@ 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.
+
+ 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 via calls to \l addService().
+ 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.
+
\sa QLowEnergyService, QLowEnergyCharacteristic, QLowEnergyDescriptor
+ \sa QLowEnergyAdvertisingParameters, QLowEnergyAdvertisingData
*/
/*!
@@ -113,6 +129,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 +146,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 +154,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 +221,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 +233,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()
*/
@@ -224,6 +268,9 @@ void QLowEnergyControllerPrivate::setError(
case QLowEnergyController::ConnectionError:
errorString = QLowEnergyController::tr("Error occurred trying to connect to remote device.");
break;
+ case QLowEnergyController::AdvertisingError:
+ errorString = QLowEnergyController::tr("Error occurred trying to start advertising");
+ break;
case QLowEnergyController::NoError:
return;
default:
@@ -261,6 +308,10 @@ void QLowEnergyControllerPrivate::setState(
return;
state = newState;
+ if (state == QLowEnergyController::UnconnectedState
+ && role == QLowEnergyController::PeripheralRole) {
+ remoteDevice.clear();
+ }
emit q->stateChanged(state);
}
@@ -409,6 +460,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 +477,7 @@ QLowEnergyController::QLowEnergyController(
the connection management.
\since 5.5
+ \obsolete
*/
QLowEnergyController::QLowEnergyController(
const QBluetoothDeviceInfo &remoteDeviceInfo,
@@ -433,6 +486,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 +516,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 +582,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 +594,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 +707,10 @@ void QLowEnergyController::discoverServices()
{
Q_D(QLowEnergyController);
+ if (d->role != CentralRole) {
+ qCWarning(QT_BT) << "Cannot discover services in peripheral role";
+ return;
+ }
if (d->state != QLowEnergyController::ConnectedState)
return;
@@ -619,7 +719,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 +773,115 @@ 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.
+ If \a parameters indicates that the advertisement should be connectable, then this function
+ also starts listening for incoming client connections.
+
+ 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 &parameters,
+ 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();
+}
+
+/*!
+ Constructs and returns a \l QLowEnergyService object with \a parent from \a service.
+ The controller must be in the \l PeripheralRole and in the \l UnconnectedState. The \a service
+ object must be valid.
+ */
+QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData &service,
+ QObject *parent)
+{
+ if (role() != PeripheralRole) {
+ qCWarning(QT_BT) << "Services can only be added in the peripheral role";
+ return nullptr;
+ }
+ if (state() != UnconnectedState) {
+ qCWarning(QT_BT) << "Services can only be added in unconnected state";
+ return nullptr;
+ }
+ if (!service.isValid()) {
+ qCWarning(QT_BT) << "Not adding invalid service";
+ return nullptr;
+ }
+
+ // Spec says services "should" be grouped by uuid length (16-bit first, then 128-bit).
+ // Since this is not mandatory, we ignore it here and let the caller take responsibility
+ // for it.
+
+ const auto servicePrivate = QSharedPointer<QLowEnergyServicePrivate>::create();
+ servicePrivate->state = QLowEnergyService::LocalService;
+ servicePrivate->setController(d_ptr);
+ servicePrivate->uuid = service.uuid();
+ servicePrivate->type = service.type() == QLowEnergyServiceData::ServiceTypePrimary
+ ? QLowEnergyService::PrimaryService : QLowEnergyService::IncludedService;
+ foreach (QLowEnergyService * const includedService, service.includedServices()) {
+ servicePrivate->includedServices << includedService->serviceUuid();
+ includedService->d_ptr->type |= QLowEnergyService::IncludedService;
+ }
+
+ // Spec v4.2, Vol 3, Part G, Section 3.
+ const QLowEnergyHandle oldLastHandle = d_ptr->lastLocalHandle;
+ servicePrivate->startHandle = ++d_ptr->lastLocalHandle; // Service declaration.
+ d_ptr->lastLocalHandle += servicePrivate->includedServices.count(); // Include declarations.
+ foreach (const QLowEnergyCharacteristicData &cd, service.characteristics()) {
+ const QLowEnergyHandle declHandle = ++d_ptr->lastLocalHandle;
+ QLowEnergyServicePrivate::CharData charData;
+ charData.valueHandle = ++d_ptr->lastLocalHandle;
+ charData.uuid = cd.uuid();
+ charData.properties = cd.properties();
+ charData.value = cd.value();
+ foreach (const QLowEnergyDescriptorData &dd, cd.descriptors()) {
+ QLowEnergyServicePrivate::DescData descData;
+ descData.uuid = dd.uuid();
+ descData.value = dd.value();
+ charData.descriptorList.insert(++d_ptr->lastLocalHandle, descData);
+ }
+ servicePrivate->characteristicList.insert(declHandle, charData);
+ }
+ servicePrivate->endHandle = d_ptr->lastLocalHandle;
+ const bool handleOverflow = d_ptr->lastLocalHandle <= oldLastHandle;
+ if (handleOverflow) {
+ qCWarning(QT_BT) << "Not enough attribute handles left to create this service";
+ d_ptr->lastLocalHandle = oldLastHandle;
+ return nullptr;
+ }
+
+ d_ptr->localServices.insert(servicePrivate->uuid, servicePrivate);
+ d_ptr->addToGenericAttributeList(service, servicePrivate->startHandle);
+ return new QLowEnergyService(servicePrivate, parent);
+}
+
+/*!
Returns the last occurred error or \l NoError.
*/
QLowEnergyController::Error QLowEnergyController::error() const
@@ -688,4 +898,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..cec0a8cf 100644
--- a/src/bluetooth/qlowenergycontroller.h
+++ b/src/bluetooth/qlowenergycontroller.h
@@ -38,11 +38,15 @@
#include <QtBluetooth/QBluetoothAddress>
#include <QtBluetooth/QBluetoothDeviceInfo>
#include <QtBluetooth/QBluetoothUuid>
+#include <QtBluetooth/QLowEnergyAdvertisingData>
#include <QtBluetooth/QLowEnergyService>
QT_BEGIN_NAMESPACE
+class QLowEnergyAdvertisingParameters;
class QLowEnergyControllerPrivate;
+class QLowEnergyServiceData;
+
class Q_BLUETOOTH_EXPORT QLowEnergyController : public QObject
{
Q_OBJECT
@@ -53,7 +57,8 @@ public:
UnknownRemoteDeviceError,
NetworkError,
InvalidBluetoothAdapterError,
- ConnectionError
+ ConnectionError,
+ AdvertisingError,
};
Q_ENUM(Error)
@@ -63,7 +68,8 @@ public:
ConnectedState,
DiscoveringState,
DiscoveredState,
- ClosingState
+ ClosingState,
+ AdvertisingState,
};
Q_ENUM(ControllerState)
@@ -73,6 +79,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 +89,13 @@ 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);
+
+ // TODO: Allow to set connection timeout (disconnect when no data has been exchanged for n seconds).
+
~QLowEnergyController();
QBluetoothAddress localAddress() const;
@@ -100,9 +116,18 @@ public:
QLowEnergyService *createServiceObject(
const QBluetoothUuid &service, QObject *parent = 0);
+ void startAdvertising(const QLowEnergyAdvertisingParameters &parameters,
+ const QLowEnergyAdvertisingData &advertisingData,
+ const QLowEnergyAdvertisingData &scanResponseData = QLowEnergyAdvertisingData());
+ void stopAdvertising();
+
+ QLowEnergyService *addService(const QLowEnergyServiceData &service, QObject *parent = 0);
+
Error error() const;
QString errorString() const;
+ Role role() const;
+
Q_SIGNALS:
void connected();
void disconnected();
@@ -113,6 +138,8 @@ Q_SIGNALS:
void discoveryFinished();
private:
+ explicit QLowEnergyController(QObject *parent = 0); // For the peripheral role.
+
Q_DECLARE_PRIVATE(QLowEnergyController)
QLowEnergyControllerPrivate *d_ptr;
};
@@ -122,5 +149,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 cb08b6f9..3e3ea830 100644
--- a/src/bluetooth/qlowenergycontroller_android.cpp
+++ b/src/bluetooth/qlowenergycontroller_android.cpp
@@ -589,4 +589,26 @@ void QLowEnergyControllerPrivate::serviceError(
service->setError(errorCode);
}
+void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters &params,
+ 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";
+}
+
+void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &service,
+ QLowEnergyHandle startHandle)
+{
+ Q_UNUSED(service);
+ Q_UNUSED(startHandle);
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp
index a00ac565..a69bd2c4 100644
--- a/src/bluetooth/qlowenergycontroller_bluez.cpp
+++ b/src/bluetooth/qlowenergycontroller_bluez.cpp
@@ -34,24 +34,35 @@
#include "qlowenergycontroller_p.h"
#include "qbluetoothsocket_p.h"
+#include "qleadvertiser_p.h"
#include "bluez/bluez_data_p.h"
#include "bluez/hcimanager_p.h"
#include <QtCore/QLoggingCategory>
+#include <QtBluetooth/QBluetoothLocalDevice>
#include <QtBluetooth/QBluetoothSocket>
+#include <QtBluetooth/QLowEnergyCharacteristicData>
+#include <QtBluetooth/QLowEnergyDescriptorData>
#include <QtBluetooth/QLowEnergyService>
+#include <QtBluetooth/QLowEnergyServiceData>
+#include <algorithm>
+#include <climits>
+#include <cstring>
#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
#define ATTRIBUTE_CHANNEL_ID 4
#define ATT_DEFAULT_LE_MTU 23
#define ATT_MAX_LE_MTU 0x200
-#define GATT_PRIMARY_SERVICE 0x2800
-#define GATT_SECONDARY_SERVICE 0x2801
-#define GATT_INCLUDED_SERVICE 0x2802
-#define GATT_CHARACTERISTIC 0x2803
+#define GATT_PRIMARY_SERVICE quint16(0x2800)
+#define GATT_SECONDARY_SERVICE quint16(0x2801)
+#define GATT_INCLUDED_SERVICE quint16(0x2802)
+#define GATT_CHARACTERISTIC quint16(0x2803)
// GATT commands
#define ATT_OP_ERROR_RESPONSE 0x1
@@ -59,12 +70,16 @@
#define ATT_OP_EXCHANGE_MTU_RESPONSE 0x3 //receive server MTU
#define ATT_OP_FIND_INFORMATION_REQUEST 0x4 //discover individual attribute info
#define ATT_OP_FIND_INFORMATION_RESPONSE 0x5
+#define ATT_OP_FIND_BY_TYPE_VALUE_REQUEST 0x6
+#define ATT_OP_FIND_BY_TYPE_VALUE_RESPONSE 0x7
#define ATT_OP_READ_BY_TYPE_REQUEST 0x8 //discover characteristics
#define ATT_OP_READ_BY_TYPE_RESPONSE 0x9
#define ATT_OP_READ_REQUEST 0xA //read characteristic & descriptor values
#define ATT_OP_READ_RESPONSE 0xB
#define ATT_OP_READ_BLOB_REQUEST 0xC //read values longer than MTU-1
#define ATT_OP_READ_BLOB_RESPONSE 0xD
+#define ATT_OP_READ_MULTIPLE_REQUEST 0xE
+#define ATT_OP_READ_MULTIPLE_RESPONSE 0xF
#define ATT_OP_READ_BY_GROUP_REQUEST 0x10 //discover services
#define ATT_OP_READ_BY_GROUP_RESPONSE 0x11
#define ATT_OP_WRITE_REQUEST 0x12 //write characteristic with response
@@ -77,6 +92,7 @@
#define ATT_OP_HANDLE_VAL_INDICATION 0x1d //informs about value change -> requires reply
#define ATT_OP_HANDLE_VAL_CONFIRMATION 0x1e //answer for ATT_OP_HANDLE_VAL_INDICATION
#define ATT_OP_WRITE_COMMAND 0x52 //write characteristic without response
+#define ATT_OP_SIGNED_WRITE_COMMAND 0x2D
//GATT command sizes in bytes
#define ERROR_RESPONSE_HEADER_SIZE 5
@@ -118,6 +134,10 @@ QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
+using namespace QBluetooth;
+
+const int maxPrepareQueueSize = 1024;
+
static inline QBluetoothUuid convert_uuid128(const quint128 *p)
{
quint128 dst_hostOrder, dst_bigEndian;
@@ -195,15 +215,44 @@ static void dumpErrorInformation(const QByteArray &response)
<< "handle:" << handle;
}
+static int getUuidSize(const QBluetoothUuid &uuid)
+{
+ return uuid.minimumSize() == 2 ? 2 : 16;
+}
+
+template<typename T> static void putDataAndIncrement(const T &src, char *&dst)
+{
+ putBtData(src, dst);
+ dst += sizeof(T);
+}
+template<> void putDataAndIncrement(const QBluetoothUuid &uuid, char *&dst)
+{
+ const int uuidSize = getUuidSize(uuid);
+ if (uuidSize == 2)
+ putBtData(uuid.toUInt16(), dst);
+ else
+ putBtData(uuid.toUInt128(), dst);
+ dst += uuidSize;
+}
+template<> void putDataAndIncrement(const QByteArray &value, char *&dst)
+{
+ using namespace std;
+ memcpy(dst, value.constData(), value.count());
+ dst += value.count();
+}
+
QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
: QObject(),
state(QLowEnergyController::UnconnectedState),
error(QLowEnergyController::NoError),
+ lastLocalHandle(0),
l2cpSocket(0), requestPending(false),
mtuSize(ATT_DEFAULT_LE_MTU),
securityLevelValue(-1),
encryptionChangePending(false),
- hciManager(0)
+ hciManager(0),
+ advertiser(0),
+ serverSocketNotifier(0)
{
registerQLowEnergyControllerMetaType();
qRegisterMetaType<QList<QLowEnergyHandle> >();
@@ -219,6 +268,95 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate()
{
+ closeServerSocket();
+}
+
+class ServerSocket
+{
+public:
+ bool listen(const QBluetoothAddress &localAdapter)
+ {
+ m_socket = ::socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+ if (m_socket == -1) {
+ qCWarning(QT_BT_BLUEZ) << "socket creation failed:" << qt_error_string(errno);
+ return false;
+ }
+ sockaddr_l2 addr;
+
+ // memset should be in std namespace for C++ compilers, but we also need to support
+ // broken ones that put it in the global one.
+ using namespace std;
+ memset(&addr, 0, sizeof addr);
+
+ addr.l2_family = AF_BLUETOOTH;
+ addr.l2_cid = htobs(ATTRIBUTE_CHANNEL_ID);
+ addr.l2_bdaddr_type = BDADDR_LE_PUBLIC;
+ convertAddress(localAdapter.toUInt64(), addr.l2_bdaddr.b);
+ if (::bind(m_socket, reinterpret_cast<sockaddr *>(&addr), sizeof addr) == -1) {
+ qCWarning(QT_BT_BLUEZ) << "bind() failed:" << qt_error_string(errno);
+ return false;
+ }
+ if (::listen(m_socket, 1)) {
+ qCWarning(QT_BT_BLUEZ) << "listen() failed:" << qt_error_string(errno);
+ return false;
+ }
+ return true;
+ }
+
+ ~ServerSocket()
+ {
+ if (m_socket != -1)
+ close(m_socket);
+ }
+
+ int takeSocket()
+ {
+ const int socket = m_socket;
+ m_socket = -1;
+ return socket;
+ }
+
+private:
+ int m_socket = -1;
+};
+
+void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters &params,
+ 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();
+ if (params.mode() == QLowEnergyAdvertisingParameters::AdvNonConnInd
+ || params.mode() == QLowEnergyAdvertisingParameters::AdvScanInd) {
+ qCDebug(QT_BT_BLUEZ) << "Non-connectable advertising requested, "
+ "not listening for connections.";
+ return;
+ }
+
+ ServerSocket serverSocket;
+ if (!serverSocket.listen(localAdapter)) {
+ setError(QLowEnergyController::AdvertisingError);
+ setState(QLowEnergyController::UnconnectedState);
+ return;
+ }
+
+ const int socketFd = serverSocket.takeSocket();
+ serverSocketNotifier = new QSocketNotifier(socketFd, QSocketNotifier::Read, this);
+ connect(serverSocketNotifier, &QSocketNotifier::activated, this,
+ &QLowEnergyControllerPrivate::handleConnectionRequest);
+}
+
+void QLowEnergyControllerPrivate::stopAdvertising()
+{
+ setState(QLowEnergyController::UnconnectedState);
+ advertiser->stopAdvertising();
}
void QLowEnergyControllerPrivate::connectToDevice()
@@ -296,7 +434,10 @@ void QLowEnergyControllerPrivate::l2cpDisconnected()
{
Q_Q(QLowEnergyController);
- securityLevelValue = -1;
+ if (role == QLowEnergyController::PeripheralRole)
+ storeClientConfigurations();
+ invalidateServices();
+ resetController();
setState(QLowEnergyController::UnconnectedState);
emit q->disconnected();
}
@@ -333,23 +474,28 @@ void QLowEnergyControllerPrivate::l2cpErrorChanged(QBluetoothSocket::SocketError
void QLowEnergyControllerPrivate::resetController()
{
openRequests.clear();
+ openPrepareWriteRequests.clear();
+ scheduledIndications.clear();
+ indicationInFlight = false;
requestPending = false;
encryptionChangePending = false;
+ receivedMtuExchangeRequest = false;
securityLevelValue = -1;
}
void QLowEnergyControllerPrivate::l2cpReadyRead()
{
- const QByteArray reply = l2cpSocket->readAll();
- qCDebug(QT_BT_BLUEZ) << "Received size:" << reply.size() << "data:" << reply.toHex();
- if (reply.isEmpty())
+ const QByteArray incomingPacket = l2cpSocket->readAll();
+ qCDebug(QT_BT_BLUEZ) << "Received size:" << incomingPacket.size() << "data:"
+ << incomingPacket.toHex();
+ if (incomingPacket.isEmpty())
return;
- const quint8 command = reply.constData()[0];
+ const quint8 command = incomingPacket.constData()[0];
switch (command) {
case ATT_OP_HANDLE_VAL_NOTIFICATION:
{
- processUnsolicitedReply(reply);
+ processUnsolicitedReply(incomingPacket);
return;
}
case ATT_OP_HANDLE_VAL_INDICATION:
@@ -357,31 +503,55 @@ void QLowEnergyControllerPrivate::l2cpReadyRead()
//send confirmation
QByteArray packet;
packet.append(static_cast<char>(ATT_OP_HANDLE_VAL_CONFIRMATION));
- sendCommand(packet);
+ sendPacket(packet);
- processUnsolicitedReply(reply);
+ processUnsolicitedReply(incomingPacket);
return;
}
+
case ATT_OP_EXCHANGE_MTU_REQUEST:
- case ATT_OP_READ_BY_GROUP_REQUEST:
+ handleExchangeMtuRequest(incomingPacket);
+ return;
+ case ATT_OP_FIND_INFORMATION_REQUEST:
+ handleFindInformationRequest(incomingPacket);
+ return;
+ case ATT_OP_FIND_BY_TYPE_VALUE_REQUEST:
+ handleFindByTypeValueRequest(incomingPacket);
+ return;
case ATT_OP_READ_BY_TYPE_REQUEST:
+ handleReadByTypeRequest(incomingPacket);
+ return;
case ATT_OP_READ_REQUEST:
- case ATT_OP_FIND_INFORMATION_REQUEST:
+ handleReadRequest(incomingPacket);
+ return;
+ case ATT_OP_READ_BLOB_REQUEST:
+ handleReadBlobRequest(incomingPacket);
+ return;
+ case ATT_OP_READ_MULTIPLE_REQUEST:
+ handleReadMultipleRequest(incomingPacket);
+ return;
+ case ATT_OP_READ_BY_GROUP_REQUEST:
+ handleReadByGroupTypeRequest(incomingPacket);
+ return;
case ATT_OP_WRITE_REQUEST:
- {
- qCDebug(QT_BT_BLUEZ) << "Server request" << hex << command;
-
- //send not supported
- QByteArray packet(ERROR_RESPONSE_HEADER_SIZE, Qt::Uninitialized);
- packet[0] = ATT_OP_ERROR_RESPONSE;
- packet[1] = command;
- bt_put_unaligned(htobs(0), (quint16 *)(packet.data() + 2));
- packet[4] = ATT_ERROR_REQUEST_NOT_SUPPORTED;
-
- sendCommand(packet);
-
+ case ATT_OP_WRITE_COMMAND:
+ case ATT_OP_SIGNED_WRITE_COMMAND:
+ handleWriteRequestOrCommand(incomingPacket);
+ return;
+ case ATT_OP_PREPARE_WRITE_REQUEST:
+ handlePrepareWriteRequest(incomingPacket);
+ return;
+ case ATT_OP_EXECUTE_WRITE_REQUEST:
+ handleExecuteWriteRequest(incomingPacket);
+ return;
+ case ATT_OP_HANDLE_VAL_CONFIRMATION:
+ if (indicationInFlight) {
+ indicationInFlight = false;
+ sendNextIndication();
+ } else {
+ qCWarning(QT_BT_BLUEZ) << "received unexpected handle value confirmation";
+ }
return;
- }
default:
//only solicited replies finish pending requests
requestPending = false;
@@ -390,7 +560,7 @@ void QLowEnergyControllerPrivate::l2cpReadyRead()
Q_ASSERT(!openRequests.isEmpty());
const Request request = openRequests.dequeue();
- processReply(request, reply);
+ processReply(request, incomingPacket);
sendNextPendingRequest();
}
@@ -453,12 +623,12 @@ void QLowEnergyControllerPrivate::encryptionChangedEvent(
sendNextPendingRequest();
}
-void QLowEnergyControllerPrivate::sendCommand(const QByteArray &packet)
+void QLowEnergyControllerPrivate::sendPacket(const QByteArray &packet)
{
qint64 result = l2cpSocket->write(packet.constData(),
packet.size());
if (result == -1) {
- qCDebug(QT_BT_BLUEZ) << "Cannot write L2CP command:" << hex
+ qCDebug(QT_BT_BLUEZ) << "Cannot write L2CP packet:" << hex
<< packet.toHex()
<< l2cpSocket->errorString();
setError(QLowEnergyController::NetworkError);
@@ -475,7 +645,7 @@ void QLowEnergyControllerPrivate::sendNextPendingRequest()
// << request.payload.toHex();
requestPending = true;
- sendCommand(request.payload);
+ sendPacket(request.payload);
}
QLowEnergyHandle parseReadByTypeCharDiscovery(
@@ -1072,9 +1242,9 @@ void QLowEnergyControllerPrivate::sendReadByGroupRequest(
quint8 packet[GRP_TYPE_REQ_HEADER_SIZE];
packet[0] = ATT_OP_READ_BY_GROUP_REQUEST;
- bt_put_unaligned(htobs(start), (quint16 *) &packet[1]);
- bt_put_unaligned(htobs(end), (quint16 *) &packet[3]);
- bt_put_unaligned(htobs(type), (quint16 *) &packet[5]);
+ putBtData(start, &packet[1]);
+ putBtData(end, &packet[3]);
+ putBtData(type, &packet[5]);
QByteArray data(GRP_TYPE_REQ_HEADER_SIZE, Qt::Uninitialized);
memcpy(data.data(), packet, GRP_TYPE_REQ_HEADER_SIZE);
@@ -1110,9 +1280,9 @@ void QLowEnergyControllerPrivate::sendReadByTypeRequest(
quint8 packet[READ_BY_TYPE_REQ_HEADER_SIZE];
packet[0] = ATT_OP_READ_BY_TYPE_REQUEST;
- bt_put_unaligned(htobs(nextHandle), (quint16 *) &packet[1]);
- bt_put_unaligned(htobs(serviceData->endHandle), (quint16 *) &packet[3]);
- bt_put_unaligned(htobs(attributeType), (quint16 *) &packet[5]);
+ putBtData(nextHandle, &packet[1]);
+ putBtData(serviceData->endHandle, &packet[3]);
+ putBtData(attributeType, &packet[5]);
QByteArray data(READ_BY_TYPE_REQ_HEADER_SIZE, Qt::Uninitialized);
memcpy(data.data(), packet, READ_BY_TYPE_REQ_HEADER_SIZE);
@@ -1206,7 +1376,7 @@ void QLowEnergyControllerPrivate::readServiceValues(
for (int i = 0; i < targetHandles.count(); i++) {
pair = targetHandles.at(i);
packet[0] = ATT_OP_READ_REQUEST;
- bt_put_unaligned(htobs(pair.first), (quint16 *) &packet[1]);
+ putBtData(pair.first, &packet[1]);
QByteArray data(READ_REQUEST_HEADER_SIZE, Qt::Uninitialized);
memcpy(data.data(), packet, READ_REQUEST_HEADER_SIZE);
@@ -1261,8 +1431,8 @@ void QLowEnergyControllerPrivate::readServiceValuesByOffset(
}
}
- bt_put_unaligned(htobs(handleToRead), (quint16 *) &packet[1]);
- bt_put_unaligned(htobs(offset), (quint16 *) &packet[3]);
+ putBtData(handleToRead, &packet[1]);
+ putBtData(offset, &packet[3]);
QByteArray data(READ_BLOB_REQUEST_HEADER_SIZE, Qt::Uninitialized);
memcpy(data.data(), packet, READ_BLOB_REQUEST_HEADER_SIZE);
@@ -1325,7 +1495,7 @@ void QLowEnergyControllerPrivate::exchangeMTU()
quint8 packet[MTU_EXCHANGE_HEADER_SIZE];
packet[0] = ATT_OP_EXCHANGE_MTU_REQUEST;
- bt_put_unaligned(htobs(ATT_MAX_LE_MTU), (quint16 *) &packet[1]);
+ putBtData(quint16(ATT_MAX_LE_MTU), &packet[1]);
QByteArray data(MTU_EXCHANGE_HEADER_SIZE, Qt::Uninitialized);
memcpy(data.data(), packet, MTU_EXCHANGE_HEADER_SIZE);
@@ -1443,8 +1613,8 @@ void QLowEnergyControllerPrivate::discoverNextDescriptor(
else
charEndHandle = pendingCharHandles[1] - 1;
- bt_put_unaligned(htobs(charStartHandle), (quint16 *) &packet[1]);
- bt_put_unaligned(htobs(charEndHandle), (quint16 *) &packet[3]);
+ putBtData(charStartHandle, &packet[1]);
+ putBtData(charEndHandle, &packet[3]);
QByteArray data(FIND_INFO_REQUEST_HEADER_SIZE, Qt::Uninitialized);
memcpy(data.data(), packet, FIND_INFO_REQUEST_HEADER_SIZE);
@@ -1479,8 +1649,8 @@ void QLowEnergyControllerPrivate::sendNextPrepareWriteRequest(
quint8 packet[PREPARE_WRITE_HEADER_SIZE];
packet[0] = ATT_OP_PREPARE_WRITE_REQUEST;
- bt_put_unaligned(htobs(targetHandle), (quint16 *) &packet[1]); // attribute handle
- bt_put_unaligned(htobs(offset), (quint16 *) &packet[3]); // offset into newValue
+ putBtData(targetHandle, &packet[1]); // attribute handle
+ putBtData(offset, &packet[3]); // offset into newValue
qCDebug(QT_BT_BLUEZ) << "Writing long characteristic (prepare):"
<< hex << handle;
@@ -1557,49 +1727,13 @@ void QLowEnergyControllerPrivate::writeCharacteristic(
if (!service->characteristicList.contains(charHandle))
return;
- const QLowEnergyHandle valueHandle = service->characteristicList[charHandle].valueHandle;
- const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
-
- quint8 packet[WRITE_REQUEST_HEADER_SIZE];
- if (writeWithResponse) {
- if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
- sendNextPrepareWriteRequest(charHandle, newValue, 0);
- sendNextPendingRequest();
- return;
- } else {
- // write value fits into single package
- packet[0] = ATT_OP_WRITE_REQUEST;
- }
+ QLowEnergyServicePrivate::CharData &charData = service->characteristicList[charHandle];
+ if (role == QLowEnergyController::PeripheralRole) {
+ writeCharacteristicForPeripheral(charData, newValue);
} else {
- // write without response
- packet[0] = ATT_OP_WRITE_COMMAND;
- }
-
- bt_put_unaligned(htobs(valueHandle), (quint16 *) &packet[1]);
-
- QByteArray data(size, Qt::Uninitialized);
- memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
- memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
-
- qCDebug(QT_BT_BLUEZ) << "Writing characteristic" << hex << charHandle
- << "(size:" << size << "with response:" << writeWithResponse << ")";
-
- // Advantage of write without response is the quick turnaround.
- // It can be send at any time and does not produce responses.
- // Therefore we will not put them into the openRequest queue at all.
- if (!writeWithResponse) {
- sendCommand(data);
- return;
+ writeCharacteristicForCentral(charHandle, charData.valueHandle, newValue,
+ writeWithResponse);
}
-
- Request request;
- request.payload = data;
- request.command = ATT_OP_WRITE_REQUEST;
- request.reference = charHandle;
- request.reference2 = newValue;
- openRequests.enqueue(request);
-
- sendNextPendingRequest();
}
void QLowEnergyControllerPrivate::writeDescriptor(
@@ -1610,32 +1744,10 @@ void QLowEnergyControllerPrivate::writeDescriptor(
{
Q_ASSERT(!service.isNull());
- if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
- sendNextPrepareWriteRequest(descriptorHandle, newValue, 0);
- sendNextPendingRequest();
- return;
- }
-
- quint8 packet[WRITE_REQUEST_HEADER_SIZE];
- packet[0] = ATT_OP_WRITE_REQUEST;
- bt_put_unaligned(htobs(descriptorHandle), (quint16 *) &packet[1]);
-
- const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
- QByteArray data(size, Qt::Uninitialized);
- memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
- memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
-
- qCDebug(QT_BT_BLUEZ) << "Writing descriptor" << hex << descriptorHandle
- << "(size:" << size << ")";
-
- Request request;
- request.payload = data;
- request.command = ATT_OP_WRITE_REQUEST;
- request.reference = (charHandle | (descriptorHandle << 16));
- request.reference2 = newValue;
- openRequests.enqueue(request);
-
- sendNextPendingRequest();
+ if (role == QLowEnergyController::PeripheralRole)
+ writeDescriptorForPeripheral(service, charHandle, descriptorHandle, newValue);
+ else
+ writeDescriptorForCentral(charHandle, descriptorHandle, newValue);
}
/*!
@@ -1662,7 +1774,7 @@ void QLowEnergyControllerPrivate::readCharacteristic(
quint8 packet[READ_REQUEST_HEADER_SIZE];
packet[0] = ATT_OP_READ_REQUEST;
- bt_put_unaligned(htobs(charDetails.valueHandle), (quint16 *) &packet[1]);
+ putBtData(charDetails.valueHandle, &packet[1]);
QByteArray data(READ_REQUEST_HEADER_SIZE, Qt::Uninitialized);
memcpy(data.data(), packet, READ_REQUEST_HEADER_SIZE);
@@ -1697,7 +1809,7 @@ void QLowEnergyControllerPrivate::readDescriptor(
quint8 packet[READ_REQUEST_HEADER_SIZE];
packet[0] = ATT_OP_READ_REQUEST;
- bt_put_unaligned(htobs(descriptorHandle), (quint16 *) &packet[1]);
+ putBtData(descriptorHandle, &packet[1]);
QByteArray data(READ_REQUEST_HEADER_SIZE, Qt::Uninitialized);
memcpy(data.data(), packet, READ_REQUEST_HEADER_SIZE);
@@ -1746,4 +1858,1092 @@ bool QLowEnergyControllerPrivate::increaseEncryptLevelfRequired(quint8 errorCode
return false;
}
+void QLowEnergyControllerPrivate::handleAdvertisingError()
+{
+ qCWarning(QT_BT_BLUEZ) << "received advertising error";
+ setError(QLowEnergyController::AdvertisingError);
+ setState(QLowEnergyController::UnconnectedState);
+}
+
+bool QLowEnergyControllerPrivate::checkPacketSize(const QByteArray &packet, int minSize,
+ int maxSize)
+{
+ if (maxSize == -1)
+ maxSize = minSize;
+ if (Q_LIKELY(packet.count() >= minSize && packet.count() <= maxSize))
+ return true;
+ qCWarning(QT_BT_BLUEZ) << "client request of type" << packet.at(0)
+ << "has unexpected packet size" << packet.count();
+ sendErrorResponse(packet.at(0), 0, ATT_ERROR_INVALID_PDU);
+ return false;
+}
+
+bool QLowEnergyControllerPrivate::checkHandle(const QByteArray &packet, QLowEnergyHandle handle)
+{
+ if (handle != 0 && handle <= lastLocalHandle)
+ return true;
+ sendErrorResponse(packet.at(0), handle, ATT_ERROR_INVALID_HANDLE);
+ return false;
+}
+
+bool QLowEnergyControllerPrivate::checkHandlePair(quint8 request, QLowEnergyHandle startingHandle,
+ QLowEnergyHandle endingHandle)
+{
+ if (startingHandle == 0 || startingHandle > endingHandle) {
+ qCDebug(QT_BT_BLUEZ) << "handle range invalid";
+ sendErrorResponse(request, startingHandle, ATT_ERROR_INVALID_HANDLE);
+ return false;
+ }
+ return true;
+}
+
+void QLowEnergyControllerPrivate::handleExchangeMtuRequest(const QByteArray &packet)
+{
+ // Spec v4.2, Vol 3, Part F, 3.4.2
+
+ if (!checkPacketSize(packet, 3))
+ return;
+ if (receivedMtuExchangeRequest) { // Client must only send this once per connection.
+ qCDebug(QT_BT_BLUEZ) << "Client sent extraneous MTU exchange packet";
+ sendErrorResponse(packet.at(0), 0, ATT_ERROR_REQUEST_NOT_SUPPORTED);
+ return;
+ }
+ receivedMtuExchangeRequest = true;
+
+ // Send reply.
+ QByteArray reply(MTU_EXCHANGE_HEADER_SIZE, Qt::Uninitialized);
+ reply[0] = ATT_OP_EXCHANGE_MTU_RESPONSE;
+ putBtData(static_cast<quint16>(ATT_MAX_LE_MTU), reply.data() + 1);
+ sendPacket(reply);
+
+ // Apply requested MTU.
+ const quint16 clientRxMtu = bt_get_le16(packet.constData() + 1);
+ mtuSize = qMax<quint16>(ATT_DEFAULT_LE_MTU, qMin<quint16>(clientRxMtu, ATT_MAX_LE_MTU));
+ qCDebug(QT_BT_BLUEZ) << "MTU request from client:" << clientRxMtu
+ << "effective client RX MTU:" << mtuSize;
+ qCDebug(QT_BT_BLUEZ) << "Sending server RX MTU" << ATT_MAX_LE_MTU;
+}
+
+void QLowEnergyControllerPrivate::handleFindInformationRequest(const QByteArray &packet)
+{
+ // Spec v4.2, Vol 3, Part F, 3.4.3.1-2
+
+ if (!checkPacketSize(packet, 5))
+ return;
+ const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1);
+ const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3);
+ qCDebug(QT_BT_BLUEZ) << "client sends find information request; start:" << startingHandle
+ << "end:" << endingHandle;
+ if (!checkHandlePair(packet.at(0), startingHandle, endingHandle))
+ return;
+
+ QVector<Attribute> results = getAttributes(startingHandle, endingHandle);
+ if (results.isEmpty()) {
+ sendErrorResponse(packet.at(0), startingHandle, ATT_ERROR_ATTRIBUTE_NOT_FOUND);
+ return;
+ }
+ ensureUniformUuidSizes(results);
+
+ QByteArray responsePrefix(2, Qt::Uninitialized);
+ const int uuidSize = getUuidSize(results.first().type);
+ responsePrefix[0] = ATT_OP_FIND_INFORMATION_RESPONSE;
+ responsePrefix[1] = uuidSize == 2 ? 0x1 : 0x2;
+ const int elementSize = sizeof(QLowEnergyHandle) + uuidSize;
+ const auto elemWriter = [](const Attribute &attr, char *&data) {
+ putDataAndIncrement(attr.handle, data);
+ putDataAndIncrement(attr.type, data);
+ };
+ sendListResponse(responsePrefix, elementSize, results, elemWriter);
+
+}
+
+void QLowEnergyControllerPrivate::handleFindByTypeValueRequest(const QByteArray &packet)
+{
+ // Spec v4.2, Vol 3, Part F, 3.4.3.3-4
+
+ if (!checkPacketSize(packet, 7, mtuSize))
+ return;
+ const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1);
+ const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3);
+ const quint16 type = bt_get_le16(packet.constData() + 5);
+ const QByteArray value = QByteArray::fromRawData(packet.constData() + 7, packet.count() - 7);
+ qCDebug(QT_BT_BLUEZ) << "client sends find by type value request; start:" << startingHandle
+ << "end:" << endingHandle << "type:" << type
+ << "value:" << value.toHex();
+ if (!checkHandlePair(packet.at(0), startingHandle, endingHandle))
+ return;
+
+ const auto predicate = [value, this, type](const Attribute &attr) {
+ return attr.type == QBluetoothUuid(type) && attr.value == value
+ && checkReadPermissions(attr) == 0;
+ };
+ const QVector<Attribute> results = getAttributes(startingHandle, endingHandle, predicate);
+ if (results.isEmpty()) {
+ sendErrorResponse(packet.at(0), startingHandle, ATT_ERROR_ATTRIBUTE_NOT_FOUND);
+ return;
+ }
+
+ QByteArray responsePrefix(1, ATT_OP_FIND_BY_TYPE_VALUE_RESPONSE);
+ const int elemSize = 2 * sizeof(QLowEnergyHandle);
+ const auto elemWriter = [](const Attribute &attr, char *&data) {
+ putDataAndIncrement(attr.handle, data);
+ putDataAndIncrement(attr.groupEndHandle, data);
+ };
+ sendListResponse(responsePrefix, elemSize, results, elemWriter);
+}
+
+void QLowEnergyControllerPrivate::handleReadByTypeRequest(const QByteArray &packet)
+{
+ // Spec v4.2, Vol 3, Part F, 3.4.4.1-2
+
+ if (!checkPacketSize(packet, 7, 21))
+ return;
+ const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1);
+ const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3);
+ const void * const typeStart = packet.constData() + 5;
+ const bool is16BitUuid = packet.count() == 7;
+ const bool is128BitUuid = packet.count() == 21;
+ QBluetoothUuid type;
+ if (is16BitUuid) {
+ type = QBluetoothUuid(bt_get_le16(typeStart));
+ } else if (is128BitUuid) {
+ type = QBluetoothUuid(convert_uuid128(reinterpret_cast<const quint128 *>(typeStart)));
+ } else {
+ qCWarning(QT_BT_BLUEZ) << "read by type request has invalid packet size" << packet.count();
+ sendErrorResponse(packet.at(0), 0, ATT_ERROR_INVALID_PDU);
+ return;
+ }
+ qCDebug(QT_BT_BLUEZ) << "client sends read by type request, start:" << startingHandle
+ << "end:" << endingHandle << "type:" << type;
+ if (!checkHandlePair(packet.at(0), startingHandle, endingHandle))
+ return;
+
+ // Get all attributes with matching type.
+ QVector<Attribute> results = getAttributes(startingHandle, endingHandle,
+ [type](const Attribute &attr) { return attr.type == type; });
+ ensureUniformValueSizes(results);
+
+ if (results.isEmpty()) {
+ sendErrorResponse(packet.at(0), startingHandle, ATT_ERROR_ATTRIBUTE_NOT_FOUND);
+ return;
+ }
+
+ const int error = checkReadPermissions(results);
+ if (error) {
+ sendErrorResponse(packet.at(0), results.first().handle, error);
+ return;
+ }
+
+ const int elementSize = sizeof(QLowEnergyHandle) + results.first().value.count();
+ QByteArray responsePrefix(2, Qt::Uninitialized);
+ responsePrefix[0] = ATT_OP_READ_BY_TYPE_RESPONSE;
+ responsePrefix[1] = elementSize;
+ const auto elemWriter = [](const Attribute &attr, char *&data) {
+ putDataAndIncrement(attr.handle, data);
+ putDataAndIncrement(attr.value, data);
+ };
+ sendListResponse(responsePrefix, elementSize, results, elemWriter);
+}
+
+void QLowEnergyControllerPrivate::handleReadRequest(const QByteArray &packet)
+{
+ // Spec v4.2, Vol 3, Part F, 3.4.4.3-4
+
+ if (!checkPacketSize(packet, 3))
+ return;
+ const QLowEnergyHandle handle = bt_get_le16(packet.constData() + 1);
+ qCDebug(QT_BT_BLUEZ) << "client sends read request; handle:" << handle;
+
+ if (!checkHandle(packet, handle))
+ return;
+ const Attribute &attribute = localAttributes.at(handle);
+ const int permissionsError = checkReadPermissions(attribute);
+ if (permissionsError) {
+ sendErrorResponse(packet.at(0), handle, permissionsError);
+ return;
+ }
+
+ const int sentValueLength = qMin(attribute.value.count(), mtuSize - 1);
+ QByteArray response(1 + sentValueLength, Qt::Uninitialized);
+ response[0] = ATT_OP_READ_RESPONSE;
+ using namespace std;
+ memcpy(response.data() + 1, attribute.value.constData(), sentValueLength);
+ qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex();
+ sendPacket(response);
+}
+
+void QLowEnergyControllerPrivate::handleReadBlobRequest(const QByteArray &packet)
+{
+ // Spec v4.2, Vol 3, Part F, 3.4.4.5-6
+
+ if (!checkPacketSize(packet, 5))
+ return;
+ const QLowEnergyHandle handle = bt_get_le16(packet.constData() + 1);
+ const quint16 valueOffset = bt_get_le16(packet.constData() + 3);
+ qCDebug(QT_BT_BLUEZ) << "client sends read blob request; handle:" << handle
+ << "offset:" << valueOffset;
+
+ if (!checkHandle(packet, handle))
+ return;
+ const Attribute &attribute = localAttributes.at(handle);
+ const int permissionsError = checkReadPermissions(attribute);
+ if (permissionsError) {
+ sendErrorResponse(packet.at(0), handle, permissionsError);
+ return;
+ }
+ if (valueOffset > attribute.value.count()) {
+ sendErrorResponse(packet.at(0), handle, ATT_ERROR_INVALID_OFFSET);
+ return;
+ }
+ if (attribute.value.count() <= mtuSize - 3) {
+ sendErrorResponse(packet.at(0), handle, ATT_ERROR_ATTRIBUTE_NOT_LONG);
+ return;
+ }
+
+ // Yes, this value can be zero.
+ const int sentValueLength = qMin(attribute.value.count() - valueOffset, mtuSize - 1);
+
+ QByteArray response(1 + sentValueLength, Qt::Uninitialized);
+ response[0] = ATT_OP_READ_BLOB_RESPONSE;
+ using namespace std;
+ memcpy(response.data() + 1, attribute.value.constData() + valueOffset, sentValueLength);
+ qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex();
+ sendPacket(response);
+}
+
+void QLowEnergyControllerPrivate::handleReadMultipleRequest(const QByteArray &packet)
+{
+ // Spec v4.2, Vol 3, Part F, 3.4.4.7-8
+
+ if (!checkPacketSize(packet, 5, mtuSize))
+ return;
+ QVector<QLowEnergyHandle> handles((packet.count() - 1) / sizeof(QLowEnergyHandle));
+ auto *packetPtr = reinterpret_cast<const QLowEnergyHandle *>(packet.constData() + 1);
+ for (int i = 0; i < handles.count(); ++i, ++packetPtr)
+ handles[i] = bt_get_le16(packetPtr);
+ qCDebug(QT_BT_BLUEZ) << "client sends read multiple request for handles" << handles;
+
+ const auto it = std::find_if(handles.constBegin(), handles.constEnd(),
+ [this](QLowEnergyHandle handle) { return handle >= lastLocalHandle; });
+ if (it != handles.constEnd()) {
+ sendErrorResponse(packet.at(0), *it, ATT_ERROR_INVALID_HANDLE);
+ return;
+ }
+ const QVector<Attribute> results = getAttributes(handles.first(), handles.last());
+ QByteArray response(1, ATT_OP_READ_MULTIPLE_RESPONSE);
+ foreach (const Attribute &attr, results) {
+ const int error = checkReadPermissions(attr);
+ if (error) {
+ sendErrorResponse(packet.at(0), attr.handle, error);
+ return;
+ }
+
+ // Note: We do not abort if no more values fit into the packet, because we still have to
+ // report possible permission errors for the other handles.
+ response += attr.value.left(mtuSize - response.count());
+ }
+
+ qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex();
+ sendPacket(response);
+}
+
+void QLowEnergyControllerPrivate::handleReadByGroupTypeRequest(const QByteArray &packet)
+{
+ // Spec v4.2, Vol 3, Part F, 3.4.4.9-10
+
+ if (!checkPacketSize(packet, 7, 21))
+ return;
+ const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1);
+ const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3);
+ const bool is16BitUuid = packet.count() == 7;
+ const bool is128BitUuid = packet.count() == 21;
+ const void * const typeStart = packet.constData() + 5;
+ QBluetoothUuid type;
+ if (is16BitUuid) {
+ type = QBluetoothUuid(bt_get_le16(typeStart));
+ } else if (is128BitUuid) {
+ type = QBluetoothUuid(convert_uuid128(reinterpret_cast<const quint128 *>(typeStart)));
+ } else {
+ qCWarning(QT_BT_BLUEZ) << "read by group type request has invalid packet size"
+ << packet.count();
+ sendErrorResponse(packet.at(0), 0, ATT_ERROR_INVALID_PDU);
+ return;
+ }
+ qCDebug(QT_BT_BLUEZ) << "client sends read by group type request, start:" << startingHandle
+ << "end:" << endingHandle << "type:" << type;
+
+ if (!checkHandlePair(packet.at(0), startingHandle, endingHandle))
+ return;
+ if (type != QBluetoothUuid(static_cast<quint16>(GATT_PRIMARY_SERVICE))
+ && type != QBluetoothUuid(static_cast<quint16>(GATT_SECONDARY_SERVICE))) {
+ sendErrorResponse(packet.at(0), startingHandle, ATT_ERROR_UNSUPPRTED_GROUP_TYPE);
+ return;
+ }
+
+ QVector<Attribute> results = getAttributes(startingHandle, endingHandle,
+ [type](const Attribute &attr) { return attr.type == type; });
+ if (results.isEmpty()) {
+ sendErrorResponse(packet.at(0), startingHandle, ATT_ERROR_ATTRIBUTE_NOT_FOUND);
+ return;
+ }
+ const int error = checkReadPermissions(results);
+ if (error) {
+ sendErrorResponse(packet.at(0), results.first().handle, error);
+ return;
+ }
+
+ ensureUniformValueSizes(results);
+
+ const int elementSize = 2 * sizeof(QLowEnergyHandle) + results.first().value.count();
+ QByteArray responsePrefix(2, Qt::Uninitialized);
+ responsePrefix[0] = ATT_OP_READ_BY_GROUP_RESPONSE;
+ responsePrefix[1] = elementSize;
+ const auto elemWriter = [](const Attribute &attr, char *&data) {
+ putDataAndIncrement(attr.handle, data);
+ putDataAndIncrement(attr.groupEndHandle, data);
+ putDataAndIncrement(attr.value, data);
+ };
+ sendListResponse(responsePrefix, elementSize, results, elemWriter);
+}
+
+void QLowEnergyControllerPrivate::updateLocalAttributeValue(
+ QLowEnergyHandle handle,
+ const QByteArray &value,
+ QLowEnergyCharacteristic &characteristic,
+ QLowEnergyDescriptor &descriptor)
+{
+ localAttributes[handle].value = value;
+ foreach (const auto &service, localServices) {
+ if (handle < service->startHandle || handle > service->endHandle)
+ continue;
+ for (auto charIt = service->characteristicList.begin();
+ charIt != service->characteristicList.end(); ++charIt) {
+ QLowEnergyServicePrivate::CharData &charData = charIt.value();
+ if (handle == charIt.key() + 1) { // Char value decl comes right after char decl.
+ charData.value = value;
+ characteristic = QLowEnergyCharacteristic(service, charIt.key());
+ return;
+ }
+ for (auto descIt = charData.descriptorList.begin();
+ descIt != charData.descriptorList.end(); ++descIt) {
+ if (handle == descIt.key()) {
+ descIt.value().value = value;
+ descriptor = QLowEnergyDescriptor(service, charIt.key(), handle);
+ return;
+ }
+ }
+ }
+ }
+ qFatal("local services map inconsistent with local attribute map");
+}
+
+static bool isNotificationEnabled(quint16 clientConfigValue) { return clientConfigValue & 0x1; }
+static bool isIndicationEnabled(quint16 clientConfigValue) { return clientConfigValue & 0x2; }
+
+void QLowEnergyControllerPrivate::writeCharacteristicForPeripheral(
+ QLowEnergyServicePrivate::CharData &charData,
+ const QByteArray &newValue)
+{
+ const QLowEnergyHandle valueHandle = charData.valueHandle;
+ Q_ASSERT(valueHandle <= lastLocalHandle);
+ Attribute &attribute = localAttributes[valueHandle];
+ if (newValue.count() < attribute.minLength || newValue.count() > attribute.maxLength) {
+ qCWarning(QT_BT_BLUEZ) << "ignoring value of invalid length" << newValue.count()
+ << "for attribute" << valueHandle;
+ return;
+ }
+ attribute.value = newValue;
+ charData.value = newValue;
+ const bool hasNotifyProperty = attribute.properties & QLowEnergyCharacteristic::Notify;
+ const bool hasIndicateProperty
+ = attribute.properties & QLowEnergyCharacteristic::Indicate;
+ if (!hasNotifyProperty && !hasIndicateProperty)
+ return;
+ foreach (const QLowEnergyServicePrivate::DescData &desc, charData.descriptorList) {
+ if (desc.uuid != QBluetoothUuid::ClientCharacteristicConfiguration)
+ continue;
+
+ // Notify/indicate currently connected client.
+ const bool isConnected = state == QLowEnergyController::ConnectedState;
+ if (isConnected) {
+ Q_ASSERT(desc.value.count() == 2);
+ quint16 configValue = bt_get_le16(desc.value.constData());
+ if (isNotificationEnabled(configValue) && hasNotifyProperty) {
+ sendNotification(valueHandle);
+ } else if (isIndicationEnabled(configValue) && hasIndicateProperty) {
+ if (indicationInFlight)
+ scheduledIndications << valueHandle;
+ else
+ sendIndication(valueHandle);
+ }
+ }
+
+ // Prepare notification/indication of unconnected, bonded clients.
+ for (auto it = clientConfigData.begin(); it != clientConfigData.end(); ++it) {
+ if (isConnected && it.key() == remoteDevice.toUInt64())
+ continue;
+ QVector<ClientConfigurationData> &configDataList = it.value();
+ for (ClientConfigurationData &configData : configDataList) {
+ if (configData.charValueHandle != valueHandle)
+ continue;
+ if ((isNotificationEnabled(configData.configValue) && hasNotifyProperty)
+ || (isIndicationEnabled(configData.configValue) && hasIndicateProperty)) {
+ configData.charValueWasUpdated = true;
+ break;
+ }
+ }
+ }
+ break;
+ }
+}
+
+void QLowEnergyControllerPrivate::writeCharacteristicForCentral(
+ QLowEnergyHandle charHandle,
+ QLowEnergyHandle valueHandle,
+ const QByteArray &newValue,
+ bool writeWithResponse)
+{
+ const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
+
+ quint8 packet[WRITE_REQUEST_HEADER_SIZE];
+ if (writeWithResponse) {
+ if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
+ sendNextPrepareWriteRequest(charHandle, newValue, 0);
+ sendNextPendingRequest();
+ return;
+ } else {
+ // write value fits into single package
+ packet[0] = ATT_OP_WRITE_REQUEST;
+ }
+ } else {
+ // write without response
+ packet[0] = ATT_OP_WRITE_COMMAND;
+ }
+
+ putBtData(valueHandle, &packet[1]);
+
+ QByteArray data(size, Qt::Uninitialized);
+ memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
+ memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
+
+ qCDebug(QT_BT_BLUEZ) << "Writing characteristic" << hex << charHandle
+ << "(size:" << size << "with response:" << writeWithResponse << ")";
+
+ // Advantage of write without response is the quick turnaround.
+ // It can be send at any time and does not produce responses.
+ // Therefore we will not put them into the openRequest queue at all.
+ if (!writeWithResponse) {
+ sendPacket(data);
+ return;
+ }
+
+ Request request;
+ request.payload = data;
+ request.command = ATT_OP_WRITE_REQUEST;
+ request.reference = charHandle;
+ request.reference2 = newValue;
+ openRequests.enqueue(request);
+
+ sendNextPendingRequest();
+}
+
+void QLowEnergyControllerPrivate::writeDescriptorForPeripheral(
+ const QSharedPointer<QLowEnergyServicePrivate> &service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle,
+ const QByteArray &newValue)
+{
+ Q_ASSERT(descriptorHandle <= lastLocalHandle);
+ Attribute &attribute = localAttributes[descriptorHandle];
+ if (newValue.count() < attribute.minLength || newValue.count() > attribute.maxLength) {
+ qCWarning(QT_BT_BLUEZ) << "invalid value of size" << newValue.count()
+ << "for attribute" << descriptorHandle;
+ return;
+ }
+ attribute.value = newValue;
+ service->characteristicList[charHandle].descriptorList[descriptorHandle].value = newValue;
+}
+
+void QLowEnergyControllerPrivate::writeDescriptorForCentral(
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle,
+ const QByteArray &newValue)
+{
+ if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
+ sendNextPrepareWriteRequest(descriptorHandle, newValue, 0);
+ sendNextPendingRequest();
+ return;
+ }
+
+ quint8 packet[WRITE_REQUEST_HEADER_SIZE];
+ packet[0] = ATT_OP_WRITE_REQUEST;
+ putBtData(descriptorHandle, &packet[1]);
+
+ const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
+ QByteArray data(size, Qt::Uninitialized);
+ memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
+ memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
+
+ qCDebug(QT_BT_BLUEZ) << "Writing descriptor" << hex << descriptorHandle
+ << "(size:" << size << ")";
+
+ Request request;
+ request.payload = data;
+ request.command = ATT_OP_WRITE_REQUEST;
+ request.reference = (charHandle | (descriptorHandle << 16));
+ request.reference2 = newValue;
+ openRequests.enqueue(request);
+
+ sendNextPendingRequest();
+}
+
+void QLowEnergyControllerPrivate::handleWriteRequestOrCommand(const QByteArray &packet)
+{
+ // Spec v4.2, Vol 3, Part F, 3.4.5.1-3
+
+ const bool isRequest = packet.at(0) == ATT_OP_WRITE_REQUEST;
+ const bool isSigned = packet.at(0) == ATT_OP_SIGNED_WRITE_COMMAND;
+ if (!checkPacketSize(packet, isSigned ? 15 : 3, mtuSize))
+ return;
+ const QLowEnergyHandle handle = bt_get_le16(packet.constData() + 1);
+ qCDebug(QT_BT_BLUEZ) << "client sends" << (isSigned ? "signed" : "") << "write"
+ << (isRequest ? "request" : "command") << "for handle" << handle;
+
+ if (!checkHandle(packet, handle))
+ return;
+
+ int valueLength;
+ if (isSigned) {
+ // const QByteArray signature = packet.right(12);
+ return; // TODO: Check signature and continue if it's valid. Store sign counter.
+ valueLength = packet.count() - 15;
+ } else {
+ valueLength = packet.count() - 3;
+ }
+
+ Attribute &attribute = localAttributes[handle];
+ const QLowEnergyCharacteristic::PropertyType type = isRequest
+ ? QLowEnergyCharacteristic::Write : isSigned
+ ? QLowEnergyCharacteristic::WriteSigned : QLowEnergyCharacteristic::WriteNoResponse;
+ const int permissionsError = checkPermissions(attribute, type);
+ if (permissionsError) {
+ sendErrorResponse(packet.at(0), handle, permissionsError);
+ return;
+ }
+ if (valueLength > attribute.maxLength) {
+ sendErrorResponse(packet.at(0), handle, ATT_ERROR_INVAL_ATTR_VALUE_LEN);
+ return;
+ }
+
+ // If the attribute value has a fixed size and the value in the packet is shorter,
+ // then we overwrite only the start of the attribute value and keep the rest.
+ QByteArray value = packet.mid(3, valueLength);
+ if (attribute.minLength == attribute.maxLength && valueLength < attribute.minLength)
+ value += attribute.value.mid(valueLength, attribute.maxLength - valueLength);
+
+ QLowEnergyCharacteristic characteristic;
+ QLowEnergyDescriptor descriptor;
+ updateLocalAttributeValue(handle, value, characteristic, descriptor);
+
+ if (isRequest) {
+ const QByteArray response = QByteArray(1, ATT_OP_WRITE_RESPONSE);
+ sendPacket(response);
+ }
+
+ if (characteristic.isValid()) {
+ emit characteristic.d_ptr->characteristicChanged(characteristic, value);
+ } else {
+ Q_ASSERT(descriptor.isValid());
+ emit descriptor.d_ptr->descriptorWritten(descriptor, value);
+ }
+}
+
+void QLowEnergyControllerPrivate::handlePrepareWriteRequest(const QByteArray &packet)
+{
+ // Spec v4.2, Vol 3, Part F, 3.4.6.1
+
+ if (!checkPacketSize(packet, 5, mtuSize))
+ return;
+ const quint16 handle = bt_get_le16(packet.constData() + 1);
+ qCDebug(QT_BT_BLUEZ) << "client sends prepare write request for handle" << handle;
+
+ if (!checkHandle(packet, handle))
+ return;
+ const Attribute &attribute = localAttributes.at(handle);
+ const int permissionsError = checkPermissions(attribute, QLowEnergyCharacteristic::Write);
+ if (permissionsError) {
+ sendErrorResponse(packet.at(0), handle, permissionsError);
+ return;
+ }
+ if (openPrepareWriteRequests.count() >= maxPrepareQueueSize) {
+ sendErrorResponse(packet.at(0), handle, ATT_ERROR_PREPARE_QUEUE_FULL);
+ return;
+ }
+
+ // The value is not checked here, but on the Execute request.
+ openPrepareWriteRequests << WriteRequest(handle, bt_get_le16(packet.constData() + 3),
+ packet.mid(5));
+
+ QByteArray response = packet;
+ response[0] = ATT_OP_PREPARE_WRITE_RESPONSE;
+ sendPacket(response);
+}
+
+void QLowEnergyControllerPrivate::handleExecuteWriteRequest(const QByteArray &packet)
+{
+ // Spec v4.2, Vol 3, Part F, 3.4.6.3
+
+ if (!checkPacketSize(packet, 2))
+ return;
+ const bool cancel = packet.at(1) == 0;
+ qCDebug(QT_BT_BLUEZ) << "client sends execute write request; flag is"
+ << (cancel ? "cancel" : "flush");
+
+ QVector<WriteRequest> requests = openPrepareWriteRequests;
+ openPrepareWriteRequests.clear();
+ QVector<QLowEnergyCharacteristic> characteristics;
+ QVector<QLowEnergyDescriptor> descriptors;
+ if (!cancel) {
+ foreach (const WriteRequest &request, requests) {
+ Attribute &attribute = localAttributes[request.handle];
+ if (request.valueOffset > attribute.value.count()) {
+ sendErrorResponse(packet.at(0), request.handle, ATT_ERROR_INVALID_OFFSET);
+ return;
+ }
+ const QByteArray newValue = attribute.value.left(request.valueOffset) + request.value;
+ if (newValue.count() > attribute.maxLength) {
+ sendErrorResponse(packet.at(0), request.handle, ATT_ERROR_INVAL_ATTR_VALUE_LEN);
+ return;
+ }
+ QLowEnergyCharacteristic characteristic;
+ QLowEnergyDescriptor descriptor;
+ // TODO: Redundant attribute lookup for the case of the same handle appearing
+ // more than once.
+ updateLocalAttributeValue(request.handle, newValue, characteristic, descriptor);
+ if (characteristic.isValid()) {
+ characteristics << characteristic;
+ } else if (descriptor.isValid()) {
+ Q_ASSERT(descriptor.isValid());
+ descriptors << descriptor;
+ }
+ }
+ }
+
+ sendPacket(QByteArray(1, ATT_OP_EXECUTE_WRITE_RESPONSE));
+
+ foreach (const QLowEnergyCharacteristic &characteristic, characteristics)
+ emit characteristic.d_ptr->characteristicChanged(characteristic, characteristic.value());
+ foreach (const QLowEnergyDescriptor &descriptor, descriptors)
+ emit descriptor.d_ptr->descriptorWritten(descriptor, descriptor.value());
+}
+
+void QLowEnergyControllerPrivate::sendErrorResponse(quint8 request, quint16 handle, quint8 code)
+{
+ // An ATT command never receives an error response.
+ if (request == ATT_OP_WRITE_COMMAND || request == ATT_OP_SIGNED_WRITE_COMMAND)
+ return;
+
+ QByteArray packet(ERROR_RESPONSE_HEADER_SIZE, Qt::Uninitialized);
+ packet[0] = ATT_OP_ERROR_RESPONSE;
+ packet[1] = request;
+ putBtData(handle, packet.data() + 2);
+ packet[4] = code;
+ qCWarning(QT_BT_BLUEZ) << "sending error response; request:" << request << "handle:" << handle
+ << "code:" << code;
+ sendPacket(packet);
+}
+
+void QLowEnergyControllerPrivate::sendListResponse(const QByteArray &packetStart, int elemSize,
+ const QVector<Attribute> &attributes, const ElemWriter &elemWriter)
+{
+ const int offset = packetStart.count();
+ const int elemCount = qMin(attributes.count(), (mtuSize - offset) / elemSize);
+ const int totalPacketSize = offset + elemCount * elemSize;
+ QByteArray response(totalPacketSize, Qt::Uninitialized);
+ using namespace std;
+ memcpy(response.data(), packetStart.constData(), offset);
+ char *data = response.data() + offset;
+ for_each(attributes.constBegin(), attributes.constBegin() + elemCount,
+ [&data, elemWriter](const Attribute &attr) { elemWriter(attr, data); });
+ qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex();
+ sendPacket(response);
+}
+
+void QLowEnergyControllerPrivate::sendNotification(QLowEnergyHandle handle)
+{
+ sendNotificationOrIndication(ATT_OP_HANDLE_VAL_NOTIFICATION, handle);
+}
+
+void QLowEnergyControllerPrivate::sendIndication(QLowEnergyHandle handle)
+{
+ Q_ASSERT(!indicationInFlight);
+ indicationInFlight = true;
+ sendNotificationOrIndication(ATT_OP_HANDLE_VAL_INDICATION, handle);
+}
+
+void QLowEnergyControllerPrivate::sendNotificationOrIndication(
+ quint8 opCode,
+ QLowEnergyHandle handle)
+{
+ Q_ASSERT(handle <= lastLocalHandle);
+ const Attribute &attribute = localAttributes.at(handle);
+ const int maxValueLength = qMin(attribute.value.count(), mtuSize - 3);
+ QByteArray packet(3 + maxValueLength, Qt::Uninitialized);
+ packet[0] = opCode;
+ putBtData(handle, packet.data() + 1);
+ using namespace std;
+ memcpy(packet.data() + 3, attribute.value.constData(), maxValueLength);
+ qCDebug(QT_BT_BLUEZ) << "sending notification/indication:" << packet.toHex();
+ sendPacket(packet);
+}
+
+void QLowEnergyControllerPrivate::sendNextIndication()
+{
+ if (!scheduledIndications.isEmpty())
+ sendIndication(scheduledIndications.takeFirst());
+}
+
+void QLowEnergyControllerPrivate::handleConnectionRequest()
+{
+ if (state != QLowEnergyController::AdvertisingState) {
+ qCWarning(QT_BT_BLUEZ) << "Incoming connection request in unexpected state" << state;
+ return;
+ }
+ Q_ASSERT(serverSocketNotifier);
+ serverSocketNotifier->setEnabled(false);
+ sockaddr_l2 clientAddr;
+ socklen_t clientAddrSize = sizeof clientAddr;
+ const int clientSocket = accept(serverSocketNotifier->socket(),
+ reinterpret_cast<sockaddr *>(&clientAddr), &clientAddrSize);
+ if (clientSocket == -1) {
+ // Not fatal in itself. The next one might succeed.
+ qCWarning(QT_BT_BLUEZ) << "accept() failed:" << qt_error_string(errno);
+ serverSocketNotifier->setEnabled(true);
+ return;
+ }
+ remoteDevice = QBluetoothAddress(convertAddress(clientAddr.l2_bdaddr.b));
+ qCDebug(QT_BT_BLUEZ) << "GATT connection from device" << remoteDevice;
+
+ closeServerSocket();
+ l2cpSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol, this);
+ connect(l2cpSocket, &QBluetoothSocket::disconnected,
+ this, &QLowEnergyControllerPrivate::l2cpDisconnected);
+ connect(l2cpSocket, static_cast<void (QBluetoothSocket::*)(QBluetoothSocket::SocketError)>
+ (&QBluetoothSocket::error), this, &QLowEnergyControllerPrivate::l2cpErrorChanged);
+ connect(l2cpSocket, &QIODevice::readyRead, this, &QLowEnergyControllerPrivate::l2cpReadyRead);
+ l2cpSocket->d_ptr->lowEnergySocketType = addressType == QLowEnergyController::PublicAddress
+ ? BDADDR_LE_PUBLIC : BDADDR_LE_RANDOM;
+ l2cpSocket->setSocketDescriptor(clientSocket, QBluetoothServiceInfo::L2capProtocol,
+ QBluetoothSocket::ConnectedState, QIODevice::ReadWrite | QIODevice::Unbuffered);
+ restoreClientConfigurations();
+ setState(QLowEnergyController::ConnectedState);
+}
+
+void QLowEnergyControllerPrivate::closeServerSocket()
+{
+ if (!serverSocketNotifier)
+ return;
+ serverSocketNotifier->disconnect();
+ close(serverSocketNotifier->socket());
+ serverSocketNotifier->deleteLater();
+ serverSocketNotifier = nullptr;
+}
+
+bool QLowEnergyControllerPrivate::isBonded() const
+{
+ // Pairing does not necessarily imply bonding, but we don't know whether the
+ // bonding flag was set in the original pairing request.
+ return QBluetoothLocalDevice(localAdapter).pairingStatus(remoteDevice)
+ != QBluetoothLocalDevice::Unpaired;
+}
+
+QVector<QLowEnergyControllerPrivate::TempClientConfigurationData> QLowEnergyControllerPrivate::gatherClientConfigData()
+{
+ QVector<TempClientConfigurationData> data;
+ foreach (const auto &service, localServices) {
+ for (auto charIt = service->characteristicList.begin();
+ charIt != service->characteristicList.end(); ++charIt) {
+ QLowEnergyServicePrivate::CharData &charData = charIt.value();
+ for (auto descIt = charData.descriptorList.begin();
+ descIt != charData.descriptorList.end(); ++descIt) {
+ QLowEnergyServicePrivate::DescData &descData = descIt.value();
+ if (descData.uuid == QBluetoothUuid::ClientCharacteristicConfiguration) {
+ data << TempClientConfigurationData(&descData, charData.valueHandle,
+ descIt.key());
+ break;
+ }
+ }
+ }
+ }
+ return data;
+}
+
+void QLowEnergyControllerPrivate::storeClientConfigurations()
+{
+ if (!isBonded()) {
+ clientConfigData.remove(remoteDevice.toUInt64());
+ return;
+ }
+ QVector<ClientConfigurationData> clientConfigs;
+ const QVector<TempClientConfigurationData> &tempConfigList = gatherClientConfigData();
+ foreach (const auto &tempConfigData, tempConfigList) {
+ Q_ASSERT(tempConfigData.descData->value.count() == 2);
+ const quint16 value = bt_get_le16(tempConfigData.descData->value.constData());
+ if (value != 0) {
+ clientConfigs << ClientConfigurationData(tempConfigData.charValueHandle,
+ tempConfigData.configHandle, value);
+ }
+ }
+ clientConfigData.insert(remoteDevice.toUInt64(), clientConfigs);
+}
+
+void QLowEnergyControllerPrivate::restoreClientConfigurations()
+{
+ const QVector<TempClientConfigurationData> &tempConfigList = gatherClientConfigData();
+ const QVector<ClientConfigurationData> &restoredClientConfigs = isBonded()
+ ? clientConfigData.value(remoteDevice.toUInt64()) : QVector<ClientConfigurationData>();
+ QVector<QLowEnergyHandle> notifications;
+ foreach (const auto &tempConfigData, tempConfigList) {
+ bool wasRestored = false;
+ foreach (const auto &restoredData, restoredClientConfigs) {
+ if (restoredData.charValueHandle == tempConfigData.charValueHandle) {
+ Q_ASSERT(tempConfigData.descData->value.count() == 2);
+ putBtData(restoredData.configValue, tempConfigData.descData->value.data());
+ wasRestored = true;
+ if (restoredData.charValueWasUpdated) {
+ if (isNotificationEnabled(restoredData.configValue))
+ notifications << restoredData.charValueHandle;
+ else if (isIndicationEnabled(restoredData.configValue))
+ scheduledIndications << restoredData.charValueHandle;
+ }
+ break;
+ }
+ }
+ if (!wasRestored)
+ tempConfigData.descData->value = QByteArray(2, 0); // Default value.
+ Q_ASSERT(lastLocalHandle >= tempConfigData.configHandle);
+ Q_ASSERT(tempConfigData.configHandle > tempConfigData.charValueHandle);
+ localAttributes[tempConfigData.configHandle].value = tempConfigData.descData->value;
+ }
+
+ foreach (const QLowEnergyHandle handle, notifications)
+ sendNotification(handle);
+ sendNextIndication();
+}
+
+static QByteArray uuidToByteArray(const QBluetoothUuid &uuid)
+{
+ QByteArray ba;
+ if (uuid.minimumSize() == 2) {
+ ba.resize(2);
+ putBtData(uuid.toUInt16(), ba.data());
+ } else {
+ ba.resize(16);
+ putBtData(uuid.toUInt128(), ba.data());
+ }
+ return ba;
+}
+
+void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &service,
+ QLowEnergyHandle startHandle)
+{
+ // Construct generic attribute data for the service with handles as keys.
+ // Otherwise a number of request handling functions will be awkward to write
+ // as well as computationally inefficient.
+
+ localAttributes.resize(lastLocalHandle + 1);
+ Attribute serviceAttribute;
+ serviceAttribute.handle = startHandle;
+ serviceAttribute.type = QBluetoothUuid(static_cast<quint16>(service.type()));
+ serviceAttribute.properties = QLowEnergyCharacteristic::Read;
+ serviceAttribute.value = uuidToByteArray(service.uuid());
+ QLowEnergyHandle currentHandle = startHandle;
+ foreach (const QLowEnergyService * const service, service.includedServices()) {
+ Attribute attribute;
+ attribute.handle = ++currentHandle;
+ attribute.type = QBluetoothUuid(GATT_INCLUDED_SERVICE);
+ attribute.properties = QLowEnergyCharacteristic::Read;
+ const bool includeUuidInValue = service->serviceUuid().minimumSize() == 2;
+ attribute.value.resize((2 + includeUuidInValue) * sizeof(QLowEnergyHandle));
+ char *valueData = attribute.value.data();
+ putDataAndIncrement(service->d_ptr->startHandle, valueData);
+ putDataAndIncrement(service->d_ptr->endHandle, valueData);
+ if (includeUuidInValue)
+ putDataAndIncrement(service->serviceUuid(), valueData);
+ localAttributes[attribute.handle] = attribute;
+ }
+ foreach (const QLowEnergyCharacteristicData &cd, service.characteristics()) {
+ Attribute attribute;
+
+ // Characteristic declaration;
+ attribute.handle = ++currentHandle;
+ attribute.groupEndHandle = attribute.handle + 1 + cd.descriptors().count();
+ attribute.type = QBluetoothUuid(GATT_CHARACTERISTIC);
+ attribute.properties = QLowEnergyCharacteristic::Read;
+ attribute.value.resize(1 + sizeof(QLowEnergyHandle) + cd.uuid().minimumSize());
+ char *valueData = attribute.value.data();
+ putDataAndIncrement(static_cast<quint8>(cd.properties()), valueData);
+ putDataAndIncrement(QLowEnergyHandle(currentHandle + 1), valueData);
+ putDataAndIncrement(cd.uuid(), valueData);
+ localAttributes[attribute.handle] = attribute;
+
+ // Characteristic value declaration.
+ attribute.handle = ++currentHandle;
+ attribute.groupEndHandle = attribute.handle;
+ attribute.type = cd.uuid();
+ attribute.properties = cd.properties();
+ attribute.readConstraints = cd.readConstraints();
+ attribute.writeConstraints = cd.writeConstraints();
+ attribute.value = cd.value();
+ attribute.minLength = cd.minimumValueLength();
+ attribute.maxLength = cd.maximumValueLength();
+ localAttributes[attribute.handle] = attribute;
+
+ foreach (const QLowEnergyDescriptorData &dd, cd.descriptors()) {
+ attribute.handle = ++currentHandle;
+ attribute.groupEndHandle = attribute.handle;
+ attribute.type = dd.uuid();
+ attribute.properties = QLowEnergyCharacteristic::PropertyTypes();
+ attribute.readConstraints = AttAccessConstraints();
+ attribute.writeConstraints = AttAccessConstraints();
+ attribute.minLength = 0;
+ attribute.maxLength = INT_MAX;
+
+ // Spec v4.2, Vol. 3, Part G, 3.3.3.x
+ if (attribute.type == QBluetoothUuid::CharacteristicExtendedProperties) {
+ attribute.properties = QLowEnergyCharacteristic::Read;
+ attribute.minLength = attribute.maxLength = 2;
+ } else if (attribute.type == QBluetoothUuid::CharacteristicPresentationFormat) {
+ attribute.properties = QLowEnergyCharacteristic::Read;
+ attribute.minLength = attribute.maxLength = 7;
+ } else if (attribute.type == QBluetoothUuid::CharacteristicAggregateFormat) {
+ attribute.properties = QLowEnergyCharacteristic::Read;
+ attribute.minLength = 4;
+ } else if (attribute.type == QBluetoothUuid::ClientCharacteristicConfiguration
+ || attribute.type == QBluetoothUuid::ServerCharacteristicConfiguration) {
+ attribute.properties = QLowEnergyCharacteristic::Read
+ | QLowEnergyCharacteristic::Write
+ | QLowEnergyCharacteristic::WriteNoResponse
+ | QLowEnergyCharacteristic::WriteSigned;
+ attribute.writeConstraints = dd.writeConstraints();
+ attribute.minLength = attribute.maxLength = 2;
+ } else {
+ if (dd.isReadable())
+ attribute.properties |= QLowEnergyCharacteristic::Read;
+ if (dd.isWritable()) {
+ attribute.properties |= QLowEnergyCharacteristic::Write;
+ attribute.properties |= QLowEnergyCharacteristic::WriteNoResponse;
+ attribute.properties |= QLowEnergyCharacteristic::WriteSigned;
+ }
+ attribute.readConstraints = dd.readConstraints();
+ attribute.writeConstraints = dd.writeConstraints();
+ }
+
+ attribute.value = dd.value();
+ if (attribute.value.count() < attribute.minLength
+ || attribute.value.count() > attribute.maxLength) {
+ qCWarning(QT_BT_BLUEZ) << "attribute of type" << attribute.type
+ << "has invalid length of" << attribute.value.count()
+ << "bytes";
+ attribute.value = QByteArray(attribute.minLength, 0);
+ }
+ localAttributes[attribute.handle] = attribute;
+ }
+ }
+ serviceAttribute.groupEndHandle = currentHandle;
+ localAttributes[serviceAttribute.handle] = serviceAttribute;
+}
+
+void QLowEnergyControllerPrivate::ensureUniformAttributes(QVector<Attribute> &attributes,
+ const std::function<int (const Attribute &)> &getSize)
+{
+ if (attributes.isEmpty())
+ return;
+ const int firstSize = getSize(attributes.first());
+ const auto it = std::find_if(attributes.begin() + 1, attributes.end(),
+ [firstSize, getSize](const Attribute &attr) { return getSize(attr) != firstSize; });
+ if (it != attributes.end())
+ attributes.erase(it, attributes.end());
+
+}
+
+void QLowEnergyControllerPrivate::ensureUniformUuidSizes(QVector<Attribute> &attributes)
+{
+ ensureUniformAttributes(attributes,
+ [](const Attribute &attr) { return getUuidSize(attr.type); });
+}
+
+void QLowEnergyControllerPrivate::ensureUniformValueSizes(QVector<Attribute> &attributes)
+{
+ ensureUniformAttributes(attributes,
+ [](const Attribute &attr) { return attr.value.count(); });
+}
+
+QVector<QLowEnergyControllerPrivate::Attribute> QLowEnergyControllerPrivate::getAttributes(QLowEnergyHandle startHandle,
+ QLowEnergyHandle endHandle, const AttributePredicate &attributePredicate)
+{
+ QVector<Attribute> results;
+ if (startHandle > lastLocalHandle)
+ return results;
+ if (lastLocalHandle == 0) // We have no services at all.
+ return results;
+ Q_ASSERT(startHandle <= endHandle); // Must have been checked before.
+ const QLowEnergyHandle firstHandle = qMin(startHandle, lastLocalHandle);
+ const QLowEnergyHandle lastHandle = qMin(endHandle, lastLocalHandle);
+ for (QLowEnergyHandle i = firstHandle; i <= lastHandle; ++i) {
+ const Attribute &attr = localAttributes.at(i);
+ if (attributePredicate(attr))
+ results << attr;
+ }
+ return results;
+}
+
+int QLowEnergyControllerPrivate::checkPermissions(const Attribute &attr,
+ QLowEnergyCharacteristic::PropertyType type)
+{
+ const bool isReadAccess = type == QLowEnergyCharacteristic::Read;
+ const bool isWriteAccess = type == QLowEnergyCharacteristic::Write
+ || type == QLowEnergyCharacteristic::WriteNoResponse
+ || type == QLowEnergyCharacteristic::WriteSigned;
+ Q_ASSERT(isReadAccess || isWriteAccess);
+ if (!(attr.properties & type))
+ return isReadAccess ? ATT_ERROR_READ_NOT_PERM : ATT_ERROR_WRITE_NOT_PERM;
+ const AttAccessConstraints constraints = isReadAccess
+ ? attr.readConstraints : attr.writeConstraints;
+ if (constraints.testFlag(AttAuthorizationRequired))
+ return ATT_ERROR_INSUF_AUTHORIZATION; // TODO: emit signal (and offer authorization function)?
+ if (constraints.testFlag(AttEncryptionRequired) && securityLevel() < BT_SECURITY_MEDIUM)
+ return ATT_ERROR_INSUF_ENCRYPTION;
+ if (constraints.testFlag(AttAuthenticationRequired) && securityLevel() < BT_SECURITY_HIGH)
+ return ATT_ERROR_INSUF_AUTHENTICATION;
+ if (false)
+ return ATT_ERROR_INSUF_ENCR_KEY_SIZE;
+ return 0;
+}
+
+int QLowEnergyControllerPrivate::checkReadPermissions(const Attribute &attr)
+{
+ return checkPermissions(attr, QLowEnergyCharacteristic::Read);
+}
+
+int QLowEnergyControllerPrivate::checkReadPermissions(QVector<Attribute> &attributes)
+{
+ if (attributes.isEmpty())
+ return 0;
+
+ // The logic prescribed in the spec is as follows:
+ // 1) If the first in a list of matching attributes has a permissions error,
+ // then that error is returned via an error response.
+ // 2) If any other element of that list would cause a permissions error, then all
+ // attributes from this one on are not part of the result set, but no error is returned.
+ const int error = checkReadPermissions(attributes.first());
+ if (error)
+ return error;
+ const auto it = std::find_if(attributes.begin() + 1, attributes.end(),
+ [this](const Attribute &attr) { return checkReadPermissions(attr) != 0; });
+ if (it != attributes.end())
+ attributes.erase(it, attributes.end());
+ return 0;
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm
index a0b33a95..7ca0d9ac 100644
--- a/src/bluetooth/qlowenergycontroller_osx.mm
+++ b/src/bluetooth/qlowenergycontroller_osx.mm
@@ -933,6 +933,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();
@@ -947,6 +948,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'.
@@ -960,6 +962,7 @@ QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddres
{
OSX_D_PTR;
+ osx_d_ptr->role = CentralRole;
osx_d_ptr->remoteAddress = remoteAddress;
osx_d_ptr->localAddress = localAddress;
@@ -967,12 +970,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;
@@ -1119,6 +1149,30 @@ QString QLowEnergyController::errorString() const
return osx_d_ptr->errorString;
}
+void QLowEnergyController::startAdvertising(const QLowEnergyAdvertisingParameters &params,
+ 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";
+}
+
+QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData &service,
+ QObject *parent)
+{
+ Q_UNUSED(service);
+ Q_UNUSED(parent);
+ qCWarning(QT_BT_OSX) << "GATT server functionality not implemented for OS X";
+ return nullptr;
+}
+
QT_END_NAMESPACE
#include "moc_qlowenergycontroller_osx_p.cpp"
diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h
index 4694203a..190f2ac5 100644
--- a/src/bluetooth/qlowenergycontroller_osx_p.h
+++ b/src/bluetooth/qlowenergycontroller_osx_p.h
@@ -155,6 +155,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 79addae2..c04d0b3c 100644
--- a/src/bluetooth/qlowenergycontroller_p.cpp
+++ b/src/bluetooth/qlowenergycontroller_p.cpp
@@ -38,7 +38,8 @@ QT_BEGIN_NAMESPACE
QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
: QObject(),
state(QLowEnergyController::UnconnectedState),
- error(QLowEnergyController::NoError)
+ error(QLowEnergyController::NoError),
+ lastLocalHandle(0)
{
registerQLowEnergyControllerMetaType();
}
@@ -105,4 +106,19 @@ void QLowEnergyControllerPrivate::writeDescriptor(
}
+void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters &/* params */,
+ const QLowEnergyAdvertisingData &/* advertisingData */,
+ const QLowEnergyAdvertisingData &/* scanResponseData */)
+{
+}
+
+void QLowEnergyControllerPrivate::stopAdvertising()
+{
+}
+
+void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &/* service */,
+ QLowEnergyHandle /* startHandle */)
+{
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h
index 40318700..f3b28f5c 100644
--- a/src/bluetooth/qlowenergycontroller_p.h
+++ b/src/bluetooth/qlowenergycontroller_p.h
@@ -65,7 +65,9 @@ QT_END_NAMESPACE
#include <qglobal.h>
#include <QtCore/QQueue>
+#include <QtCore/QVector>
#include <QtBluetooth/qbluetooth.h>
+#include <QtBluetooth/qlowenergycharacteristic.h>
#include "qlowenergycontroller.h"
#include "qlowenergyserviceprivate_p.h"
@@ -76,10 +78,15 @@ QT_END_NAMESPACE
#include "android/lowenergynotificationhub_p.h"
#endif
+#include <functional>
+
QT_BEGIN_NAMESPACE
+class QLowEnergyServiceData;
+
#if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE)
class HciManager;
+class QSocketNotifier;
#elif defined(QT_ANDROID_BLUETOOTH)
class LowEnergyNotificationHub;
#endif
@@ -87,6 +94,7 @@ class LowEnergyNotificationHub;
extern void registerQLowEnergyControllerMetaType();
typedef QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate> > ServiceDataMap;
+class QLeAdvertiser;
class QLowEnergyControllerPrivate : public QObject
{
@@ -109,6 +117,11 @@ public:
void discoverServiceDetails(const QBluetoothUuid &service);
+ void startAdvertising(const QLowEnergyAdvertisingParameters &params,
+ const QLowEnergyAdvertisingData &advertisingData,
+ const QLowEnergyAdvertisingData &scanResponseData);
+ void stopAdvertising();
+
// misc helpers
QSharedPointer<QLowEnergyServicePrivate> serviceForHandle(
QLowEnergyHandle handle);
@@ -141,9 +154,12 @@ public:
const QLowEnergyHandle descriptorHandle,
const QByteArray &newValue);
+ void addToGenericAttributeList(const QLowEnergyServiceData &service,
+ QLowEnergyHandle startHandle);
QBluetoothAddress remoteDevice;
QBluetoothAddress localAdapter;
+ QLowEnergyController::Role role;
QString remoteName;
@@ -154,6 +170,24 @@ public:
// list of all found service uuids
ServiceDataMap serviceList;
+ QLowEnergyHandle lastLocalHandle;
+ ServiceDataMap localServices;
+
+ struct Attribute {
+ Attribute() : handle(0) {}
+
+ QLowEnergyHandle handle;
+ QLowEnergyHandle groupEndHandle;
+ QLowEnergyCharacteristic::PropertyTypes properties;
+ QBluetooth::AttAccessConstraints readConstraints;
+ QBluetooth::AttAccessConstraints writeConstraints;
+ QBluetoothUuid type;
+ QByteArray value;
+ int minLength;
+ int maxLength;
+ };
+ QVector<Attribute> localAttributes;
+
QLowEnergyController::RemoteAddressType addressType;
private:
@@ -168,14 +202,62 @@ private:
QVariant reference2;
};
QQueue<Request> openRequests;
+
+ struct WriteRequest {
+ WriteRequest() {}
+ WriteRequest(quint16 h, quint16 o, const QByteArray &v)
+ : handle(h), valueOffset(o), value(v) {}
+ quint16 handle;
+ quint16 valueOffset;
+ QByteArray value;
+ };
+ QVector<WriteRequest> openPrepareWriteRequests;
+
+ // Invariant: !scheduledIndications.isEmpty => indicationInFlight == true
+ QVector<QLowEnergyHandle> scheduledIndications;
+ bool indicationInFlight = false;
+
+ struct TempClientConfigurationData {
+ TempClientConfigurationData(QLowEnergyServicePrivate::DescData *dd = nullptr,
+ QLowEnergyHandle chHndl = 0, QLowEnergyHandle coHndl = 0)
+ : descData(dd), charValueHandle(chHndl), configHandle(coHndl) {}
+
+ QLowEnergyServicePrivate::DescData *descData;
+ QLowEnergyHandle charValueHandle;
+ QLowEnergyHandle configHandle;
+ };
+
+ struct ClientConfigurationData {
+ ClientConfigurationData(QLowEnergyHandle chHndl = 0, QLowEnergyHandle coHndl = 0,
+ quint16 val = 0)
+ : charValueHandle(chHndl), configHandle(coHndl), configValue(val) {}
+
+ QLowEnergyHandle charValueHandle;
+ QLowEnergyHandle configHandle;
+ quint16 configValue;
+ bool charValueWasUpdated = false;
+ };
+ QHash<quint64, QVector<ClientConfigurationData>> clientConfigData;
+
bool requestPending;
quint16 mtuSize;
int securityLevelValue;
bool encryptionChangePending;
+ bool receivedMtuExchangeRequest = false;
HciManager *hciManager;
+ QLeAdvertiser *advertiser;
+ QSocketNotifier *serverSocketNotifier;
- void sendCommand(const QByteArray &packet);
+ void handleConnectionRequest();
+ void closeServerSocket();
+
+ bool isBonded() const;
+ QVector<TempClientConfigurationData> gatherClientConfigData();
+ void storeClientConfigurations();
+ void restoreClientConfigurations();
+
+ void sendPacket(const QByteArray &packet);
void sendNextPendingRequest();
void processReply(const Request &request, const QByteArray &reply);
@@ -206,6 +288,73 @@ private:
void resetController();
+ void handleAdvertisingError();
+
+ bool checkPacketSize(const QByteArray &packet, int minSize, int maxSize = -1);
+ bool checkHandle(const QByteArray &packet, QLowEnergyHandle handle);
+ bool checkHandlePair(quint8 request, QLowEnergyHandle startingHandle,
+ QLowEnergyHandle endingHandle);
+
+ void handleExchangeMtuRequest(const QByteArray &packet);
+ void handleFindInformationRequest(const QByteArray &packet);
+ void handleFindByTypeValueRequest(const QByteArray &packet);
+ void handleReadByTypeRequest(const QByteArray &packet);
+ void handleReadRequest(const QByteArray &packet);
+ void handleReadBlobRequest(const QByteArray &packet);
+ void handleReadMultipleRequest(const QByteArray &packet);
+ void handleReadByGroupTypeRequest(const QByteArray &packet);
+ void handleWriteRequestOrCommand(const QByteArray &packet);
+ void handlePrepareWriteRequest(const QByteArray &packet);
+ void handleExecuteWriteRequest(const QByteArray &packet);
+
+ void sendErrorResponse(quint8 request, quint16 handle, quint8 code);
+
+ using ElemWriter = std::function<void(const Attribute &, char *&)>;
+ void sendListResponse(const QByteArray &packetStart, int elemSize,
+ const QVector<Attribute> &attributes, const ElemWriter &elemWriter);
+
+ void sendNotification(QLowEnergyHandle handle);
+ void sendIndication(QLowEnergyHandle handle);
+ void sendNotificationOrIndication(quint8 opCode, QLowEnergyHandle handle);
+ void sendNextIndication();
+
+ void ensureUniformAttributes(QVector<Attribute> &attributes, const std::function<int(const Attribute &)> &getSize);
+ void ensureUniformUuidSizes(QVector<Attribute> &attributes);
+ void ensureUniformValueSizes(QVector<Attribute> &attributes);
+
+ using AttributePredicate = std::function<bool(const Attribute &)>;
+ QVector<Attribute> getAttributes(QLowEnergyHandle startHandle, QLowEnergyHandle endHandle,
+ const AttributePredicate &attributePredicate = [](const Attribute &) { return true; });
+
+ int checkPermissions(const Attribute &attr, QLowEnergyCharacteristic::PropertyType type);
+ int checkReadPermissions(const Attribute &attr);
+ int checkReadPermissions(QVector<Attribute> &attributes);
+
+ void updateLocalAttributeValue(
+ QLowEnergyHandle handle,
+ const QByteArray &value,
+ QLowEnergyCharacteristic &characteristic,
+ QLowEnergyDescriptor &descriptor);
+
+ void writeCharacteristicForPeripheral(
+ QLowEnergyServicePrivate::CharData &charData,
+ const QByteArray &newValue);
+ void writeCharacteristicForCentral(
+ QLowEnergyHandle charHandle,
+ QLowEnergyHandle valueHandle,
+ const QByteArray &newValue,
+ bool writeWithResponse);
+
+ void writeDescriptorForPeripheral(
+ const QSharedPointer<QLowEnergyServicePrivate> &service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle,
+ const QByteArray &newValue);
+ void writeDescriptorForCentral(
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descriptorHandle,
+ const QByteArray &newValue);
+
private slots:
void l2cpConnected();
void l2cpDisconnected();
@@ -239,6 +388,8 @@ private:
};
+Q_DECLARE_TYPEINFO(QLowEnergyControllerPrivate::Attribute, Q_MOVABLE_TYPE);
+
QT_END_NAMESPACE
#endif // QT_OSX_BLUETOOTH || QT_IOS_BLUETOOTH
diff --git a/src/bluetooth/qlowenergydescriptordata.cpp b/src/bluetooth/qlowenergydescriptordata.cpp
new file mode 100644
index 00000000..db5ea238
--- /dev/null
+++ b/src/bluetooth/qlowenergydescriptordata.cpp
@@ -0,0 +1,219 @@
+/****************************************************************************
+**
+** 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 "qlowenergydescriptordata.h"
+
+#include <QtCore/qbytearray.h>
+
+QT_BEGIN_NAMESPACE
+
+struct QLowEnergyDescriptorDataPrivate : public QSharedData
+{
+ QLowEnergyDescriptorDataPrivate() : readable(true), writable(true) {}
+
+ QBluetoothUuid uuid;
+ QByteArray value;
+ QBluetooth::AttAccessConstraints readConstraints;
+ QBluetooth::AttAccessConstraints writeConstraints;
+ bool readable;
+ bool writable;
+};
+
+/*!
+ \since 5.7
+ \class QLowEnergyDescriptorData
+ \brief The QLowEnergyDescriptorData class is used to create GATT service data.
+ \inmodule QtBluetooth
+ \ingroup shared
+
+ An object of this class provides a descriptor to be added to a
+ \l QLowEnergyCharacteristicData object via \l QLowEnergyCharacteristicData::addDescriptor().
+
+ \note The member functions related to access permissions are only applicable to those
+ types of descriptors for which the Bluetooth specification does not prescribe if
+ and how their values can be accessed.
+
+ \sa QLowEnergyCharacteristicData
+ \sa QLowEnergyServiceData
+ \sa QLowEnergyController::addService
+*/
+
+/*! Creates a new invalid object of this class. */
+QLowEnergyDescriptorData::QLowEnergyDescriptorData() : d(new QLowEnergyDescriptorDataPrivate)
+{
+}
+
+/*!
+ Creates a new object of this class with UUID and value being provided by \a uuid and \a value,
+ respectively.
+ */
+QLowEnergyDescriptorData::QLowEnergyDescriptorData(const QBluetoothUuid &uuid,
+ const QByteArray &value)
+ : d(new QLowEnergyDescriptorDataPrivate)
+{
+ setUuid(uuid);
+ setValue(value);
+}
+
+/*! Constructs a new object of this class that is a copy of \a other. */
+QLowEnergyDescriptorData::QLowEnergyDescriptorData(const QLowEnergyDescriptorData &other)
+ : d(other.d)
+{
+}
+
+/*! Destroys this object. */
+QLowEnergyDescriptorData::~QLowEnergyDescriptorData()
+{
+}
+
+/*! Makes this object a copy of \a other and returns the new value of this object. */
+QLowEnergyDescriptorData &QLowEnergyDescriptorData::operator=(const QLowEnergyDescriptorData &other)
+{
+ d = other.d;
+ return *this;
+}
+
+/*! Returns the value of this descriptor. */
+QByteArray QLowEnergyDescriptorData::value() const
+{
+ return d->value;
+}
+
+/*!
+ Sets the value of this descriptor. It will be sent to a peer device exactly the way it is
+ provided here, so callers need to take care of things such as endianness.
+ */
+void QLowEnergyDescriptorData::setValue(const QByteArray &value)
+{
+ d->value = value;
+}
+
+/*! Returns the UUID of this descriptor. */
+QBluetoothUuid QLowEnergyDescriptorData::uuid() const
+{
+ return d->uuid;
+}
+
+/*! Sets the UUID of this descriptor to \a uuid. */
+void QLowEnergyDescriptorData::setUuid(const QBluetoothUuid &uuid)
+{
+ d->uuid = uuid;
+}
+
+/*! Returns true if and only if this object has a non-null UUID. */
+bool QLowEnergyDescriptorData::isValid() const
+{
+ return !uuid().isNull();
+}
+
+/*!
+ Specifies whether the value of this descriptor is \a readable and if so, under which
+ \a constraints.
+ \sa setWritePermissions()
+ */
+void QLowEnergyDescriptorData::setReadPermissions(bool readable,
+ QBluetooth::AttAccessConstraints constraints)
+{
+ d->readable = readable;
+ d->readConstraints = constraints;
+}
+
+/*! Returns \c true if the value of this descriptor is readable and \c false otherwise. */
+bool QLowEnergyDescriptorData::isReadable() const
+{
+ return d->readable;
+}
+
+/*!
+ Returns the constraints under which the value of this descriptor can be read. This value
+ is only relevant if \l isReadable() returns \c true.
+ */
+QBluetooth::AttAccessConstraints QLowEnergyDescriptorData::readConstraints() const
+{
+ return d->readConstraints;
+}
+
+/*!
+ Specifies whether the value of this descriptor is \a writable and if so, under which
+ \a constraints.
+ \sa setReadPermissions()
+ */
+void QLowEnergyDescriptorData::setWritePermissions(bool writable,
+ QBluetooth::AttAccessConstraints constraints)
+{
+ d->writable = writable;
+ d->writeConstraints = constraints;
+}
+
+/*! Returns \c true if the value of this descriptor is writable and \c false otherwise. */
+bool QLowEnergyDescriptorData::isWritable() const
+{
+ return d->writable;
+}
+
+/*!
+ Returns the constraints under which the value of this descriptor can be written. This value
+ is only relevant if \l isWritable() returns \c true.
+ */
+QBluetooth::AttAccessConstraints QLowEnergyDescriptorData::writeConstraints() const
+{
+ return d->writeConstraints;
+}
+
+/*!
+ \fn void QLowEnergyDescriptorData::swap(QLowEnergyDescriptorData &other)
+ Swaps this object with \a other.
+ */
+
+/*!
+ Returns \c true if \a d1 and \a d2 are equal with respect to their public state,
+ otherwise returns \c false.
+ */
+bool operator==(const QLowEnergyDescriptorData &d1, const QLowEnergyDescriptorData &d2)
+{
+ return d1.d == d2.d || (
+ d1.uuid() == d2.uuid()
+ && d1.value() == d2.value()
+ && d1.isReadable() == d2.isReadable()
+ && d1.isWritable() == d2.isWritable()
+ && d1.readConstraints() == d2.readConstraints()
+ && d1.writeConstraints() == d2.writeConstraints());
+}
+
+/*!
+ \fn bool operator!=(const QLowEnergyDescriptorData &d1, const QLowEnergyDescriptorData &d2)
+ Returns \c true if \a d1 and \a d2 are not equal with respect to their public state,
+ otherwise returns \c false.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergydescriptordata.h b/src/bluetooth/qlowenergydescriptordata.h
new file mode 100644
index 00000000..c46eb249
--- /dev/null
+++ b/src/bluetooth/qlowenergydescriptordata.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** 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 QLOWENERGYDESCRIPTORDATA_H
+#define QLOWENERGYDESCRIPTORDATA_H
+
+#include <QtBluetooth/qbluetooth.h>
+#include <QtBluetooth/qbluetoothuuid.h>
+#include <QtCore/qshareddata.h>
+
+QT_BEGIN_NAMESPACE
+
+class QByteArray;
+struct QLowEnergyDescriptorDataPrivate;
+
+class Q_BLUETOOTH_EXPORT QLowEnergyDescriptorData
+{
+ friend Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyDescriptorData &d1,
+ const QLowEnergyDescriptorData &d12);
+public:
+ QLowEnergyDescriptorData();
+ QLowEnergyDescriptorData(const QBluetoothUuid &uuid,
+ const QByteArray &value);
+ QLowEnergyDescriptorData(const QLowEnergyDescriptorData &other);
+ ~QLowEnergyDescriptorData();
+
+ QLowEnergyDescriptorData &operator=(const QLowEnergyDescriptorData &other);
+
+ QByteArray value() const;
+ void setValue(const QByteArray &value);
+
+ QBluetoothUuid uuid() const;
+ void setUuid(const QBluetoothUuid &uuid);
+
+ bool isValid() const;
+
+ void setReadPermissions(bool readable,
+ QBluetooth::AttAccessConstraints constraints = QBluetooth::AttAccessConstraints());
+ bool isReadable() const;
+ QBluetooth::AttAccessConstraints readConstraints() const;
+
+ void setWritePermissions(bool writable,
+ QBluetooth::AttAccessConstraints constraints = QBluetooth::AttAccessConstraints());
+ bool isWritable() const;
+ QBluetooth::AttAccessConstraints writeConstraints() const;
+
+ void swap(QLowEnergyDescriptorData &other) Q_DECL_NOTHROW { qSwap(d, other.d); }
+
+private:
+ QSharedDataPointer<QLowEnergyDescriptorDataPrivate> d;
+};
+
+Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyDescriptorData &d1,
+ const QLowEnergyDescriptorData &d2);
+
+inline bool operator!=(const QLowEnergyDescriptorData &d1, const QLowEnergyDescriptorData &d2)
+{
+ return !(d1 == d2);
+}
+
+Q_DECLARE_SHARED(QLowEnergyDescriptorData)
+
+QT_END_NAMESPACE
+
+#endif // Include guard.
diff --git a/src/bluetooth/qlowenergyservice.cpp b/src/bluetooth/qlowenergyservice.cpp
index 30a66db8..62beff91 100644
--- a/src/bluetooth/qlowenergyservice.cpp
+++ b/src/bluetooth/qlowenergyservice.cpp
@@ -220,6 +220,9 @@ QT_BEGIN_NAMESPACE
\l serviceUuid() and \l serviceName().
\value DiscoveringServices The service details are being discovered.
\value ServiceDiscovered The service details have been discovered.
+ \value LocalService The service is associated with a controller object in the
+ \l{QLowEnergyController::PeripheralRole}{peripheral role}. Such
+ service objects do not change their state.
*/
/*!
@@ -302,15 +305,20 @@ QT_BEGIN_NAMESPACE
/*!
\fn void QLowEnergyService::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
- This signal is emitted when the value of \a characteristic is changed
- by an event on the peripheral. The \a newValue parameter contains the
- updated value of the \a characteristic.
-
- The signal emission implies that change notifications must
+ If the associated controller object is in the \l {QLowEnergyController::CentralRole}{central}
+ role, this signal is emitted when the value of \a characteristic is changed by an event on the
+ peripheral. In that case, the signal emission implies that change notifications must
have been activated via the characteristic's
\l {QBluetoothUuid::ClientCharacteristicConfiguration}{ClientCharacteristicConfiguration}
descriptor prior to the change event on the peripheral. More details on how this might be
done can be found further \l{notifications}{above}.
+
+ If the controller is in the \l {QLowEnergyController::PeripheralRole}{peripheral} role, that is,
+ the service object was created via \l QLowEnergyController::addService, the signal is emitted
+ when a GATT client has written the value of the characteristic using a write request or command.
+
+ The \a newValue parameter contains the updated value of the \a characteristic.
+
*/
/*!
@@ -329,8 +337,10 @@ QT_BEGIN_NAMESPACE
\fn void QLowEnergyService::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
This signal is emitted when the value of \a descriptor
- is successfully changed to \a newValue. The change must have been caused
- by calling \l writeDescriptor().
+ is successfully changed to \a newValue. If the associated controller object is in the
+ \l {QLowEnergyController::CentralRole}{central} role, the change must have been caused
+ by calling \l writeDescriptor(). Otherwise, the signal is the result of a write request or
+ command from a GATT client to the respective descriptor.
\sa writeDescriptor()
*/
@@ -611,7 +621,13 @@ void QLowEnergyService::readCharacteristic(
}
/*!
- Writes \a newValue as value for the \a characteristic. If the operation is successful,
+ Writes \a newValue as value for the \a characteristic. The exact semantics depend on
+ the role that the associated controller object is in.
+
+ \b {Central role}
+
+ The call results in a write request or command to a remote peripheral.
+ If the operation is successful,
the \l characteristicWritten() signal is emitted; otherwise the \l CharacteristicWriteError
is set.
@@ -640,6 +656,21 @@ void QLowEnergyService::readCharacteristic(
characteristic may only support \l WriteWithResponse. If the hardware returns
with an error the \l CharacteristicWriteError is set.
+ \b {Peripheral role}
+
+ The call results in the value of the characteristic getting updated in the local database.
+
+ If a client is currently connected and it has enabled notifications or indications for
+ the characteristic, the respective information will be sent.
+ If a device has enabled notifications or indications for the characteristic and that device
+ is currently not connected, but a bond exists between it and the local device, then
+ the notification or indication will be sent on the next reconnection.
+
+ If there is a constraint on the length of the characteristic value and \a newValue
+ does not adhere to that constraint, the behavior is unspecified.
+
+ \note The \a mode argument is ignored in peripheral mode.
+
\sa QLowEnergyService::characteristicWritten(), QLowEnergyService::readCharacteristic()
*/
@@ -650,7 +681,10 @@ void QLowEnergyService::writeCharacteristic(
//TODO check behavior when writing to WriteSigned characteristic
Q_D(QLowEnergyService);
- if (d->controller == Q_NULLPTR || state() != ServiceDiscovered || !contains(characteristic)) {
+ if (d->controller == Q_NULLPTR
+ || (d->controller->role == QLowEnergyController::CentralRole
+ && state() != ServiceDiscovered)
+ || !contains(characteristic)) {
d->setError(QLowEnergyService::OperationError);
return;
}
@@ -727,9 +761,14 @@ void QLowEnergyService::readDescriptor(
}
/*!
- Writes \a newValue as value for \a descriptor. If the operation is successful,
- the \l descriptorWritten() signal is emitted; otherwise the \l DescriptorWriteError
- is emitted.
+ Writes \a newValue as value for \a descriptor. The exact semantics depend on
+ the role that the associated controller object is in.
+
+ \b {Central role}
+
+ A call to this function results in a write request to the remote device.
+ If the operation is successful, the \l descriptorWritten() signal is emitted; otherwise
+ the \l DescriptorWriteError is emitted.
All descriptor and characteristic requests towards the same remote device are
serialised. A queue is employed when issuing multiple write requests at the same time.
@@ -741,6 +780,11 @@ void QLowEnergyService::readDescriptor(
belongs to the service. If one of these conditions is
not true the \l QLowEnergyService::OperationError is set.
+ \b {Peripheral Role}
+
+ The value is written to the local service database. If the contents of \a newValue are not
+ valid for \a descriptor, the behavior is unspecified.
+
\sa descriptorWritten(), readDescriptor()
*/
void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor,
@@ -748,7 +792,10 @@ void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor,
{
Q_D(QLowEnergyService);
- if (d->controller == Q_NULLPTR || state() != ServiceDiscovered || !contains(descriptor)) {
+ if (d->controller == Q_NULLPTR
+ || (d->controller->role == QLowEnergyController::CentralRole
+ && state() != ServiceDiscovered)
+ || !contains(descriptor)) {
d->setError(QLowEnergyService::OperationError);
return;
}
diff --git a/src/bluetooth/qlowenergyservice.h b/src/bluetooth/qlowenergyservice.h
index 524650c9..6e65aefd 100644
--- a/src/bluetooth/qlowenergyservice.h
+++ b/src/bluetooth/qlowenergyservice.h
@@ -69,7 +69,8 @@ public:
DiscoveryRequired, // we know start/end handle but nothing more
//TODO Rename DiscoveringServices -> DiscoveringDetails or DiscoveringService
DiscoveringServices,// discoverDetails() called and running
- ServiceDiscovered // all details have been synchronized
+ ServiceDiscovered, // all details have been synchronized
+ LocalService,
};
Q_ENUM(ServiceState)
@@ -126,6 +127,7 @@ private:
// QLowEnergyController is the factory for this class
friend class QLowEnergyController;
+ friend class QLowEnergyControllerPrivate;
QLowEnergyService(QSharedPointer<QLowEnergyServicePrivate> p,
QObject *parent = 0);
};
diff --git a/src/bluetooth/qlowenergyservicedata.cpp b/src/bluetooth/qlowenergyservicedata.cpp
new file mode 100644
index 00000000..539f8472
--- /dev/null
+++ b/src/bluetooth/qlowenergyservicedata.cpp
@@ -0,0 +1,209 @@
+/****************************************************************************
+**
+** 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 "qlowenergyservicedata.h"
+
+#include "qlowenergycharacteristicdata.h"
+
+#include <QtCore/qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT)
+
+struct QLowEnergyServiceDataPrivate : public QSharedData
+{
+ QLowEnergyServiceDataPrivate() : type(QLowEnergyServiceData::ServiceTypePrimary) {}
+
+ QLowEnergyServiceData::ServiceType type;
+ QBluetoothUuid uuid;
+ QList<QLowEnergyService *> includedServices;
+ QList<QLowEnergyCharacteristicData> characteristics;
+};
+
+
+/*!
+ \since 5.7
+ \class QLowEnergyServiceData
+ \brief The QLowEnergyServiceData class is used to set up GATT service data.
+ \inmodule QtBluetooth
+ \ingroup shared
+
+ An Object of this class provides a service to be added to a GATT server via
+ \l QLowEnergyController::addService().
+*/
+
+/*!
+ \enum QLowEnergyServiceData::ServiceType
+ The type of GATT service.
+
+ \value ServiceTypePrimary
+ The service is a primary service.
+ \value ServiceTypeSecondary
+ The service is a secondary service. Secondary services are included by other services
+ to implement some higher-level functionality.
+ */
+
+/*! Creates a new invalid object of this class. */
+QLowEnergyServiceData::QLowEnergyServiceData() : d(new QLowEnergyServiceDataPrivate)
+{
+}
+
+/*! Constructs a new object of this class that is a copy of \a other. */
+QLowEnergyServiceData::QLowEnergyServiceData(const QLowEnergyServiceData &other) : d(other.d)
+{
+}
+
+/*! Destroys this object. */
+QLowEnergyServiceData::~QLowEnergyServiceData()
+{
+}
+
+/*! Makes this object a copy of \a other and returns the new value of this object. */
+QLowEnergyServiceData &QLowEnergyServiceData::operator=(const QLowEnergyServiceData &other)
+{
+ d = other.d;
+ return *this;
+}
+
+/*! Returns the type of this service. */
+QLowEnergyServiceData::ServiceType QLowEnergyServiceData::type() const
+{
+ return d->type;
+}
+
+/*! Sets the type of this service to \a type. */
+void QLowEnergyServiceData::setType(ServiceType type)
+{
+ d->type = type;
+}
+
+/*! Returns the UUID of this service. */
+QBluetoothUuid QLowEnergyServiceData::uuid() const
+{
+ return d->uuid;
+}
+
+/*! Sets the UUID of this service to \a uuid. */
+void QLowEnergyServiceData::setUuid(const QBluetoothUuid &uuid)
+{
+ d->uuid = uuid;
+}
+
+/*! Returns the list of included services. */
+QList<QLowEnergyService *> QLowEnergyServiceData::includedServices() const
+{
+ return d->includedServices;
+}
+
+/*!
+ Sets the list of included services to \a services.
+ All objects in this list must have been returned from a call to
+ \l QLowEnergyController::addService.
+ \sa addIncludedService()
+*/
+void QLowEnergyServiceData::setIncludedServices(const QList<QLowEnergyService *> &services)
+{
+ d->includedServices = services;
+}
+
+/*!
+ Adds \a service to the list of included services.
+ The \a service object must have been returned from a call to
+ \l QLowEnergyController::addService.
+ \sa setIncludedServices()
+*/
+void QLowEnergyServiceData::addIncludedService(QLowEnergyService *service)
+{
+ d->includedServices << service;
+}
+
+/*! Returns the list of characteristics. */
+QList<QLowEnergyCharacteristicData> QLowEnergyServiceData::characteristics() const
+{
+ return d->characteristics;
+}
+
+/*!
+ Sets the list of characteristics to \a characteristics.
+ Only valid characteristics are considered.
+ \sa addCharacteristic()
+ */
+void QLowEnergyServiceData::setCharacteristics(const QList<QLowEnergyCharacteristicData> &characteristics)
+{
+ foreach (const QLowEnergyCharacteristicData &cd, characteristics)
+ addCharacteristic(cd);
+}
+
+/*!
+ Adds \a characteristic to the list of characteristics, if it is valid.
+ \sa setCharacteristics()
+ */
+void QLowEnergyServiceData::addCharacteristic(const QLowEnergyCharacteristicData &characteristic)
+{
+ if (characteristic.isValid())
+ d->characteristics << characteristic;
+ else
+ qCWarning(QT_BT) << "not adding invalid characteristic to service";
+}
+
+/*! Returns \c true if this service is has a non-null UUID. */
+bool QLowEnergyServiceData::isValid() const
+{
+ return !uuid().isNull();
+}
+
+/*!
+ \fn void QLowEnergyServiceData::swap(QLowEnergyServiceData &other)
+ Swaps this object with \a other.
+ */
+
+/*!
+ Returns \c true if \a sd1 and \a sd2 are equal with respect to their public state,
+ otherwise returns \c false.
+ */
+bool operator==(const QLowEnergyServiceData sd1, const QLowEnergyServiceData &sd2)
+{
+ return sd1.d == sd2.d || (sd1.type() == sd2.type() && sd1.uuid() == sd2.uuid()
+ && sd1.includedServices() == sd2.includedServices()
+ && sd1.characteristics() == sd2.characteristics());
+}
+
+/*!
+ \fn bool operator!=(const QLowEnergyServiceData &sd1,
+ const QLowEnergyServiceData &sd2)
+ Returns \c true if \a sd1 and \a sd2 are not equal with respect to their public state,
+ otherwise returns \c false.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergyservicedata.h b/src/bluetooth/qlowenergyservicedata.h
new file mode 100644
index 00000000..c60dd8ee
--- /dev/null
+++ b/src/bluetooth/qlowenergyservicedata.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** 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 QLOWENERGYSERVICEDATA_H
+#define QLOWENERGYSERVICEDATA_H
+
+#include <QtBluetooth/qbluetoothglobal.h>
+#include <QtCore/qshareddata.h>
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothUuid;
+class QLowEnergyCharacteristicData;
+class QLowEnergyService;
+struct QLowEnergyServiceDataPrivate;
+
+class Q_BLUETOOTH_EXPORT QLowEnergyServiceData
+{
+ friend Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyServiceData sd1,
+ const QLowEnergyServiceData &sd2);
+public:
+ QLowEnergyServiceData();
+ QLowEnergyServiceData(const QLowEnergyServiceData &other);
+ ~QLowEnergyServiceData();
+
+ QLowEnergyServiceData &operator=(const QLowEnergyServiceData &other);
+
+ enum ServiceType { ServiceTypePrimary = 0x2800, ServiceTypeSecondary = 0x2801 };
+ ServiceType type() const;
+ void setType(ServiceType type);
+
+ QBluetoothUuid uuid() const;
+ void setUuid(const QBluetoothUuid &uuid);
+
+ QList<QLowEnergyService *> includedServices() const;
+ void setIncludedServices(const QList<QLowEnergyService *> &services);
+ void addIncludedService(QLowEnergyService *service);
+
+ QList<QLowEnergyCharacteristicData> characteristics() const;
+ void setCharacteristics(const QList<QLowEnergyCharacteristicData> &characteristics);
+ void addCharacteristic(const QLowEnergyCharacteristicData &characteristic);
+
+ bool isValid() const;
+
+ void swap(QLowEnergyServiceData &other) Q_DECL_NOTHROW { qSwap(d, other.d); }
+
+private:
+ QSharedDataPointer<QLowEnergyServiceDataPrivate> d;
+};
+
+Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyServiceData sd1,
+ const QLowEnergyServiceData &sd2);
+inline bool operator!=(const QLowEnergyServiceData sd1, const QLowEnergyServiceData &sd2)
+{
+ return !(sd1 == sd2);
+}
+
+Q_DECLARE_SHARED(QLowEnergyServiceData)
+
+QT_END_NAMESPACE
+
+#endif // Include guard.
diff --git a/src/bluetooth/qlowenergyserviceprivate.cpp b/src/bluetooth/qlowenergyserviceprivate.cpp
index 6f112017..a29d5648 100644
--- a/src/bluetooth/qlowenergyserviceprivate.cpp
+++ b/src/bluetooth/qlowenergyserviceprivate.cpp
@@ -33,6 +33,8 @@
#include "qlowenergyserviceprivate_p.h"
+#include "qlowenergycontroller_p.h"
+
QT_BEGIN_NAMESPACE
QLowEnergyServicePrivate::QLowEnergyServicePrivate(QObject *parent) :
diff --git a/src/bluetooth/qlowenergyserviceprivate_p.h b/src/bluetooth/qlowenergyserviceprivate_p.h
index fc2276e6..c17932cf 100644
--- a/src/bluetooth/qlowenergyserviceprivate_p.h
+++ b/src/bluetooth/qlowenergyserviceprivate_p.h
@@ -51,10 +51,10 @@
#include <QtBluetooth/QLowEnergyService>
#include <QtBluetooth/QLowEnergyCharacteristic>
-#include "qlowenergycontroller_p.h"
-
QT_BEGIN_NAMESPACE
+class QLowEnergyControllerPrivate;
+
class QLowEnergyServicePrivate : public QObject
{
Q_OBJECT
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index 9bbf792d..f1112240 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -20,6 +20,7 @@ qtHaveModule(bluetooth) {
qlowenergycharacteristic \
qlowenergydescriptor \
qlowenergycontroller \
+ qlowenergycontroller-gattserver \
qlowenergyservice
}
diff --git a/tests/auto/qlowenergycontroller-gattserver/README b/tests/auto/qlowenergycontroller-gattserver/README
new file mode 100644
index 00000000..bd63ef85
--- /dev/null
+++ b/tests/auto/qlowenergycontroller-gattserver/README
@@ -0,0 +1,10 @@
+This test is split into a server and a client part. The former is supplying data, and
+the latter is implementing the actual test application.
+To run a full test, follow these steps:
+ 1) Start the server application on some machine that has a Bluetooth LE adapter
+ and is close enough to the client machine.
+ 2) On the client machine, set the QT_BT_GATTSERVER_TEST_ADDRESS environment variable
+ to the address of the Bluetooth adapter on the server machine.
+ 3) Run the test on the client.
+If you skip steps 1) or 2), only a few unit tests will be run. These do not require the
+test machine to have a Bluetooth adapter.
diff --git a/tests/auto/qlowenergycontroller-gattserver/qlowenergycontroller-gattserver.pro b/tests/auto/qlowenergycontroller-gattserver/qlowenergycontroller-gattserver.pro
new file mode 100644
index 00000000..8b6c52e7
--- /dev/null
+++ b/tests/auto/qlowenergycontroller-gattserver/qlowenergycontroller-gattserver.pro
@@ -0,0 +1,2 @@
+TEMPLATE = subdirs
+SUBDIRS = server test
diff --git a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp
new file mode 100644
index 00000000..e2c930bf
--- /dev/null
+++ b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp
@@ -0,0 +1,220 @@
+/****************************************************************************
+**
+** 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 <QtBluetooth/qlowenergyadvertisingdata.h>
+#include <QtBluetooth/qlowenergyadvertisingparameters.h>
+#include <QtBluetooth/qlowenergycontroller.h>
+#include <QtBluetooth/qlowenergycharacteristicdata.h>
+#include <QtBluetooth/qlowenergydescriptordata.h>
+#include <QtBluetooth/qlowenergyservicedata.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qendian.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qsharedpointer.h>
+#include <QtCore/qvector.h>
+
+static QByteArray deviceName() { return "Qt GATT server"; }
+
+static QScopedPointer<QLowEnergyController> leController;
+typedef QSharedPointer<QLowEnergyService> ServicePtr;
+static QHash<QBluetoothUuid, ServicePtr> services;
+static int descriptorWriteCount = 0;
+static int disconnectCount = 0;
+static QBluetoothAddress remoteDevice;
+
+void addService(const QLowEnergyServiceData &serviceData)
+{
+ const ServicePtr service(leController->addService(serviceData));
+ Q_ASSERT(service);
+ services.insert(service->serviceUuid(), service);
+}
+
+void addRunningSpeedService()
+{
+ QLowEnergyServiceData serviceData;
+ serviceData.setUuid(QBluetoothUuid::RunningSpeedAndCadence);
+ serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
+
+ QLowEnergyDescriptorData desc;
+ desc.setUuid(QBluetoothUuid::ClientCharacteristicConfiguration);
+ desc.setValue(QByteArray(2, 0)); // Default: No indication, no notification.
+ QLowEnergyCharacteristicData charData;
+ charData.setUuid(QBluetoothUuid::RSCMeasurement);
+ charData.addDescriptor(desc);
+ charData.setProperties(QLowEnergyCharacteristic::Notify);
+ QByteArray value(4, 0);
+ value[0] = 1 << 2; // "Running", no optional fields.
+ charData.setValue(value);
+ serviceData.addCharacteristic(charData);
+
+ charData = QLowEnergyCharacteristicData();
+ charData.setUuid(QBluetoothUuid::RSCFeature);
+ charData.setProperties(QLowEnergyCharacteristic::Read);
+ value = QByteArray(2, 0);
+ qToLittleEndian<quint16>(1 << 2, reinterpret_cast<uchar *>(value.data()));
+ charData.setValue(value);
+ serviceData.addCharacteristic(charData);
+ addService(serviceData);
+}
+
+void addGenericAccessService()
+{
+ QLowEnergyServiceData serviceData;
+ serviceData.setUuid(QBluetoothUuid::GenericAccess);
+ serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
+
+ QLowEnergyCharacteristicData charData;
+ charData.setUuid(QBluetoothUuid::DeviceName);
+ charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Write);
+ charData.setValue(deviceName());
+ serviceData.addCharacteristic(charData);
+
+ charData = QLowEnergyCharacteristicData();
+ charData.setUuid(QBluetoothUuid::Appearance);
+ charData.setProperties(QLowEnergyCharacteristic::Read);
+ QByteArray value(2, 0);
+ qToLittleEndian<quint16>(128, reinterpret_cast<uchar *>(value.data())); // Generic computer.
+ charData.setValue(value);
+ serviceData.addCharacteristic(charData);
+
+ serviceData.addIncludedService(services.value(QBluetoothUuid::RunningSpeedAndCadence).data());
+ addService(serviceData);
+}
+
+void addCustomService()
+{
+ QLowEnergyServiceData serviceData;
+ serviceData.setUuid(QBluetoothUuid(quint16(0x2000)));
+ serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
+
+ QLowEnergyCharacteristicData charData;
+ charData.setUuid(QBluetoothUuid(quint16(0x5000))); // Made up.
+ charData.setProperties(QLowEnergyCharacteristic::Read);
+ charData.setValue(QByteArray(1024, 'x')); // Long value to test "Read Blob".
+ serviceData.addCharacteristic(charData);
+
+ charData.setUuid(QBluetoothUuid(quint16(0x5001)));
+ charData.setProperties(QLowEnergyCharacteristic::Read);
+ charData.setReadConstraints(QBluetooth::AttAuthorizationRequired); // To test read failure.
+ serviceData.addCharacteristic(charData);
+ charData.setValue("something");
+
+ charData.setUuid(QBluetoothUuid(quint16(0x5002)));
+ charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Indicate);
+ charData.setReadConstraints(QBluetooth::AttAccessConstraints());
+ const QLowEnergyDescriptorData desc(QBluetoothUuid::ClientCharacteristicConfiguration,
+ QByteArray(2, 0));
+ charData.addDescriptor(desc);
+ serviceData.addCharacteristic(charData);
+
+ charData.setUuid(QBluetoothUuid(quint16(0x5003)));
+ charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify);
+ serviceData.addCharacteristic(charData);
+
+ addService(serviceData);
+}
+
+void startAdvertising()
+{
+ QLowEnergyAdvertisingParameters params;
+ params.setMode(QLowEnergyAdvertisingParameters::AdvInd);
+ QLowEnergyAdvertisingData data;
+ data.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityLimited);
+ data.setServices(services.keys());
+ data.setIncludePowerLevel(true);
+ data.setLocalName(deviceName());
+ leController->startAdvertising(params, data);
+}
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+ leController.reset(QLowEnergyController::createPeripheral());
+ addRunningSpeedService();
+ addGenericAccessService();
+ addCustomService();
+ startAdvertising();
+
+ const ServicePtr customService = services.value(QBluetoothUuid(quint16(0x2000)));
+ Q_ASSERT(customService);
+
+ const auto stateChangedHandler = [customService]() {
+ switch (leController->state()) {
+ case QLowEnergyController::ConnectedState:
+ remoteDevice = leController->remoteAddress();
+ break;
+ case QLowEnergyController::UnconnectedState: {
+ if (++disconnectCount == 2) {
+ qApp->quit();
+ break;
+ }
+ Q_ASSERT(disconnectCount == 1);
+ const QLowEnergyCharacteristic indicatableChar
+ = customService->characteristic(QBluetoothUuid(quint16(0x5002)));
+ Q_ASSERT(indicatableChar.isValid());
+ customService->writeCharacteristic(indicatableChar, "indicated2");
+ Q_ASSERT(indicatableChar.value() == "indicated2");
+ const QLowEnergyCharacteristic notifiableChar
+ = customService->characteristic(QBluetoothUuid(quint16(0x5003)));
+ Q_ASSERT(notifiableChar.isValid());
+ customService->writeCharacteristic(notifiableChar, "notified2");
+ Q_ASSERT(notifiableChar.value() == "notified2");
+ startAdvertising();
+ break;
+ }
+ default:
+ break;
+ }
+ };
+
+ QObject::connect(leController.data(), &QLowEnergyController::stateChanged, stateChangedHandler);
+ const auto descriptorWriteHandler = [customService]() {
+ if (++descriptorWriteCount != 2)
+ return;
+ const QLowEnergyCharacteristic indicatableChar
+ = customService->characteristic(QBluetoothUuid(quint16(0x5002)));
+ Q_ASSERT(indicatableChar.isValid());
+ customService->writeCharacteristic(indicatableChar, "indicated");
+ Q_ASSERT(indicatableChar.value() == "indicated");
+ const QLowEnergyCharacteristic notifiableChar
+ = customService->characteristic(QBluetoothUuid(quint16(0x5003)));
+ Q_ASSERT(notifiableChar.isValid());
+ customService->writeCharacteristic(notifiableChar, "notified");
+ Q_ASSERT(notifiableChar.value() == "notified");
+ };
+ QObject::connect(customService.data(), &QLowEnergyService::descriptorWritten,
+ descriptorWriteHandler);
+
+ return app.exec();
+}
diff --git a/tests/auto/qlowenergycontroller-gattserver/server/server.pro b/tests/auto/qlowenergycontroller-gattserver/server/server.pro
new file mode 100644
index 00000000..b9f2ccf9
--- /dev/null
+++ b/tests/auto/qlowenergycontroller-gattserver/server/server.pro
@@ -0,0 +1,5 @@
+QT = core bluetooth
+
+CONFIG += c++11
+
+SOURCES = qlowenergycontroller-gattserver.cpp
diff --git a/tests/auto/qlowenergycontroller-gattserver/test/test.pro b/tests/auto/qlowenergycontroller-gattserver/test/test.pro
new file mode 100644
index 00000000..bd1f0874
--- /dev/null
+++ b/tests/auto/qlowenergycontroller-gattserver/test/test.pro
@@ -0,0 +1,6 @@
+QT = core bluetooth testlib
+
+TARGET = tst_qlowenergycontroller-gattserver
+CONFIG += testcase c++11
+
+SOURCES += tst_qlowenergycontroller-gattserver.cpp
diff --git a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp
new file mode 100644
index 00000000..6c45eed7
--- /dev/null
+++ b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp
@@ -0,0 +1,492 @@
+/****************************************************************************
+**
+** 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 <QtBluetooth/qbluetoothaddress.h>
+#include <QtBluetooth/qbluetoothdevicediscoveryagent.h>
+#include <QtBluetooth/qbluetoothdeviceinfo.h>
+#include <QtBluetooth/qbluetoothlocaldevice.h>
+#include <QtBluetooth/qlowenergyadvertisingdata.h>
+#include <QtBluetooth/qlowenergyadvertisingparameters.h>
+#include <QtBluetooth/qlowenergycontroller.h>
+#include <QtBluetooth/qlowenergycharacteristicdata.h>
+#include <QtBluetooth/qlowenergydescriptordata.h>
+#include <QtBluetooth/qlowenergyservicedata.h>
+#include <QtCore/qendian.h>
+#include <QtCore/qscopedpointer.h>
+#include <QtTest/qsignalspy.h>
+#include <QtTest/QtTest>
+
+#include <algorithm>
+
+using namespace QBluetooth;
+
+class TestQLowEnergyControllerGattServer : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+
+ // Static, local stuff goes here.
+ void advertisingParameters();
+ void advertisingData();
+ void controllerType();
+ void serviceData();
+
+ // Interaction with actual GATT server goes here. Order is relevant.
+ void advertisedData();
+ void serverCommunication();
+
+private:
+ QBluetoothAddress m_serverAddress;
+ QBluetoothDeviceInfo m_serverInfo;
+ QScopedPointer<QLowEnergyController> m_leController;
+};
+
+
+void TestQLowEnergyControllerGattServer::initTestCase()
+{
+ const QString serverAddress = qgetenv("QT_BT_GATTSERVER_TEST_ADDRESS");
+ if (serverAddress.isEmpty())
+ return;
+ m_serverAddress = QBluetoothAddress(serverAddress);
+ QVERIFY(!m_serverAddress.isNull());
+}
+
+void TestQLowEnergyControllerGattServer::advertisingParameters()
+{
+ QLowEnergyAdvertisingParameters params;
+ QCOMPARE(params, QLowEnergyAdvertisingParameters());
+ QCOMPARE(params.filterPolicy(), QLowEnergyAdvertisingParameters::IgnoreWhiteList);
+ QCOMPARE(params.minimumInterval(), 1280);
+ QCOMPARE(params.maximumInterval(), 1280);
+ QCOMPARE(params.mode(), QLowEnergyAdvertisingParameters::AdvInd);
+ QVERIFY(params.whiteList().isEmpty());
+
+ params.setInterval(100, 200);
+ QCOMPARE(params.minimumInterval(), 100);
+ QCOMPARE(params.maximumInterval(), 200);
+ params.setInterval(200, 100);
+ QCOMPARE(params.minimumInterval(), 200);
+ QCOMPARE(params.maximumInterval(), 200);
+
+ params.setMode(QLowEnergyAdvertisingParameters::AdvScanInd);
+ QCOMPARE(params.mode(), QLowEnergyAdvertisingParameters::AdvScanInd);
+
+ const auto whiteList = QList<QLowEnergyAdvertisingParameters::AddressInfo>()
+ << QLowEnergyAdvertisingParameters::AddressInfo(QBluetoothAddress(),
+ QLowEnergyController::PublicAddress);
+ params.setWhiteList(whiteList, QLowEnergyAdvertisingParameters::UseWhiteListForConnecting);
+ QCOMPARE(params.whiteList(), whiteList);
+ QCOMPARE(params.filterPolicy(), QLowEnergyAdvertisingParameters::UseWhiteListForConnecting);
+ QVERIFY(params != QLowEnergyAdvertisingParameters());
+}
+
+void TestQLowEnergyControllerGattServer::advertisingData()
+{
+ QLowEnergyAdvertisingData data;
+ QCOMPARE(data, QLowEnergyAdvertisingData());
+ QCOMPARE(data.discoverability(), QLowEnergyAdvertisingData::DiscoverabilityNone);
+ QCOMPARE(data.includePowerLevel(), false);
+ QCOMPARE(data.localName(), QString());
+ QCOMPARE(data.manufacturerData(), QByteArray());
+ QCOMPARE(data.manufacturerId(), QLowEnergyAdvertisingData::invalidManufacturerId());
+ QVERIFY(data.services().isEmpty());
+
+ data.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityLimited);
+ QCOMPARE(data.discoverability(), QLowEnergyAdvertisingData::DiscoverabilityLimited);
+
+ data.setIncludePowerLevel(true);
+ QCOMPARE(data.includePowerLevel(), true);
+
+ data.setLocalName("A device name");
+ QCOMPARE(data.localName(), QString("A device name"));
+
+ data.setManufacturerData(0xfffd, "some data");
+ QCOMPARE(data.manufacturerId(), quint16(0xfffd));
+ QCOMPARE(data.manufacturerData(), QByteArray("some data"));
+
+ const auto services = QList<QBluetoothUuid>() << QBluetoothUuid::CurrentTimeService
+ << QBluetoothUuid::DeviceInformation;
+ data.setServices(services);
+ QCOMPARE(data.services(), services);
+
+ QByteArray rawData(7, 'x');
+ data.setRawData(rawData);
+ QCOMPARE(data.rawData(), rawData);
+
+ QVERIFY(data != QLowEnergyAdvertisingData());
+}
+
+void TestQLowEnergyControllerGattServer::advertisedData()
+{
+ if (m_serverAddress.isNull())
+ QSKIP("No server address provided");
+ QBluetoothDeviceDiscoveryAgent discoveryAgent;
+ discoveryAgent.start();
+ QSignalSpy spy(&discoveryAgent, SIGNAL(finished()));
+ QVERIFY(spy.wait(30000));
+ const QList<QBluetoothDeviceInfo> devices = discoveryAgent.discoveredDevices();
+ const auto it = std::find_if(devices.constBegin(), devices.constEnd(),
+ [this](const QBluetoothDeviceInfo &device) { return device.address() == m_serverAddress; });
+ QVERIFY(it != devices.constEnd());
+ m_serverInfo = *it;
+
+ // BlueZ seems to interfere with the advertising in some way, so that in addition to the name
+ // we set, the host name of the machine is also sent. Therefore we cannot guarantee that "our"
+ // name is seen on the scanning machine.
+ // QCOMPARE(m_serverInfo.name(), QString("Qt GATT server"));
+
+ QCOMPARE(m_serverInfo.serviceUuids().count(), 3);
+ QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid::GenericAccess));
+ QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid::RunningSpeedAndCadence));
+ QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid(quint16(0x2000))));
+}
+
+// TODO: Why on earth is this not in the library???
+Q_DECLARE_METATYPE(QLowEnergyCharacteristic)
+Q_DECLARE_METATYPE(QLowEnergyDescriptor)
+
+void TestQLowEnergyControllerGattServer::serverCommunication()
+{
+ qRegisterMetaType<QLowEnergyCharacteristic>();
+ qRegisterMetaType<QLowEnergyDescriptor>();
+
+ if (m_serverAddress.isNull())
+ QSKIP("No server address provided");
+ m_leController.reset(QLowEnergyController::createCentral(m_serverInfo));
+ QVERIFY(!m_leController.isNull());
+ m_leController->connectToDevice();
+ QScopedPointer<QSignalSpy> spy(new QSignalSpy(m_leController.data(),
+ &QLowEnergyController::connected));
+ QVERIFY(spy->wait(30000));
+ m_leController->discoverServices();
+ spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::discoveryFinished));
+ QVERIFY(spy->wait(30000));
+ const QList<QBluetoothUuid> serviceUuids = m_leController->services();
+ QCOMPARE(serviceUuids.count(), 3);
+ QVERIFY(serviceUuids.contains(QBluetoothUuid::GenericAccess));
+ QVERIFY(serviceUuids.contains(QBluetoothUuid::RunningSpeedAndCadence));
+ QVERIFY(serviceUuids.contains(QBluetoothUuid(quint16(0x2000))));
+
+ const QScopedPointer<QLowEnergyService> genericAccessService(
+ m_leController->createServiceObject(QBluetoothUuid::GenericAccess));
+ QVERIFY(!genericAccessService.isNull());
+ genericAccessService->discoverDetails();
+ while (genericAccessService->state() != QLowEnergyService::ServiceDiscovered) {
+ spy.reset(new QSignalSpy(genericAccessService.data(), &QLowEnergyService::stateChanged));
+ QVERIFY(spy->wait(3000));
+ }
+ QCOMPARE(genericAccessService->includedServices().count(), 1);
+ QCOMPARE(genericAccessService->includedServices().first(),
+ QBluetoothUuid(QBluetoothUuid::RunningSpeedAndCadence));
+ QCOMPARE(genericAccessService->characteristics().count(), 2);
+ const QLowEnergyCharacteristic deviceNameChar
+ = genericAccessService->characteristic(QBluetoothUuid::DeviceName);
+ QVERIFY(deviceNameChar.isValid());
+ QCOMPARE(deviceNameChar.descriptors().count(), 0);
+ QCOMPARE(deviceNameChar.properties(),
+ QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Write);
+ QCOMPARE(deviceNameChar.value().constData(), "Qt GATT server");
+ const QLowEnergyCharacteristic appearanceChar
+ = genericAccessService->characteristic(QBluetoothUuid::Appearance);
+ QVERIFY(appearanceChar.isValid());
+ QCOMPARE(appearanceChar.descriptors().count(), 0);
+ QCOMPARE(appearanceChar.properties(), QLowEnergyCharacteristic::Read);
+ auto value = qFromLittleEndian<quint16>(reinterpret_cast<const uchar *>(
+ appearanceChar.value().constData()));
+ QCOMPARE(value, quint16(128));
+
+ const QScopedPointer<QLowEnergyService> runningSpeedService(
+ m_leController->createServiceObject(QBluetoothUuid::RunningSpeedAndCadence));
+ QVERIFY(!runningSpeedService.isNull());
+ runningSpeedService->discoverDetails();
+ while (runningSpeedService->state() != QLowEnergyService::ServiceDiscovered) {
+ spy.reset(new QSignalSpy(runningSpeedService.data(), &QLowEnergyService::stateChanged));
+ QVERIFY(spy->wait(3000));
+ }
+ QCOMPARE(runningSpeedService->includedServices().count(), 0);
+ QCOMPARE(runningSpeedService->characteristics().count(), 2);
+ QLowEnergyCharacteristic measurementChar
+ = runningSpeedService->characteristic(QBluetoothUuid::RSCMeasurement);
+ QVERIFY(measurementChar.isValid());
+ QCOMPARE(measurementChar.descriptors().count(), 1);
+ const QLowEnergyDescriptor clientConfigDesc
+ = measurementChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
+ QVERIFY(clientConfigDesc.isValid());
+ QCOMPARE(clientConfigDesc.value(), QByteArray(2, 0));
+ QCOMPARE(measurementChar.properties(), QLowEnergyCharacteristic::Notify);
+ QCOMPARE(measurementChar.value(), QByteArray()); // Empty because Read property not set
+ QLowEnergyCharacteristic featureChar
+ = runningSpeedService->characteristic(QBluetoothUuid::RSCFeature);
+ QVERIFY(featureChar.isValid());
+ QCOMPARE(featureChar.descriptors().count(), 0);
+ QCOMPARE(featureChar.properties(), QLowEnergyCharacteristic::Read);
+ value = qFromLittleEndian<quint16>(reinterpret_cast<const uchar *>(
+ featureChar.value().constData()));
+ QCOMPARE(value, quint16(1 << 2));
+
+ QScopedPointer<QLowEnergyService> customService(
+ m_leController->createServiceObject(QBluetoothUuid(quint16(0x2000))));
+ QVERIFY(!customService.isNull());
+ customService->discoverDetails();
+ while (customService->state() != QLowEnergyService::ServiceDiscovered) {
+ spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::stateChanged));
+ QVERIFY(spy->wait(5000));
+ }
+ QCOMPARE(customService->includedServices().count(), 0);
+ QCOMPARE(customService->characteristics().count(), 4);
+ QLowEnergyCharacteristic customChar
+ = customService->characteristic(QBluetoothUuid(quint16(0x5000)));
+ QVERIFY(customChar.isValid());
+ QCOMPARE(customChar.descriptors().count(), 0);
+ QCOMPARE(customChar.value(), QByteArray(1024, 'x'));
+
+ QLowEnergyCharacteristic customChar2
+ = customService->characteristic(QBluetoothUuid(quint16(0x5001)));
+ QVERIFY(customChar2.isValid());
+ QCOMPARE(customChar2.descriptors().count(), 0);
+ QCOMPARE(customChar2.value(), QByteArray()); // Was not readable due to authorization requirement.
+
+ QLowEnergyCharacteristic customChar3
+ = customService->characteristic(QBluetoothUuid(quint16(0x5002)));
+ QVERIFY(customChar3.isValid());
+ QCOMPARE(customChar3.properties(),
+ QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Indicate);
+ QCOMPARE(customChar3.descriptors().count(), 1);
+ QLowEnergyDescriptor cc3ClientConfig
+ = customChar3.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
+ QVERIFY(cc3ClientConfig.isValid());
+
+ QLowEnergyCharacteristic customChar4
+ = customService->characteristic(QBluetoothUuid(quint16(0x5003)));
+ QVERIFY(customChar4.isValid());
+ QCOMPARE(customChar4.properties(),
+ QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify);
+ QCOMPARE(customChar4.descriptors().count(), 1);
+ QLowEnergyDescriptor cc4ClientConfig
+ = customChar4.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
+ QVERIFY(cc4ClientConfig.isValid());
+
+ customService->writeCharacteristic(customChar, "whatever");
+ spy.reset(new QSignalSpy(customService.data(), static_cast<void (QLowEnergyService::*)
+ (QLowEnergyService::ServiceError)>(&QLowEnergyService::error)));
+ QVERIFY(spy->wait(3000));
+ QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError);
+
+ QByteArray indicateValue(2, 0);
+ qToLittleEndian<quint16>(2, reinterpret_cast<uchar *>(indicateValue.data()));
+ customService->writeDescriptor(cc3ClientConfig, indicateValue);
+ spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::descriptorWritten));
+ QVERIFY(spy->wait(3000));
+
+ QByteArray notifyValue(2, 0);
+ qToLittleEndian<quint16>(1, reinterpret_cast<uchar *>(notifyValue.data()));
+ customService->writeDescriptor(cc4ClientConfig, notifyValue);
+ spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::descriptorWritten));
+ QVERIFY(spy->wait(3000));
+
+ // Server now changes the characteristic values.
+
+ spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicChanged));
+ QVERIFY(spy->wait(3000));
+ if (spy->count() == 1)
+ QVERIFY(spy->wait(3000));
+ QCOMPARE(customChar3.value().constData(), "indicated");
+ QCOMPARE(customChar4.value().constData(), "notified");
+
+ const bool isBonded = QBluetoothLocalDevice().pairingStatus(m_serverAddress)
+ != QBluetoothLocalDevice::Unpaired;
+ m_leController->disconnectFromDevice();
+
+ if (m_leController->state() != QLowEnergyController::UnconnectedState) {
+ spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::stateChanged));
+ QVERIFY(spy->wait(3000));
+ }
+ QCOMPARE(m_leController->state(), QLowEnergyController::UnconnectedState);
+
+ // Server now changes the characteristic values again while we're offline.
+ // Note: We cannot test indications and notifications for this case, as the client does
+ // not cache the old information and thus does not yet know the characteristics
+ // at the time the notification/indication is received.
+
+ QTest::qWait(3000);
+ m_leController->connectToDevice();
+ spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::connected));
+ QVERIFY(spy->wait(30000));
+ m_leController->discoverServices();
+ spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::discoveryFinished));
+ QVERIFY(spy->wait(30000));
+ customService.reset(m_leController->createServiceObject(QBluetoothUuid(quint16(0x2000))));
+ QVERIFY(!customService.isNull());
+ customService->discoverDetails();
+ while (customService->state() != QLowEnergyService::ServiceDiscovered) {
+ spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::stateChanged));
+ QVERIFY(spy->wait(5000));
+ }
+ customChar3 = customService->characteristic(QBluetoothUuid(quint16(0x5002)));
+ QVERIFY(customChar3.isValid());
+ QCOMPARE(customChar3.value().constData(), "indicated2");
+ customChar4 = customService->characteristic(QBluetoothUuid(quint16(0x5003)));
+ QVERIFY(customChar4.isValid());
+ QCOMPARE(customChar4.value().constData(), "notified2");
+ cc3ClientConfig = customChar3.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
+ QVERIFY(cc3ClientConfig.isValid());
+ cc4ClientConfig = customChar4.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
+ QVERIFY(cc4ClientConfig.isValid());
+ if (isBonded) {
+ QCOMPARE(cc3ClientConfig.value(), indicateValue);
+ QCOMPARE(cc4ClientConfig.value(), notifyValue);
+ } else {
+ QCOMPARE(cc3ClientConfig.value(), QByteArray(2, 0));
+ QCOMPARE(cc4ClientConfig.value(), QByteArray(2, 0));
+ }
+}
+
+void TestQLowEnergyControllerGattServer::controllerType()
+{
+ const QScopedPointer<QLowEnergyController> controller(QLowEnergyController::createPeripheral());
+ QVERIFY(!controller.isNull());
+ QCOMPARE(controller->role(), QLowEnergyController::PeripheralRole);
+}
+
+void TestQLowEnergyControllerGattServer::serviceData()
+{
+ QLowEnergyDescriptorData descData;
+ QVERIFY(!descData.isValid());
+
+ descData.setUuid(QBluetoothUuid::ValidRange);
+ QCOMPARE(descData.uuid(), QBluetoothUuid(QBluetoothUuid::ValidRange));
+ QVERIFY(descData.isValid());
+ QVERIFY(descData != QLowEnergyDescriptorData());
+
+ descData.setValue("xyz");
+ QCOMPARE(descData.value().constData(), "xyz");
+
+ descData.setReadPermissions(true, AttAuthenticationRequired);
+ QCOMPARE(descData.isReadable(), true);
+ QCOMPARE(descData.readConstraints(), AttAuthenticationRequired);
+
+ descData.setWritePermissions(false);
+ QCOMPARE(descData.isWritable(), false);
+
+ QLowEnergyDescriptorData descData2(QBluetoothUuid::ReportReference, "abc");
+ QVERIFY(descData2 != QLowEnergyDescriptorData());
+ QVERIFY(descData2.isValid());
+ QCOMPARE(descData2.uuid(), QBluetoothUuid(QBluetoothUuid::ReportReference));
+ QCOMPARE(descData2.value().constData(), "abc");
+
+ QLowEnergyCharacteristicData charData;
+ QVERIFY(!charData.isValid());
+
+ charData.setUuid(QBluetoothUuid::BatteryLevel);
+ QVERIFY(charData != QLowEnergyCharacteristicData());
+ QCOMPARE(charData.uuid(), QBluetoothUuid(QBluetoothUuid::BatteryLevel));
+ QVERIFY(charData.isValid());
+
+ charData.setValue("value");
+ QCOMPARE(charData.value().constData(), "value");
+
+ charData.setValueLength(4, 7);
+ QCOMPARE(charData.minimumValueLength(), 4);
+ QCOMPARE(charData.maximumValueLength(), 7);
+ charData.setValueLength(5, 2);
+ QCOMPARE(charData.minimumValueLength(), 5);
+ QCOMPARE(charData.maximumValueLength(), 5);
+
+ const QLowEnergyCharacteristic::PropertyTypes props
+ = QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned;
+ charData.setProperties(props);
+ QCOMPARE(charData.properties(), props);
+
+ charData.setReadConstraints(AttEncryptionRequired);
+ QCOMPARE(charData.readConstraints(), AttEncryptionRequired);
+ charData.setWriteConstraints(AttAuthenticationRequired | AttAuthorizationRequired);
+ QCOMPARE(charData.writeConstraints(), AttAuthenticationRequired | AttAuthorizationRequired);
+
+ charData.setDescriptors(QList<QLowEnergyDescriptorData>() << descData << descData2);
+ QLowEnergyDescriptorData descData3(QBluetoothUuid::ExternalReportReference, "someval");
+ charData.addDescriptor(descData3);
+ charData.addDescriptor(QLowEnergyDescriptorData()); // Invalid.
+ QCOMPARE(charData.descriptors(),
+ QList<QLowEnergyDescriptorData>() << descData << descData2 << descData3);
+
+ QLowEnergyServiceData secondaryData;
+ QVERIFY(!secondaryData.isValid());
+
+ secondaryData.setUuid(QBluetoothUuid::SerialPort);
+ QCOMPARE(secondaryData.uuid(), QBluetoothUuid(QBluetoothUuid::SerialPort));
+ QVERIFY(secondaryData.isValid());
+ QVERIFY(secondaryData != QLowEnergyServiceData());
+
+ secondaryData.setType(QLowEnergyServiceData::ServiceTypeSecondary);
+ QCOMPARE(secondaryData.type(), QLowEnergyServiceData::ServiceTypeSecondary);
+
+ secondaryData.setCharacteristics(QList<QLowEnergyCharacteristicData>()
+ << charData << QLowEnergyCharacteristicData());
+ QCOMPARE(secondaryData.characteristics(), QList<QLowEnergyCharacteristicData>() << charData);
+
+#ifdef Q_OS_DARWIN
+ QSKIP("GATT server functionality not implemented for Apple platforms");
+#endif
+ const QScopedPointer<QLowEnergyController> controller(QLowEnergyController::createPeripheral());
+ QVERIFY(!controller->addService(QLowEnergyServiceData()));
+ const QScopedPointer<QLowEnergyService> secondaryService(controller->addService(secondaryData));
+ QVERIFY(!secondaryService.isNull());
+ QCOMPARE(secondaryService->serviceUuid(), secondaryData.uuid());
+ const QList<QLowEnergyCharacteristic> characteristics = secondaryService->characteristics();
+ QCOMPARE(characteristics.count(), 1);
+ QCOMPARE(characteristics.first().uuid(), charData.uuid());
+ const QList<QLowEnergyDescriptor> descriptors = characteristics.first().descriptors();
+ QCOMPARE(descriptors.count(), 3);
+ const auto inUuids = QSet<QBluetoothUuid>() << descData.uuid() << descData2.uuid()
+ << descData3.uuid();
+ QSet<QBluetoothUuid> outUuids;
+ foreach (const QLowEnergyDescriptor &desc, descriptors)
+ outUuids << desc.uuid();
+ QCOMPARE(inUuids, outUuids);
+
+ QLowEnergyServiceData primaryData;
+ primaryData.setUuid(QBluetoothUuid::Headset);
+ primaryData.addIncludedService(secondaryService.data());
+ const QScopedPointer<QLowEnergyService> primaryService(controller->addService(primaryData));
+ QVERIFY(!primaryService.isNull());
+ QCOMPARE(primaryService->characteristics().count(), 0);
+ const QList<QBluetoothUuid> includedServices = primaryService->includedServices();
+ QCOMPARE(includedServices.count(), 1);
+ QCOMPARE(includedServices.first(), secondaryService->serviceUuid());
+}
+
+QTEST_MAIN(TestQLowEnergyControllerGattServer)
+
+#include "tst_qlowenergycontroller-gattserver.moc"
diff --git a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
index b31b35f0..1386f75c 100644
--- a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
+++ b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
@@ -257,6 +257,7 @@ void tst_QLowEnergyController::tst_connect()
QSKIP("No local Bluetooth or remote BTLE device found. Skipping test.");
QLowEnergyController control(remoteDeviceInfo);
+ QCOMPARE(control.role(), QLowEnergyController::CentralRole);
QSignalSpy connectedSpy(&control, SIGNAL(connected()));
QSignalSpy disconnectedSpy(&control, SIGNAL(disconnected()));
if (remoteDeviceInfo.name().isEmpty())