Site icon AranaCorp

Communication BLE avec ESP32

Dans ce tutoriel, nous allons apprendre comment activer et gérer le Bluetooth Low Energy(BLE) sur un ESP32 en utilisant le langage de programmation Arduino.

Le Bluetooth Low Energy est une version du bluetooth à faible énergie qui permet d’envoyer des petits paquets de données à intervalle régulier.

Matériels

Environnement et Configuration de l’IDE

Pour programmer votre ESP32 avec l’IDE Arduino, vous pouvez suivre ce tutoriel précédent.

Communication Série via BLE

La communication BLE doit être configuré avec un certains nombre d’adresses (UIID) qui sont comme des registres mémoires dans lesquels nous allons pouvoir lire et/ou écrire. Dans l’exemple, nous définissons un service avec une caractéristique disponible en lecture et en écriture. Nous pourrons donc écrire et lire cette valeur en adressant directement l’uiid CHARACTERISTIC_UUID.

//https://github.com/espressif/arduino-esp32/tree/master/libraries/BLE

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

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"


class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
        Serial.println("*********");
      }
    }
};

void setup() {
  Serial.begin(115200);

  Serial.println("1- Download and install an BLE Terminal FREE");
  Serial.println("2- Scan for BLE devices in the app");
  Serial.println("3- Connect to ESP32BLE");
  Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");

  BLEDevice::init("ESP32BLE");
  BLEServer *pServer = BLEDevice::createServer();

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

  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setCallbacks(new MyCallbacks());

  pCharacteristic->setValue("Hello World");
  pService->start();

  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->start();

  Serial.print("Server address:");
  Serial.println(BLEDevice::getAddress().toString().c_str());
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
}

N.B.: il est possible de récupérer l’adresse MAC de l’ESP32 avec la fonction BLEDevice::getAddress().toString().c_str()

Appairage

Une fois la configuration du module effectuée comme vous le désirez, vous pouvez appairé l’ESP32 avec le système de votre choix comme n’importe quel périphérique Bluetooth. Sélectionnez le nom dans la liste des périphériques détectés (nom ESP32BLE)

Test de la communication BLE à l’aide de BLE Terminal

Nous allons tester la communication BLE à l’aide de l’application BLE Terminal

Le message est bien échangé entre le téléphone et l’ESP32 via Bluetooth LE

Communication bidirectionnel entre appareil et ESP32BLE

Voici le code permettant de modifier la valeur de la caractéristique à l’aide du moniteur série. Ainsi l’appareil connecté et le moniteur série viennent modifier la valeur de la caractéristique.

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

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

BLECharacteristic *pCharacteristic = NULL;

std::string msg;

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
        Serial.println("*********");
      }
    }
};

void setup() {
  Serial.begin(115200);

  Serial.println("1- Download and install an BLE Terminal Free");
  Serial.println("2- Scan for BLE devices in the app");
  Serial.println("3- Connect to ESP32BLE");
  Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");

  BLEDevice::init("ESP32BLE");
  BLEServer *pServer = BLEDevice::createServer();

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

  pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setCallbacks(new MyCallbacks());

  pCharacteristic->setValue("Hello World");
  pService->start();

  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  pAdvertising->start();

  Serial.print("Server address:");
  Serial.println(BLEDevice::getAddress().toString().c_str());
}

void loop() {
  readSerialPort();

  //Send data to slave
  if(msg!=""){
    pCharacteristic->setValue(msg);
    msg="";
  }
  
  delay(2000);
}

void readSerialPort(){
 while (Serial.available()) {
   delay(10);  
   if (Serial.available() >0) {
     char c = Serial.read();  //gets one byte from serial buffer
     msg += c; //add to String
   }
 }
 Serial.flush(); //clean buffer
}

En pratique, il sera certainement préférable d’utiliser un service pour l’écriture et un service pour la lecture.

Communiquer entre ESP32 et Python via BLE

Vous pouvez gérer la communication Bluetooth Low Energy depuis votre PC.

Pour cela installer le paquet Bleak

python -m pip install bleak

Detect bluetooth devices

import asyncio
from bleak import BleakScanner

async def main():
	target_name = "ESP32BLE"
	target_address = None

	devices = await BleakScanner.discover()
	for d in devices:
		print(d)
		if target_name == d.name:
			target_address = d.address
			print("found target {} bluetooth device with address {} ".format(target_name,target_address))
			break
asyncio.run(main())

Output

C1:2E:C6:8E:47:E8: None
3C:61:05:31:5F:12: ESP32BLE
found target ESP32BLE bluetooth device with address 3C:61:05:31:5F:12

Se connecter et communiquer avec l’ESP32

Voici un script Python pour se connecter automatiquement à l’appareil BLE ESP32 depuis un PC. Pour communiquer avec l’appareil BLE, il est important de connaître les uiid des différents services. Nous définissons les UUID comme ceux définis dans le code de l’ESP32

import asyncio
from bleak import BleakScanner
from bleak import BleakClient

