Site : tvaira.free.fr
Bluetooth est une norme de communications permettant l’échange bidirectionnel de données à très courte distance en utilisant des ondes radio UHF sur une bande de fréquence de 2,4 GHz.
BLE (Bluetooth Low Energy, Bluetooth à basse consommation ou Bluetooth à basse énergie) est une technique de transmission sans fil créée par Nokia en 2006 sous la forme d’un standard ouvert basé sur Bluetooth, qu’il complète mais sans le remplacer. Cette technologie est apparue en 2010 avec la sortie de la version 4.0 du Bluetooth Core Specification.
Comparé au Bluetooth, le BLE permet un débit du même ordre de grandeur (1 Mb/s) pour une consommation d’énergie 10 fois moindre. Cela permet d’intégrer cette technologie dans de nouveaux types d’équipements : montres, appareils de surveillance médicale ou capteurs pour sportifs.
Lire : Bluetooth BLE et Bluetooth Classic
L’API Qt Bluetooth Low Energy a été introduite dans Qt 5.4. Depuis Qt 5.5, cette partie de l’API est définitive et une garantie de compatibilité est donnée pour les versions futures. Depuis Qt 5.7, une API supplémentaire prenant en charge le rôle de périphérique a été ajoutée, avec le backend implémenté pour Linux/BlueZ, iOS et macOS.
Il faut donc Qt 5.7.
$ sudo apt-get install libqt5bluetooth5 libbluetooth-dev qtconnectivity5-dev qtconnectivity5-examples
$ qmake -v
QMake version 3.0
Using Qt version 5.7.1 in /usr/lib/arm-linux-gnueabihf
Documentation : Qt Bluetooth LE
Pour utiliser l’API Qt Bluetooth dans votre application, il faut commencer par ajouter l’option de configuration suivante à votre fichier de projet .pro
:
QT += bluetooth
Du côté du client, l’API permet de créer 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. Du côté du serveur, cela permet de configurer des services, de les publier et de recevoir des notifications lorsque le client écrit les caractéristiques.
La classe QLowEnergyController
permet d’accéder aux périphériques BLE.
Il existe deux types d’appareils : le périphérique et le central, chacun effectuant une tâche différente. Le périphérique fournit des données utilisées par les périphériques centraux. Un exemple pourrait être un capteur d’humidité qui mesure l’humidité. Un appareil tel qu’un téléphone portable peut lire la valeur du capteur et l’afficher dans le contexte plus large de tous les capteurs du même environnement. Dans ce cas, le capteur est le périphérique et le téléphone portable agit comme le central.
Un contrôleur dans le rôle central est créé via la méthode de fabrique createCentral()
.
Après avoir créé un objet contrôleur dans le rôle central, la première étape consiste à établir une connexion via connectToDevice()
. Une fois la connexion établie, le signal connected()
est émis. La fonction disconnectFromDevice()
est utilisée pour fermer la connexion existante.
La deuxième étape après l’établissement de la connexion consiste à découvrir les services offerts par le périphérique distant. Ce processus est démarré via discoverServices()
et se termine lorsque le signal discoveryFinished()
a été émis. Les services découverts peuvent être énumérés via services()
.
La dernière étape consiste à créer des objets de service. La fonction createServiceObject()
fait office de fabrique pour chaque objet de service et attend le UUID de service comme paramètre. Le contexte appelant doit s’approprier l’instance QLowEnergyService
renvoyée.
Un contrôleur dans le rôle périphérique est créé via la méthode de fabrique createPeripheral()
. Un tel objet agit comme un périphérique lui-même, activant des fonctionnalités telles que les services de publicité et permettant aux clients d’être informés des modifications apportées aux valeurs caractéristiques.
Après avoir créé un objet contrôleur dans le rôle périphérique, la première étape consiste à remplir l’ensemble des services GATT proposés aux périphériques clients via des appels à addService()
. Ensuite, on appellerait startAdvertising()
pour permettre à l’appareil de diffuser certaines données et, selon le type de publicité, d’écouter les connexions entrantes des clients de GATT.
Voir :
On va tester la communication avec notre faux objet connecté à base d’ESP32 de l’exemple n°1 :
Celui-ci fonctionnant en serveur GATT, il nous faut développer un client GATT sous Qt5. On réalisera une interface graphique simple en QML et le client sous la forme d’une classe C++. Il a été réalisé sous Qt 5.10.1 avec les deux kits de développements suivants :
Nous aurons donc une application pour Linux et une pour Android. Sous linux, on utilisera le dongle USB Bluetooth Inateck Nano Bluetooth V4.0 (CSR8510).
Comme on réalise une application QML, vous pouvez lire ce cours QML pour en savoir plus.
Le projet Qt :
Le fichier .pro
nécessite d’ajouter le module bluetooth
:
TEMPLATE = app
QT += qml quick bluetooth
CONFIG += c++11
SOURCES += main.cpp \
ClientBLE.cpp
RESOURCES += qml.qrc
HEADERS += \
ClientBLE.h
Le fichier main.cpp
instanciera notre ClientBLE
, l’associera au document QML puis chargera le tout :
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "ClientBLE.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
ClientBLE *clientBLE = new ClientBLE();
engine.rootContext()->setContextProperty("ClientBLE", clientBLE);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
La déclaration de la classe ClientBLE
doit hériter de QObject
pour utiliser les mécanismes propres à Qt (signals/slots, propriétés, etc …). Pour des raisons de simplicité, l’adresse du serveur et les UUIDs sont définies sous forme de constantes.
Deux propriétés sont déclarées afin d’être utilisable depuis le document QML :
etatConnexion
: true
si le client est connecté au serveur GATT sinon false
compteur
: la donnée du serveur GATTRemarque : Sous Qt, une propriété est lue par l’accesseur du nom de la propriété (compteur()
par exemple) et écrite par un mutateur préfixé par set (setCompteur()
par exemple).
Quatre méthodes sont déclarées Q_INVOKABLE
pour pouvoir être appelées depuis le document QML :
void start()
: démarre une connexion vers le périphériquevoid stop()
: assure une déconnexionvoid read()
: permet de lire la caractéristiquevoid gererNotification(bool notification)
: active (true
) ou désactive (false
) les notificationsCette classe émet aussi deux signaux Qt : connecte()
et compteurChange()
. Ils sont donc utilisable depuis le document QML.
Les attributs pour gérer le Bluetooth BLE sont :
*m_controller
: notre controleur BLE local*m_service
: un service BLEm_characteristic
: une caractéristique d’un service BLELes deux autres attributs sont nos deux propriétés.
#ifndef ClientBLE_H
#define ClientBLE_H
#include <QLowEnergyController>
#define MON_WEMOS_LOLIN_ESP32_OLED "30:AE:A4:23:D6:7E"
#define MON_AZDELIVERY_ESP32 "30:AE:A4:7B:80:46"
#define SERVICE_UUID "{042bd80f-14f6-42be-a45c-a62836a4fa3f}"
#define CHARACTERISTIC_UUID "{065de41b-79fb-479d-b592-47caf39bfccb}"
class ClientBLE : public QObject
{
Q_OBJECT
Q_PROPERTY(bool etatConnexion MEMBER m_etatConnexion NOTIFY connecte)
Q_PROPERTY(float compteur MEMBER m_compteur NOTIFY compteurChange)
public:
ClientBLE();
~ClientBLE();
Q_INVOKABLE void start();
Q_INVOKABLE void stop();
Q_INVOKABLE void read();
Q_INVOKABLE void gererNotification(bool notification);
protected slots:
void connecterAppareil(const QString &adresseServeur);
void connecterService(QLowEnergyService *service);
void ajouterService(QBluetoothUuid serviceUuid);
void serviceDetailsDiscovered(QLowEnergyService::ServiceState newState);
void serviceCharacteristicChanged(const QLowEnergyCharacteristic &c, const QByteArray &value);
void appareilConnecte();
void appareilDeconnecte();
private:
QList<QObject*> m_devices;
QLowEnergyController *m_controller;
QLowEnergyService *m_service;
bool m_etatConnexion;
int m_compteur;
QLowEnergyCharacteristic m_characteristic;
signals:
void connecte();
void compteurChange();
};
#endif // ClientBLE_H
La définition de la classe ClientBLE
implémente les bases présentées ci-dessus. La méthode connecterAppareil(const QString &adresseServeur)
est appelée au moment de la demande connexion et on lui passe en argument l’adresse MAC du serveur BLE à joindre. Elle instancie un objet QLowEnergyController pour notre adaptateur BLE local. On connecte les slots dont on a besoin : connexion/déconnexion et les services à découvrir. On démarre avec connectToDevice()
.
Ensuite, la méthode ajouterService()
va être déclenchée pour chaque service découvert. On appelle alors createServiceObject()
en lui passant l’UUID du service pour créer une instance de ce service. On appelle notre méthode connecterService()
pour découvrir les caractéristiques du service. On connecte les slots dont on a besoin et on lance la découverte avec discoverDetails()
. Pour chaque caractéristique découverte, le slot serviceDetailsDiscovered()
sera déclenché. C’est là qu’il faut rechercher la présence de notre caractéristique 065de41b-79fb-479d-b592-47caf39bfccb.
Pour lire la caractéristique, on fournit la méthode read()
qui appelle d’abord readCharacteristic()
puis on obtiendra la valeur lue avec value()
. Finalement le plus dur est d’assurer la conversion dans le type voulu des données reçues sous forme d’une suite d’octets (un QByteArray
avec Qt), limitée à 20 octets.
Pour les notifications, on fournit une méthode gererNotification()
qui gère le descripteur BLE appelé “Configuration des caractéristiques du client” qui a pour UUID 0x2902. Celui-ci contient (entre autres choses) deux champs de bits distincts pouvant être activés ou désactivés :
Cela donnera les valeurs suivantes : “0100” pour activer la notification et “0000” pour la désactiver.
Pour finir, si la valeur de la caractéristique change (soir par une lecture soit par une notifiacation), on émetrra le signal compteurChange()
. Même principe pour le signal connecte()
si l’état de connexion/déconnexion change.
#include "ClientBLE.h"
#include <QDebug>
//#include <QtEndian>
ClientBLE::ClientBLE() : m_controller(NULL), m_service(NULL), m_etatConnexion(false), m_compteur(0)
{
qDebug() << Q_FUNC_INFO;
}
ClientBLE::~ClientBLE()
{
if (m_controller)
m_controller->disconnectFromDevice();
delete m_controller;
qDeleteAll(m_devices);
qDebug() << Q_FUNC_INFO;
}
void ClientBLE::start()
{
qDebug() << Q_FUNC_INFO << MON_WEMOS_LOLIN_ESP32_OLED;
connecterAppareil(MON_WEMOS_LOLIN_ESP32_OLED);
//qDebug() << Q_FUNC_INFO << MON_AZDELIVERY_ESP32;
//connecterAppareil(MON_AZDELIVERY_ESP32);
}
void ClientBLE::stop()
{
qDebug() << Q_FUNC_INFO << MON_WEMOS_LOLIN_ESP32_OLED;
//qDebug() << Q_FUNC_INFO << MON_AZDELIVERY_ESP32;
if (m_controller)
m_controller->disconnectFromDevice();
}
void ClientBLE::read()
{
if(m_service && m_characteristic.isValid())
{
m_service->readCharacteristic(m_characteristic);
bool ok;
m_compteur = m_characteristic.value().toHex().toInt(&ok, 16);
qDebug() << Q_FUNC_INFO << m_characteristic.value() << m_compteur;
//qDebug() << (int)qFromLittleEndian<quint8>(m_characteristic.value().constData());
emit compteurChange();
}
}
void ClientBLE::gererNotification(bool notification)
{
if(m_service && m_characteristic.isValid())
{
QLowEnergyDescriptor descripteurNotification = m_characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
if (descripteurNotification.isValid())
{
// active la notification : 0100 ou désactive 0000
qDebug() << Q_FUNC_INFO << "modification notification" << m_characteristic.uuid().toString() << notification;
if(notification)
m_service->writeDescriptor(descripteurNotification, QByteArray::fromHex("0100"));
else
m_service->writeDescriptor(descripteurNotification, QByteArray::fromHex("0000"));
}
}
}
void ClientBLE::connecterAppareil(const QString &adresseServeur)
{
m_controller = new QLowEnergyController(QBluetoothAddress(adresseServeur), this);
// Slot pour la récupération des services
connect(m_controller, SIGNAL(serviceDiscovered(QBluetoothUuid)), this, SLOT(ajouterService(QBluetoothUuid)));
connect(m_controller, SIGNAL(connected()), this, SLOT(appareilConnecte()));
connect(m_controller, SIGNAL(disconnected()), this, SLOT(appareilDeconnecte()));
qDebug() << Q_FUNC_INFO << "demande de connexion";
m_controller->setRemoteAddressType(QLowEnergyController::PublicAddress);
m_controller->connectToDevice();
}
void ClientBLE::connecterService(QLowEnergyService *service)
{
m_service = service;
if (m_service->state() == QLowEnergyService::DiscoveryRequired)
{
// Slot pour le changement d'une caractéristique
connect(m_service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)), this, SLOT(serviceCharacteristicChanged(QLowEnergyCharacteristic,QByteArray)));
// Slot pour la récupération des caractéristiques
connect(m_service, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(serviceDetailsDiscovered(QLowEnergyService::ServiceState)));
qDebug() << Q_FUNC_INFO << "découverte des détails des services";
m_service->discoverDetails();
}
}
void ClientBLE::ajouterService(QBluetoothUuid serviceUuid)
{
qDebug() << Q_FUNC_INFO << serviceUuid.toString();
QLowEnergyService *service = m_controller->createServiceObject(serviceUuid);
connecterService(service);
}
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic &c, const QByteArray &value)
{
if (c.uuid().toString() == CHARACTERISTIC_UUID)
{
bool ok;
m_compteur = value.toHex().toInt(&ok, 16);
qDebug() << Q_FUNC_INFO << value << m_compteur;
//qDebug() << (int)qFromLittleEndian<quint8>(value.constData());
emit compteurChange();
}
}
void ClientBLE::serviceDetailsDiscovered(QLowEnergyService::ServiceState newState)
{
Q_UNUSED(newState)
// décourverte ?
if (newState != QLowEnergyService::ServiceDiscovered)
{
return;
}
QLowEnergyService *service = qobject_cast<QLowEnergyService *>(sender());
qDebug() << Q_FUNC_INFO << "service" << service->serviceUuid().toString();
if (service->serviceUuid().toString() == SERVICE_UUID)
{
foreach (QLowEnergyCharacteristic c, service->characteristics())
{
qDebug() << Q_FUNC_INFO << "characteristic" << c.uuid().toString();
if (c.uuid().toString() == CHARACTERISTIC_UUID)
{
qDebug() << Q_FUNC_INFO << "my characteristic" << c.uuid().toString() << c.value();
if (c.properties() & QLowEnergyCharacteristic::Read)
{
m_service = service;
m_characteristic = c;
}
QLowEnergyDescriptor descripteurNotification = c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
if (descripteurNotification.isValid())
{
// active la notification : 0100 ou désactive 0000
qDebug() << Q_FUNC_INFO << "modification notification" << c.uuid().toString();
service->writeDescriptor(descripteurNotification, QByteArray::fromHex("0000"));
}
}
m_etatConnexion = true;
emit connecte();
}
}
}
void ClientBLE::appareilConnecte()
{
qDebug() << Q_FUNC_INFO;
m_controller->discoverServices();
}
void ClientBLE::appareilDeconnecte()
{
qDebug() << Q_FUNC_INFO;
m_etatConnexion = false;
emit connecte();
}
Notre interface graphique reste simple. On place dans notre fenêtre (Window
) quatre objets QML en colonne (Column
) :
RoundButton
) pour les actions “Connecter”, “Déconnecter”, “Notifier” et “Lire”Text
) pour l’affichage de notre compteurPour accéder à notre classe C++ depuis le document QML, on utilisera la propriété ClientBLE
associé à notre objet par setContextProperty()
dans le main.cpp
.
Les méthodes déclarées Q_INVOKABLE
peuvent être appelées directement : ClientBLE.start()
par exemple. Pour gérer les états des bouton, on va utiliser leur propriété enabled
en l’affectant avec la propriété etatConnexion
de l’objet ClientBLE
.
La valeur du compteur est directement affichée en affectant la propriété text
avec la propriété compteur
de l’objet ClientBLE
.
Pour en savoir plus sur QML, lire le cours QML.
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
Window {
title: qsTr("Client BLE")
visible: true
width: Screen.desktopAvailableWidth
height: Screen.desktopAvailableHeight
property bool notification: false
Column {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: 5
RoundButton {
text: qsTr("Connecter")
radius: 5; width: 300; height: 50;
enabled: !ClientBLE.etatConnexion
onClicked: {
ClientBLE.start();
}
}
RoundButton {
text: qsTr("Déconnecter")
radius: 5; width: 300; height: 50
enabled: ClientBLE.etatConnexion
onClicked: {
ClientBLE.stop();
}
}
RoundButton {
text: qsTr("Notifier")
radius: 5; width: 300; height: 50
enabled: ClientBLE.etatConnexion
onEnabledChanged: {
ClientBLE.gererNotification(false);
notification = true;
}
onClicked: {
ClientBLE.gererNotification(notification);
notification = !notification;
}
}
RoundButton {
text: qsTr("Lire")
radius: 5; width: 300; height: 50
enabled: ClientBLE.etatConnexion
onClicked: {
ClientBLE.read();
}
}
Rectangle {
color: "lightblue"; radius: 10.0; width: 300; height: 50
Text {
anchors.centerIn: parent;
font.pointSize: 18;
text: ClientBLE.compteur
}
}
}
}
Les tests ont été réalisés avec notre faux objet connecté à base d’ESP32 de l’exemple n°1 :
Au lancement de l’application, on obtient :
Debug :
ClientBLE::ClientBLE()
On clique sur Connecter :
Le service 042bd80f-14f6-42be-a45c-a62836a4fa3f est bien découvert et on récupère la caractéristique 065de41b-79fb-479d-b592-47caf39bfccb. On désactive aussi la notification.
Debug :
void ClientBLE::start() 30:AE:A4:23:D6:7E
void ClientBLE::connecterAppareil(const QString&) demande de connexion
void ClientBLE::appareilConnecte()
void ClientBLE::ajouterService(QBluetoothUuid) "{00001801-0000-1000-8000-00805f9b34fb}"
void ClientBLE::connecterService(QLowEnergyService*) découverte des détails des services
void ClientBLE::ajouterService(QBluetoothUuid) "{00001800-0000-1000-8000-00805f9b34fb}"
void ClientBLE::connecterService(QLowEnergyService*) découverte des détails des services
void ClientBLE::ajouterService(QBluetoothUuid) "{042bd80f-14f6-42be-a45c-a62836a4fa3f}"
void ClientBLE::connecterService(QLowEnergyService*) découverte des détails des services
void ClientBLE::serviceDetailsDiscovered(QLowEnergyService::ServiceState) service "{00001801-0000-1000-8000-00805f9b34fb}"
void ClientBLE::serviceDetailsDiscovered(QLowEnergyService::ServiceState) service "{00001800-0000-1000-8000-00805f9b34fb}"
void ClientBLE::serviceDetailsDiscovered(QLowEnergyService::ServiceState) service "{042bd80f-14f6-42be-a45c-a62836a4fa3f}"
void ClientBLE::serviceDetailsDiscovered(QLowEnergyService::ServiceState) characteristic "{065de41b-79fb-479d-b592-47caf39bfccb}"
void ClientBLE::serviceDetailsDiscovered(QLowEnergyService::ServiceState) my characteristic "{065de41b-79fb-479d-b592-47caf39bfccb}" "\xA6"
void ClientBLE::serviceDetailsDiscovered(QLowEnergyService::ServiceState) modification notification "{065de41b-79fb-479d-b592-47caf39bfccb}"
void ClientBLE::gererNotification(bool) modification notification "{065de41b-79fb-479d-b592-47caf39bfccb}" false
On va faire une lecture en cliquant sur Lire :
Debug :
void ClientBLE::read() "\xA6" 166
Puis, on va activer la notification de la valeur du compteur :
Debug :
void ClientBLE::gererNotification(bool) modification notification "{065de41b-79fb-479d-b592-47caf39bfccb}" true
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "`" 96
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "a" 97
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "b" 98
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "c" 99
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "d" 100
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "e" 101
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "f" 102
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "g" 103
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "h" 104
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "i" 105
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "j" 106
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "k" 107
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "l" 108
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "m" 109
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "n" 110
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic&, const QByteArray&) "o" 111
...
Pour finir on désactive la notification puis on se déconnecte.
Debug :
void ClientBLE::gererNotification(bool) modification notification "{065de41b-79fb-479d-b592-47caf39bfccb}" false
void ClientBLE::stop() 30:AE:A4:23:D6:7E
void ClientBLE::appareilDeconnecte()
On va tester maintenant notre application QML pour Android :
On sélectionne ensuite notre tablette :
On obtient :
Code source : exemple_qt_client_ble_1.zip
Voir aussi : Ampoule Bluetooth Magic Blue LED
En reprenant l’exemple n°1 précédent, on va tester maintenant la communication avec notre faux objet connecté à base d’ESP32 de l’exemple n°2.
Ici, on va communiquer en utilisant le Nordic UART Service (NUS) qui est un exemple d’émulation d’un port série sur BLE. Cela inclut le service “Nordic UART” dont l’UUID spécifique est 6E400001-B5A3-F393-E0A9-E50E24DCCA9E.
Ce service présente deux caractéristiques (l’une pour l’émission et l’autre pour la réception) :
La transmission UART sur BLE ne fait pas partie des profils définis par Bluetooth SIG. C’est un service personnalisé par Nordic Semiconductor. BLE NUS est donc un service BLE propriétaire, en quelque sorte l’équivalent du profil SPP (Serial Port Profile), basé sur le protocole RFCOMM, du Bluetooth Classic.
Liens :
On reprend la base Qt déjà écrite à laquelle on va intégrer la capacité d’écrire des données (Client -> Serveur).
La déclaration de la classe ClientBLE
intègre une nouvelle méthode Q_INVOKABLE
pour écrire des données sur la caractéristque m_rxCharacteristic
: write(const QByteArray &data)
. On a ajouté aussi deux attributs pour les deux caractéristiques du service UART : TX et RX.
#ifndef ClientBLE_H
#define ClientBLE_H
#include <QLowEnergyController>
#define MON_WEMOS_LOLIN_ESP32_OLED "30:AE:A4:23:D6:7E"
#define MON_AZDELIVERY_ESP32 "30:AE:A4:7B:80:46"
#define SERVICE_UART_UUID "{6e400001-b5a3-f393-e0a9-e50e24dcca9e}" // UART service UUID
#define CHARACTERISTIC_UUID_RX "{6e400002-b5a3-f393-e0a9-e50e24dcca9e}"
#define CHARACTERISTIC_UUID_TX "{6e400003-b5a3-f393-e0a9-e50e24dcca9e}"
#define MAX_SIZE 20 // 20 octets de données max
class ClientBLE : public QObject
{
Q_OBJECT
Q_PROPERTY(bool etatConnexion MEMBER m_etatConnexion NOTIFY connecte)
Q_PROPERTY(float compteur MEMBER m_compteur NOTIFY compteurChange)
public:
ClientBLE();
~ClientBLE();
Q_INVOKABLE void start();
Q_INVOKABLE void stop();
Q_INVOKABLE void read();
Q_INVOKABLE void write(const QByteArray &data);
Q_INVOKABLE void gererNotification(bool notification);
protected slots:
void connecterAppareil(const QString &adresseServeur);
void connecterService(QLowEnergyService *service);
void ajouterService(QBluetoothUuid serviceUuid);
void serviceDetailsDiscovered(QLowEnergyService::ServiceState newState);
void serviceCharacteristicChanged(const QLowEnergyCharacteristic &c, const QByteArray &value);
void appareilConnecte();
void appareilDeconnecte();
private:
QList<QObject*> m_devices;
QLowEnergyController *m_controller;
QLowEnergyService *m_service;
bool m_etatConnexion;
int m_compteur;
QLowEnergyCharacteristic m_txCharacteristic;
QLowEnergyCharacteristic m_rxCharacteristic;
signals:
void connecte();
void compteurChange();
};
#endif // ClientBLE_H
Dans la définition de la classe ClientBLE
, on implémente la méthode write()
qui doit au moins tenir compte de la limite des données à envoyer fixée à 20 octets. Il faudrait envisager d’envoyer des données en plusieurs fois si la limite est dépassée. On pourrait alors utiliser QLowEnergyService::WriteWithResponse
en argument de writeCharacteristic()
.
#include "ClientBLE.h"
#include <QDebug>
#include <QtEndian>
ClientBLE::ClientBLE() : m_controller(NULL), m_service(NULL), m_etatConnexion(false), m_compteur(0)
{
qDebug() << Q_FUNC_INFO;
}
ClientBLE::~ClientBLE()
{
if (m_controller)
m_controller->disconnectFromDevice();
delete m_controller;
qDeleteAll(m_devices);
qDebug() << Q_FUNC_INFO;
}
void ClientBLE::start()
{
//qDebug() << Q_FUNC_INFO << MON_WEMOS_LOLIN_ESP32_OLED;
//connecterAppareil(MON_WEMOS_LOLIN_ESP32_OLED);
qDebug() << Q_FUNC_INFO << MON_AZDELIVERY_ESP32;
connecterAppareil(MON_AZDELIVERY_ESP32);
}
void ClientBLE::stop()
{
//qDebug() << Q_FUNC_INFO << MON_WEMOS_LOLIN_ESP32_OLED;
qDebug() << Q_FUNC_INFO << MON_AZDELIVERY_ESP32;
if (m_controller)
m_controller->disconnectFromDevice();
}
void ClientBLE::read()
{
if(m_service && m_txCharacteristic.isValid())
{
if (m_txCharacteristic.properties() & QLowEnergyCharacteristic::Read)
{
m_service->readCharacteristic(m_txCharacteristic);
bool ok;
m_compteur = m_txCharacteristic.value().toHex().toInt(&ok, 16);
qDebug() << Q_FUNC_INFO << m_txCharacteristic.value() << m_compteur;
//qDebug() << (int)qFromLittleEndian<quint8>(m_txCharacteristic.value().constData());
emit compteurChange();
}
}
}
void ClientBLE::write(const QByteArray &data)
{
if(m_service && m_rxCharacteristic.isValid())
{
if (m_rxCharacteristic.properties() & QLowEnergyCharacteristic::Write)
{
qDebug() << Q_FUNC_INFO << data;
if(data.length() <= MAX_SIZE)
m_service->writeCharacteristic(m_rxCharacteristic, data, QLowEnergyService::WriteWithoutResponse);
else
m_service->writeCharacteristic(m_rxCharacteristic, data.mid(0, MAX_SIZE), QLowEnergyService::WriteWithoutResponse); // TODO : et les autres octets ?
}
}
}
void ClientBLE::gererNotification(bool notification)
{
if(m_service && m_txCharacteristic.isValid())
{
if (m_txCharacteristic.properties() & QLowEnergyCharacteristic::Notify)
{
QLowEnergyDescriptor descripteurNotification = m_txCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
if (descripteurNotification.isValid())
{
// active la notification : 0100 ou désactive 0000
qDebug() << Q_FUNC_INFO << "modification notification" << m_txCharacteristic.uuid().toString() << notification;
if(notification)
m_service->writeDescriptor(descripteurNotification, QByteArray::fromHex("0100"));
else
m_service->writeDescriptor(descripteurNotification, QByteArray::fromHex("0000"));
}
}
}
}
void ClientBLE::connecterAppareil(const QString &adresseServeur)
{
m_controller = new QLowEnergyController(QBluetoothAddress(adresseServeur), this);
// Slot pour la récupération des services
connect(m_controller, SIGNAL(serviceDiscovered(QBluetoothUuid)), this, SLOT(ajouterService(QBluetoothUuid)));
connect(m_controller, SIGNAL(connected()), this, SLOT(appareilConnecte()));
connect(m_controller, SIGNAL(disconnected()), this, SLOT(appareilDeconnecte()));
qDebug() << Q_FUNC_INFO << "demande de connexion";
m_controller->setRemoteAddressType(QLowEnergyController::PublicAddress);
m_controller->connectToDevice();
}
void ClientBLE::connecterService(QLowEnergyService *service)
{
m_service = service;
if (m_service->state() == QLowEnergyService::DiscoveryRequired)
{
// Slot pour le changement d'une caractéristique
connect(m_service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)), this, SLOT(serviceCharacteristicChanged(QLowEnergyCharacteristic,QByteArray)));
// Slot pour la récupération des caractéristiques
connect(m_service, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(serviceDetailsDiscovered(QLowEnergyService::ServiceState)));
qDebug() << Q_FUNC_INFO << "découverte des détails des services";
m_service->discoverDetails();
}
}
void ClientBLE::ajouterService(QBluetoothUuid serviceUuid)
{
qDebug() << Q_FUNC_INFO << serviceUuid.toString();
QLowEnergyService *service = m_controller->createServiceObject(serviceUuid);
connecterService(service);
}
void ClientBLE::serviceCharacteristicChanged(const QLowEnergyCharacteristic &c, const QByteArray &value)
{
if (c.uuid().toString() == CHARACTERISTIC_UUID_TX)
{
bool ok;
m_compteur = value.toHex().toInt(&ok, 16);
qDebug() << Q_FUNC_INFO << value << m_compteur;
//qDebug() << (int)qFromLittleEndian<quint8>(value.constData());
emit compteurChange();
}
}
void ClientBLE::serviceDetailsDiscovered(QLowEnergyService::ServiceState newState)
{
Q_UNUSED(newState)
// décourverte ?
if (newState != QLowEnergyService::ServiceDiscovered)
{
return;
}
QLowEnergyService *service = qobject_cast<QLowEnergyService *>(sender());
qDebug() << Q_FUNC_INFO << "service" << service->serviceUuid().toString();
if (service->serviceUuid().toString() == SERVICE_UART_UUID)
{
foreach (QLowEnergyCharacteristic c, service->characteristics())
{
qDebug() << Q_FUNC_INFO << "characteristic" << c.uuid().toString();
if (c.uuid().toString() == CHARACTERISTIC_UUID_TX)
{
qDebug() << Q_FUNC_INFO << "my characteristic TX" << c.uuid().toString() << (c.properties() & QLowEnergyCharacteristic::Notify);
m_txCharacteristic = c;
QLowEnergyDescriptor descripteurNotification = c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
if (descripteurNotification.isValid())
{
// active la notification : 0100 ou désactive 0000
qDebug() << Q_FUNC_INFO << "modification notification" << c.uuid().toString();
service->writeDescriptor(descripteurNotification, QByteArray::fromHex("0100"));
}
}
if (c.uuid().toString() == CHARACTERISTIC_UUID_RX)
{
qDebug() << Q_FUNC_INFO << "my characteristic RX" << c.uuid().toString() << (c.properties() & QLowEnergyCharacteristic::Write);
m_service = service;
m_rxCharacteristic = c;
}
m_etatConnexion = true;
emit connecte();
}
}
}
void ClientBLE::appareilConnecte()
{
qDebug() << Q_FUNC_INFO;
m_controller->discoverServices();
}
void ClientBLE::appareilDeconnecte()
{
qDebug() << Q_FUNC_INFO;
m_etatConnexion = false;
emit connecte();
}
Dans l’exemple n°2 du serveur ESP32, les caractéristiques ont été configurés de cette manière :
BLECharacteristic::PROPERTY_NOTIFY
BLECharacteristic::PROPERTY_WRITE
Le bouton “Lire” n’a donc plus d’utilité. On va intégrer un champ de saisie (TextInput
) pour les données à envoyer et un bouton (RoundButton
) “Écrire”. Lorsqu’on cliquera sur ce bouton, on exécutera ClientBLE.write(message.text)
dans son gestionnaire OnClicked
.
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
Window {
title: qsTr("Client BLE")
visible: true
width: Screen.desktopAvailableWidth
height: Screen.desktopAvailableHeight
property bool notification: false
Column {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: 5
RoundButton {
text: qsTr("Connecter")
radius: 5; width: 300; height: 50;
enabled: !ClientBLE.etatConnexion
onClicked: {
ClientBLE.start();
}
}
RoundButton {
text: qsTr("Déconnecter")
radius: 5; width: 300; height: 50
enabled: ClientBLE.etatConnexion
onClicked: {
ClientBLE.stop();
}
}
RoundButton {
text: qsTr("Notifier")
radius: 5; width: 300; height: 50
enabled: ClientBLE.etatConnexion
onEnabledChanged: {
ClientBLE.gererNotification(false);
notification = true;
}
onClicked: {
ClientBLE.gererNotification(notification);
notification = !notification;
}
}
/*RoundButton {
text: qsTr("Lire")
radius: 5; width: 300; height: 50
enabled: ClientBLE.etatConnexion
onClicked: {
ClientBLE.read();
}
}*/
Rectangle {
color: "lightblue"; radius: 10.0; width: 300; height: 50
Text {
anchors.centerIn: parent;
font.pointSize: 18;
text: ClientBLE.compteur
}
}
RoundButton {
text: qsTr("Écrire")
radius: 5; width: 300; height: 50
enabled: ClientBLE.etatConnexion
onClicked: {
ClientBLE.write(message.text);
}
}
Rectangle {
color: "lightgreen"; radius: 10.0; width: 300; height: 50
TextInput {
anchors.centerIn: parent;
id: message
text: "hello"
font.family: "Helvetica"
font.pointSize: 18;
enabled: ClientBLE.etatConnexion
cursorVisible: true
//focus: true
}
}
}
}
On obtient :
On peut voir la réception dans le moniteur série de notre ESP32 :
Code source : exemple_qt_client_ble_2.zip
Voir aussi : Ampoule Bluetooth Magic Blue LED
Cet exemple montre comment créer et configurer un service du GATT côté périphérique (esclave). Du côté du serveur, l’API Qt permet de configurer des services, de les publier et d’être avertis lorsque le client écrit des caractéristiques.
On va utiliser les classes Qt suivantes:
Quelques exemples de serveur GATT :
L’exemple implémente une application serveur BLE, ce qui signifie qu’elle ne possède aucune interface utilisateur graphique. Le serveur sera codé dans une classe ServeurBLE
. L’exemple fonctionne sous Linux, sous Raspberry Pi et sur tous systèmes supportant Linux/Qt5.
//Activer l'advertising :
$ sudo hciconfig hci0 leadv 0
// Désactiver l'advertising :
$ sudo hciconfig hci0 noleadv
Remarque : Sous Linux, l’advertising nécessite un accès root. Vous devez donc exécuter le programme en tant qu’utilisateur root, par exemple via sudo
. Il est aussi possible d’utiliser la commande setcap
pour affecter ces capacités à l’exécutable :
$ sudo apt-get install libcap2-bin
$ sudo setcap 'cap_net_raw,cap_net_admin+eip' chemin_et_nom_de_l_exécutable
On a donc juste besoin d’une application Qt console :
QT += core bluetooth
QT -= gui
CONFIG += c++11
TARGET = ServeurBLE
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp \
ServeurBLE.cpp
HEADERS += \
ServeurBLE.h
target.path = /usr/bin
INSTALLS += target
Le programme principal instanciera un objet ServeurBLE
puis on appelera sa méthode demarrer()
:
#include <QCoreApplication>
#include "ServeurBLE.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ServeurBLE *serveurBLE = new ServeurBLE();
serverBLE->demarrer();
return a.exec();
}
Pour définir notre serveur GATT, il faudra compléter la table d’attributs suivante :
Chaque service, caractéristique et descripteur doit posséder un UUID (identificateur unique universel). Un UUID est codé sur 128 bits (16 octets). Par exemple : 67d1fdb0-219a-4cc9-82dc-ad6ef0ee12f5
Remarque : Il existe des UUID sur 16 bits (ou de 32 bits) abrégés pour les standards du SIG (Bluetooth Special Interest Group).
On peut utiliser un générateur d’UUID en ligne : www.uuidgenerator.net
Notre serveur BLE fournira un service personnalisé (67d1fdb0-219a-4cc9-82dc-ad6ef0ee12f5) auquel on va ajouter deux caractéristiques :
La valeur de la température est donc codée sur 16 bits (sint16 pour signed short int) en dixième de degré celcius. Le minimum étant de -2732 soit -273.2 °C.
La déclaration de la classe ServeurBLE
:
#ifndef ServeurBLE_H
#define ServeurBLE_H
#include <QtBluetooth>
#include <QLowEnergyController>
#include <QLowEnergyCharacteristic>
#include <QLowEnergyCharacteristicData>
#include <QLowEnergyDescriptorData>
#include <QLowEnergyAdvertisingData>
#include <QLowEnergyAdvertisingParameters>
#include <QLowEnergyService>
#include <QLowEnergyServiceData>
#include <QTimer>
#define SERVICE_UUID "{67d1fdb0-219a-4cc9-82dc-ad6ef0ee12f5}"
#define CHAR_UUID_USER1 "{114dcc56-941e-434e-b254-ee6b61485336}"
#define CHAR_UUID_TEMPERATURE 0x2a1f
class ServeurBLE : public QObject
{
Q_OBJECT
public:
ServeurBLE();
~ServeurBLE();
public:
void demarrer();
protected slots:
void controllerStateChanged(QLowEnergyController::ControllerState state);
void characteristicChanged(QLowEnergyCharacteristic c, QByteArray data);
void updateTemperature();
void onDeviceConnected();
void onDeviceDisconnected();
void onError(QLowEnergyController::Error);
protected:
QLowEnergyCharacteristicData createCharacteristic(QBluetoothUuid uuid, QLowEnergyCharacteristic::PropertyTypes type);
void setValue(QBluetoothUuid uuid, quint32 value);
private:
QLowEnergyAdvertisingData advertisingData;
QLowEnergyAdvertisingData responseData;
QLowEnergyService *service;
QLowEnergyController *controleurBLE;
QTimer *timerTemperature;
QByteArray readValueFromFile(QString filePath);
};
#endif // ServeurBLE_H
On lira la température dans /sys/devices/virtual/thermal/thermal_zone0/temp
toutes les secondes. On définit la classe ServeurBLE
:
#include "ServeurBLE.h"
#include <QFile>
ServeurBLE::ServeurBLE() : service(NULL), controleurBLE(NULL), timerTemperature(NULL)
{
qDebug() << Q_FUNC_INFO;
timerTemperature = new QTimer(this);
connect(timerTemperature, SIGNAL(timeout()), this, SLOT(updateTemperature()));
}
ServeurBLE::~ServeurBLE()
{
qDebug() << Q_FUNC_INFO;
}
void ServeurBLE::demarrer()
{
qDebug() << Q_FUNC_INFO;
// Création du serveur GATT sur le périphérique
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
//advertisingData.setIncludePowerLevel(true);
advertisingData.setLocalName("MonServeurBLE");
// Définition des services à offrir aux périphériques
QBluetoothUuid serviceUuid(QString(SERVICE_UUID));
advertisingData.setServices(QList<QBluetoothUuid>() << serviceUuid);
// Création des caractéristiques
QBluetoothUuid userUuid(QString(CHAR_UUID_USER1));
QBluetoothUuid temperatureUuid((quint32)CHAR_UUID_TEMPERATURE);
QLowEnergyCharacteristicData charUser = createCharacteristic(userUuid, QLowEnergyCharacteristic::Write | QLowEnergyCharacteristic::Read);
QLowEnergyCharacteristicData charTemperature = createCharacteristic(temperatureUuid, QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify);
// Association des caractéristiques au service
QLowEnergyServiceData serviceData;
serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
serviceData.setUuid(serviceUuid);
serviceData.addCharacteristic(charUser);
serviceData.addCharacteristic(charTemperature);
// Création du controleur BLE
controleurBLE = QLowEnergyController::createPeripheral();
connect(controleurBLE, SIGNAL(stateChanged(QLowEnergyController::ControllerState)), this, SLOT(controllerStateChanged(QLowEnergyController::ControllerState)));
// Ajout du service
service = controleurBLE->addService(serviceData);
connect(service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)), this, SLOT(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
// Démarrage
controleurBLE->startAdvertising(QLowEnergyAdvertisingParameters(), advertisingData, advertisingData);
}
void ServeurBLE::onDeviceConnected()
{
qDebug() << Q_FUNC_INFO;
}
void ServeurBLE::onDeviceDisconnected()
{
qDebug() << Q_FUNC_INFO;
}
void ServeurBLE::onError(QLowEnergyController::Error erreur)
{
qDebug() << Q_FUNC_INFO << erreur;
}
void ServeurBLE::controllerStateChanged(QLowEnergyController::ControllerState state)
{
if (state == QLowEnergyController::UnconnectedState)
{
qDebug() << Q_FUNC_INFO << "client déconnecté";
controleurBLE->startAdvertising(QLowEnergyAdvertisingParameters(), advertisingData, advertisingData);
timerTemperature->stop();
}
if (state == QLowEnergyController::ConnectedState)
{
qDebug() << Q_FUNC_INFO << "client connecté";
//démarrage lecture température
timerTemperature->start(1000);
}
}
void ServeurBLE::characteristicChanged(QLowEnergyCharacteristic c, QByteArray data)
{
qDebug() << Q_FUNC_INFO << c.uuid().toString() << data;
}
void ServeurBLE::updateTemperature()
{
// Récupération de la valeur de température
QByteArray rawTemp = readValueFromFile("/sys/devices/virtual/thermal/thermal_zone0/temp");
float temperatureC = rawTemp.toInt() / 1000.;
qDebug() << Q_FUNC_INFO << temperatureC;
qint16 temperature = rawTemp.toInt() / 100;
// Récupération de la caratéristique du service
QLowEnergyCharacteristic characteristic = service->characteristic(QBluetoothUuid((quint32)CHAR_UUID_TEMPERATURE));
if(characteristic.isValid())
{
// Mise à jour de la donnée
QByteArray datas((char*)&temperature, sizeof(qint16));
service->writeCharacteristic(characteristic, datas); // Potentially causes notification.
qDebug() << Q_FUNC_INFO << datas;
}
else
qDebug() << Q_FUNC_INFO << characteristic.uuid().toString() << "not valid";
}
void ServeurBLE::setValue(QBluetoothUuid uuid, quint32 value)
{
QLowEnergyCharacteristic characteristic = service->characteristic(uuid);
if(characteristic.isValid())
{
//if (characteristic.properties() & QLowEnergyCharacteristic::Write)
{
service->writeCharacteristic(characteristic, QByteArray::number(value)); // Potentially causes notification.
qDebug() << Q_FUNC_INFO << QByteArray::number(value);
}
}
}
QLowEnergyCharacteristicData ServeurBLE::createCharacteristic(QBluetoothUuid uuid, QLowEnergyCharacteristic::PropertyTypes type)
{
QLowEnergyCharacteristicData charData;
charData.setUuid(QBluetoothUuid(uuid));
charData.setValue(QByteArray(2, 0));
charData.setProperties(type);
const QLowEnergyDescriptorData clientConfig(QBluetoothUuid::ClientCharacteristicConfiguration, QByteArray(2, 0));
charData.addDescriptor(clientConfig);
return charData;
}
QByteArray ServeurBLE::readValueFromFile(QString filePath)
{
QByteArray data;
QFile file(filePath);
if (file.open(QIODevice::ReadOnly))
{
data = file.readAll();
data.remove(data.length()-1, 1);
file.close();
}
return data;
}
Sur le PC sous Linux, on utilisera Utilisation un dongle USB Bluetooth : Inateck Nano Bluetooth V4.0 (CSR8510).
$ hciconfig
hci0: Type: Primary Bus: USB
BD Address: 00:1A:7D:DA:71:13 ACL MTU: 310:10 SCO MTU: 64:8
UP RUNNING PSCAN ISCAN
RX bytes:1148408 acl:478 sco:0 events:46787 errors:0
TX bytes:108846 acl:467 sco:0 commands:10191 errors:0
$ bluetoothctl
[NEW] Controller 00:1A:7D:DA:71:13 sedatech [default]
Agent registered
[bluetooth]# help
...
Voici les résultats obtenus avec l’application mobile nRF Connect for Mobile :
On se connecte pour voir les services. On effecture une lecture de la température puis on envoie des données :
Debug :
...
void CServerBLE::characteristicChanged(QLowEnergyCharacteristic, QByteArray) "{114dcc56-941e-434e-b254-ee6b61485336}" "hello"
Sur une Raspberry Pi 3 :
pi@raspberrypi:~/ServeurBLE $ hciconfig
hci0: Type: Primary Bus: UART
BD Address: B8:27:EB:01:98:7E ACL MTU: 1021:8 SCO MTU: 64:1
UP RUNNING PSCAN
RX bytes:836 acl:0 sco:0 events:59 errors:0
TX bytes:4481 acl:0 sco:0 commands:59 errors:0
$ sudo hciconfig hci0 leadv 0
LE set advertise enable on hci0 returned status 12
pi@raspberrypi:~/ServeurBLE $ qmake
$ make
g++ -c -pipe -O2 -std=gnu++11 -Wall -W -D_REENTRANT -fPIC -DQT_NO_DEBUG -DQT_BLUETOOTH_LIB -DQT_CORE_LIB -I. -isystem /usr/include/arm-linux-gnueabihf/qt5 -isystem /usr/include/arm-linux-gnueabihf/qt5/QtBluetooth -isystem /usr/include/arm-linux-gnueabihf/qt5/QtCore -I. -I/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/linux-g++ -o main.o main.cpp
g++ -c -pipe -O2 -std=gnu++11 -Wall -W -D_REENTRANT -fPIC -DQT_NO_DEBUG -DQT_BLUETOOTH_LIB -DQT_CORE_LIB -I. -isystem /usr/include/arm-linux-gnueabihf/qt5 -isystem /usr/include/arm-linux-gnueabihf/qt5/QtBluetooth -isystem /usr/include/arm-linux-gnueabihf/qt5/QtCore -I. -I/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/linux-g++ -o ServeurBLE.o ServeurBLE.cpp
/usr/lib/arm-linux-gnueabihf/qt5/bin/moc -DQT_NO_DEBUG -DQT_BLUETOOTH_LIB -DQT_CORE_LIB -I/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/linux-g++ -I/home/pi/ServeurBLE -I/usr/include/arm-linux-gnueabihf/qt5 -I/usr/include/arm-linux-gnueabihf/qt5/QtBluetooth -I/usr/include/arm-linux-gnueabihf/qt5/QtCore -I/usr/include/c++/6 -I/usr/include/arm-linux-gnueabihf/c++/6 -I/usr/include/c++/6/backward -I/usr/lib/gcc/arm-linux-gnueabihf/6/include -I/usr/local/include -I/usr/lib/gcc/arm-linux-gnueabihf/6/include-fixed -I/usr/include/arm-linux-gnueabihf -I/usr/include ServeurBLE.h -o moc_ServeurBLE.cpp
g++ -c -pipe -O2 -std=gnu++11 -Wall -W -D_REENTRANT -fPIC -DQT_NO_DEBUG -DQT_BLUETOOTH_LIB -DQT_CORE_LIB -I. -isystem /usr/include/arm-linux-gnueabihf/qt5 -isystem /usr/include/arm-linux-gnueabihf/qt5/QtBluetooth -isystem /usr/include/arm-linux-gnueabihf/qt5/QtCore -I. -I/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/linux-g++ -o moc_ServeurBLE.o moc_ServeurBLE.cpp
g++ -Wl,-O1 -Wl,-rpath-link,/usr/lib/arm-linux-gnueabihf -o ServeurBLE main.o ServeurBLE.o moc_ServeurBLE.o -lQt5Bluetooth -lQt5DBus -lQt5Core -lpthread
$ sudo ./ServeurBLE
Voici les résultats obtenus avec l’application mobile nRF Connect for Mobile :
On peut voir le service 67d1fdb0-219a-4cc9-82dc-ad6ef0ee12f5 et ses deux caractéristiques 114dcc56-941e-434e-b254-ee6b61485336 et 0x2A1F (Temperature Celsius). On désactive la notification puis on fait une lecture de la température :
Debug :
$ sudo ./ServeurBLE
ServeurBLE::ServeurBLE()
void ServeurBLE::demarrer()
void ServeurBLE::controllerStateChanged(QLowEnergyController::ControllerState) client connecté
...
void ServeurBLE::updateTemperature() 48.312
void ServeurBLE::updateTemperature() "\xE3\x01"
void ServeurBLE::updateTemperature() 47.774
void ServeurBLE::updateTemperature() "\xDD\x01"
void ServeurBLE::updateTemperature() 47.774
void ServeurBLE::updateTemperature() "\xDD\x01"
void ServeurBLE::updateTemperature() 47.774
void ServeurBLE::updateTemperature() "\xDD\x01"
void ServeurBLE::updateTemperature() 47.774
void ServeurBLE::updateTemperature() "\xDD\x01"
void ServeurBLE::updateTemperature() 47.774
void ServeurBLE::updateTemperature() "\xDD\x01"
void ServeurBLE::controllerStateChanged(QLowEnergyController::ControllerState) client déconnecté
Code source : exemple_qt_serveur_ble.zip
Bluetooth est une norme de communications permettant l’échange bidirectionnel de données à très courte distance en utilisant des ondes radio UHF sur une bande de fréquence de 2,4 GHz. BLE (Bluetooth Low Energy, Bluetooth à basse consommation ou Bluetooth à basse énergie) est apparu en 2010 avec la sortie de la version 4.0 du Bluetooth Core Specification.
Lire : Bluetooth BLE et Bluetooth Classic
Exemple : Ampoule Bluetooth Magic Blue LED
Android 4.3 (API de niveau 18) introduit le support pour le Bluetooth Low Energy (BLE) dans le rôle central et fournit des API que les applications peuvent utiliser pour découvrir des périphériques, rechercher des services et transmettre des informations.
Activité : Mise en oeuvre du Bluetooth BLE sous Android
L’ESP32 est un microcontrôleur intégrant notamment le WiFi et le Bluetooth.
Activité : Mise en oeuvre du Bluetooth BLE sur ESP32