Site : tvaira.free.fr

Mise en oeuvre du Bluetooth BLE sur ESP32

Bluetooth BLE

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

ESP32

L’ESP32 est un microcontrôleur intégrant notamment le WiFi et le Bluetooth :

  • Microprocesseur dual core de 80 MHz à 240 MHz
  • 4MB de mémoire flash
  • WiFi 802.11 b/g/n
  • Bluetooth 4.0 LE et BR/EDR
  • 26x E/S digitales (3.3V)
  • 12x entrées analogiques
  • 4x SPI, 2x I²S, 2x I²C, 3x UART, CAN 2.0, IR, Touch Sensor (écran tactile)
  • Capteur de température

Lire : Mise en oeuvre du module ESP32

Programmation (Arduino IDE)

Dans l’IDE Arduino, on sélectionne la carte dans Outils -> Type de carte :

On va utiliser la bibliothèque intégrée au SDK ESP32 pour l’Arduino : ESP32_BLE_Arduino. Cette bibliothèque est installée par défaut lorsque vous avez installé l’ESP32 pour l’EDI Arduino.

L’ESP32 peut agir en tant que serveur BLE (ou en tant que client BLE). Il existe plusieurs exemples BLE pour l’ESP32 dans cette bibliothèque :

Pour créer un serveur BLE, on doit suivre les étapes suivantes :

  • Initialiser le périphérique BLE et lui donner un nom : BLEDevice::init()
  • Créer un serveur BLE : BLEDevice::createServer()
  • Créer un (ou plusieurs) service(s) BLE : BLEServer::createService()
  • Créer une (ou plusieurs) caractéristique(s) BLE associée(s) au service : BLEService::createCharacteristic()
  • Créer un descripteur BLE sur la caractéristique si nécessaire : BLECharacteristic::addDescriptor()
  • Démarrer le(s) service(s) : BLEService::start()
  • Lancer l’advertising : BLEServer::getAdvertising()->start()

Pour cela, on utilisera les classes suivantes :

Pour lire et/ou écrire la valeur d’une caractéristique, on utilisera les méthodes de la classe BLECharacteristic :

  • getValue() : qui retourne la valeur sous la forme d’un std::string
  • setValue() : qui fixe la valeur sous la forme soit d’une chaîne de caractère (char * ou std::string), soit d’un nombre entier ou soit d’un nombre en virgule flottante (float ou double)
  • notify() : qui permet de notifier un changement de la valeur

Le serveur BLE peut recevoir des demandes de lecture pour obtenir la valeur actuelle de la caractéristique ou des demandes d’écriture pour définir une nouvelle valeur. Mais le serveur BLE peut aussi vouloir “transmettre” des données lorsque quelque chose d’intéressant se produit. Cette opération s’appelle “notify” (notification). Elle est utilisée pour signaler (ou notifier) au client que la valeur de la caractéristique a changé. Le client recevra un événement d’indication pour l’informer de la modification.

Remarque : Il existe uen fonction similaire à notify() appelée indicate(). La différence est qu’indicate() reçoit une confirmation, tandis que notify() n’en reçoit pas.

Pour disposer des indications/notifications, il faut ajouter un 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 :

  • Bit 0 :
    • 0 = Notifications disabled
    • 1 = Notifications enabled
  • Bit 1 :
    • 0 = Indications disabled
    • 1 = Indications enabled

Attention : La spécification BLE limite la quantité maximale de données pouvant être envoyée via une notification (ou une indication) à 20 octets maximum. Si la valeur d’une caractéristique est supérieure à cette quantité, seuls les 20 premiers octets des données seront transmis.

La bibliothèque utilise aussi le principe des fonctions de rappel (callback) sur les évènements suivants :

  • lecture (onRead) et écriture (onWrite) d’une caractéristique
  • connexion (onConnect) et déconnexion (onDisconnect) du serveur BLE

Ces fonctions sont des méthodes à redéfinir des classes :

Il faudra donc dériver ces classes pour écrire ses propres fonctions de rappel :

Puis, il faudra les installer en appelant :

  • BLECharacteristic::setCallbacks(new MyCallback())
  • BLEServer::setCallbacks(new MyServerCallbacks())

Remarque : Les méthodes onConnect et onDisconnect peuvent être utilisées pour activer ou désactiver les lectures du capteur. Par exemple, si aucun client n’est connecté, il n’est pas nécessaire de dépenser de l’énergie pour échantillonner une valeur s’il n’y a personne pour la lire. Cependant, lorsqu’un client se connecte, nous pouvons le détecter et commencer à lire le capteur jusqu’à ce qu’une indication de déconnexion soit détectée.

Exemple n°1 : un compteur BLE

