En esta página

Visión general de Bluetooth Low Energy

La API Qt Bluetooth Low Energy soporta los roles periférico/servidor y central/cliente. Es compatible con las principales plataformas Qt. La única excepción es la falta de soporte para periféricos en Windows.

Qué es Bluetooth Low Energy

Bluetooth Low Energy, también conocido como Bluetooth Smart, es una tecnología de red informática inalámbrica, que se introdujo oficialmente en 2011. Funciona en la misma frecuencia de 2,4 GHz que el Bluetooth "clásico". La principal diferencia es, como indica su nombre tecnológico, el bajo consumo de energía. Permite que los dispositivos Bluetooth Low Energy funcionen durante meses, incluso años, con pilas de botón. La tecnología se introdujo con Bluetooth v4.0. Los dispositivos compatibles con esta tecnología se denominan dispositivos Bluetooth Smart Ready. Las características clave de la tecnología son:

  • Consumo de energía pico, medio y en modo inactivo ultrabajo.
  • Funcionamiento durante años con pilas de botón estándar.
  • Bajo coste
  • Interoperabilidad con múltiples proveedores
  • Mayor autonomía

Bluetooth Low Energy utiliza una arquitectura cliente-servidor. El servidor (también conocido como periférico) ofrece servicios como la temperatura o la frecuencia cardiaca y los anuncia. El cliente (conocido como dispositivo central) se conecta al servidor y lee los valores anunciados por el servidor. Un ejemplo podría ser un apartamento con sensores Bluetooth Smart Ready como un termostato, un sensor de humedad o de presión. Estos sensores son dispositivos periféricos que anuncian los valores ambientales de la vivienda. Al mismo tiempo, un teléfono móvil o un ordenador podrían conectarse a esos sensores, recuperar sus valores y presentarlos al usuario como parte de una aplicación de control ambiental más amplia.

Estructura básica del servicio

Bluetooth Low Energy se basa en dos protocolos: ATT (Attribute Protocol) y GATT (Generic Attribute Profile). Especifican las capas de comunicación utilizadas por cada dispositivo Bluetooth Smart Ready.

Protocolo ATT

El elemento básico de ATT es un atributo. Cada atributo consta de tres elementos:

  • un valor: la carga útil o información deseada
  • un UUID: tipo de atributo (utilizado por GATT)
  • un "handle" de 16 bits: identificador único del atributo.

El servidor almacena los atributos y el cliente utiliza el protocolo ATT para leer y escribir valores en el servidor.

Perfil GATT

GATT define la agrupación de un conjunto de atributos aplicando un significado a UUID predefinidos. La tabla siguiente muestra un ejemplo de servicio que expone la frecuencia cardíaca en un día concreto. Los valores reales se almacenan dentro de las dos características:

MangoUUIDValorDescripción
0x00010x2800UUID 0x180DIniciar servicio de Frecuencia Cardíaca
0x00020x2803UUID 0x2A37, Manejador de valor: 0x0003Característica de tipo Medición de la Frecuencia Cardíaca (HRM)
0x00030x2A3765 lpmValor de frecuencia cardíaca
0x00040x2803UUID 0x2A08, Manejador de valor: 0x0005Característica de tipo Fecha Hora
0x00050x2A0818/08/2014 11:00Fecha y hora de la medición
0x00060x2800UUID xxxxxxIniciar siguiente servicio
............

El GATT especifica que el UUID 0x2800 utilizado anteriormente marca el inicio de la definición de un servicio. Cada atributo que sigue a 0x2800 forma parte del servicio hasta que se encuentra el siguiente 0x2800 o el final. De forma similar, el conocido UUID 0x2803 indica que se debe encontrar una característica y cada una de las características tiene un tipo que define la naturaleza del valor. En el ejemplo anterior se utilizan los UUID 0x2A08 (Fecha Hora) y 0x2A37 (Medición del Ritmo Cardíaco). Cada uno de los UUID anteriores está definido por el Bluetooth Special Interest Group. y puede encontrarse en las especificaciones del GATT. Aunque es aconsejable utilizar UUID predefinidos cuando estén disponibles, es totalmente posible utilizar UUID nuevos y aún no utilizados para tipos de características y servicios.

En general, cada servicio puede constar de una o varias características. Una característica contiene datos y puede describirse además mediante descriptores, que proporcionan información adicional o medios para manipular la característica. Todos los servicios, características y descriptores se reconocen por su UUID de 128 bits. Por último, es posible incluir servicios dentro de otros servicios (véase la imagen siguiente).

Estructura de un periférico BLE en Qt

