fbpixel
Etiquetas: , , ,

Neste tutorial, aprenderemos a gerenciar e testar a comunicação BLE (Bluetooth Low Energy) em um ESP32 usando MicroPython.

Equipamento

  • Um módulo ESP32
  • Um computador com Python instalado
  • Cabo USB para ligação ao computador ESP32
  • Um dispositivo Android

Configuração do ambiente e do IDE

Para comunicar e programar o seu ESP32 em Python, pode seguir este tutorial anterior para utilizar o MicroPython.

Também pode instalar a aplicação bluetoothbleterminal.free&hl=fr&gl=US’>Terminal BLE no seu telemóvel Android para testar a comunicação BLE.

Ativação do Bluetooth LE

Para ativar o BLE no seu EPS32, copie o seguinte código micropython para o editor do Thonny IDE e exporte-o para o seu módulo. Neste exemplo, utilizamos a biblioteca bluetooth (outra opção 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.: é possível obter o endereço MAC em formato hexadecimal utilizando a função hex() ou a biblioteca ubinascii.

Uma vez carregado o código e ativado o anúncio, pode encontrar o dispositivo na aplicação Terminal BLE.

esp32-ble-device-advertised Gerenciando BLE em um ESP32 com MicroPython

IMPORTANTE: o aviso deve ser ativado após cada desconexão para voltar a ligar o ESP32.

Registo de serviços

A comunicação BLE baseia-se no conceito de serviços e características com determinados direitos de acesso (leitura, escrita, notificação). Existem serviços predefinidos (serviço Nordic UART (NUS), ritmo cardíaco (HR)) ou pode criar os seus próprios serviços com identificadores UUID únicos.

Um serviço tem um UUID único e pode conter diferentes características. Cada caraterística é definida por um UUID único e diferentes direitos de acesso.

  • A função bluetooth.UUID é utilizada para definir os endereços dos serviços e funcionalidades.
  • bluetooth.FLAG_READ dá acesso de leitura ao cliente
  • bluetooth.FLAG_NOTIFY notifica o cliente sem qualquer ação da sua parte
  • bluetooth.FLAG_WRITE dá acesso de escrita ao cliente
    #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.: a publicidade deve ser interrompida antes do registo dos serviços.

esp32-ble-custom-services Gerenciando BLE em um ESP32 com MicroPython

Definição de funções de eventos

Para gerir corretamente o módulo BLE, vamos criar funções de retorno de chamada para detetar e atuar em vários eventos

Para tal, é necessário conhecer os diferentes identificadores de eventos

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)

 

Podemos então utilizar isto para definir as acções para cada evento

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

 

Resultados

Com este código simples, pode ligar-se ao dispositivo e, em seguida, ler e escrever no serviço escolhido na aplicação

esp32-ble-read-write Gerenciando BLE em um ESP32 com 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)

 

Criação de uma classe MicroPython para gerir a comunicação BLE do ESP32

Vale a pena criar uma classe para gerir a comunicação BLE que possa ser reutilizada em diferentes projectos. No código da classe, encontrará todos os elementos descritos acima

  • Inicialização e ativação BLE
  • definição das funções de retorno de chamada self.ble_irq
  • registo de serviços self.register
  • anúncio self.advertise
  • Também adicionámos uma função de notificação para atualizar o valor de um sensor 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 Gerenciando BLE em um ESP32 com 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)

 

Código completo de gestão do ESP32 BLE com 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()

 

Fontes