async def main():
	target_name = "ESP32BLE"
	target_address = None

	SERVICE_UUID=        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
	CHARACTERISTIC_UUID= "beb5483e-36e1-4688-b7f5-ea07361b26a8"

	devices = await BleakScanner.discover()
	for d in devices:
		print(d)
		if target_name == d.name:
			target_address = d.address
			print("found target {} bluetooth device with address {} ".format(target_name,target_address))
			break

	if target_address is not None:		
		async with BleakClient(target_address) as client:
			print(f"Connected: {client.is_connected}")
				
			while 1:
				text = input()
				if text == "quit":
					break

				await client.write_gatt_char(CHARACTERISTIC_UUID, bytes(text, 'UTF-8'), response=True)
				
				try:
					data = await client.read_gatt_char(CHARACTERISTIC_UUID)
					data = data.decode('utf-8') #convert byte to str
					print("data: {}".format(data))
				except Exception:
					pass
				
			
	else:
		print("could not find target bluetooth device nearby")


asyncio.run(main())

Créer des caractéristiques, descripteurs et services

Le principe du BLE repose sur l’envoie de petits paquets de données contrairement au Bluetooth classique qui permet l’envoie de données en continu. En ce sens, vous pouvez créer des services et/ou des caractéristiques différentes en fonctions des données échangées. Si, par exemple, vous avez plusieurs capteurs branchés à l’ESP32, vous pouvez créer une caractéristique par capteur.

Chaque caractéristique est identifiée par UUID (16 bytes Universally Unique IDentifier) et gérée par un service.

Chaque service peut gérer par défaut 7 caractéristiques (15 handles au total, deux pour une caractéristique et un pour un descripteur)

Il est possible d’augmenter ce nombre à la création du service

BLEService* pService = pServer->createService(BLEUUID(SERVICE_UUID), 32); // 32 available handles

Nous créons donc les variables et fonctions nécessaires pour la gestions de 3 caractéristiques supplémentaires

Pour définir les UUD, nous utilisons le site uuid-generator

/*
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp
    Ported to Arduino ESP32 by Evandro Copercini
*/

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

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

#define POINTS_UUID "ff0a1ece-8a3a-4dc8-bfb8-eae1371dc3fb"
#define IP_UUID "ff673592-0b83-4151-a871-7d222c9dab3c"
#define EEPROM_UUID "5056c1b6-f123-4ade-aa77-9b7f44c717eb"

BLECharacteristic *pCharacteristic = NULL;
BLECharacteristic* pPoints = NULL;
BLECharacteristic* pIP = NULL;
BLECharacteristic* pEeprom = NULL;

std::string msg;
bool deviceConnected = false;
bool oldDeviceConnected = false;

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.print("Charac value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
      }
    }

};
class PtsCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pPoints) {
      std::string value = pPoints->getValue();

      if (value.length() > 0) {
        Serial.print("Points value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
      }
    }    

};
class IpCallbacks: public BLECharacteristicCallbacks {   
    void onWrite(BLECharacteristic *pIP) {
      std::string value = pIP->getValue();

      if (value.length() > 0) {
        Serial.print("IP value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
      }
    }    

};
class EeCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pEeprom) {
      std::string value = pEeprom->getValue();

      if (value.length() > 0) {
        Serial.print("EEPROM value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
      }
    }    
    

};

class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    Serial.println("Client connected");
  }
  void onDisconnect(BLEServer* pServer) {
    Serial.println("Client disconnected");
    BLEDevice::startAdvertising(); // needed for reconnection
  }
};

void setup() {
  Serial.begin(115200);

  Serial.println("ESP32BLE server ready");

  BLEDevice::init("ESP32BLE");
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  
  BLEService *pService = pServer->createService(SERVICE_UUID);

  //create BLE characteristics
  pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE
                                       );
  pCharacteristic->setCallbacks(new MyCallbacks());
  pCharacteristic->setValue("Hello World");

  
    // Create a BLE Characteristic
  pPoints = pService->createCharacteristic(
    POINTS_UUID,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
  pPoints->setCallbacks(new PtsCallbacks());
  pPoints->setValue("0");
  pIP = pService->createCharacteristic(
    IP_UUID,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
  pIP->setCallbacks(new IpCallbacks());
  pIP->setValue("0");
  pEeprom = pService->createCharacteristic(
    EEPROM_UUID,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
  pEeprom->setCallbacks(new EeCallbacks());
  pEeprom->setValue("0");

  //start servers
  pService->start();

  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  pAdvertising->start();

  Serial.print("Server address:");
  Serial.println(BLEDevice::getAddress().toString().c_str());
}

void loop() {
  readSerialPort();

  //Send data to slave
  if(msg!=""){
    pCharacteristic->setValue(msg);
    msg="";
  }
}

void readSerialPort(){
 while (Serial.available()) {
   delay(10);  
   if (Serial.available() >0) {
     char c = Serial.read();  //gets one byte from serial buffer
     msg += c; //add to String
   }
 }
 Serial.flush(); //clean buffer
}

N.B.: lorsque vous modifier les services et caractéristiques de l’appareil, vous devez l’appairer de nouveau

13:02:48.359 -> ESP32BLE server ready
13:02:49.049 -> Server address:3c:61:05:31:5f:12
13:02:53.967 -> Client connected
13:02:59.679 -> Client disconnected
13:03:16.293 -> Client connected
13:08:15.321 -> Points value: myPoints
13:08:28.964 -> Client disconnected
13:08:34.952 -> Client connected
13:08:48.883 -> IP value: myipaddress
13:08:53.353 -> Client disconnected
13:08:58.916 -> Client connected
13:09:07.574 -> EEPROM value: myeepromval

Application

Sources

Quitter la version mobile