Uso de Qt Bluetooth Low Energy API

Esta sección describe cómo utilizar la API Bluetooth Low Energy proporcionada por Qt. En el lado del cliente, la API permite crear conexiones con dispositivos periféricos, descubrir sus servicios, así como leer y escribir datos almacenados en el dispositivo. En el lado del servidor, permite configurar servicios, anunciarlos y recibir notificaciones cuando el cliente escribe características. El código de ejemplo que se muestra a continuación está tomado de los ejemplos Heart Rate Game y Heart Rate Server.

Establecer una conexión

Para poder leer y escribir las características del dispositivo periférico Bluetooth Low Energy, es necesario encontrar y conectar el dispositivo. Esto requiere que el dispositivo periférico anuncie su presencia y servicios. Iniciamos el descubrimiento del dispositivo con la ayuda de la clase QBluetoothDeviceDiscoveryAgent. Nos conectamos a su señal QBluetoothDeviceDiscoveryAgent::deviceDiscovered() e iniciamos la búsqueda con start():

m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
m_deviceDiscoveryAgent->setLowEnergyDiscoveryTimeout(15000);

connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
        this, &DeviceFinder::addDevice);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::errorOccurred,
        this, &DeviceFinder::scanError);

connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished,
        this, &DeviceFinder::scanFinished);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::canceled,
        this, &DeviceFinder::scanFinished);
m_deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);

Como sólo nos interesan los dispositivos de baja energía, filtramos el tipo de dispositivo dentro de la ranura de recepción. El tipo de dispositivo se puede averiguar utilizando la bandera QBluetoothDeviceInfo::coreConfigurations(). La señal deviceDiscovered() puede emitirse varias veces para el mismo dispositivo a medida que se descubren más detalles. Aquí emparejamos estos descubrimientos de dispositivos para que el usuario sólo vea los dispositivos individuales:

void DeviceFinder::addDevice(const QBluetoothDeviceInfo &device)
{
    // If device is LowEnergy-device, add it to the list
    if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
        auto devInfo = new DeviceInfo(device);
        auto it = std::find_if(m_devices.begin(), m_devices.end(),
                               [devInfo](DeviceInfo *dev) {
                                   return devInfo->getAddress() == dev->getAddress();
                               });
        if (it == m_devices.end()) {
            m_devices.append(devInfo);
        } else {
            auto oldDev = *it;
            *it = devInfo;
            delete oldDev;
        }
        setInfo(tr("Low Energy device found. Scanning more..."));
        setIcon(IconProgress);
    }
    //...
}

Una vez conocida la dirección del dispositivo periférico utilizamos la clase QLowEnergyController. Esta clase es el punto de entrada para todo el desarrollo de Bluetooth Low Energy. El constructor de la clase acepta la dirección QBluetoothAddress del dispositivo remoto. Finalmente configuramos las ranuras habituales y nos conectamos directamente al dispositivo utilizando connectToDevice():

m_control = QLowEnergyController::createCentral(m_currentDevice->getDevice(), this);
connect(m_control, &QLowEnergyController::serviceDiscovered,
        this, &DeviceHandler::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished,
        this, &DeviceHandler::serviceScanDone);

connect(m_control, &QLowEnergyController::errorOccurred, this,
        [this](QLowEnergyController::Error error) {
            Q_UNUSED(error);
            setError("Cannot connect to remote device.");
            setIcon(IconError);
        });
connect(m_control, &QLowEnergyController::connected, this, [this]() {
    setInfo("Controller connected. Search services...");
    setIcon(IconProgress);
    m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
    setError("LowEnergy controller disconnected");
    setIcon(IconError);
});

// Connect
m_control->connectToDevice();

El fragmento de código anterior muestra cómo la aplicación inicia la búsqueda de servicios una vez establecida la conexión.

El fragmento serviceDiscovered() que aparece a continuación se activa como resultado de la señal QLowEnergyController::serviceDiscovered() y proporciona un informe de progreso intermitente. Dado que estamos hablando de la aplicación heart listener que monitoriza los dispositivos HeartRate en las proximidades, ignoramos cualquier servicio que no sea del tipo QBluetoothUuid::ServiceClassUuid::HeartRate.

void DeviceHandler::serviceDiscovered(const QBluetoothUuid &gatt)
{
    if (gatt == QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::HeartRate)) {
        setInfo("Heart Rate service discovered. Waiting for service scan to be done...");
        setIcon(IconProgress);
        m_foundHeartRateService = true;
    }
}

