Serveur et client ESP32 BLE (Bluetooth Low Energy)

Serveur et client ESP32 BLE (Bluetooth Low Energy)

Apprenez à établir une connexion BLE (Bluetooth Low Energy) entre deux cartes ESP32. Un ESP32 sera le serveur et l’autre ESP32 sera le client. Le serveur BLE annonce des caractéristiques qui contiennent des lectures de capteur que le client peut lire. Le client ESP32 BLE lit les valeurs de ces caractéristiques (température et humidité) et les affiche sur un écran OLED.

ESP32 Serveur BLE et Client BLuetooth Low Energy Arduino IDE

Lecture recommandée : Premiers pas avec ESP32 Bluetooth Low Energy (BLE)

Qu’est-ce que le Bluetooth Low Energy ?

Avant de passer directement au projet, il est important de jeter un coup d’œil rapide sur quelques concepts BLE essentiels afin de pouvoir mieux comprendre le projet par la suite. Si vous connaissez déjà BLE, vous pouvez passer à la Aperçu du projet section.

Bluetooth Low Energy, BLE en abrégé, est une variante de Bluetooth économe en énergie. L’application principale de BLE est la transmission à courte distance de petites quantités de données (faible bande passante). Contrairement au Bluetooth qui est toujours activé, le BLE reste constamment en mode veille, sauf lorsqu’une connexion est établie.

Cela lui fait consommer très peu d’énergie. BLE consomme environ 100 fois moins d’énergie que Bluetooth (selon le cas d’utilisation). Tu peux vérifiez les principales différences entre Bluetooth et Bluetooth Low Energy ici.

Serveur et client BLE

Avec Bluetooth Low Energy, il existe deux types d’appareils : le serveur et le client. L’ESP32 peut agir soit en tant que client, soit en tant que serveur.

Le serveur annonce son existence, afin qu’il puisse être trouvé par d’autres appareils et contient des données que le client peut lire. Le client analyse les appareils à proximité et lorsqu’il trouve le serveur qu’il recherche, il établit une connexion et écoute les données entrantes. C’est ce qu’on appelle la communication point à point.

BLE Client Serveur Serveur Publicité

Il existe d’autres modes de communication possibles comme le mode de diffusion et le réseau maillé (non couverts dans ce tutoriel).

GATT

GATT signifie Generic Attributes et définit une structure de données hiérarchique qui est exposée aux appareils BLE connectés. Cela signifie que le GATT définit la manière dont deux appareils BLE envoient et reçoivent des messages standard. Comprendre cette hiérarchie est important car cela facilitera la compréhension de l’utilisation du BLE avec l’ESP32.

Hiérarchie GATT Exemple de client de serveur ESP32 BLE
  • Profil : collection standard de services pour un cas d’utilisation spécifique ;
  • Service : collecte d’informations connexes, telles que les relevés des capteurs, le niveau de la batterie, la fréquence cardiaque, etc. ;
  • Caractéristique : c’est là que les données réelles sont enregistrées dans la hiérarchie (valeur) ;
  • Descripteur : métadonnées sur les données ;
  • Propriétés : décrivent comment la valeur caractéristique peut être interagi avec. Par exemple : lire, écrire, notifier, diffuser, indiquer, etc.

Dans notre exemple, nous allons créer un service avec deux caractéristiques. Un pour la température et un autre pour l’humidité. Les lectures réelles de température et d’humidité sont enregistrées sur la valeur sous leurs caractéristiques. Chaque caractéristique a la propriété notify, de sorte qu’elle informe le client chaque fois que les valeurs changent.

UUID

Chaque service, caractéristique et descripteur possède un UUID (Universally Unique Identifier). Un UUID est un numéro unique de 128 bits (16 octets). Par exemple:

55072829-bc9e-4c53-938a-74a6d4c78776

Il existe des UUID raccourcis pour tous les types, services et profils spécifiés dans le SIG (Groupe d’Intérêt Spécial Bluetooth).

Mais si votre application a besoin de son propre UUID, vous pouvez le générer en utilisant ce Site Web du générateur d’UUID.

En résumé, l’UUID est utilisé pour identifier de manière unique les informations. Par exemple, il peut identifier un service particulier fourni par un appareil Bluetooth.

Pour une introduction plus détaillée sur BLE, lisez notre guide de démarrage :

Aperçu du projet

Dans ce tutoriel, vous allez apprendre à établir une connexion BLE entre deux cartes ESP32. Un ESP32 sera le serveur BLE et l’autre ESP32 sera le client BLE.

Démonstration de l'affichage OLED du serveur client ESP32 BLE

Le serveur ESP32 BLE est connecté à un capteur BME280 et il met à jour ses valeurs caractéristiques de température et d’humidité toutes les 30 secondes.

Le client ESP32 se connecte au serveur BLE et il est informé de ses valeurs caractéristiques de température et d’humidité. Cet ESP32 est connecté à un écran OLED et imprime les dernières lectures.

Ce projet est divisé en deux parties :

Pièces requises

Voici une liste des pièces nécessaires pour suivre ce projet :

Serveur ESP32 BLE :

Client ESP32 BLE :