On va utiliser notre ESP32 comme un simple compteur (8 bits). Cette donnée pourra donc être lue ou notifiée à chaque changement de valeur du compteur.

Pour faire en sorte que notre notre caractéristique soit opérationnelle, il faudra :

  • Ajouter un service.
  • Déclarer et configurer la caractéristique.
  • Ajouter un descripteur “Configuration des caractéristiques du client” pour permettre à la caractéristique d’envoyer des notifications à intervalles réguliers ou chaque fois que la valeur change.

Il faudra donc 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 : 4fafc201-1fb5-459e-8fcc-c5c9c331914b

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

Tester sur une carte Wemos LOLIN ESP32 OLED, on obtient :

Notre serveur BLE fournit deux services : celui que l’on a créé (042bd80f-14f6-42be-a45c-a62836a4fa3f) et le “Generic Attribute” (0x1801) :

On va lire maintenant notre caractéristique (“065de41b-79fb-479d-b592-47caf39bfccb”) :

#define DISPLAY_SSD1306Wire

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>

#define SERVICE_UUID        "042bd80f-14f6-42be-a45c-a62836a4fa3f"
#define CHARACTERISTIC_UUID "065de41b-79fb-479d-b592-47caf39bfccb"

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;

#define NOTIFY_CHARACTERISTIC

bool estConnecte = false;
bool etaitConnecte = false;
uint8_t valeur = 0; // le compteur

class EtatServeur : public BLEServerCallbacks 
{
    void onConnect(BLEServer* pServer) 
    {
      estConnecte = true;
    }

    void onDisconnect(BLEServer* pServer) 
    {
      estConnecte = false;
    }
};

#ifdef DISPLAY_SSD1306Wire
#include "SSD1306Wire.h"

SSD1306Wire  display(0x3c, 5, 4);
void initDisplay();
void afficherMessage(String msg, int duree);
void afficherDatas(String msg, String datas, int duree);
#endif

void setup() 
{
  Serial.begin(115200);
  Serial.println("Test BLE init");

  #ifdef DISPLAY_SSD1306Wire
  initDisplay();

  afficherMessage("Test BLE init server", 0);
  #endif

  Serial.println("Test BLE init server");  

  BLEDevice::init("MonESP32");
  //BLEDevice::getAddress(); // Retrieve our own local BD BLEAddress
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new EtatServeur());
  
  BLEService *pService = pServer->createService(SERVICE_UUID);
  
  #ifdef NOTIFY_CHARACTERISTIC
  pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID,BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE  | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE);
  // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
  // Crée un descripteur : Client Characteristic Configuration (pour les indications/notifications)
  pCharacteristic->addDescriptor(new BLE2902());
  #else
  pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
  #endif

  pService->start();

  pServer->getAdvertising()->start();
  //BLEAdvertising *pAdvertising = pServer->getAdvertising();
  //pAdvertising->start();
  Serial.println("Test BLE start advertising");

  Serial.println("Test BLE wait connection");

  #ifdef DISPLAY_SSD1306Wire
  afficherMessage("Test BLE wait", 1000);
  #endif
}

void loop() 
{
  bool fini = false;

  while(!fini)
  { 
    // notification 
    if (estConnecte) 
    { 
      pCharacteristic->setValue(&valeur, 1); // la nouvelle valeur du compteur
      #ifdef NOTIFY_CHARACTERISTIC
      pCharacteristic->notify();  
      #endif      
      delay(10); // bluetooth stack will go into congestion, if too many packets are sent

      String datas(valeur);
      //Serial.println("BLE notify");
      Serial.printf("BLE notify : %d\n", valeur);
      #ifdef DISPLAY_SSD1306Wire
      afficherDatas("BLE notify", datas, 500);
      #endif
      
      valeur++; // on compte ...
    }
    // déconnecté ?
    if (!estConnecte && etaitConnecte) 
    {
      Serial.println("BLE deconnection");
      #ifdef DISPLAY_SSD1306Wire
      afficherMessage("BLE deconnecte", 500);
      #else
      delay(500); // give the bluetooth stack the chance to get things ready
      #endif
      
      pServer->startAdvertising(); // restart advertising
      Serial.println("BLE restart advertising");

      Serial.println("Test BLE wait connection");
      #ifdef DISPLAY_SSD1306Wire
      afficherMessage("Test BLE wait", 0);
      #endif
      
      etaitConnecte = estConnecte;
    }
    // connecté ?
    if (estConnecte && !etaitConnecte) 
    {
      Serial.println("BLE connection");
      #ifdef DISPLAY_SSD1306Wire
      afficherMessage("BLE connecte", 0);
      #endif
      
      etaitConnecte = estConnecte;
    }
  }
}

// ...

Code source : exemple_ble_1.ino

