Bluetooth Low Energy Overview

The Qt Bluetooth Low Energy API was introduced by Qt 5.4. Since Qt 5.5 the API is final and a compatibility guarantee is given for future releases. At the moment, Qt supports the Bluetooth Low Energy central role. For more details on this limitation see below.

What Is Bluetooth Low Energy

Bluetooth Low Energy, also known as Bluetooth Smart, is a wireless computer network technology, which was officially introduced in 2011. It works on the same 2.4 GHz frequency as ”classic” Bluetooth. The main difference is, as stated by its technology name, low energy consumption. It provides an opportunity for Bluetooth Low Energy devices to operate for months, even years, on coin-cell batteries. The technology was introduced by Bluetooth v4.0. Devices which support this technology are called Bluetooth Smart Ready Devices. The key features of the technology are:

  • Ultra-low peak, average and idle mode power consumption
  • Ability to run for years on standard, coin-cell batteries
  • Low cost
  • Multi-vendor interoperability
  • Enhanced range

Bluetooth Low Energy uses a client-server architecture. The server (also known as peripheral) offers services such as temperature or heart rate and advertises them. The client (known as central device) connects to the server and reads the values advertised by the server. An example might be an apartment with Bluetooth Smart Ready sensors such as a thermostat, humidity or pressure sensor. Those sensors are peripheral devices advertising the environment values of the apartment. At the same time a mobile phone or computer might connect to those sensors, retrieve their values and present them as part of a larger environment control application to the user.

Basic Service Structure

Bluetooth Low Energy is based on two protocols: ATT (Attribute Protocol) and GATT (Generic Attribute Profile). They specify the communication layers used by every Bluetooth Smart Ready device.

ATT Protocol

The basic building block of ATT is an attribute. Each attribute consists of three elements:

  • a value - the payload or desirable piece of information
  • a UUID - the type of attribute (used by GATT)
  • a 16-bit handle - a unique identifier for the attribute

The server stores the attributes and the client uses the ATT protocol to read and write values on the server.

GATT Profile

GATT defines grouping for a set of attributes by applying a meaning to predefined UUIDs. The table below shows an example service exposing a heart rate on a particular day. The actual values are stored inside the two characteristics:

HandleUUIDValueDescription
0x00010x2800UUID 0x180DBegin Heart Rate service
0x00020x2803UUID 0x2A37, Value handle: 0x0003Characteristic of type Heart Rate Measurement (HRM)
0x00030x2A3765 bpmHeart rate value
0x00040x2803UUID 0x2A08, Value handle: 0x0006Characteristic of type Date Time
0x00050x2A0818/08/2014 11:00Date and Time of the measurement
0x00060x2800UUID xxxxxxBegin next service
............

GATT specifies that the above used UUID 0x2800 marks the begin of a service definition. Every attribute following 0x2800 is part of the service until the next 0x2800 or the end is encountered. In similar ways the well known UUID 0x2803 states that a characteristic is to be found and each of the characteristics has a type defining the nature of the value. The example above uses the UUIDs 0x2A08 (Date Time) and 0x2A37 (Heart Rate Measurement). Each of the above UUIDs is defined by the Bluetooth Special Interest Group. and can be found in the GATT specification. While it is advisable to use pre-defined UUIDs where available it is entirely possible to use new and not yet used UUIDs for characteristic and service types.

In general, each service may consist of one or more characteristics. A characteristic contains data and can be further described by descriptors, which provide additional information or means of manipulating the characteristic. All services, characteristics and descriptors are recognized by their 128-bit UUID. Finally, it is possible to include services inside of services (see picture below).

Using Qt Bluetooth Low Energy API

This section describes how to use the Bluetooth Low Energy API provided by Qt. Currently the API permits creating connections to peripheral devices, discovering their services, as well as reading and writing data stored on the device. The example code below is taken from the Heart Listener example.

Establishing a Connection

To be able to read and write the characteristics of the Bluetooth Low Energy peripheral device, it is necessary to find and connect the device. This requires the peripheral device to advertise its presence and services. We start the device discovery with the help of the QBluetoothDeviceDiscoveryAgent class. We connect to its QBluetoothDeviceDiscoveryAgent::deviceDiscovered() signal and start the search with start():

m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);

connect(m_deviceDiscoveryAgent, SIGNAL(deviceDiscovered(const QBluetoothDeviceInfo&)),
        this, SLOT(addDevice(const QBluetoothDeviceInfo&)));
connect(m_deviceDiscoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)),
        this, SLOT(deviceScanError(QBluetoothDeviceDiscoveryAgent::Error)));
connect(m_deviceDiscoveryAgent, SIGNAL(finished()), this, SLOT(scanFinished()));
m_deviceDiscoveryAgent->start();

Since we are only interested in Low Energy devices we filter the device type within the receiving slot. The device type can be ascertained using the QBluetoothDeviceInfo::coreConfigurations() flag:

void HeartRate::addDevice(const QBluetoothDeviceInfo &device)
{
    if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
        qWarning() << "Discovered LE Device name: " << device.name() << " Address: "
                   << device.address().toString();
    }
    //...
}

Once the address of the peripheral device is known we use the QLowEnergyController class. This class is the entry point for all Bluetooth Low Energy development. The constructor of the class accepts the remote device's QBluetoothAddress. Finally we set up the customary slots and directly connect to the device using connectToDevice():

m_control = new QLowEnergyController(m_currentDevice.getDevice(), this);
connect(m_control, SIGNAL(serviceDiscovered(QBluetoothUuid)),
        this, SLOT(serviceDiscovered(QBluetoothUuid)));