Vous pouvez utiliser les liens précédents ou accéder directement à MakerAdvisor.com/tools pour trouver toutes les pièces pour vos projets au meilleur prix !

1679793977 326 Serveur et client ESP32 BLE Bluetooth Low Energy

1) Serveur ESP32 BLE

Dans cette partie, nous allons configurer le serveur BLE qui annonce un service contenant deux caractéristiques : une pour la température et une autre pour l’humidité. Ces caractéristiques ont la propriété Notify pour notifier les nouvelles valeurs au client.

Serveur ESP32 BLE connecté à l'application nRF Connect

Diagramme schématique

Le serveur ESP32 BLE annoncera les caractéristiques de température et d’humidité d’un capteur BME280. Vous pouvez utiliser n’importe quel autre capteur tant que vous ajoutez les lignes requises dans le code.

Nous allons utiliser la communication I2C avec le module de capteur BME280. Pour cela, câblez le capteur aux broches ESP32 SCL (GPIO 22) et SDA (GPIO 21) par défaut, comme indiqué dans le schéma suivant.

Schéma de principe du circuit de câblage ESP32 au BME280

Lecture recommandée : Référence de brochage ESP32 : Quelles broches GPIO devez-vous utiliser ?

Installation des bibliothèques BME280

Comme mentionné précédemment, nous annoncerons les lectures de capteur d’un capteur BME280. Vous devez donc installer les bibliothèques pour s’interfacer avec le capteur BME280.

Vous pouvez installer les bibliothèques à l’aide du gestionnaire de bibliothèque Arduino. Accédez à Esquisse > Inclure la bibliothèque > Gérer les bibliothèques et recherchez le nom de la bibliothèque.

Installation de bibliothèques (VS Code + PlatformIO)

Si vous utilisez VS Code avec l’extension PlatformIO, copiez ce qui suit dans le fichier platformio.ini pour inclure les bibliothèques.

lib_deps = adafruit/Adafruit Unified Sensor @ ^1.1.4
            adafruit/Adafruit BME280 Library @ ^2.1.2  

Serveur ESP32 BLE – Code

Avec le circuit prêt et les bibliothèques requises installées, copiez le code suivant dans l’IDE Arduino ou dans le fichier main.cpp si vous utilisez VS Code.