Analyse Wireshark

On peut aussi tester avec l’application mobile nRF Connect for Mobile.

Notre serveur BLE fournit trois services : celui que l’on a créé (042bd80f-14f6-42be-a45c-a62836a4fa3f), le “Generic Access” (0x1800) et le “Generic Attribute” (0x1801) :

On va lire maintenant notre caractéristique (“065de41b-79fb-479d-b592-47caf39bfccb”) :

On peut aussi demander les indications/notifications :

Activer les options de développement de la tablette, puis la journalisation des paquets Blutooth :

Maintenant, la tablette enregistrera tous les paquets Blutooth échangés dans un fichier de journalisation.

Il suffit ensuite de récupérer le fichier btsnoop_hci.log dans le dossier Android/data/. On le renomme en btsnoop_hci.pcap et on l’ouvre dans Wireshark. On peut directement filtrer la réponse à la demande de lecture de la caractéristique avec btatt.opcode.method==0x0b :

Exemple n°2 : UART Over BLE

La transmission UART sur BLE ne fait pas partie des profils définis par Bluetooth SIG. C’est un service personnalisé par Nordic Semiconductor. Nordic Semiconductor (fabricant des puces nRFxxx) a créé 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) :

  • Caractéristique RX (UUID: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E) : le client peut envoyer des données au périphérique en écrivant dans la caractéristique de réception du service.
  • Caractéristique TX (UUID: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E) : si client a activé les notifications pour cette caractéristique, le serveur peut lui envoyer des données en tant que notifications.

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 :

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>

#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"

BLEServer* pServer = NULL;
BLECharacteristic* pTxCharacteristic = NULL;
BLECharacteristic* pRxCharacteristic = NULL;

bool estConnecte = false;
bool etaitConnecte = false;
uint8_t valeur = 0;

class EtatServeur : public BLEServerCallbacks 
{
    void onConnect(BLEServer* pServer) 
    {
      estConnecte = true;
    }

    void onDisconnect(BLEServer* pServer) 
    {
      estConnecte = false;
    }
};

class CharacteristicUART : public BLECharacteristicCallbacks 
{
    void onWrite(BLECharacteristic *pCharacteristique) 
    {
      std::string rxValue = pCharacteristique->getValue();

      if (rxValue.length() > 0) 
      {
        Serial.println("*********");
        Serial.print("Received Value: ");
        for (int i = 0; i < rxValue.length(); i++)
          Serial.print(rxValue[i]);
        Serial.println();
        Serial.println("*********");
      }
    }
};

void setup() 
{
  Serial.begin(115200);
  Serial.println("UART Over BLE init");
  
  BLEDevice::init("MonESP32");
  //BLEDevice::getAddress(); // Retrieve our own local BD BLEAddress
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new EtatServeur());
  
  BLEService *pServiceUART = pServer->createService(SERVICE_UART_UUID);
  pTxCharacteristic = pServiceUART->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
  // Create a BLE Descriptor : Client Characteristic Configuration (for indications/notifications)
  pTxCharacteristic->addDescriptor(new BLE2902());
  pRxCharacteristic = pServiceUART->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
  pRxCharacteristic->setCallbacks(new CharacteristicUART());
  
  pServiceUART->start();

  pServer->getAdvertising()->start();
  //BLEAdvertising *pAdvertising = pServer->getAdvertising();
  //pAdvertising->start();
  Serial.println("UART Over BLE start advertising");

  Serial.println("UART Over BLE wait connection");
}

void loop() 
{
  bool fini = false;

  while(!fini)
  { 
    // notification 
    if (estConnecte) 
    {
      String datas(valeur);      
      pTxCharacteristic->setValue(&valeur, 1);
      pTxCharacteristic->notify();        
      delay(10); // bluetooth stack will go into congestion, if too many packets are sent
      valeur++;
    }
    // déconnecté ?
    if (!estConnecte && etaitConnecte) 
    {
      Serial.println("UART Over BLE deconnection");      
      delay(500); // give the bluetooth stack the chance to get things ready      
      pServer->startAdvertising(); // restart advertising
      Serial.println("UART Over BLE restart advertising");      
      etaitConnecte = estConnecte;
    }
    // connecté ?
    if (estConnecte && !etaitConnecte) 
    {
      Serial.println("UART Over BLE connection");      
      etaitConnecte = estConnecte;
    }
  }
}

Code source : exemple_ble_2.ino

Exemple (avec une classe sécifique) : esp32-ble-uart.zip

Tester sur une carte AZDelivery ESP32 :

Voici les résultats obtenus avec l’application mobile nRF Connect for Mobile :

Maintenant, on active les notifications :

Voir aussi

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

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.

Activités :

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

Liens

Retour au sommaire