Sur cette page

Présentation de Bluetooth Low Energy

L'API Bluetooth Low Energy de Qt Bluetooth prend en charge les rôles « périphérique/serveur » et « central/client ». Elle est prise en charge sur toutes les principales plateformes Qt. La seule exception concerne Windows, qui ne prend pas en charge le rôle « périphérique ».

Qu'est-ce que le Bluetooth Low Energy ?

Le Bluetooth Low Energy, également appelé Bluetooth Smart, est une technologie de réseau informatique sans fil qui a été officiellement lancée en 2011. Elle fonctionne sur la même fréquence de 2,4 GHz que le Bluetooth « classique ». La principale différence réside, comme l’indique son nom, dans sa faible consommation d’énergie. Elle permet aux appareils Bluetooth Low Energy de fonctionner pendant des mois, voire des années, avec des piles boutons. Cette technologie a été introduite avec la version 4.0 de Bluetooth. Les appareils prenant en charge cette technologie sont appelés « appareils Bluetooth Smart Ready ». Les principales caractéristiques de cette technologie sont les suivantes :

  • Une consommation d’énergie ultra-faible en mode de pointe, en mode moyen et en mode veille
  • Capacité à fonctionner pendant des années avec des piles boutons standard
  • Faible coût
  • Interopérabilité entre différents fabricants
  • Portée améliorée

Bluetooth Low Energy utilise une architecture client-serveur. Le serveur (également appelé « périphérique ») propose des services tels que la température ou la fréquence cardiaque et les diffuse. Le client (appelé « appareil central ») se connecte au serveur et lit les valeurs diffusées par ce dernier. Prenons l’exemple d’un appartement équipé de capteurs compatibles Bluetooth Smart Ready, tels qu’un thermostat, un capteur d’humidité ou un capteur de pression. Ces capteurs sont des périphériques qui diffusent les valeurs environnementales de l’appartement. Parallèlement, un téléphone mobile ou un ordinateur peut se connecter à ces capteurs, récupérer leurs valeurs et les présenter à l’utilisateur dans le cadre d’une application plus large de contrôle de l’environnement.

Structure de base du service

Bluetooth Low Energy repose sur deux protocoles : ATT (Attribute Protocol) et GATT (Generic Attribute Profile). Ceux-ci définissent les couches de communication utilisées par chaque appareil compatible Bluetooth Smart Ready.

Protocole ATT

L'attribut constitue l'élément de base de l'ATT. Chaque attribut se compose de trois éléments :

  • une valeur – la charge utile ou l'information souhaitée
  • un UUID : le type d’attribut (utilisé par GATT)
  • un identifiant de 16 bits : un identifiant unique pour l'attribut

Le serveur stocke les attributs et le client utilise le protocole ATT pour lire et écrire des valeurs sur le serveur.

Profil GATT

Le profil GATT définit le regroupement d'un ensemble d'attributs en attribuant une signification à des UUID prédéfinis. Le tableau ci-dessous présente un exemple de service exposant la fréquence cardiaque un jour donné. Les valeurs réelles sont stockées dans les deux caractéristiques suivantes :

IdentifiantUUIDValeurDescription
0x00010x2800UUID 0x180DLancement du service de fréquence cardiaque
0x00020x2803UUID 0x2A37, identifiant de valeur : 0x0003Caractéristique de type « Mesure de la fréquence cardiaque » (HRM)
0x00030x2A3765 bpmValeur de la fréquence cardiaque
0x00040x2803UUID 0x2A08, identifiant de valeur : 0x0005Caractéristique de type Date-Heure
0x00050x2A0818/08/2014 11:00Date et heure de la mesure
0x00060x2800UUID xxxxxxDébut du prochain service
............