/*********
  Rui Santos
  Complete instructions at https://Raspberryme.com/esp32-ble-server-client/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

//Default Temperature is in Celsius
//Comment the next line for Temperature in Fahrenheit
#define temperatureCelsius

//BLE server name
#define bleServerName "BME280_ESP32"

Adafruit_BME280 bme; // I2C

float temp;
float tempF;
float hum;

// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;

bool deviceConnected = false;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59"

// Temperature Characteristic and Descriptor
#ifdef temperatureCelsius
  BLECharacteristic bmeTemperatureCelsiusCharacteristics("cba1d466-344c-4be3-ab3f-189f80dd7518", BLECharacteristic::PROPERTY_NOTIFY);
  BLEDescriptor bmeTemperatureCelsiusDescriptor(BLEUUID((uint16_t)0x2902));
#else
  BLECharacteristic bmeTemperatureFahrenheitCharacteristics("f78ebbff-c8b7-4107-93de-889a6a06d408", BLECharacteristic::PROPERTY_NOTIFY);
  BLEDescriptor bmeTemperatureFahrenheitDescriptor(BLEUUID((uint16_t)0x2902));
#endif

// Humidity Characteristic and Descriptor
BLECharacteristic bmeHumidityCharacteristics("ca73b3ba-39f6-4ab3-91ae-186dc9577d99", BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor bmeHumidityDescriptor(BLEUUID((uint16_t)0x2903));

//Setup callbacks onConnect and onDisconnect
class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    deviceConnected = true;
  };
  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
  }
};

void initBME(){
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
}

void setup() {
  // Start serial communication 
  Serial.begin(115200);

  // Init BME Sensor
  initBME();

  // Create the BLE Device
  BLEDevice::init(bleServerName);

  // Create the BLE Server
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *bmeService = pServer->createService(SERVICE_UUID);

  // Create BLE Characteristics and Create a BLE Descriptor
  // Temperature
  #ifdef temperatureCelsius
    bmeService->addCharacteristic(&bmeTemperatureCelsiusCharacteristics);
    bmeTemperatureCelsiusDescriptor.setValue("BME temperature Celsius");
    bmeTemperatureCelsiusCharacteristics.addDescriptor(&bmeTemperatureCelsiusDescriptor);
  #else
    bmeService->addCharacteristic(&bmeTemperatureFahrenheitCharacteristics);
    bmeTemperatureFahrenheitDescriptor.setValue("BME temperature Fahrenheit");
    bmeTemperatureFahrenheitCharacteristics.addDescriptor(&bmeTemperatureFahrenheitDescriptor);
  #endif  

  // Humidity
  bmeService->addCharacteristic(&bmeHumidityCharacteristics);
  bmeHumidityDescriptor.setValue("BME humidity");
  bmeHumidityCharacteristics.addDescriptor(new BLE2902());
  
  // Start the service
  bmeService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
  if (deviceConnected) {
    if ((millis() - lastTime) > timerDelay) {
      // Read temperature as Celsius (the default)
      temp = bme.readTemperature();
      // Fahrenheit
      tempF = 1.8*temp +32;
      // Read humidity
      hum = bme.readHumidity();
  
      //Notify temperature reading from BME sensor
      #ifdef temperatureCelsius
        static char temperatureCTemp[6];
        dtostrf(temp, 6, 2, temperatureCTemp);
        //Set temperature Characteristic value and notify connected client
        bmeTemperatureCelsiusCharacteristics.setValue(temperatureCTemp);
        bmeTemperatureCelsiusCharacteristics.notify();
        Serial.print("Temperature Celsius: ");
        Serial.print(temp);
        Serial.print(" ºC");
      #else
        static char temperatureFTemp[6];
        dtostrf(tempF, 6, 2, temperatureFTemp);
        //Set temperature Characteristic value and notify connected client
        bmeTemperatureFahrenheitCharacteristics.setValue(temperatureFTemp);
        bmeTemperatureFahrenheitCharacteristics.notify();
        Serial.print("Temperature Fahrenheit: ");
        Serial.print(tempF);
        Serial.print(" ºF");
      #endif
      
      //Notify humidity reading from BME
      static char humidityTemp[6];
      dtostrf(hum, 6, 2, humidityTemp);
      //Set humidity Characteristic value and notify connected client
      bmeHumidityCharacteristics.setValue(humidityTemp);
      bmeHumidityCharacteristics.notify();   
      Serial.print(" - Humidity: ");
      Serial.print(hum);
      Serial.println(" %");
      
      lastTime = millis();
    }
  }
}

Afficher le code brut

Vous pouvez télécharger le code, et il fonctionnera immédiatement en annonçant son service avec les caractéristiques de température et d’humidité. Continuez à lire pour savoir comment le code fonctionne, ou passez à la Rubrique clients.

Il existe plusieurs exemples montrant comment utiliser BLE avec l’ESP32 dans la section Exemples. Dans votre IDE Arduino, allez dans Fichier > Exemples > ESP32 BLE Arduino. Cette esquisse de serveur est basée sur l’exemple Notify.

Importation de bibliothèques

Le code commence par importer les bibliothèques requises.

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

Choix de l’unité de température

Par défaut, l’ESP envoie la température en degrés Celsius. Vous pouvez commenter la ligne suivante ou la supprimer pour envoyer la température en degrés Fahrenheit.

//Comment the next line for Temperature in Fahrenheit
#define temperatureCelsius

Nom du serveur BLE

La ligne suivante définit un nom pour notre serveur BLE. Laissez le nom du serveur BLE par défaut. Sinon, le nom du serveur dans le code client doit également être modifié (car ils doivent correspondre).

//BLE server name
#define bleServerName "BME280_ESP32"

Capteur BME280

Créez un objet Adafruit_BME280 appelé bme sur les broches ESP32 I2C par défaut.

Adafruit_BME280 bme; // I2C

Les variables temp, tempF et hum maintiendront la température en degrés Celsius, la température en degrés Fahrenheit et l’humidité lue par le capteur BME280.

float temp;
float tempF;
float hum;

Autres variables

Les variables de minuterie suivantes définissent la fréquence à laquelle nous voulons écrire dans les caractéristiques de température et d’humidité. Nous définissons la variable timerDelay sur 30 000 millisecondes (30 secondes), mais vous pouvez la modifier.

// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;

La variable booléenne deviceConnected nous permet de savoir si un client est connecté au serveur.

bool deviceConnected = false;

UUID BLE

Dans les lignes suivantes, nous définissons les UUID pour le service, pour la caractéristique de température en Celsius, pour la caractéristique de température en Fahrenheit et pour l’humidité.

// https://www.uuidgenerator.net/
#define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59"

// Temperature Characteristic and Descriptor
#ifdef temperatureCelsius
  BLECharacteristic bmeTemperatureCelsiusCharacteristics("cba1d466-344c-4be3-ab3f-189f80dd7518", BLECharacteristic::PROPERTY_NOTIFY);
  BLEDescriptor bmeTemperatureCelsiusDescriptor(BLEUUID((uint16_t)0x2902));
#else
  BLECharacteristic bmeTemperatureFahrenheitCharacteristics("f78ebbff-c8b7-4107-93de-889a6a06d408", BLECharacteristic::PROPERTY_NOTIFY);
  BLEDescriptor bmeTemperatureFahrenheitDescriptor(BLEUUID((uint16_t)0x2901));
#endif

// Humidity Characteristic and Descriptor
BLECharacteristic bmeHumidityCharacteristics("ca73b3ba-39f6-4ab3-91ae-186dc9577d99", BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor bmeHumidityDescriptor(BLEUUID((uint16_t)0x2903));

Je recommande de laisser tous les UUID par défaut. Sinon, vous devez également modifier le code côté client, afin que le client puisse trouver le service et récupérer les valeurs des caractéristiques.

installation()

Dans setup(), initialisez le Serial Monitor et le capteur BME280.

// Start serial communication 
Serial.begin(115200);

// Init BME Sensor
initBME();

Créez un nouvel appareil BLE avec le nom de serveur BLE que vous avez défini précédemment :

// Create the BLE Device
BLEDevice::init(bleServerName);

Définissez l’appareil BLE en tant que serveur et attribuez une fonction de rappel.

// Create the BLE Server
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());

La fonction de rappel MyServerCallbacks() change la variable booléenne deviceConnected en true ou false selon l’état actuel de l’appareil BLE. Cela signifie que si un client est connecté au serveur, l’état est vrai. Si le client se déconnecte, la variable booléenne devient fausse. Voici la partie du code qui définit la fonction MyServerCallbacks().

//Setup callbacks onConnect and onDisconnect
class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    deviceConnected = true;
  };
  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
  }
};

Démarrez un service BLE avec l’UUID de service défini précédemment.

BLEService *bmeService = pServer->createService(SERVICE_UUID);

Ensuite, créez la caractéristique de température BLE. Si vous utilisez des degrés Celsius, il définit la caractéristique et le descripteur suivants :

#ifdef temperatureCelsius
  bmeService->addCharacteristic(&bmeTemperatureCelsiusCharacteristics);
  bmeTemperatureCelsiusDescriptor.setValue("BME temperature Celsius");
  bmeTemperatureCelsiusCharacteristics.addDescriptor(new BLE2902());

Sinon, il définit la caractéristique Fahrenheit :

#else
  bmeService->addCharacteristic(&dhtTemperatureFahrenheitCharacteristics);
  bmeTemperatureFahrenheitDescriptor.setValue("BME temperature Fahrenheit");
  bmeTemperatureFahrenheitCharacteristics.addDescriptor(new BLE2902());
#endif  

Après cela, il définit la caractéristique d’humidité :

// Humidity
bmeService->addCharacteristic(&bmeHumidityCharacteristics);
bmeHumidityDescriptor.setValue("BME humidity");
bmeHumidityCharacteristics.addDescriptor(new BLE2902());

Enfin, vous démarrez le service et le serveur lance la publicité afin que d’autres appareils puissent la trouver.

// Start the service
bmeService->start();

// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pServer->getAdvertising()->start();
Serial.println("Waiting a client connection to notify...");

boucle()

La fonction loop() est assez simple. Vous vérifiez constamment si l’appareil est connecté à un client ou non. S’il est connecté et que le timerDelay est passé, il lit la température et l’humidité actuelles.

if (deviceConnected) {
  if ((millis() - lastTime) > timerDelay) {
    // Read temperature as Celsius (the default)
    temp = bme.readTemperature();
    // Fahrenheit
    tempF = temp*1.8 +32;
    // Read humidity
    hum = bme.readHumidity();

Si vous utilisez la température en Celsius, il exécute la section de code suivante. Tout d’abord, il convertit la température en une variable char (variable temperatureCTemp). Nous devons convertir la température en une variable de type char pour l’utiliser dans la fonction setValue().

static char temperatureCTemp[6];
dtostrf(temp, 6, 2, temperatureCTemp);

Ensuite, il définit la valeur bmeTemperatureCelsiusCharacteristic sur la nouvelle valeur de température (temperatureCTemp) à l’aide de la fonction setValue(). Après avoir défini la nouvelle valeur, nous pouvons notifier le client connecté à l’aide de la fonction notify().

//Set temperature Characteristic value and notify connected client
bmeTemperatureCelsiusCharacteristics.setValue(temperatureCTemp);
bmeTemperatureCelsiusCharacteristics.notify();

Nous suivons une procédure similaire pour la température en Fahrenheit.

#else
    static char temperatureFTemp[6];
    dtostrf(f, 6, 2, temperatureFTemp);
    //Set temperature Characteristic value and notify connected client
    bmeTemperatureFahrenheitCharacteristics.setValue(tempF);
    bmeTemperatureFahrenheitCharacteristics.notify();
    Serial.print("Temperature Fahrenheit: ");
    Serial.print(tempF);
    Serial.print(" *F");
#endif

L’envoi de l’humidité utilise également le même procédé.

//Notify humidity reading from DHT
static char humidityTemp[6];
dtostrf(hum, 6, 2, humidityTemp);
//Set humidity Characteristic value and notify connected client
bmeHumidityCharacteristics.setValue(humidityTemp);
bmeHumidityCharacteristics.notify();   
Serial.print(" - Humidity: ");
Serial.print(hum);
Serial.println(" %");

Test du serveur ESP32 BLE

Téléchargez le code sur votre carte, puis ouvrez le moniteur série. Il affichera un message comme indiqué ci-dessous.

Le serveur ESP32 BLE démarre le moniteur série

Ensuite, vous pouvez tester si le serveur BLE fonctionne comme prévu en utilisant une application de numérisation BLE sur votre smartphone comme nRF Connect. Cette application est disponible pour Android et iOS.

Après avoir installé l’application, activez le Bluetooth sur votre smartphone. Ouvrez l’application nRF Connect et cliquez sur le bouton Numériser. Il trouvera tous les appareils Bluetooth à proximité, y compris votre appareil BME280_ESP32 (c’est le nom du serveur BLE que vous avez défini sur le code).

Application de scanner de serveur ESP32 BLE

Connectez-vous à votre appareil BME280_ESP32, puis sélectionnez l’onglet client (l’interface peut être légèrement différente). Vous pouvez vérifier qu’il annonce le service avec l’UUID que nous avons défini dans le code, ainsi que les caractéristiques de température et d’humidité. Notez que ces caractéristiques ont la propriété Notify.

Caractéristiques du serveur ESP32 BLE Application nRF Connect

Votre serveur ESP32 BLE est prêt !

Passez à la section suivante pour créer un client ESP32 qui se connecte au serveur pour accéder aux caractéristiques de température et d’humidité et obtenir les lectures pour les afficher sur un écran OLED.


2) Client ESP32 BLE

Dans cette section, nous allons créer le client ESP32 BLE qui établira une connexion avec le serveur ESP32 BLE et affichera les lectures sur un écran OLED.

Schématique

Le client ESP32 BLE est connecté à un écran OLED. L’écran affiche les lectures reçues via Bluetooth.

Câblez votre écran OLED à l’ESP32 en suivant le schéma suivant. La broche SCL se connecte au GPIO 22 et la broche SDA au GPIO 21.

Schéma de principe du circuit de câblage ESP32 au SSD1306 OLED

Installation des bibliothèques SSD1306, GFX et BusIO

Vous devez installer les bibliothèques suivantes pour vous connecter à l’écran OLED :

Pour installer les bibliothèques, accédez à Sketch> Inclure la bibliothèque> Gérer les bibliothèques et recherchez les noms des bibliothèques.

Installation de bibliothèques (VS Code + PlatformIO)

Si vous utilisez VS Code avec l’extension PlatformIO, copiez ce qui suit dans le fichier platformio.ini pour inclure les bibliothèques.

lib_deps = 
	adafruit/Adafruit GFX [email protected]^1.10.12
	adafruit/Adafruit [email protected]^2.4.6

Client BLE ESP32 – Code

Copiez l’esquisse du client BLE dans votre IDE Arduino ou dans le fichier main.cpp si vous utilisez VS Code avec PlatformIO.

/*********
  Rui Santos
  Complete instructions at https://Raspberryme.com/esp32-ble-server-client/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/

#include "BLEDevice.h"
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>

//Default Temperature is in Celsius
//Comment the next line for Temperature in Fahrenheit
#define temperatureCelsius

//BLE Server name (the other ESP32 name running the server sketch)
#define bleServerName "BME280_ESP32"

/* UUID's of the service, characteristic that we want to read*/
// BLE Service
static BLEUUID bmeServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59");