Finalmente, se emite la señal QLowEnergyController::discoveryFinished() para indicar que se ha completado con éxito el descubrimiento del servicio. Si se encuentra un servicio HeartRate, se crea una instancia QLowEnergyService para representarlo. El objeto de servicio devuelto proporciona las señales necesarias para las notificaciones de actualización y el descubrimiento de los detalles del servicio se activa mediante QLowEnergyService::discoverDetails():

    // If heartRateService found, create new service
    if (m_foundHeartRateService)
        m_service = m_control->createServiceObject(QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::HeartRate), this);

    if (m_service) {
        connect(m_service, &QLowEnergyService::stateChanged, this, &DeviceHandler::serviceStateChanged);
        connect(m_service, &QLowEnergyService::characteristicChanged, this, &DeviceHandler::updateHeartRateValue);
        connect(m_service, &QLowEnergyService::descriptorWritten, this, &DeviceHandler::confirmedDescriptorWrite);
        m_service->discoverDetails();
    } else {
        setError("Heart Rate Service not found.");
        setIcon(IconError);
    }

Durante la búsqueda de detalles, la instancia state() del servicio pasa de RemoteService a RemoteServiceDiscovering y finalmente termina con RemoteServiceDiscovered:

void DeviceHandler::serviceStateChanged(QLowEnergyService::ServiceState s)
{
    switch (s) {
    case QLowEnergyService::RemoteServiceDiscovering:
        setInfo(tr("Discovering services..."));
        setIcon(IconProgress);
        break;
    case QLowEnergyService::RemoteServiceDiscovered:
    {
        setInfo(tr("Service discovered."));
        setIcon(IconBluetooth);

        const QLowEnergyCharacteristic hrChar =
                m_service->characteristic(QBluetoothUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement));
        if (!hrChar.isValid()) {
            setError("HR Data not found.");
            setIcon(IconError);
            break;
        }

        m_notificationDesc = hrChar.descriptor(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
        if (m_notificationDesc.isValid())
            m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100"));

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

    emit aliveChanged();
}

Interacción con el dispositivo periférico

En el ejemplo de código anterior, la característica deseada es del tipo HeartRateMeasurement. Dado que la aplicación mide los cambios del ritmo cardíaco, debe habilitar notificaciones de cambio para la característica. Tenga en cuenta que no todas las características proporcionan notificaciones de cambio. Dado que la característica HeartRate ha sido estandarizada, es posible asumir que se pueden recibir notificaciones. En última instancia, QLowEnergyCharacteristic::properties() debe tener activada la bandera QLowEnergyCharacteristic::Notify y debe existir un descriptor de tipo QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration para confirmar la disponibilidad de una notificación adecuada.

Por último, procesamos el valor de la característica HeartRate, según el estándar Bluetooth Low Energy:

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

    auto data = reinterpret_cast<const quint8 *>(value.constData());
    quint8 flags = *data;

    //Heart Rate
    int hrvalue = 0;
    if (flags & 0x1) // HR 16 bit? otherwise 8 bit
        hrvalue = static_cast<int>(qFromLittleEndian<quint16>(data[1]));
    else
        hrvalue = static_cast<int>(data[1]);

    addMeasurement(hrvalue);
}

En general, el valor de una característica es una serie de bytes. La interpretación exacta de esos bytes depende del tipo de característica y de la estructura de valores. Un número significativo ha sido estandarizado por Bluetooth SIG, mientras que otros pueden seguir un protocolo personalizado. El fragmento de código anterior muestra cómo leer el valor estandarizado de HeartRate.

Servicios de publicidad

Si estamos implementando una aplicación de servidor GATT en un dispositivo periférico, necesitamos definir los servicios que queremos ofrecer a los dispositivos centrales y anunciarlos:

QLowEnergyAdvertisingData advertisingData; advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral); advertisingData.setIncludePowerLevel(true); advertisingData.setLocalName("HeartRateServer"); advertisingData.setServices(QList<QBluetoothUuid>()<< QBluetoothUuid::ServiceClassUuid::HeartRate);bool errorOccurred = false;const std::unique_ptr<QLowEnergyController> leController(QLowEnergyController::createPeripheral());auto errorHandler = [&leController, &errorOccurred](QLowEnergyController::Error errorCode) {        qWarning().noquote().nospace() << errorCode << " occurred: "
           <<  leController->errorString(); if (errorCode! = QLowEnergyController::RemoteHostClosedError) {            qWarning("Heartrate-server quitting due to the error.");
            errorOccurred = true;     QCoreApplication::quit(); } };QObject::connect(leController.get(), &QLowEnergyController::errorOccurred, errorHandler); std::unique_ptr<QLowEnergyService> service(leController->addService(serviceData));  leController->startAdvertising(QLowEnergyAdvertisingParameters(), advertisingData,advertisingData);if (errorOccurred) return-1;