connect(m_control, SIGNAL(discoveryFinished()),
        this, SLOT(serviceScanDone()));
connect(m_control, SIGNAL(error(QLowEnergyController::Error)),
        this, SLOT(controllerError(QLowEnergyController::Error)));
connect(m_control, SIGNAL(connected()),
        this, SLOT(deviceConnected()));
connect(m_control, SIGNAL(disconnected()),
        this, SLOT(deviceDisconnected()));

m_control->connectToDevice();

Service Search

As soon as the connection is established we initiate the service discovery:

void HeartRate::deviceConnected()
{
    m_control->discoverServices();
}

void HeartRate::deviceDisconnected()
{
    setMessage("Heart Rate service disconnected");
    qWarning() << "Remote device disconnected";
}

The serviceDiscovered() slot below is triggered as a result of the QLowEnergyController::serviceDiscovered() signal and provides an intermittent progress report. Since we are talking about the heart listener app which monitors HeartRate devices in the vicinity we ignore any service that is not of type QBluetoothUuid::HeartRate.

void HeartRate::serviceDiscovered(const QBluetoothUuid &gatt)
{
    if (gatt == QBluetoothUuid(QBluetoothUuid::HeartRate)) {
        setMessage("Heart Rate service discovered. Waiting for service scan to be done...");
        foundHeartRateService = true;
    }
}

Eventually the QLowEnergyController::discoveryFinished() signal is emitted to indicate the successful completion of the service discovery. Provided a HeartRate service was found, a QLowEnergyService instance is created to represent the service. The returned service object provides the required signals for update notifications and the discovery of service details is triggered using QLowEnergyService::discoverDetails():

if (foundHeartRateService) {
    setMessage("Connecting to service...");
    m_service = m_control->createServiceObject(
                QBluetoothUuid(QBluetoothUuid::HeartRate), this);
}

if (!m_service) {
    setMessage("Heart Rate Service not found.");
    return;
}

connect(m_service, SIGNAL(stateChanged(QLowEnergyService::ServiceState)),
        this, SLOT(serviceStateChanged(QLowEnergyService::ServiceState)));
connect(m_service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
        this, SLOT(updateHeartRateValue(QLowEnergyCharacteristic,QByteArray)));
connect(m_service, SIGNAL(descriptorWritten(QLowEnergyDescriptor,QByteArray)),
        this, SLOT(confirmedDescriptorWrite(QLowEnergyDescriptor,QByteArray)));

m_service->discoverDetails();

During the detail search the service's state() transitions from DiscoveryRequired to DiscoveringServices and eventually ends with ServiceDiscovered:

void HeartRate::serviceStateChanged(QLowEnergyService::ServiceState s)
{
    switch (s) {
    case QLowEnergyService::ServiceDiscovered:
    {
        const QLowEnergyCharacteristic hrChar = m_service->characteristic(
                    QBluetoothUuid(QBluetoothUuid::HeartRateMeasurement));
        if (!hrChar.isValid()) {
            setMessage("HR Data not found.");
            break;
        }

        m_notificationDesc = hrChar.descriptor(
                    QBluetoothUuid::ClientCharacteristicConfiguration);
        if (m_notificationDesc.isValid()) {
            m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100"));
            setMessage("Measuring");
            m_start = QDateTime::currentDateTime();
        }

        break;
    }
    default:
        //nothing for now
        break;
    }
}

Interaction with the Peripheral Device

In the code example above, the desired characteristic is of type HeartRateMeasurement. Since the application measures the heart rate changes, it must enable change notifications for the characteristic. Note that not all characteristics provide change notifications. Since the HeartRate characteristic has been standardized it is possible to assume that notifications can be received. Ultimately QLowEnergyCharacteristic::properties() must have the QLowEnergyCharacteristic::Notify flag set and a descriptor of type QBluetoothUuid::ClientCharacteristicConfiguration must exist to confirm the availability of an appropriate notification.

Finally, we process the value of the HeartRate characteristic, as per Bluetooth Low Energy standard:

void HeartRate::updateHeartRateValue(const QLowEnergyCharacteristic &c,
                                     const QByteArray &value)
{
    // ignore any other characteristic change -> shouldn't really happen though
    if (c.uuid() != QBluetoothUuid(QBluetoothUuid::HeartRateMeasurement))
        return;

    const quint8 *data = reinterpret_cast<const quint8 *>(value.constData());
    quint8 flags = data[0];

    //Heart Rate
    if (flags & 0x1) { // HR 16 bit? otherwise 8 bit
        const quint16 heartRate = qFromLittleEndian<quint16>(data[1]);
        //qDebug() << "16 bit HR value:" << heartRate;
        m_measurements.append(heartRate);
    } else {
        const quint8 *heartRate = &data[1];
        m_measurements.append(*heartRate);
        //qDebug() << "8 bit HR value:" << *heartRate;
    }

    //Energy Expended
    if (flags & 0x8) {
        int index = (flags & 0x1) ? 5 : 3;
        const quint16 energy = qFromLittleEndian<quint16>(data[index]);
        qDebug() << "Used Energy:" << energy;
    }
}

In general a characteristic value is a series of bytes. The precise interpretation of those bytes depends on the characteristic type and value structure. A significant number has been standardized by the Bluetooth SIG whereas others may follow a custom protocol. The above code snippet demonstrates how to the read the standardized HeartRate value.

© 2017 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.