// BLE Characteristics
#ifdef temperatureCelsius
  //Temperature Celsius Characteristic
  static BLEUUID temperatureCharacteristicUUID("cba1d466-344c-4be3-ab3f-189f80dd7518");
#else
  //Temperature Fahrenheit Characteristic
  static BLEUUID temperatureCharacteristicUUID("f78ebbff-c8b7-4107-93de-889a6a06d408");
#endif

// Humidity Characteristic
static BLEUUID humidityCharacteristicUUID("ca73b3ba-39f6-4ab3-91ae-186dc9577d99");

//Flags stating if should begin connecting and if the connection is up
static boolean doConnect = false;
static boolean connected = false;

//Address of the peripheral device. Address will be found during scanning...
static BLEAddress *pServerAddress;
 
//Characteristicd that we want to read
static BLERemoteCharacteristic* temperatureCharacteristic;
static BLERemoteCharacteristic* humidityCharacteristic;

//Activate notify
const uint8_t notificationOn[] = {0x1, 0x0};
const uint8_t notificationOff[] = {0x0, 0x0};

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

//Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

//Variables to store temperature and humidity
char* temperatureChar;
char* humidityChar;

//Flags to check whether new temperature and humidity readings are available
boolean newTemperature = false;
boolean newHumidity = false;