Ahora los clientes potenciales pueden conectarse a nuestro dispositivo, descubrir el servicio prestado y registrarse para recibir notificaciones de los cambios en el valor de la característica. Esta parte de la API ya estaba cubierta en las secciones anteriores.

Implementación de un servicio en el dispositivo periférico

El primer paso consiste en definir el servicio, sus características y descriptores. Para ello se utilizan las clases QLowEnergyServiceData, QLowEnergyCharacteristicData y QLowEnergyDescriptorData. Estas clases actúan como contenedores o bloques de construcción para la información esencial que comprende el servicio Bluetooth Low Energy que se va a definir. El siguiente fragmento de código define un sencillo servicio HeartRate que publica las pulsaciones por minuto medidas. Un ejemplo en el que podría utilizarse un servicio de este tipo es un reloj de pulsera.

QLowEnergyCharacteristicData charData;
charData.setUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement);
charData.setValue(QByteArray(2, 0));
charData.setProperties(QLowEnergyCharacteristic::Notify);
const QLowEnergyDescriptorData clientConfig(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration,
                                            QByteArray(2, 0));
charData.addDescriptor(clientConfig);

QLowEnergyServiceData serviceData;
serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
serviceData.setUuid(QBluetoothUuid::ServiceClassUuid::HeartRate);
serviceData.addCharacteristic(charData);

El objeto serviceData resultante puede publicarse tal y como se describe en la sección anterior Servicios de publicidad. A pesar del solapamiento parcial de la información envuelta por QLowEnergyServiceData y QLowEnergyAdvertisingData, las dos clases sirven para dos tareas muy diferentes. Los datos publicitarios se publican en dispositivos cercanos y a menudo tienen un alcance limitado debido a su restricción de tamaño de 29 bytes. Por lo tanto, no siempre están completos al 100%. En comparación, los datos de servicio contenidos en QLowEnergyServiceData proporcionan el conjunto completo de datos de servicio y sólo se hacen visibles para el cliente que se conecta cuando se ha realizado una conexión con un descubrimiento de servicio activo.

La siguiente sección demuestra cómo el servicio puede actualizar el valor de la frecuencia cardíaca. Dependiendo de la naturaleza del servicio, puede que tenga que ajustarse a la definición oficial de servicio definida en https://www.bluetooth.org. Otros servicios pueden ser completamente personalizados. Se ha adoptado el servicio de frecuencia cardíaca, cuya especificación puede consultarse en https://www.bluetooth.com/specifications/adopted-specifications.

QTimer heartbeatTimer;
quint8 currentHeartRate = 60;
enum ValueChange { ValueUp, ValueDown } valueChange = ValueUp;
const auto heartbeatProvider = [&service, &currentHeartRate, &valueChange]() {
    QByteArray value;
    value.append(char(0)); // Flags that specify the format of the value.
    value.append(char(currentHeartRate)); // Actual value.
    QLowEnergyCharacteristic characteristic
            = service->characteristic(QBluetoothUuid::CharacteristicType::HeartRateMeasurement);
    Q_ASSERT(characteristic.isValid());
    service->writeCharacteristic(characteristic, value); // Potentially causes notification.
    if (currentHeartRate == 60)
        valueChange = ValueUp;
    else if (currentHeartRate == 100)
        valueChange = ValueDown;
    if (valueChange == ValueUp)
        ++currentHeartRate;
    else
        --currentHeartRate;
};
QObject::connect(&heartbeatTimer, &QTimer::timeout, heartbeatProvider);
heartbeatTimer.start(1000);

En general, las actualizaciones de características y valores de descriptor en el dispositivo periférico utilizan los mismos métodos que la conexión de dispositivos Bluetooth Low Energy.

Nota: Para utilizar Qt Bluetooth (tanto en funciones centrales como periféricas) en iOS, debe proporcionar un archivo Info.plist que contenga la descripción de uso. Según la documentación de CoreBluetooth: Tu aplicación se bloqueará si su Info.plist no incluye claves de descripción de uso para los tipos de datos a los que necesita acceder. Para acceder a las API Core Bluetooth en aplicaciones enlazadas en iOS 13 o posteriores, incluya la clave NSBluetoothAlwaysUsageDescription. En iOS 12 y anteriores, incluya NSBluetoothPeripheralUsageDescription para acceder a los datos de los periféricos Bluetooth.

© 2026 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.