summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qlowenergycontroller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/qlowenergycontroller.cpp')
-rw-r--r--src/bluetooth/qlowenergycontroller.cpp398
1 files changed, 370 insertions, 28 deletions
diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp
index e39df97c..a3aad282 100644
--- a/src/bluetooth/qlowenergycontroller.cpp
+++ b/src/bluetooth/qlowenergycontroller.cpp
@@ -1,31 +1,37 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing/
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtBluetooth module of the Qt Toolkit.
**
-** $QT_BEGIN_LICENSE:LGPL21$
+** $QT_BEGIN_LICENSE:LGPL$
** 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.
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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.
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.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.
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
@@ -34,12 +40,20 @@
#include "qlowenergycontroller.h"
#include "qlowenergycontroller_p.h"
+#include "qlowenergycharacteristicdata.h"
+#include "qlowenergyconnectionparameters.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 +63,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 +74,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 +105,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 +136,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 +153,8 @@ 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.
+ This value was introduced by Qt 5.7.
*/
/*!
@@ -135,26 +162,53 @@ 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. In addition, handling the
+ "Signed Write" ATT command on the server side requires BlueZ 5 and kernel version 3.7
+ or newer.
+ */
+
/*!
\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).
+ On iOS and OS X this signal is not reliable if the controller is in the \l PeripheralRole
+ - the controller only guesses that some central connected to our peripheral as
+ soon as this central tries to write/read a characteristic/descriptor.
*/
/*!
\fn void QLowEnergyController::disconnected()
This signal is emitted when the controller disconnects from the remote
- Low Energy device.
+ Low Energy device or vice versa. On iOS and OS X this signal is unreliable
+ if the controller is in the \l PeripheralRole.
*/
/*!
@@ -181,6 +235,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,15 +247,31 @@ 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()
*/
+/*!
+ \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.
+
+ \since 5.7
+ \sa requestConnectionUpdate()
+*/
+
+
void registerQLowEnergyControllerMetaType()
{
static bool initDone = false;
if (!initDone) {
qRegisterMetaType<QLowEnergyController::ControllerState>();
qRegisterMetaType<QLowEnergyController::Error>();
+ qRegisterMetaType<QLowEnergyConnectionParameters>();
initDone = true;
}
}
@@ -224,8 +296,12 @@ 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:
case QLowEnergyController::UnknownError:
errorString = QLowEnergyController::tr("Unknown Error");
break;
@@ -236,6 +312,9 @@ void QLowEnergyControllerPrivate::setError(
bool QLowEnergyControllerPrivate::isValidLocalAdapter()
{
+#ifdef QT_WINRT_BLUETOOTH
+ return true;
+#endif
if (localAdapter.isNull())
return false;
@@ -260,6 +339,10 @@ void QLowEnergyControllerPrivate::setState(
return;
state = newState;
+ if (state == QLowEnergyController::UnconnectedState
+ && role == QLowEnergyController::PeripheralRole) {
+ remoteDevice.clear();
+ }
emit q->stateChanged(state);
}
@@ -276,7 +359,12 @@ void QLowEnergyControllerPrivate::invalidateServices()
QSharedPointer<QLowEnergyServicePrivate> QLowEnergyControllerPrivate::serviceForHandle(
QLowEnergyHandle handle)
{
- foreach (QSharedPointer<QLowEnergyServicePrivate> service, serviceList.values())
+ ServiceDataMap &currentList = serviceList;
+ if (role == QLowEnergyController::PeripheralRole)
+ currentList = localServices;
+
+ const QList<QSharedPointer<QLowEnergyServicePrivate>> values = currentList.values();
+ for (auto service: values)
if (service->startHandle <= handle && handle <= service->endHandle)
return service;
@@ -408,9 +496,11 @@ QLowEnergyController::QLowEnergyController(
{
Q_D(QLowEnergyController);
d->q_ptr = this;
+ d->role = CentralRole;
d->remoteDevice = remoteDevice;
d->localAdapter = QBluetoothLocalDevice().address();
d->addressType = QLowEnergyController::PublicAddress;
+ d->init();
}
/*!
@@ -424,6 +514,7 @@ QLowEnergyController::QLowEnergyController(
the connection management.
\since 5.5
+ \obsolete
*/
QLowEnergyController::QLowEnergyController(
const QBluetoothDeviceInfo &remoteDeviceInfo,
@@ -432,10 +523,12 @@ QLowEnergyController::QLowEnergyController(
{
Q_D(QLowEnergyController);
d->q_ptr = this;
+ d->role = CentralRole;
d->remoteDevice = remoteDeviceInfo.address();
d->localAdapter = QBluetoothLocalDevice().address();
d->addressType = QLowEnergyController::PublicAddress;
d->remoteName = remoteDeviceInfo.name();
+ d->init();
}
/*!
@@ -461,8 +554,52 @@ QLowEnergyController::QLowEnergyController(
{
Q_D(QLowEnergyController);
d->q_ptr = this;
+ d->role = CentralRole;
d->remoteDevice = remoteDevice;
d->localAdapter = localDevice;
+ d->init();
+}
+
+/*!
+ Returns a new object of this class that is in the \l CentralRole and has the
+ parent object \a parent.
+ 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
+ \since 5.7
+ */
+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 and has the
+ parent object \a parent.
+ 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
+ \since 5.7
+ */
+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();
+ d->init();
}
/*!
@@ -491,6 +628,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
{
@@ -498,7 +640,25 @@ QBluetoothAddress QLowEnergyController::remoteAddress() const
}
/*!
- Returns the name of the remote Bluetooth Low Energy device.
+ Returns the unique identifier of the remote Bluetooth Low Energy device.
+
+ On macOS/iOS/tvOS CoreBluetooth does not expose/accept hardware addresses for
+ LE devices; instead developers are supposed to use unique 128-bit UUIDs, generated
+ by CoreBluetooth. These UUIDS will stay constant for the same central <-> peripheral
+ pair and we use them when connecting to a remote 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 invalid.
+
+ \since 5.8
+ */
+QBluetoothUuid QLowEnergyController::remoteDeviceUuid() const
+{
+ return QBluetoothUuid();
+}
+
+/*!
+ 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
*/
@@ -556,6 +716,11 @@ void QLowEnergyController::connectToDevice()
{
Q_D(QLowEnergyController);
+ if (role() != CentralRole) {
+ qCWarning(QT_BT) << "Connection can only be established while in central role";
+ return;
+ }
+
if (!d->isValidLocalAdapter()) {
d->setError(QLowEnergyController::InvalidBluetoothAdapterError);
return;
@@ -577,6 +742,10 @@ void QLowEnergyController::connectToDevice()
This function does nothing if the controller is in the \l UnconnectedState.
+ If the controller is in the peripheral role, it stops advertising too.
+ The application must restart the advertising mode by calling
+ \l startAdvertising().
+
\sa connectToDevice()
*/
void QLowEnergyController::disconnectFromDevice()
@@ -610,6 +779,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;
@@ -618,7 +791,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.
@@ -671,6 +845,161 @@ 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. \a advertisingData and \a scanResponseData are limited
+ to 31 byte user data. If, for example, several 128bit uuids are added to \a advertisingData,
+ the advertised packets may not contain all uuids. The existing limit may have caused the truncation
+ of uuids. In such cases \a scanResponseData may be used for additional information.
+
+ 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.
+
+ \since 5.7
+ \sa stopAdvertising()
+ */
+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.
+
+ \since 5.7
+ \sa startAdvertising()
+ */
+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.
+
+ \since 5.7
+ \sa QLowEnergyServiceData::addIncludedService
+ */
+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);
+}
+
+/*!
+ 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.
+
+ Android only indirectly permits the adjustment of this parameter set.
+ The connection parameters are separated into three categories (high, low & balanced priority).
+ Each category implies a pre-configured set of values for
+ \l QLowEnergyConnectionParameters::minimumInterval(),
+ \l QLowEnergyConnectionParameters::maximumInterval() and
+ \l QLowEnergyConnectionParameters::latency(). Although the connection request is an asynchronous
+ operation, Android does not provide a callback stating the result of the request. This is
+ an acknowledged Android bug. Due to this bug Android does not emit the \l connectionUpdated()
+ signal.
+
+ \note Currently, this functionality is only implemented on Linux and Android.
+
+ \sa connectionUpdated()
+ \since 5.7
+ */
+void QLowEnergyController::requestConnectionUpdate(const QLowEnergyConnectionParameters &parameters)
+{
+ switch (state()) {
+ case ConnectedState:
+ case DiscoveredState:
+ case DiscoveringState:
+ d_ptr->requestConnectionUpdate(parameters);
+ break;
+ default:
+ qCWarning(QT_BT) << "Connection update request only possible in connected state";
+ }
+}
+
+/*!
Returns the last occurred error or \l NoError.
*/
QLowEnergyController::Error QLowEnergyController::error() const
@@ -687,4 +1016,17 @@ QString QLowEnergyController::errorString() const
return d_ptr->errorString;
}
+/*!
+ Returns the role that this controller object is in.
+
+ The role is determined when constructing a QLowEnergyController instance
+ using \l createCentral() or \l createPeripheral().
+
+ \since 5.7
+ */
+QLowEnergyController::Role QLowEnergyController::role() const
+{
+ return d_ptr->role;
+}
+
QT_END_NAMESPACE