//Connect to the BLE Server that has the name, Service, and Characteristics
bool connectToServer(BLEAddress pAddress) {
   BLEClient* pClient = BLEDevice::createClient();
 
  // Connect to the remove BLE Server.
  pClient->connect(pAddress);
  Serial.println(" - Connected to server");
 
  // Obtain a reference to the service we are after in the remote BLE server.
  BLERemoteService* pRemoteService = pClient->getService(bmeServiceUUID);
  if (pRemoteService == nullptr) {
    Serial.print("Failed to find our service UUID: ");
    Serial.println(bmeServiceUUID.toString().c_str());
    return (false);
  }
 
  // Obtain a reference to the characteristics in the service of the remote BLE server.
  temperatureCharacteristic = pRemoteService->getCharacteristic(temperatureCharacteristicUUID);
  humidityCharacteristic = pRemoteService->getCharacteristic(humidityCharacteristicUUID);

  if (temperatureCharacteristic == nullptr || humidityCharacteristic == nullptr) {
    Serial.print("Failed to find our characteristic UUID");
    return false;
  }
  Serial.println(" - Found our characteristics");
 
  //Assign callback functions for the Characteristics
  temperatureCharacteristic->registerForNotify(temperatureNotifyCallback);
  humidityCharacteristic->registerForNotify(humidityNotifyCallback);
  return true;
}