Le GATT spécifie que l'UUID « 0x2800 » utilisé ci-dessus marque le début d'une définition de service. Chaque attribut suivant 0x2800 fait partie du service jusqu’à ce que l’on rencontre le prochain 0x2800 ou la fin. De la même manière, l’UUID bien connu 0x2803 indique qu’une caractéristique est présente, et chacune de ces caractéristiques possède un type définissant la nature de la valeur. L’exemple ci-dessus utilise les UUID 0x2A08 (Date et heure) et 0x2A37 (Mesure de la fréquence cardiaque). Chacun des UUID ci-dessus est défini par le Bluetooth Special Interest Group et figure dans les spécifications GATT. Bien qu’il soit conseillé d’utiliser des UUID prédéfinis lorsqu’ils sont disponibles, il est tout à fait possible d’utiliser de nouveaux UUID, non encore utilisés, pour les types de caractéristiques et de services.

En général, chaque service peut comporter une ou plusieurs caractéristiques. Une caractéristique contient des données et peut être décrite plus en détail par des descripteurs, qui fournissent des informations supplémentaires ou des moyens de manipuler la caractéristique. Tous les services, caractéristiques et descripteurs sont identifiés par leur UUID de 128 bits. Enfin, il est possible d’inclure des services au sein d’autres services (voir l’illustration ci-dessous).

Structure d'un périphérique BLE dans Qt

Utilisation de l’API « Qt Bluetooth » Low Energy

Cette section décrit comment utiliser l’API Bluetooth Low Energy fournie par Qt. Côté client, l’API permet d’établir des connexions avec des périphériques, de découvrir leurs services, ainsi que de lire et d’écrire des données stockées sur le périphérique. Côté serveur, elle permet de configurer des services, de les diffuser et d’être notifié lorsque le client écrit des caractéristiques. Le code d’exemple ci-dessous est tiré des exemples « Heart Rate Game » et « Heart Rate Server ».

Établissement d'une connexion

Pour pouvoir lire et écrire les caractéristiques du périphérique Bluetooth Low Energy, il est nécessaire de localiser et de se connecter à ce périphérique. Cela nécessite que le périphérique diffuse sa présence et ses services. Nous lançons la découverte du périphérique à l’aide de la classe ` QBluetoothDeviceDiscoveryAgent `. Nous nous connectons à son signal ` QBluetoothDeviceDiscoveryAgent::deviceDiscovered()` et lançons la recherche avec ` 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);

Comme nous ne nous intéressons qu'aux périphériques Low Energy, nous filtrons le type de périphérique au sein du slot de réception. Le type de périphérique peut être déterminé à l'aide de l'indicateur ` QBluetoothDeviceInfo::coreConfigurations()`. Le signal ` deviceDiscovered()` peut être émis plusieurs fois pour un même périphérique à mesure que de nouveaux détails sont découverts. Ici, nous mettons en correspondance ces découvertes de périphériques afin que l'utilisateur ne voie que les périphériques individuels :

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);
    }
    //...
}

Une fois l’adresse du périphérique connue, nous utilisons la classe ` QLowEnergyController `. Cette classe constitue le point d’entrée de tout développement Bluetooth Low Energy. Le constructeur de la classe accepte le signal ` QBluetoothAddress` du périphérique distant. Enfin, nous configurons les slots habituels et nous nous connectons directement au périphérique à l’aide de ` 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();

L’extrait de code ci-dessus montre comment l’application lance la recherche de services une fois la connexion établie.

Le slot « serviceDiscovered() » ci-dessous est déclenché par le signal « QLowEnergyController::serviceDiscovered() » et fournit un rapport de progression intermittent. Puisqu'il s'agit de l'application « heart listener » qui surveille les appareils HeartRate à proximité, nous ignorons tout service qui n'est pas de type « 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;
    }
}

