From e94fe90e7d2e1df9bd7d1d014fb468e5e73da834 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Tue, 12 Jan 2016 12:22:11 +0100 Subject: Bluetooth LE: Add connection update functionality. Implemented for BlueZ only. Change-Id: I358a98bbc7499d5ce5437fb0d4672fde46c3b831 Reviewed-by: Alex Blasche --- src/bluetooth/bluetooth.pro | 2 + src/bluetooth/bluez/bluez_data_p.h | 1 + src/bluetooth/bluez/hcimanager.cpp | 140 ++++++++++++++ src/bluetooth/bluez/hcimanager_p.h | 12 ++ src/bluetooth/qlowenergyconnectionparameters.cpp | 202 +++++++++++++++++++++ src/bluetooth/qlowenergyconnectionparameters.h | 86 +++++++++ src/bluetooth/qlowenergycontroller.cpp | 30 +++ src/bluetooth/qlowenergycontroller.h | 4 + src/bluetooth/qlowenergycontroller_android.cpp | 6 + src/bluetooth/qlowenergycontroller_bluez.cpp | 26 +++ src/bluetooth/qlowenergycontroller_osx.mm | 6 + src/bluetooth/qlowenergycontroller_p.cpp | 4 + src/bluetooth/qlowenergycontroller_p.h | 3 + .../server/qlowenergycontroller-gattserver.cpp | 6 + .../test/tst_qlowenergycontroller-gattserver.cpp | 27 +++ 15 files changed, 555 insertions(+) create mode 100644 src/bluetooth/qlowenergyconnectionparameters.cpp create mode 100644 src/bluetooth/qlowenergyconnectionparameters.h diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index 920fbe9a..ef5337b5 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -32,6 +32,7 @@ PUBLIC_HEADERS += \ qbluetoothtransferreply.h \ qlowenergyadvertisingdata.h \ qlowenergyadvertisingparameters.h \ + qlowenergyconnectionparameters.h \ qlowenergycontroller.h PRIVATE_HEADERS += \ @@ -68,6 +69,7 @@ SOURCES += \ qbluetoothtransferreply.cpp \ qlowenergyadvertisingdata.cpp \ qlowenergyadvertisingparameters.cpp \ + qlowenergyconnectionparameters.cpp \ qlowenergyservice.cpp \ qlowenergyservicedata.cpp \ qlowenergycharacteristic.cpp \ diff --git a/src/bluetooth/bluez/bluez_data_p.h b/src/bluetooth/bluez/bluez_data_p.h index 7db0e50b..aa0d0166 100644 --- a/src/bluetooth/bluez/bluez_data_p.h +++ b/src/bluetooth/bluez/bluez_data_p.h @@ -348,6 +348,7 @@ enum OpCodeCommandField { OcfLeSetAdvEnable = 0xa, OcfLeClearWhiteList = 0x10, OcfLeAddToWhiteList = 0x11, + OcfLeConnectionUpdate = 0x13, }; /* Command opcode pack/unpack */ diff --git a/src/bluetooth/bluez/hcimanager.cpp b/src/bluetooth/bluez/hcimanager.cpp index 388f3e0c..3ff03fd4 100644 --- a/src/bluetooth/bluez/hcimanager.cpp +++ b/src/bluetooth/bluez/hcimanager.cpp @@ -35,6 +35,7 @@ #include "hcimanager_p.h" #include "qbluetoothsocket_p.h" +#include "qlowenergyconnectionparameters.h" #include @@ -258,6 +259,108 @@ QBluetoothAddress HciManager::addressForConnectionHandle(quint16 handle) const return QBluetoothAddress(); } +quint16 forceIntervalIntoRange(double connectionInterval) +{ + return qMin(qMax(7.5, connectionInterval), 4000) / 1.25; +} + +struct ConnectionUpdateData { + quint16 minInterval; + quint16 maxInterval; + quint16 slaveLatency; + quint16 timeout; +}; +ConnectionUpdateData connectionUpdateData(const QLowEnergyConnectionParameters ¶ms) +{ + ConnectionUpdateData data; + const quint16 minInterval = forceIntervalIntoRange(params.minimumInterval()); + const quint16 maxInterval = forceIntervalIntoRange(params.maximumInterval()); + data.minInterval = qToLittleEndian(minInterval); + data.maxInterval = qToLittleEndian(maxInterval); + const quint16 latency = qMax(0, qMin(params.latency(), 499)); + data.slaveLatency = qToLittleEndian(latency); + const quint16 timeout + = qMax(100, qMin(32000, params.supervisionTimeout())) / 10; + data.timeout = qToLittleEndian(timeout); + return data; +} + +bool HciManager::sendConnectionUpdateCommand(quint16 handle, + const QLowEnergyConnectionParameters ¶ms) +{ + struct CommandParams { + quint16 handle; + ConnectionUpdateData data; + quint16 minCeLength; + quint16 maxCeLength; + } commandParams; + commandParams.handle = qToLittleEndian(handle); + commandParams.data = connectionUpdateData(params); + commandParams.minCeLength = 0; + commandParams.maxCeLength = qToLittleEndian(quint16(0xffff)); + const QByteArray data = QByteArray::fromRawData(reinterpret_cast(&commandParams), + sizeof commandParams); + return sendCommand(OgfLinkControl, OcfLeConnectionUpdate, data); +} + +bool HciManager::sendConnectionParameterUpdateRequest(quint16 handle, + const QLowEnergyConnectionParameters ¶ms) +{ + ConnectionUpdateData connUpdateData = connectionUpdateData(params); + + // Vol 3, part A, 4 + struct SignalingPacket { + quint8 code; + quint8 identifier; + quint16 length; + } signalingPacket; + signalingPacket.code = 0x12; + signalingPacket.identifier = ++sigPacketIdentifier; + const quint16 sigPacketLen = sizeof connUpdateData; + signalingPacket.length = qToLittleEndian(sigPacketLen); + + struct L2CapHeader { + quint16 length; + quint16 channelId; + } l2CapHeader; + const quint16 l2CapHeaderLen = sizeof signalingPacket + sigPacketLen; + l2CapHeader.length = qToLittleEndian(l2CapHeaderLen); + l2CapHeader.channelId = qToLittleEndian(quint16(5)); + + // Vol 2, part E, 5.4.2 + struct AclData { + quint16 handle: 12; + quint16 pbFlag: 2; + quint16 bcFlag: 2; + quint16 dataLen; + } aclData; + aclData.handle = qToLittleEndian(handle); // Works because the next two values are zero. + aclData.pbFlag = 0; + aclData.bcFlag = 0; + aclData.dataLen = qToLittleEndian(quint16(sizeof l2CapHeader + l2CapHeaderLen)); + + struct iovec iv[5]; + quint8 packetType = 2; + iv[0].iov_base = &packetType; + iv[0].iov_len = 1; + iv[1].iov_base = &aclData; + iv[1].iov_len = sizeof aclData; + iv[2].iov_base = &l2CapHeader; + iv[2].iov_len = sizeof l2CapHeader; + iv[3].iov_base = &signalingPacket; + iv[3].iov_len = sizeof signalingPacket; + iv[4].iov_base = &connUpdateData; + iv[4].iov_len = sizeof connUpdateData; + while (writev(hciSocket, iv, sizeof iv / sizeof *iv) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + qCDebug(QT_BT_BLUEZ()) << "failure writing HCI ACL packet:" << strerror(errno); + return false; + } + qCDebug(QT_BT_BLUEZ) << "Connection Update Request packet sent successfully"; + return true; +} + /*! * Process all incoming HCI events. Function cannot process anything else but events. */ @@ -319,10 +422,47 @@ void HciManager::_q_readNotify() emit commandCompleted(event->opcode, status, additionalData); } break; + case LeMetaEvent: + handleLeMetaEvent(data); + break; default: break; } } +void HciManager::handleLeMetaEvent(const quint8 *data) +{ + // Spec v4.2, Vol 2, part E, 7.7.65ff + switch (*data) { + case 0x1: { + const quint16 handle = bt_get_le16(data + 2); + emit connectionComplete(handle); + break; + } + case 0x3: { + // TODO: From little endian! + struct ConnectionUpdateData { + quint8 status; + quint16 handle; + quint16 interval; + quint16 latency; + quint16 timeout; + } __attribute((packed)); + const auto * const updateData + = reinterpret_cast(data + 1); + if (updateData->status == 0) { + QLowEnergyConnectionParameters params; + const double interval = qFromLittleEndian(updateData->interval) * 1.25; + params.setIntervalRange(interval, interval); + params.setLatency(qFromLittleEndian(updateData->latency)); + params.setSupervisionTimeout(qFromLittleEndian(updateData->timeout) * 10); + emit connectionUpdate(qFromLittleEndian(updateData->handle), params); + } + break; + } + default: + break; + } +} QT_END_NAMESPACE diff --git a/src/bluetooth/bluez/hcimanager_p.h b/src/bluetooth/bluez/hcimanager_p.h index c8f2fe56..f1bd6d46 100644 --- a/src/bluetooth/bluez/hcimanager_p.h +++ b/src/bluetooth/bluez/hcimanager_p.h @@ -53,6 +53,8 @@ QT_BEGIN_NAMESPACE +class QLowEnergyConnectionParameters; + class HciManager : public QObject { Q_OBJECT @@ -60,6 +62,7 @@ public: enum HciEvent { EncryptChangeEvent = EVT_ENCRYPT_CHANGE, CommandCompleteEvent = EVT_CMD_COMPLETE, + LeMetaEvent = 0x3e, }; explicit HciManager(const QBluetoothAddress &deviceAdapter, QObject *parent = 0); @@ -68,21 +71,30 @@ public: bool isValid() const; bool monitorEvent(HciManager::HciEvent event); bool sendCommand(OpCodeGroupField ogf, OpCodeCommandField ocf, const QByteArray ¶meters); + void stopEvents(); QBluetoothAddress addressForConnectionHandle(quint16 handle) const; + bool sendConnectionUpdateCommand(quint16 handle, const QLowEnergyConnectionParameters ¶ms); + bool sendConnectionParameterUpdateRequest(quint16 handle, + const QLowEnergyConnectionParameters ¶ms); + signals: void encryptionChangedEvent(const QBluetoothAddress &address, bool wasSuccess); void commandCompleted(quint16 opCode, quint8 status, const QByteArray &data); + void connectionComplete(quint16 handle); + void connectionUpdate(quint16 handle, const QLowEnergyConnectionParameters ¶meters); private slots: void _q_readNotify(); private: int hciForAddress(const QBluetoothAddress &deviceAdapter); + void handleLeMetaEvent(const quint8 *data); int hciSocket; int hciDev; + quint8 sigPacketIdentifier = 0; QSocketNotifier *notifier; QSet runningEvents; }; diff --git a/src/bluetooth/qlowenergyconnectionparameters.cpp b/src/bluetooth/qlowenergyconnectionparameters.cpp new file mode 100644 index 00000000..727ed141 --- /dev/null +++ b/src/bluetooth/qlowenergyconnectionparameters.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** 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 "qlowenergyconnectionparameters.h" + +QT_BEGIN_NAMESPACE + +class QLowEnergyConnectionParametersPrivate : public QSharedData +{ +public: + QLowEnergyConnectionParametersPrivate() + : minInterval(7.5) + , maxInterval(4000) + , latency(0) + , timeout(32000) + { + } + + double minInterval; + double maxInterval; + int latency; + int timeout; +}; + +/*! + \since 5.7 + \class QLowEnergyConnectionParameters + \brief The QLowEnergyConnectionParameters class is used when requesting or reporting + an update of the parameters of a Bluetooth LE connection. + + The connection parameters influence how often a master and a slave device synchronize + with each other. In general, a lower connection interval and latency means faster communication, + but also higher power consumption. How these criteria should be weighed against each other + is highly dependent on the concrete use case. + \inmodule QtBluetooth + \ingroup shared + + \sa QLowEnergyController::requestConnectionUpdate + \sa QLowEnergyController::connectionUpdated +*/ + + +/*! + Constructs a new object of this class. All values are initialized to valid defaults. + */ +QLowEnergyConnectionParameters::QLowEnergyConnectionParameters() + : d(new QLowEnergyConnectionParametersPrivate) +{ +} + +/*! Constructs a new object of this class that is a copy of \a other. */ +QLowEnergyConnectionParameters::QLowEnergyConnectionParameters(const QLowEnergyConnectionParameters &other) + : d(other.d) +{ +} + +/*! Destroys this object. */ +QLowEnergyConnectionParameters::~QLowEnergyConnectionParameters() +{ +} + +/*! Makes this object a copy of \a other and returns the new value of this object. */ +QLowEnergyConnectionParameters &QLowEnergyConnectionParameters::operator=(const QLowEnergyConnectionParameters &other) +{ + d = other.d; + return *this; +} + +/*! + Sets the range in which the connection interval should be. The actual value will be decided by + the controller. 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. + The smallest possible connection interval is 7.5 milliseconds, the largest one is + 4000 milliseconds. + \sa minimumInterval(), maximumInterval() + */ +void QLowEnergyConnectionParameters::setIntervalRange(double minimum, double maximum) +{ + d->minInterval = minimum; + d->maxInterval = qMax(minimum, maximum); +} + +/*! + Returns the minimum connection interval in milliseconds. The default is 7.5. + \note If this object was emitted via \l QLowEnergyController::connectionUpdated(), then + this value is the same as \l maximumInterval() and refers to the actual + connection interval. + \sa setIntervalRange() + */ +double QLowEnergyConnectionParameters::minimumInterval() const +{ + return d->minInterval; +} + +/*! + Returns the maximum connection interval in milliseconds. The default is 4000. + \note If this object was emitted via \l QLowEnergyController::connectionUpdated(), then + this value is the same as \l minimumInterval() and refers to the actual + connection interval. + \sa setIntervalRange() + */ +double QLowEnergyConnectionParameters::maximumInterval() const +{ + return d->maxInterval; +} + +/*! + Sets the slave latency of the connection (that is, the number of connection events that a slave + device is allowed to ignore) to \a latency. The minimum value is 0, the maximum is 499. + \sa latency() + */ +void QLowEnergyConnectionParameters::setLatency(int latency) +{ + d->latency = latency; +} + +/*! + Returns the slave latency of the connection. + \sa setLatency() +*/ +int QLowEnergyConnectionParameters::latency() const +{ + return d->latency; +} + +/*! + Sets the link supervision timeout to \a timeout milliseconds. + There are several constraints on this value: It must be in the range [100,32000] and it must be + larger than (1 + \l latency()) * 2 * \l maximumInterval(). + \sa supervisionTimeout() + */ +void QLowEnergyConnectionParameters::setSupervisionTimeout(int timeout) +{ + d->timeout = timeout; +} + +/*! + Returns the link supervision timeout of the connection in milliseconds. + \sa setSupervisionTimeout() +*/ +int QLowEnergyConnectionParameters::supervisionTimeout() const +{ + return d->timeout; +} + +/*! + \fn void QLowEnergyConnectionParameters::swap(QLowEnergyConnectionParameters &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 QLowEnergyConnectionParameters &p1, const QLowEnergyConnectionParameters &p2) +{ + if (p1.d == p2.d) + return true; + return p1.minimumInterval() == p2.minimumInterval() + && p1.maximumInterval() == p2.maximumInterval() + && p1.latency() == p2.latency() + && p1.supervisionTimeout() == p2.supervisionTimeout(); +} + +/*! + \fn bool operator!=(const QLowEnergyConnectionParameters &p1, + const QLowEnergyConnectionParameters &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/qlowenergyconnectionparameters.h b/src/bluetooth/qlowenergyconnectionparameters.h new file mode 100644 index 00000000..a7022dd2 --- /dev/null +++ b/src/bluetooth/qlowenergyconnectionparameters.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 QLOWENERGYCONNECTIONPARAMETERS_H +#define QLOWENERGYCONNECTIONPARAMETERS_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QLowEnergyConnectionParametersPrivate; + +class Q_BLUETOOTH_EXPORT QLowEnergyConnectionParameters +{ + friend Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyConnectionParameters &p1, + const QLowEnergyConnectionParameters &p2); +public: + QLowEnergyConnectionParameters(); + QLowEnergyConnectionParameters(const QLowEnergyConnectionParameters &other); + ~QLowEnergyConnectionParameters(); + + QLowEnergyConnectionParameters &operator=(const QLowEnergyConnectionParameters &other); + + void setIntervalRange(double minimum, double maximum); + double minimumInterval() const; + double maximumInterval() const; + + void setLatency(int latency); + int latency() const; + + void setSupervisionTimeout(int timeout); + int supervisionTimeout() const; + + void swap(QLowEnergyConnectionParameters &other) Q_DECL_NOTHROW { qSwap(d, other.d); } + +private: + QSharedDataPointer d; +}; + +Q_BLUETOOTH_EXPORT bool operator==(const QLowEnergyConnectionParameters &p1, + const QLowEnergyConnectionParameters &p2); +inline bool operator!=(const QLowEnergyConnectionParameters &p1, + const QLowEnergyConnectionParameters &p2) +{ + return !(p1 == p2); +} + +Q_DECLARE_SHARED(QLowEnergyConnectionParameters) + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QLowEnergyConnectionParameters) + +#endif // Include guard diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp index 8a6a2e1d..cf712e17 100644 --- a/src/bluetooth/qlowenergycontroller.cpp +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -35,6 +35,7 @@ #include "qlowenergycontroller_p.h" #include "qlowenergycharacteristicdata.h" +#include "qlowenergyconnectionparameters.h" #include "qlowenergydescriptordata.h" #include "qlowenergyservicedata.h" @@ -238,12 +239,25 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT) \sa discoverServices(), error() */ +/*! + \fn void QLowEnergyController::connectionUpdated(const QLowEnergyConnectionParameters &newParameters) + + This signal is emitted when the connection parameters change. This can happen as a result + of calling \l requestConnectionUpdate() or due to other reasons, for instance because + the other side of the connection requested new parameters. The new values can be retrieved + from \a newParameters. + + \sa requestConnectionUpdate() +*/ + + void registerQLowEnergyControllerMetaType() { static bool initDone = false; if (!initDone) { qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); initDone = true; } } @@ -881,6 +895,22 @@ QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData return new QLowEnergyService(servicePrivate, parent); } +/*! + Requests the controller to update the connection according to \a parameters. + If the request is successful, the \l connectionUpdated() signal will be emitted + with the actual new parameters. + See the \l QLowEnergyConnectionParameters class for more information on connection parameters. + \note Currently, this functionality is only implemented on Linux. + */ +void QLowEnergyController::requestConnectionUpdate(const QLowEnergyConnectionParameters ¶meters) +{ + if (state() != ConnectedState) { + qCWarning(QT_BT) << "Connection update request only possible in connected state"; + return; + } + d_ptr->requestConnectionUpdate(parameters); +} + /*! Returns the last occurred error or \l NoError. */ diff --git a/src/bluetooth/qlowenergycontroller.h b/src/bluetooth/qlowenergycontroller.h index cec0a8cf..608f6855 100644 --- a/src/bluetooth/qlowenergycontroller.h +++ b/src/bluetooth/qlowenergycontroller.h @@ -44,6 +44,7 @@ QT_BEGIN_NAMESPACE class QLowEnergyAdvertisingParameters; +class QLowEnergyConnectionParameters; class QLowEnergyControllerPrivate; class QLowEnergyServiceData; @@ -123,6 +124,8 @@ public: QLowEnergyService *addService(const QLowEnergyServiceData &service, QObject *parent = 0); + void requestConnectionUpdate(const QLowEnergyConnectionParameters ¶meters); + Error error() const; QString errorString() const; @@ -136,6 +139,7 @@ Q_SIGNALS: void serviceDiscovered(const QBluetoothUuid &newService); void discoveryFinished(); + void connectionUpdated(const QLowEnergyConnectionParameters ¶meters); private: explicit QLowEnergyController(QObject *parent = 0); // For the peripheral role. diff --git a/src/bluetooth/qlowenergycontroller_android.cpp b/src/bluetooth/qlowenergycontroller_android.cpp index 3e3ea830..b435f89d 100644 --- a/src/bluetooth/qlowenergycontroller_android.cpp +++ b/src/bluetooth/qlowenergycontroller_android.cpp @@ -604,6 +604,12 @@ void QLowEnergyControllerPrivate::stopAdvertising() qCWarning(QT_BT_ANDROID) << "LE advertising not implemented for Android"; } +void QLowEnergyControllerPrivate::requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms) +{ + Q_UNUSED(params); + qCWarning(QT_BT_ANDROID) << "Connection update not implemented for Android"; +} + void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &service, QLowEnergyHandle startHandle) { diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index a69bd2c4..35291c97 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -264,6 +264,17 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() hciManager->monitorEvent(HciManager::EncryptChangeEvent); connect(hciManager, SIGNAL(encryptionChangedEvent(QBluetoothAddress,bool)), this, SLOT(encryptionChangedEvent(QBluetoothAddress,bool))); + hciManager->monitorEvent(HciManager::LeMetaEvent); + connect(hciManager, &HciManager::connectionComplete, [this](quint16 handle) { + connectionHandle = handle; + qCDebug(QT_BT_BLUEZ) << "received connection complete event, handle:" << handle; + }); + connect(hciManager, &HciManager::connectionUpdate, + [this](quint16 handle, const QLowEnergyConnectionParameters ¶ms) { + if (handle == connectionHandle) + emit q_ptr->connectionUpdated(params); + } + ); } QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() @@ -359,6 +370,18 @@ void QLowEnergyControllerPrivate::stopAdvertising() advertiser->stopAdvertising(); } +void QLowEnergyControllerPrivate::requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms) +{ + // The spec says that the connection update command can be used by both slave and master + // devices, but BlueZ allows it only for master devices. So for slave devices, we have to use a + // connection parameter update request, which we need to wrap in an ACL command, as BlueZ + // does not allow user-space sockets for the signaling channel. + if (role == QLowEnergyController::CentralRole) + hciManager->sendConnectionUpdateCommand(connectionHandle, params); + else + hciManager->sendConnectionParameterUpdateRequest(connectionHandle, params); +} + void QLowEnergyControllerPrivate::connectToDevice() { if (remoteDevice.isNull()) { @@ -481,6 +504,7 @@ void QLowEnergyControllerPrivate::resetController() encryptionChangePending = false; receivedMtuExchangeRequest = false; securityLevelValue = -1; + connectionHandle = 0; } void QLowEnergyControllerPrivate::l2cpReadyRead() @@ -2623,6 +2647,8 @@ void QLowEnergyControllerPrivate::handleConnectionRequest() } remoteDevice = QBluetoothAddress(convertAddress(clientAddr.l2_bdaddr.b)); qCDebug(QT_BT_BLUEZ) << "GATT connection from device" << remoteDevice; + if (connectionHandle == 0) + qCWarning(QT_BT_BLUEZ) << "Received client connection, but no connection complete event"; closeServerSocket(); l2cpSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol, this); diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm index 7ca0d9ac..f730444a 100644 --- a/src/bluetooth/qlowenergycontroller_osx.mm +++ b/src/bluetooth/qlowenergycontroller_osx.mm @@ -1173,6 +1173,12 @@ QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData return nullptr; } +void QLowEnergyController::requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms) +{ + Q_UNUSED(params); + qCWarning(QT_BT_OSX) << "Connection update not implemented for OS X"; +} + QT_END_NAMESPACE #include "moc_qlowenergycontroller_osx_p.cpp" diff --git a/src/bluetooth/qlowenergycontroller_p.cpp b/src/bluetooth/qlowenergycontroller_p.cpp index c04d0b3c..fba8e9c1 100644 --- a/src/bluetooth/qlowenergycontroller_p.cpp +++ b/src/bluetooth/qlowenergycontroller_p.cpp @@ -116,6 +116,10 @@ void QLowEnergyControllerPrivate::stopAdvertising() { } +void QLowEnergyControllerPrivate::requestConnectionUpdate(const QLowEnergyConnectionParameters & /* params */) +{ +} + void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &/* service */, QLowEnergyHandle /* startHandle */) { diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index f3b28f5c..e0b2a2d7 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -122,6 +122,8 @@ public: const QLowEnergyAdvertisingData &scanResponseData); void stopAdvertising(); + void requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms); + // misc helpers QSharedPointer serviceForHandle( QLowEnergyHandle handle); @@ -192,6 +194,7 @@ public: private: #if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE) + quint16 connectionHandle = 0; QBluetoothSocket *l2cpSocket; struct Request { quint8 command; diff --git a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp index e2c930bf..087b0284 100644 --- a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp +++ b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -212,6 +213,11 @@ int main(int argc, char *argv[]) Q_ASSERT(notifiableChar.isValid()); customService->writeCharacteristic(notifiableChar, "notified"); Q_ASSERT(notifiableChar.value() == "notified"); + QLowEnergyConnectionParameters connParams; + connParams.setIntervalRange(30, 62.5); + connParams.setLatency(5); + connParams.setSupervisionTimeout(5500); + leController->requestConnectionUpdate(connParams); }; QObject::connect(customService.data(), &QLowEnergyService::descriptorWritten, descriptorWriteHandler); diff --git a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp index 6c45eed7..e2534a14 100644 --- a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp +++ b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,7 @@ private slots: // Static, local stuff goes here. void advertisingParameters(); void advertisingData(); + void connectionParameters(); void controllerType(); void serviceData(); @@ -148,6 +150,28 @@ void TestQLowEnergyControllerGattServer::advertisingData() QVERIFY(data != QLowEnergyAdvertisingData()); } +void TestQLowEnergyControllerGattServer::connectionParameters() +{ + QLowEnergyConnectionParameters connParams; + QCOMPARE(connParams, QLowEnergyConnectionParameters()); + connParams.setIntervalRange(8, 9); + QCOMPARE(connParams.minimumInterval(), double(8)); + QCOMPARE(connParams.maximumInterval(), double(9)); + connParams.setIntervalRange(9, 8); + QCOMPARE(connParams.minimumInterval(), double(9)); + QCOMPARE(connParams.maximumInterval(), double(9)); + connParams.setLatency(50); + QCOMPARE(connParams.latency(), 50); + connParams.setSupervisionTimeout(1000); + QCOMPARE(connParams.supervisionTimeout(), 1000); + const QLowEnergyConnectionParameters cp2 = connParams; + QCOMPARE(cp2, connParams); + QLowEnergyConnectionParameters cp3; + QVERIFY(cp3 != connParams); + cp3 = connParams; + QCOMPARE(cp3, connParams); +} + void TestQLowEnergyControllerGattServer::advertisedData() { if (m_serverAddress.isNull()) @@ -325,6 +349,9 @@ void TestQLowEnergyControllerGattServer::serverCommunication() QCOMPARE(customChar3.value().constData(), "indicated"); QCOMPARE(customChar4.value().constData(), "notified"); + spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::connectionUpdated)); + QVERIFY(spy->wait(5000)); + const bool isBonded = QBluetoothLocalDevice().pairingStatus(m_serverAddress) != QBluetoothLocalDevice::Unpaired; m_leController->disconnectFromDevice(); -- cgit v1.2.3