//Callback function that gets called, when another device's advertisement has been received
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    if (advertisedDevice.getName() == bleServerName) { //Check if the name of the advertiser matches
      advertisedDevice.getScan()->stop(); //Scan can be stopped, we found what we are looking for
      pServerAddress = new BLEAddress(advertisedDevice.getAddress()); //Address of advertiser is the one we need
      doConnect = true; //Set indicator, stating that we are ready to connect
      Serial.println("Device found. Connecting!");
    }
  }
};
 
//When the BLE Server sends a new temperature reading with the notify property
static void temperatureNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, 
                                        uint8_t* pData, size_t length, bool isNotify) {
  //store temperature value
  temperatureChar = (char*)pData;
  newTemperature = true;
}

//When the BLE Server sends a new humidity reading with the notify property
static void humidityNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, 
                                    uint8_t* pData, size_t length, bool isNotify) {
  //store humidity value
  humidityChar = (char*)pData;
  newHumidity = true;
  Serial.print(newHumidity);
}

//function that prints the latest sensor readings in the OLED display
void printReadings(){
  
  display.clearDisplay();  
  // display temperature
  display.setTextSize(1);
  display.setCursor(0,0);
  display.print("Temperature: ");
  display.setTextSize(2);
  display.setCursor(0,10);
  display.print(temperatureChar);
  display.setTextSize(1);
  display.cp437(true);
  display.write(167);
  display.setTextSize(2);
  Serial.print("Temperature:");
  Serial.print(temperatureChar);
  #ifdef temperatureCelsius
    //Temperature Celsius
    display.print("C");
    Serial.print("C");
  #else
    //Temperature Fahrenheit
    display.print("F");
    Serial.print("F");
  #endif

  //display humidity 
  display.setTextSize(1);
  display.setCursor(0, 35);
  display.print("Humidity: ");
  display.setTextSize(2);
  display.setCursor(0, 45);
  display.print(humidityChar);
  display.print("%");
  display.display();
  Serial.print(" Humidity:");
  Serial.print(humidityChar); 
  Serial.println("%");
}

void setup() {
  //OLED display setup
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE,0);
  display.setCursor(0,25);
  display.print("BLE Client");
  display.display();
  
  //Start serial communication
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");

  //Init BLE device
  BLEDevice::init("");
 
  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 30 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);
  pBLEScan->start(30);
}

void loop() {
  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer(*pServerAddress)) {
      Serial.println("We are now connected to the BLE Server.");
      //Activate the Notify property of each Characteristic
      temperatureCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
      humidityCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
      connected = true;
    } else {
      Serial.println("We have failed to connect to the server; Restart your device to scan for nearby BLE server again.");
    }
    doConnect = false;
  }
  //if new temperature readings are available, print in the OLED
  if (newTemperature && newHumidity){
    newTemperature = false;
    newHumidity = false;
    printReadings();
  }
  delay(1000); // Delay a second between loops.
}

Afficher le code brut

Continuez à lire pour savoir comment le code fonctionne ou passez à la Manifestation section.

Importation de bibliothèques

Vous commencez par importer les bibliothèques requises :

#include "BLEDevice.h"
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>

Choix de l’unité de température

Par défaut, le client recevra la température en degrés Celsius, si vous commentez la ligne suivante ou la supprimez, il commencera à recevoir la température en degrés Fahrenheit.

//Default Temperature is in Celsius
//Comment the next line for Temperature in Fahrenheit
#define temperatureCelsius

Nom du serveur BLE et UUID

Ensuite, définissez le nom du serveur BLE auquel nous voulons nous connecter et les UUID de service et de caractéristique que nous voulons lire. Laissez le nom du serveur BLE et les UUID par défaut correspondre à ceux définis dans l’esquisse du serveur.

//BLE Server name (the other ESP32 name running the server sketch)
#define bleServerName "BME280_ESP32"

/* UUID's of the service, characteristic that we want to read*/
// BLE Service
static BLEUUID bmeServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59");

// BLE Characteristics
#ifdef temperatureCelsius
  //Temperature Celsius Characteristic
  static BLEUUID temperatureCharacteristicUUID("cba1d466-344c-4be3-ab3f-189f80dd7518");
#else
  //Temperature Fahrenheit Characteristic
  static BLEUUID temperatureCharacteristicUUID("f78ebbff-c8b7-4107-93de-889a6a06d408");
#endif

// Humidity Characteristic
static BLEUUID humidityCharacteristicUUID("ca73b3ba-39f6-4ab3-91ae-186dc9577d99");

Déclarer des variables

Ensuite, vous devez déclarer certaines variables qui seront utilisées plus tard avec Bluetooth pour vérifier si nous sommes connectés au serveur ou non.

//Flags stating if should begin connecting and if the connection is up
static boolean doConnect = false;
static boolean connected = false;

Créez une variable de type BLEAddress qui fait référence à l’adresse du serveur auquel nous voulons nous connecter. Cette adresse sera trouvée lors de la numérisation.