Enfin, le signal ` QLowEnergyController::discoveryFinished()` est émis pour indiquer que la découverte de services s’est achevée avec succès. Si un service HeartRate a été trouvé, une instance ` QLowEnergyService ` est créée pour représenter ce service. L’objet de service renvoyé fournit les signaux nécessaires aux notifications de mise à jour, et la découverte des détails du service est déclenchée à l’aide de ` 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);
    }

Au cours de la recherche détaillée, l’ state() du service passe de RemoteService à RemoteServiceDiscovering et se termine finalement par 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();
}

Interaction avec le périphérique

Dans l’exemple de code ci-dessus, la caractéristique recherchée est de type HeartRateMeasurement. Comme l’application mesure les variations de la fréquence cardiaque, elle doit activer les notifications de changement pour cette caractéristique. Notez que toutes les caractéristiques ne fournissent pas de notifications de modification. La caractéristique HeartRate ayant été normalisée, on peut supposer que des notifications peuvent être reçues. En définitive, la fonction ` QLowEnergyCharacteristic::properties()` doit avoir le drapeau ` QLowEnergyCharacteristic::Notify ` activé et un descripteur de type ` QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration ` doit exister pour confirmer la disponibilité d’une notification appropriée.

Enfin, nous traitons la valeur de la caractéristique HeartRate, conformément à la norme 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 général, une valeur de caractéristique est une série d’octets. L’interprétation précise de ces octets dépend du type de caractéristique et de la structure de la valeur. Un grand nombre d’entre elles ont été normalisées par le Bluetooth SIG, tandis que d’autres peuvent suivre un protocole personnalisé. L’extrait de code ci-dessus montre comment lire la valeur normalisée de HeartRate.

Services de diffusion

Si nous implémentons une application serveur GATT sur un périphérique, nous devons définir les services que nous souhaitons proposer aux appareils centraux et les diffuser :

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;

Désormais, les clients potentiels peuvent se connecter à notre appareil, découvrir le service fourni et s’enregistrer pour être informés des modifications apportées à la valeur de la caractéristique. Cette partie de l’API a déjà été abordée dans les sections précédentes.

Implémentation d’un service sur le périphérique

La première étape consiste à définir le service, ses caractéristiques et ses descripteurs. Pour ce faire, on utilise les classes QLowEnergyServiceData, QLowEnergyCharacteristicData et QLowEnergyDescriptorData. Ces classes servent de conteneurs ou de blocs de construction pour les informations essentielles qui composent le service Bluetooth Low Energy à définir. L’extrait de code ci-dessous définit un service HeartRate simple qui publie le nombre de battements par minute mesuré. Une montre-bracelet constitue un exemple d’application où un tel service pourrait être utilisé.

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);

L’objet ` serviceData ` ainsi créé peut être publié comme décrit dans la section « Services de publicité » ci-dessus. Malgré un chevauchement partiel des informations entre celles encapsulées par ` QLowEnergyServiceData ` et ` QLowEnergyAdvertisingData `, ces deux classes remplissent des fonctions très différentes. Les données de publicité sont diffusées aux appareils à proximité et leur portée est souvent limitée en raison de la restriction de taille fixée à 29 octets. Elles ne sont donc pas toujours complètes à 100 %. En comparaison, les données de service contenues dans QLowEnergyServiceData fournissent l’ensemble complet des données de service et ne deviennent visibles pour le client qui se connecte que lorsqu’une connexion avec une découverte de service active a été établie.

La section suivante montre comment le service peut mettre à jour la valeur de la fréquence cardiaque. Selon la nature du service, celui-ci peut devoir se conformer à la définition officielle du service telle qu’elle est définie sur https://www.bluetooth.org. D’autres services peuvent être entièrement personnalisés. Le service de fréquence cardiaque a été adopté et sa spécification est disponible à l’adresse 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 général, les mises à jour des caractéristiques et des valeurs des descripteurs sur le périphérique utilisent les mêmes méthodes que celles utilisées pour connecter des périphériques Bluetooth Low Energy.

Remarque : pour utiliser Qt Bluetooth (que ce soit en tant qu’appareil central ou périphérique) sous iOS, vous devez fournir un fichier Info.plist contenant la description d’utilisation. Selon la documentation de CoreBluetooth : votre application plantera si son fichier Info.plist ne contient pas les clés de description d’utilisation correspondant aux types de données auxquelles elle doit accéder. Pour accéder aux API Core Bluetooth sur les applications liées à partir d’iOS 13, incluez la clé NSBluetoothAlwaysUsageDescription. Sous iOS 12 et versions antérieures, incluez NSBluetoothPeripheralUsageDescription pour accéder aux données des périphériques 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.