fbpixel
Étiquettes : , , ,

Dans ce tutoriel, nous allons apprendre comment gérer et tester la communication BLE (Bluetooth Low Energy) sur un ESP32 avec MicroPython.

Matériels

  • Un module ESP32
  • Un ordinateur avec Python installé
  • Câble USB pour la connexion ESP32-ordinateur
  • Un appareil Android

Environnement et Configuration de l’IDE

Pour communiquer et programmer en Python votre ESP32, vous pouvez suivre ce tutoriel précédent pour utiliser MicroPython.

Vous pouvez également installer l’application BLE Terminal sur votre téléphone Android pour tester la communication BLE

Activation du Bluetooth LE

Pour activer le BLE de votre EPS32, copier le code micropython suivant dans l’éditeur Thonny IDE et exportez le sur votre module. Dans cet exemple, nous utilisons la librairie bluetooth (autre option ubluetooth)

import bluetooth #https://docs.micropython.org/en/latest/library/bluetooth.html
import ubinascii

def main():
    BLE = bluetooth.BLE()
    BLE.active(True)

    #Advertise
    name = bytes("ESP32BLEmPy", 'UTF-8')
    adv_data = bytearray(b'\x02\x01\x02') + bytearray((len(name) + 1, 0x09)) + name
    BLE.gap_advertise(100, adv_data)

    #get MAC address
    mac = BLE.config('mac')[1]
    print("device MAC address is: "+ubinascii.hexlify(mac).decode())
    #print("device MAC address is: "+mac.hex())



if __name__ == "__main__":
    main()

N.B.: il est possible de récupérer l’adresse MAC au format hexadécimal en utilisant la fonction hex() ou la librairie ubinascii

Une fois le code chargé et l’annonce activé, vous pouvez retrouver l’appareil dans l’application BLE Terminal.

esp32-ble-device-advertised Gestion du BLE sur un ESP32 avec MicroPython

IMPORTANT: l’annonciation doit être activée après chaque déconnexion pour pouvoir reconnecter l’ESP32

Enregistrer des services