//Address of the peripheral device. Address will be found during scanning...
static BLEAddress *pServerAddress;

Définissez les caractéristiques que nous voulons lire (température et humidité).

//Characteristicd that we want to read
static BLERemoteCharacteristic* temperatureCharacteristic;
static BLERemoteCharacteristic* humidityCharacteristic;

Écran OLED

Vous devez également déclarer certaines variables pour travailler avec l’OLED. Définissez la largeur et la hauteur OLED :

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

Instanciez l’affichage OLED avec la largeur et la hauteur définies précédemment.

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);

Variables de température et d’humidité

Définissez des variables char pour contenir les valeurs de température et d’humidité reçues par le serveur.

//Variables to store temperature and humidity
char* temperatureChar;
char* humidityChar;

Les variables suivantes sont utilisées pour vérifier si de nouvelles lectures de température et d’humidité sont disponibles et s’il est temps de mettre à jour l’affichage OLED.

//Flags to check whether new temperature and humidity readings are available
boolean newTemperature = false;
boolean newHumidity = false;

printLectures()

Nous avons créé une fonction appelée printReadings() qui affiche les relevés de température et d’humidité sur l’écran OLED.

void printReadings(){
  
  display.clearDisplay();  
  // display temperature
  display.setTextSize(1);
  display.setCursor(0,0);
  display.print("Temperature: ");
  display.setTextSize(2);
  display.setCursor(0,10);
  display.print(temperatureChar);
  display.print(" ");
  display.setTextSize(1);
  display.cp437(true);
  display.write(167);
  display.setTextSize(2);
  Serial.print("Temperature:");
  Serial.print(temperatureChar);
  #ifdef temperatureCelsius
    //Temperature Celsius
    display.print("C");
    Serial.print("C");
  #else
    //Temperature Fahrenheit
    display.print("F");
    Serial.print("F");
  #endif

  //display humidity
  display.setTextSize(1);
  display.setCursor(0, 35);
  display.print("Humidity: ");
  display.setTextSize(2);
  display.setCursor(0, 45);
  display.print(humidityChar);
  display.print("%");
  display.display();
  Serial.print(" Humidity:");
  Serial.print(humidityChar); 
  Serial.println("%");
}

Lecture recommandée : Écran OLED ESP32 avec Arduino IDE

installation()

Dans le setup(), lancez l’affichage OLED.

//OLED display setup
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
  Serial.println(F("SSD1306 allocation failed"));
  for(;;); // Don't proceed, loop forever
}

Ensuite, imprimez un message dans la première ligne disant « BME SENSOR ».

display.clearDisplay();
display.setTextSize(2);
display.setTextColor(WHITE,0);
display.setCursor(0,25);
display.print("BLE Client");
display.display();

Démarrez la communication série à un débit en bauds de 115200.

Serial.begin(115200);

Et initialisez le périphérique BLE.

//Init BLE device
BLEDevice::init("");

Analyser les appareils à proximité

Les méthodes suivantes recherchent les appareils à proximité.

// Retrieve a Scanner and set the callback we want to use to be informed when we
// have detected a new device.  Specify that we want active scanning and start the
// scan to run for 30 seconds.
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->start(30);

Fonction MyAdvertisedDeviceCallbacks()

Notez que la fonction MyAdvertisedDeviceCallbacks(), lors de la recherche d’un appareil BLE, vérifie si l’appareil trouvé a le bon nom de serveur BLE. Si c’est le cas, il arrête l’analyse et remplace la variable booléenne doConnect par true. De cette façon, nous savons que nous avons trouvé le serveur que nous recherchons et nous pouvons commencer à établir une connexion.

//Callback function that gets called, when another device's advertisement has been received
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    if (advertisedDevice.getName() == bleServerName) { //Check if the name of the advertiser matches
      advertisedDevice.getScan()->stop(); //Scan can be stopped, we found what we are looking for
      pServerAddress = new BLEAddress(advertisedDevice.getAddress()); //Address of advertiser is the one we need
      doConnect = true; //Set indicator, stating that we are ready to connect
      Serial.println("Device found. Connecting!");
    }
  }
};

Connectez-vous au serveur

Si la variable doConnect est vraie, elle essaie de se connecter au serveur BLE. La fonction connectToServer() gère la connexion entre le client et le serveur.

//Connect to the BLE Server that has the name, Service, and Characteristics
bool connectToServer(BLEAddress pAddress) {
   BLEClient* pClient = BLEDevice::createClient();
 
  // Connect to the remove BLE Server.
  pClient->connect(pAddress);
  Serial.println(" - Connected to server");
 
  // Obtain a reference to the service we are after in the remote BLE server.
  BLERemoteService* pRemoteService = pClient->getService(bmeServiceUUID);
  if (pRemoteService == nullptr) {
    Serial.print("Failed to find our service UUID: ");
    Serial.println(bmeServiceUUID.toString().c_str());
    return (false);
  }
 
  // Obtain a reference to the characteristics in the service of the remote BLE server.
  temperatureCharacteristic = pRemoteService->getCharacteristic(temperatureCharacteristicUUID);
  humidityCharacteristic = pRemoteService->getCharacteristic(humidityCharacteristicUUID);

  if (temperatureCharacteristic == nullptr || humidityCharacteristic == nullptr) {
    Serial.print("Failed to find our characteristic UUID");
    return false;
  }
  Serial.println(" - Found our characteristics");
 
  //Assign callback functions for the Characteristics
  temperatureCharacteristic->registerForNotify(temperatureNotifyCallback);
  humidityCharacteristic->registerForNotify(humidityNotifyCallback);
  return true;
}