La communication BLE passe par le concept de services et caractéristiques avec certain droit d’accès (read, write, notify). Il existe des services par défaut (Nordic UART service (NUS), Heart rate (HR) ou vous pouvez créer vos propres services avec des identifiants uniques UUID.

Un service a un UUID unique et peut contenir différentes caractéristiques. Chaque caractéristique est définie par un UUID unique et différents droits d’accès.

  • La fonction bluetooth.UUID permet de définir les adresses des services et caractéristiques
  • bluetooth.FLAG_READ donne un accès en lecture au client
  • bluetooth.FLAG_NOTIFY permet de notifier le client sans action de sa part
  • bluetooth.FLAG_WRITE donne un accès en écriture au client

    #register
    HR_UUID = bluetooth.UUID(0x180D)
    HR_CHAR = (bluetooth.UUID(0x2A37), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
    HR_SERVICE = (HR_UUID, (HR_CHAR,),)
    UART_UUID = bluetooth.UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E')
    UART_TX = (bluetooth.UUID('6E400003-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
    UART_RX = (bluetooth.UUID('6E400002-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_WRITE,)
    UART_SERVICE = (UART_UUID, (UART_TX, UART_RX,),)
    
    MY_UUID = bluetooth.UUID("0bd62591-0b10-431a-982e-bd136821f35b")
    SEN_CHAR = (bluetooth.UUID("0bd62592-0b10-431a-982e-bd136821f35b"), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
    CMD_CHAR = (bluetooth.UUID("0bd62593-0b10-431a-982e-bd136821f35b"), bluetooth.FLAG_WRITE,)
    MY_SERVICE = (MY_UUID, (SEN_CHAR, CMD_CHAR,),)
    
    SERVICES = (HR_SERVICE, UART_SERVICE, MY_SERVICE)
    ( (hr,), (tx, rx,), (sen,cmd,), ) = BLE.gatts_register_services(SERVICES)
    
    BLE.gatts_write(sen, str(43.256), False) #init val to be read

N.B: l’annonciation (advertising) doit être stopper avant d’enregistrer des services

esp32-ble-custom-services Gestion du BLE sur un ESP32 avec MicroPython

Définir les fonctions évènement

Pour gérer correctement le module BLE, nous allons créer des fonctions callback pour détecter et agir en fonction des différents évènements

Pour cela, nous avons besoin de connaitre les différents identifiant des évènements

from micropython import const

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)
_IRQ_GATTS_READ_REQUEST = const(4)
_IRQ_SCAN_RESULT = const(5)
_IRQ_SCAN_DONE = const(6)
_IRQ_PERIPHERAL_CONNECT = const(7)
_IRQ_PERIPHERAL_DISCONNECT = const(8)
_IRQ_GATTC_SERVICE_RESULT = const(9)
_IRQ_GATTC_SERVICE_DONE = const(10)
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11)
_IRQ_GATTC_CHARACTERISTIC_DONE = const(12)
_IRQ_GATTC_DESCRIPTOR_RESULT = const(13)
_IRQ_GATTC_DESCRIPTOR_DONE = const(14)
_IRQ_GATTC_READ_RESULT = const(15)
_IRQ_GATTC_READ_DONE = const(16)
_IRQ_GATTC_WRITE_DONE = const(17)
_IRQ_GATTC_NOTIFY = const(18)
_IRQ_GATTC_INDICATE = const(19)
_IRQ_GATTS_INDICATE_DONE = const(20)
_IRQ_MTU_EXCHANGED = const(21)
_IRQ_L2CAP_ACCEPT = const(22)
_IRQ_L2CAP_CONNECT = const(23)
_IRQ_L2CAP_DISCONNECT = const(24)
_IRQ_L2CAP_RECV = const(25)
_IRQ_L2CAP_SEND_READY = const(26)
_IRQ_CONNECTION_UPDATE = const(27)
_IRQ_ENCRYPTION_UPDATE = const(28)
_IRQ_GET_SECRET = const(29)
_IRQ_SET_SECRET = const(30)

Nous pouvons ensuite utiliser définir les actions pour chaque évènement

#event callback function
def ble_irq(event,data):
    if event == _IRQ_CENTRAL_CONNECT:
        # A central has connected to this peripheral.
        conn_handle, addr_type, addr = data
        print("BLE device connected successfully")
    elif event == _IRQ_CENTRAL_DISCONNECT:
        # A central has disconnected from this peripheral.
        conn_handle, addr_type, addr = data
        print("BLE device disconnected")
        adv_data = bytearray(b'\x02\x01\x02') + bytearray((len(name) + 1, 0x09)) + name
        BLE.gap_advertise(100, adv_data)
        
    elif event == _IRQ_GATTS_WRITE:
        # A client has written to this characteristic or descriptor.
        conn_handle, attr_handle = data
        print("write event: ",BLE.gatts_read(data[1]).decode('UTF-8').strip())
    elif event == _IRQ_GATTS_READ_REQUEST:
        # A client has issued a read. Note: this is only supported on STM32.
        # Return a non-zero integer to deny the read (see below), or zero (or None)
        # to accept the read.
        conn_handle, attr_handle = data
        print("read event: ",data)

Résultat

Avec ce code simple, vous pouvez vous connecter à l’appareil puis lire et écrire sur le service choisi dans l’application

esp32-ble-read-write Gestion du BLE sur un ESP32 avec MicroPython
MicroPython v1.22.1 on 2024-01-05; Generic ESP32 module with ESP32

Type "help()" for more information.
>>> %Run -c $EDITOR_CONTENT
device MAC address is: 3c6105315f12
>>> BLE device connected successfully
BLE device disconnected
BLE device connected successfully
write event:  hello
read event:  (0, 31)

Création d’une classe MicroPython pour gérer la communication BLE ESP32

Il est intéressant de créer une classe pour gérer la communication BLE que vous pourrez réutiliser dans différents projets. Dans le code de la classe, vous retrouvez tous les éléments décrit plus haut

  • initialisation et activation du BLE
  • définition des fonctions callback self.ble_irq
  • enregistrement des services self.register
  • annonciation self.advertise
  • Nous avons également rajouté une fonction de notification pour mettre à jour la valeur d’un capteur self.set_sensor
class ESP32BLE():
    def __init__(self, name):
        # Create BLE device management
        self.name = name
        self.ble = bluetooth.BLE()
        self.ble.active(True)
        #get MAC address
        mac = self.ble.config('mac')[1]
        print("device MAC address is: "+mac.hex())
        self.ble.irq(self.ble_irq)
        self.connections = set()
        self.register()
        self.advertise()

    def ble_irq(self, event, data):
        #define event callback functions
        if event == _IRQ_CENTRAL_CONNECT:
            # A central has connected to this peripheral.
            conn_handle, addr_type, addr = data
            self.connections.add(conn_handle)
            print("BLE device connected successfully")
        elif event == _IRQ_CENTRAL_DISCONNECT:
            # A central has disconnected from this peripheral.
            conn_handle, addr_type, addr = data
            self.connections.remove(conn_handle)
            print("BLE device disconnected")
            self.advertise()
        elif event == _IRQ_GATTS_WRITE:
            # A client has written to this characteristic or descriptor.
            conn_handle, attr_handle = data
            print("write event: ",self.ble.gatts_read(data[1]).decode('UTF-8').strip())
        elif event == _IRQ_GATTS_READ_REQUEST:
            # A client has issued a read. Note: this is only supported on STM32.
            # Return a non-zero integer to deny the read (see below), or zero (or None)
            # to accept the read.
            conn_handle, attr_handle = data
            print("read event: ",data)
            
    def register(self):
        #define services and characteristics
        HR_UUID = bluetooth.UUID(0x180D)
        HR_CHAR = (bluetooth.UUID(0x2A37), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
        HR_SERVICE = (HR_UUID, (HR_CHAR,),)
        UART_UUID = bluetooth.UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E')
        UART_TX = (bluetooth.UUID('6E400003-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
        UART_RX = (bluetooth.UUID('6E400002-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_WRITE,)
        UART_SERVICE = (UART_UUID, (UART_TX, UART_RX,),)
        
        MY_UUID = bluetooth.UUID("0bd62591-0b10-431a-982e-bd136821f35b")
        SEN_CHAR = (bluetooth.UUID("0bd62592-0b10-431a-982e-bd136821f35b"), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
        CMD_CHAR = (bluetooth.UUID("0bd62593-0b10-431a-982e-bd136821f35b"), bluetooth.FLAG_WRITE,)
        MY_SERVICE = (MY_UUID, (SEN_CHAR, CMD_CHAR,),)
        
        SERVICES = (HR_SERVICE, UART_SERVICE, MY_SERVICE)
        ( (self.hr,), (self.tx, self.rx,), (self.sen,self.cmd,), ) = self.ble.gatts_register_services(SERVICES)
        
        self.ble.gatts_write(self.sen, str(43.256), False)

    
    def advertise(self):
        #advertise BLE module with name
        name = bytes(self.name, 'UTF-8')
        adv_data = bytearray(b'\x02\x01\x02') + bytearray((len(name) + 1, 0x09)) + name
        self.ble.gap_advertise(100, adv_data)
        
    def set_sensor(self, data, notify=False):
        # Data is sint16 with a resolution of 0.01.
        self.ble.gatts_write(self.sen, str(data))
        if notify:
            for conn_handle in self.connections:
                # Notify connected centrals to issue a read.
                self.ble.gatts_notify(conn_handle, self.sen)

esp32-ble-write-notify Gestion du BLE sur un ESP32 avec MicroPython
BLE device disconnected
BLE device connected successfully
write event:  hello
read event:  (65535, 31)
read event:  (0, 31)
read event:  (65535, 31)
read event:  (0, 31)
read event:  (65535, 31)
read event:  (0, 31)
write event:  hello World
read event:  (65535, 31)
read event:  (0, 31)
read event:  (65535, 31)
read event:  (0, 31)

Code complet de gestion du BLE ESP32 avec micropython

import bluetooth #https://docs.micropython.org/en/latest/library/bluetooth.html
import random
import time
from micropython import const

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)
_IRQ_GATTS_READ_REQUEST = const(4)
_IRQ_SCAN_RESULT = const(5)
_IRQ_SCAN_DONE = const(6)
_IRQ_PERIPHERAL_CONNECT = const(7)
_IRQ_PERIPHERAL_DISCONNECT = const(8)
_IRQ_GATTC_SERVICE_RESULT = const(9)
_IRQ_GATTC_SERVICE_DONE = const(10)
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11)
_IRQ_GATTC_CHARACTERISTIC_DONE = const(12)
_IRQ_GATTC_DESCRIPTOR_RESULT = const(13)
_IRQ_GATTC_DESCRIPTOR_DONE = const(14)
_IRQ_GATTC_READ_RESULT = const(15)
_IRQ_GATTC_READ_DONE = const(16)
_IRQ_GATTC_WRITE_DONE = const(17)
_IRQ_GATTC_NOTIFY = const(18)
_IRQ_GATTC_INDICATE = const(19)
_IRQ_GATTS_INDICATE_DONE = const(20)
_IRQ_MTU_EXCHANGED = const(21)
_IRQ_L2CAP_ACCEPT = const(22)
_IRQ_L2CAP_CONNECT = const(23)
_IRQ_L2CAP_DISCONNECT = const(24)
_IRQ_L2CAP_RECV = const(25)
_IRQ_L2CAP_SEND_READY = const(26)
_IRQ_CONNECTION_UPDATE = const(27)
_IRQ_ENCRYPTION_UPDATE = const(28)
_IRQ_GET_SECRET = const(29)
_IRQ_SET_SECRET = const(30)


class ESP32BLE():
    def __init__(self, name):
        # Create BLE device management
        self.name = name
        self.ble = bluetooth.BLE()
        self.ble.active(True)
        #get MAC address
        mac = self.ble.config('mac')[1]
        print("device MAC address is: "+mac.hex())
        self.ble.irq(self.ble_irq)
        self.connections = set()
        self.register()
        self.advertise()

    def ble_irq(self, event, data):
        #define event callback functions
        if event == _IRQ_CENTRAL_CONNECT:
            # A central has connected to this peripheral.
            conn_handle, addr_type, addr = data
            self.connections.add(conn_handle)
            print("BLE device connected successfully")
        elif event == _IRQ_CENTRAL_DISCONNECT:
            # A central has disconnected from this peripheral.
            conn_handle, addr_type, addr = data
            self.connections.remove(conn_handle)
            print("BLE device disconnected")
            self.advertise()
        elif event == _IRQ_GATTS_WRITE:
            # A client has written to this characteristic or descriptor.
            conn_handle, attr_handle = data
            print("write event: ",self.ble.gatts_read(data[1]).decode('UTF-8').strip())
        elif event == _IRQ_GATTS_READ_REQUEST:
            # A client has issued a read. Note: this is only supported on STM32.
            # Return a non-zero integer to deny the read (see below), or zero (or None)
            # to accept the read.
            conn_handle, attr_handle = data
            print("read event: ",data)
            
    def register(self):
        #define services and characteristics
        HR_UUID = bluetooth.UUID(0x180D)
        HR_CHAR = (bluetooth.UUID(0x2A37), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
        HR_SERVICE = (HR_UUID, (HR_CHAR,),)
        UART_UUID = bluetooth.UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E')
        UART_TX = (bluetooth.UUID('6E400003-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
        UART_RX = (bluetooth.UUID('6E400002-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_WRITE,)
        UART_SERVICE = (UART_UUID, (UART_TX, UART_RX,),)
        
        MY_UUID = bluetooth.UUID("0bd62591-0b10-431a-982e-bd136821f35b")
        SEN_CHAR = (bluetooth.UUID("0bd62592-0b10-431a-982e-bd136821f35b"), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
        CMD_CHAR = (bluetooth.UUID("0bd62593-0b10-431a-982e-bd136821f35b"), bluetooth.FLAG_WRITE,)
        MY_SERVICE = (MY_UUID, (SEN_CHAR, CMD_CHAR,),)
        
        SERVICES = (HR_SERVICE, UART_SERVICE, MY_SERVICE)
        ( (self.hr,), (self.tx, self.rx,), (self.sen,self.cmd,), ) = self.ble.gatts_register_services(SERVICES)
        
        self.ble.gatts_write(self.sen, str(43.256), False)

    
    def advertise(self):
        #advertise BLE module with name
        name = bytes(self.name, 'UTF-8')
        adv_data = bytearray(b'\x02\x01\x02') + bytearray((len(name) + 1, 0x09)) + name
        self.ble.gap_advertise(100, adv_data)
        
    def set_sensor(self, data, notify=False):
        # Data is sint16 with a resolution of 0.01.
        self.ble.gatts_write(self.sen, str(data))
        if notify:
            for conn_handle in self.connections:
                # Notify connected centrals to issue a read.
                self.ble.gatts_notify(conn_handle, self.sen)
                
def main():
    BLE = ESP32BLE("ESP32BLEmPy")

    #simulate sensor
    sensorVal=43.256
    i=0
    while True:
        # Write every second, notify every 10 seconds.
        i = (i + 1) % 10
        BLE.set_sensor(sensorVal, notify=i == 0)
        # Random walk the temperature.
        sensorVal += random.uniform(-1.5, 1.5)
        time.sleep_ms(1000)


if __name__ == "__main__":
    main()

Sources