Il attribue également une fonction de rappel chargée de gérer ce qui se passe lorsqu’une nouvelle valeur est reçue.

//Assign callback functions for the Characteristics
temperatureCharacteristic->registerForNotify(temperatureNotifyCallback);
humidityCharacteristic->registerForNotify(humidityNotifyCallback);

Une fois le client BLE connecté au serveur, vous devez activer la propriété de notification pour chaque caractéristique. Pour cela, utilisez la méthode writeValue() sur le descripteur.

if (connectToServer(*pServerAddress)) {
  Serial.println("We are now connected to the BLE Server.");
  //Activate the Notify property of each Characteristic
  temperatureCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
  humidityCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);

Notifier les nouvelles valeurs

Lorsque le client reçoit une nouvelle valeur de notification, il appelle ces deux fonctions : temperatureNotifyCallback() et humiditéNotifyCallback() qui sont chargées de récupérer la nouvelle valeur, de mettre à jour l’OLED avec les nouvelles lectures et de les imprimer sur le moniteur série.

//When the BLE Server sends a new temperature reading with the notify property
static void temperatureNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, 
                                        uint8_t* pData, size_t length, bool isNotify) {
  //store temperature value
  temperatureChar = (char*)pData;
  newTemperature = true;
}
//When the BLE Server sends a new humidity reading with the notify property
static void humidityNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, 
                                    uint8_t* pData, size_t length, bool isNotify) {
  //store humidity value
  humidityChar = (char*)pData;
  newHumidity = true;
  Serial.print(newHumidity);
}

Ces deux fonctions précédentes sont exécutées chaque fois que le serveur BLE notifie le client avec une nouvelle valeur, ce qui se produit toutes les 30 secondes. Ces fonctions enregistrent les valeurs reçues sur les variables temperatureChar et humiditéChar. Ceux-ci changent également les variables newTemperature et newHumidity sur true, afin que nous sachions que nous avons reçu de nouvelles lectures.

Afficher les nouvelles lectures de température et d’humidité

Dans la boucle (), il y a une instruction if qui vérifie si de nouvelles lectures sont disponibles. S’il y a de nouvelles lectures, nous définissons les variables newTemperature et newHumidity sur false, afin que nous puissions recevoir de nouvelles lectures plus tard. Ensuite, nous appelons la fonction printReadings() pour afficher les lectures sur l’OLED.

//if new temperature readings are available, print in the OLED
if (newTemperature && newHumidity){
  newTemperature = false;
  newHumidity = false;
  printReadings();
}

Tester le projet

Voilà pour le code. Vous pouvez le télécharger sur votre carte ESP32.

Une fois le code téléchargé. Alimentez le serveur ESP32 BLE, puis alimentez l’ESP32 avec le sketch client. Le client commence à scanner les appareils à proximité et lorsqu’il trouve l’autre ESP32, il établit une connexion Bluetooth. Toutes les 30 secondes, il met à jour l’affichage avec les dernières lectures.

Démonstration de l'affichage OLED du serveur client ESP32 BLE

Important : n’oubliez pas de déconnecter votre smartphone du serveur BLE. Sinon, le client ESP32 BLE ne pourra pas se connecter au serveur.

Client ESP32 BLE connecté au moniteur série du serveur ESP32 BLE

Conclusion

Dans ce tutoriel, vous avez appris à créer un serveur BLE et un client BLE avec l’ESP32. Vous avez appris à définir de nouvelles valeurs de température et d’humidité sur les caractéristiques du serveur BLE. Ensuite, d’autres appareils BLE (clients) peuvent se connecter à ce serveur et lire ces valeurs caractéristiques pour obtenir les dernières valeurs de température et d’humidité. Ces caractéristiques ont la propriété notify, de sorte que le client est averti chaque fois qu’il y a une nouvelle valeur.

L’utilisation de BLE est un autre protocole de communication que vous pouvez utiliser avec les cartes ESP32 en plus du Wi-Fi. Nous espérons que vous avez trouvé ce tutoriel utile. Nous avons des tutoriels pour d’autres protocoles de communication qui pourraient vous être utiles.

En savoir plus sur l’ESP32 avec nos ressources :

Cette vidéo vous emmène dans l’histoire de Raspberry Pi :

YouTube video

  • ARCELI 2 pièces ESP32 Mini Module WiFi, Type-C sans Fil WiFi + Bluetooth Dual Core CPU Carte de développement Internet Compatible avec Arduino
  • Diymore 2 pièces pour ESP32 CAM MB Cartes de développement sans Fil Bluetooth WiFi pour ESP32 Dual-Core avec Module de Carte TF 2640 Camera