En este post, exploraremos el proceso de establecer una comunicación inalámbrica entre un sensor IMU basado en Arduino y una aplicación Python utilizando Bluetooth Low Energy (BLE). El uso de BLE nos permite transferir datos inalámbricamente entre el sensor y la computadora, facilitando la recopilación, almacenamiento y análisis de los datos del sensor IMU.
Comenzaremos configurando el sensor IMU con Arduino, seguido de una guía paso a paso para conectarlo a una aplicación Python utilizando la biblioteca Bleak. El código de Arduino es la base de la comunicación inalámbrica, y establece el sensor IMU, declara el servicio BLE y las características, y envía continuamente los datos de acelerómetro y giroscopio al dispositivo central BLE conectado.
Para hacer el proceso lo más sencillo posible, utilizaremos el Arduino Nano BLE 33 Sense para esta demostración. Esta placa combina la funcionalidad del Arduino Nano con un módulo BLE, haciéndolo una opción ideal para proyectos que requieren tanto un microcontrolador como conectividad inalámbrica.
Al final de este post, tendrás una comprensión mejorada de cómo interfacear los sensores IMU con Python y usar BLE para la transferencia de datos inalámbricos. Además, tendrás un ejemplo funcionante que podrás usar como punto de partida para tus propios proyectos. Dicho esto, empecemos.
Arduino code
En esta sección, demostraré cómo configurar un sistema donde los datos de aceleración y giroscopio leídos por el Arduino se transmitan rápidamente a un script en Python.
En el código de Arduino, el primer paso es incluir las librerías necesarias, que son las bibliotecas ArduinoBLE.h y Arduino_LSM9DS1.h. La librería ArduinoBLE se utiliza para manejar la comunicación BLE, mientras que la librería Arduino_LSM9DS1 se utiliza para interactuar con el sensor IMU.
A continuación, el código declara un servicio BLE con un UUID único de «1001». Este servicio actúa como un contenedor para las dos características BLE, que son los datos de aceleración y giroscopio. Las características se declaran utilizando la clase BLECharacteristic y se asignan UUIDs de «2001» y «2011» respectivamente. Estos UUID se utilizan para identificar las características en el periférico BLE.
En la función setup(), el código inicializa el módulo BLE y el sensor IMU. Si el módulo BLE o el sensor IMU no se inicializan correctamente, se entra en un bucle infinito. El código luego establece el nombre de dispositivo y el nombre local como «IMU», agrega el servicio al módulo BLE y establece el intervalo de conexión y el estado conectable del módulo BLE. Finalmente, el módulo BLE comienza a anunciar la conexión BLE.
En la función loop(), el código envía continuamente los datos de aceleración y giroscopio al dispositivo central BLE conectado. El código obtiene primero el dispositivo central BLE conectado, y si lo hay, entra en un bucle infinito. Dentro del bucle, el código lee los datos de aceleración y giroscopio del sensor IMU, crea cadenas con los datos y escribe las cadenas en las características BLE correspondientes. El bucle luego espera 7 milisegundos antes de enviar los siguientes datos.
Aquí está el código para el Arduino Nano BLE 33 Sense:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
#include <ArduinoBLE.h> #include <Arduino_LSM9DS1.h> // Declare a BLE service with UUID "1001" BLEService IMUservice("1001"); // Declare two BLE characteristics for the accelerometer and gyroscope data, both with UUIDs "2001" and "2011" respectively. // The characteristics are readable and notify, and have a maximum data length of 20 bytes. BLECharacteristic accelData("2001", BLERead | BLENotify, 20); BLECharacteristic gyroData("2011", BLERead | BLENotify, 20); void setup() { // Initialize the BLE module if (!BLE.begin()) { // If the BLE module fails to initialize, enter an infinite loop while (1) { } } // Initialize the IMU if (!IMU.begin()) { // If the IMU fails to initialize, enter an infinite loop while (1) { } } // Set the device name and local name to "IMU" BLE.setDeviceName("IMU"); BLE.setLocalName("IMU"); // Add the service to the BLE module BLE.setAdvertisedService(IMUservice); // Add the two characteristics to the service IMUservice.addCharacteristic(accelData); IMUservice.addCharacteristic(gyroData); // Add the service to the BLE module BLE.addService(IMUservice); // Set the connection interval for the BLE connection BLE.setConnectionInterval(8, 8); // Enable the BLE module to be connectable BLE.setConnectable(true); // Start advertising the BLE connection BLE.advertise(); } void loop() { float acX, acY, acZ, gX, gY, gZ; // Get the connected BLE central device BLEDevice central = BLE.central(); if (central) { // If there is a connected BLE central device, enter an infinite loop while (central.connected()) { // Read the accelerometer data from the IMU device IMU.readAcceleration(acX, acY, acZ); // Create a string with the accelerometer data String accelString = String(acX) + "," + String(acY) + "," + String(acZ); // Write the accelerometer data to the BLE characteristic accelData.writeValue(accelString.c_str()); // Read the gyroscope data from the IMU device IMU.readGyroscope(gX, gY, gZ); // Create a string with the gyroscope data String gyroString = String(gX) + "," + String(gY) + "," + String(gZ); // Write the gyroscope data to the BLE characteristic gyroData.writeValue(gyroString.c_str()); // Wait 7 milliseconds before sending the next data delay(7); } } } |
Este código está disponible en nuestro repositorio de Github. La tasa de muestreo máxima del acelerómetro y el giroscopio en el Arduino Nano BLE 33 Sense es de 104 Hz. Esto significa que el dispositivo puede enviar datos de acelerómetro y giroscopio hasta 104 veces por segundo al dispositivo BLE conectado.
Al enviar los datos lo más rápido posible, la aplicación de Python puede recibir datos de sensores en tiempo real, lo que facilita la analítica y el procesamiento de la información en tiempo real. Esta alta tasa de muestreo hace que el Arduino Nano BLE 33 Sense sea un dispositivo ideal para una amplia gama de aplicaciones que requieren datos rápidos y confiables del IMU.
Scanner BLE en Python
En esta sección, nos adentraremos en el código de Python y veremos cómo se conecta al sensor IMU utilizando el protocolo BLE. Utilizaremos la librería Bleak para manejar la comunicación BLE, y el código se escribirá en Python 3.
Para comenzar, escaneará los dispositivos BLE disponibles y asegurará que el sensor IMU esté disponible. Una vez que se haya detectado el sensor IMU, usaremos su dirección para establecer una conexión. Aquí hay un código para eso (también disponible en Github):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import asyncio from bleak import discover # Asynchronous function to scan for BLE devices async def scan(): # Use the discover function from the bleak library to scan for devices devices = await discover() # Loop through the list of discovered devices for d in devices: # Print the information for each device print(d) # Get the event loop loop = asyncio.get_event_loop() # Run the asynchronous function to scan for devices loop.run_until_complete(scan()) |
Este código utiliza la función discover de la librería Bleak para buscar dispositivos BLE. La función de discover devuelve una lista de objetos de dispositivos, que contienen información sobre cada dispositivo, incluyendo el nombre, la dirección y el RSSI (indicador de fuerza de señal recibida) del dispositivo.
En la función de scan, el código recorre la lista de dispositivos descubiertos e imprime la información de cada dispositivo usando la función print. Finalmente, el código obtiene el bucle de eventos y ejecuta la función de scan usando loop.run_until_complete.
Al usar este código, puedes verificar fácilmente si tu dispositivo Arduino está disponible y obtener su dirección, lo que es esencial para establecer una conexión BLE entre la aplicación Python y el Arduino. Cuando ejecuto este script, obtengo una salida como esta:
IMU es el nombre del dispositivo que especificamos en el código de Arduino (BLE.setDeviceName(«IMU»)). Necesitaremos la dirección de nuestro dispositivo para crear un canal inalámbrico entre el Arduino y Python.
BLE es una tecnología ampliamente utilizada, y como resultado, muchos dispositivos BLE pueden ser detectados en cualquier ubicación dada. Dependiendo del entorno, no es raro detectar unos pocos o incluso docenas de dispositivos BLE cercanos.
Comunicación inalámbrica en tiempo real entre Arduino y Python con BLE
Ahora que tenemos la dirección BLE del Arduino Nano BLE 33 Sense, podemos iniciar el proceso de comunicación entre el sensor IMU y la aplicación de Python.
En esta sección, revisaremos el código de Python que utiliza la biblioteca Bleak para conectarse al periférico BLE y recibir datos en tiempo real del acelerómetro y giroscopio del sensor IMU.
El código utiliza dos funciones, handle_accel_notification y handle_gyro_notification, para manejar los datos del acelerómetro y giroscopio recibidos como notificaciones. Luego, los datos se procesan y se almacenan en un archivo JSON para su posterior análisis.
También usaremos una función asíncrona, run, para conectarse al periférico BLE y comenzar a recibir las notificaciones de los datos del acelerómetro y giroscopio. La función run continuará ejecutando el bucle para recibir datos en tiempo real del sensor IMU.
Aquí tenemos el código en Python (también disponible en Github):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
import asyncio, time, json from bleak import BleakClient # Function to handle accelerometer data received as a notification def handle_accel_notification(sender, data): # Split the data into separate x, y, z values x, y, z = data.decode().split(',') acX = float(x) acY = float(y) acZ = float(z) # Get the current time timestamp = time.time() # Create a dictionary with the accelerometer data accel_data = {"acc_x": acX, "acc_y": acY, "acc_z": acZ, "Timestamp": timestamp} # Write the accelerometer data to a JSON file with open("data.json", "a") as json_file: json.dump(accel_data, json_file) json_file.write(',\n') # Function to handle gyroscope data received as a notification def handle_gyro_notification(sender, data): # Split the data into separate x, y, z values gx, gy, gz = data.decode().split(',') gX = float(gx) gY = float(gy) gZ = float(gz) # Get the current time timestamp = time.time() # Create a dictionary with the gyroscope data gyro_data = {"gy_x": gX, "gy_y": gY, "gy_z": gZ, "Timestamp": timestamp} # Write the gyroscope data to a JSON file with open("data.json", "a") as json_file: json.dump(gyro_data, json_file) json_file.write(',\n') # Asynchronous function to connect to the BLE peripheral async def run(address, loop): async with BleakClient(address, loop=loop) as client: # Start receiving notifications for the accelerometer data await client.start_notify("00002001-0000-1000-8000-00805f9b34fb", handle_accel_notification) # Start receiving notifications for the gyroscope data await client.start_notify("00002011-0000-1000-8000-00805f9b34fb", handle_gyro_notification) # Continuously run the loop while True: await asyncio.sleep(0.001) # Address of the BLE peripheral to connect to address = "00:00:00:00:00:00" # Get the event loop loop = asyncio.get_event_loop() # Run the asynchronous function loop.run_until_complete(run(address, loop)) |
En este texto se describe cómo el código de Python utiliza la librería Bleak para conectarse al periférico BLE y recibir datos en tiempo real del sensor IMU. El código comienza definiendo dos funciones para manejar los datos de acelerómetro y giroscopio recibidos como notificaciones.
La función handle_accel_notification toma como argumentos el remitente y los datos y separa los datos en valores x, y, z separados. Luego obtiene la hora actual, crea un diccionario con los datos de aceleración y escribe los datos en un archivo JSON.
La función handle_gyro_notification es similar a la función handle_accel_notification, pero maneja los datos del giroscopio en lugar de los datos del acelerómetro.
La función run es una función asíncrona que se conecta al periférico BLE y comienza a recibir notificaciones de los datos de acelerómetro y giroscopio. La función utiliza el método start_notify de la clase BleakClient para comenzar a recibir notificaciones de las características BLE con UUIDs «2001» y «2011».
Luego, el código entra en un bucle infinito para recibir continuamente los datos del periférico BLE. El bucle se ejecuta utilizando el método run_until_complete del bucle de eventos. Finalmente, el código establece la dirección del periférico BLE al que se debe conectar y obtiene el bucle de eventos. La función asíncrona se ejecuta entonces utilizando el método run_until_complete del bucle de eventos.
De esta manera, el código de Python se conecta al periférico BLE y recibe datos en tiempo real de aceleración y giroscopio del sensor IMU, facilitando la recopilación, almacenamiento y análisis de los datos del sensor IMU.
Resultados
El código anterior guardará los datos del Arduino y los almacenará en un archivo JSON. Ese archivo se verá así:
También puedes imprimir las variables accel_data y gyro_data si quieres ver el flujo de datos en tiempo real. En mi experiencia con este algoritmo, puedes obtener de 180 a 200 muestras por segundo, con una distribución equitativa entre los datos de aceleración y giroscopio.
Conclusión
En conclusión, hemos demostrado con éxito cómo establecer una comunicación inalámbrica entre un sensor IMU basado en Arduino y una aplicación de Python utilizando Bluetooth de Baja Energía (BLE). Al usar el Arduino Nano BLE 33 Sense, fuimos capaces de enviar datos de acelerómetro y giroscopio del sensor IMU a la aplicación de Python en tiempo real, con una tasa de muestreo máxima de 104 Hz.
Cubrimos el proceso de configuración del sensor IMU con Arduino y su conexión a la aplicación de Python utilizando la librería Bleak. El código de Python demostró cómo escanear dispositivos BLE, conectarse al periférico BLE y recibir datos en tiempo real del sensor IMU como notificaciones.
Este post sirve como punto de partida para una amplia gama de proyectos que requieren datos IMU rápidos y confiables. Al utilizar BLE, es posible transferir datos inalámbricamente entre el sensor y el computador, lo que facilita la recopilación, almacenamiento y análisis de los datos del sensor IMU.
Gracias por tomarse el tiempo de leer este post. Si tienes alguna pregunta, comentario o retroalimentación, no dudes en comunicarte.
Justo lo que estaba buscando! Muy útil, muchas gracias!
Me alegra que te haya sido útil. Saludos.
Hola, muy buen post, tengo una duda eso si, me tira este error al correr el programa:
in start_notify raise BleakError(f»Characteristic {char_specifier} not found!»)
bleak.exc.BleakError: Characteristic 00002001-0000-1000-8000-00805f9b34fb not found!
Entiendo que no reconoce el UUID para realizar la comunicación, pero en el programa de arduino está definido como:
BLECharacteristic accelData(«2001», BLERead | BLENotify, 20);
BLECharacteristic gyroData(«2011», BLERead | BLENotify, 20);
Usted sabe que podría estar causando dicho error? probé cambiando los UUID y me sigue diciendo que no lo reconoce…
Tienes que identificar el ID de la característica, la cual puede cambiar con cada dispositivo. Usa algún BLE Scanner para conectarte al Arduino e identificar los numeros correctos. Me falta agregar eso al post, ya que para cada dispositivo será distinto.
Saludos.
Pero si estoy usando un Arduino nano 33 BLE también va a cambiar ese identificador? Porque use 2 aplicaciones para escanear el sensor (nRF Connect y BLE Scanner), y me arrojan lo mismo que está escrito en el script, lo siguiente:
CUSTOM SERVICE
UUID: 0001001-0000-1000-8000-00805F9B34FB
CUSTOM CHARACTERISTIC
UUID: 00002001-0000-1000-8000-00805F9B34FB
CUSTOM CHARACTERISTIC
UUID: 00002011-0000-1000-8000-00805F9B34FB
debajo de cada una sale un mensaje que dice:
CLIENT CHARACTERISTIC CONFIGURATION
UUID:0X2902
No influye que en el script de arduino esté escrito como «2001» y en el programa de python como «00002001-0000-1000-8000-00805F9B34FB»? Leí que eran distintas formas, una en 16 byte y la otra en 128, o algo así…
Agradecería cualquier ayuda…
Verificalo de nuevo. Algún número debe estar mal. Eso cambia de un Arduino Nano a otro. Hace unos días me pasó y no era igual al que usé en el post
Gracias por responder, existe posibilidad de enviarle una foto de los UUID que me arrojan las aplicaciones a algún correo? quizá estoy ingresando el equivocado.
Creo que debes poder copiarlas y pegarlas aquí
1) En nRF Connect:
Generic Access
UUID: 0x1800
Primary Service
Device Name
UUID:0x2A00
Properties: READ
Appearance
UUID:0x2A00
Properties: READ
Generic Attribute:
UUID: 0x1801
Primary Service
Service Changed
UUID:0x2A05
Properties: INDICATE
Descriptors:
Client Characteristic Configuration
UUID:0x2902
Unknown Service
UUID: 0x1001
Primary Service
Unknown CharacteristicUUID: 0x2001Properties: NOTIFY,READ
Descriptors:
Client Characteristic Configuration
UUID:0x2902
Unknown Characteristic
UUID: 0x2011
Properties: NOTIFY,READ
Descriptors:
Client Characteristic Configuration
UUID:0x2902
2) En BLE Scanner:
Generic Access
0x1800
Primary Service
Device Name
UUID:00002A00-0000-1000-8000-00805F9B34FBProperties: READ
Appearance
UUID:00002A01-0000-1000-8000-00805F9B34FB
Properties: READ
Generic Attribute
0x1801
Primary Service
Service Changed
UUID:00002A00-0000-1000-8000-00805F9B34FB
Properties: INDICATE
Descriptors:
Client Characteristic Configuration
UUID:0x2902
Custom Service00001001-0000-1000-8000-00805F9B34FBPrimary Service
Custom Characteristic
UUID:00002001-0000-1000-8000-00805F9B34FB
Properties: READ,NOTIFY
Descriptors:
Client Characteristic Configuration
UUID:0x2902
Custom Characteristic
UUID:00002011-0000-1000-8000-00805F9B34FB
Properties: READ,NOTIFY
Descriptors:
Client Characteristic Configuration
UUID:0x2902
Eso sería lo que veo en pantalla para cada aplicación…
Se ven igual.
Te aseguraste de cambiar el address? Talvez puede ser algún problema con la instalación de la librería en Python
Si, el address está cambiado, yo también sospecho que es algo de la librería, quizá para la última versión de Bleak no funciona, o me falta actualizar algo más…
Corrí el código service_explorer en python y obtuve lo siguiente:
C:\Users\Tomás Herrera Muño\Dropbox\PhD PUC\Tesis\Proyecto Towing Tank\Arduino\Códigos\Nano 33 BLE>python service_explorer.py –address 7E:36:B3:47:6F:DA
2023-07-31 09:28:30,443 __main__ INFO: starting scan…
2023-07-31 09:28:30,751 __main__ INFO: connecting to device…
2023-07-31 09:28:31,229 __main__ INFO: connected
2023-07-31 09:28:31,229 __main__ INFO: [Service] 00001800-0000-1000-8000-00805f9b34fb (Handle: 1): Generic Access Profile
2023-07-31 09:28:31,245 __main__ ERROR: [Characteristic] 00002a00-0000-1000-8000-00805f9b34fb (Handle: 2): Device Name (read), Error: Could not read characteristic handle 2: Unreachable
2023-07-31 09:28:31,246 __main__ ERROR: [Characteristic] 00002a01-0000-1000-8000-00805f9b34fb (Handle: 4): Appearance (read), Error: Not connected
2023-07-31 09:28:31,247 __main__ INFO: [Service] 00001801-0000-1000-8000-00805f9b34fb (Handle: 6): Generic Attribute Profile
2023-07-31 09:28:31,247 __main__ INFO: [Characteristic] 00002a05-0000-1000-8000-00805f9b34fb (Handle: 7): Service Changed (indicate)
2023-07-31 09:28:31,248 __main__ ERROR: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 9): Client Characteristic Configuration, Error: Not connected
2023-07-31 09:28:31,248 __main__ INFO: [Service] 00001101-0000-1000-8000-00805f9b34fb (Handle: 10): Serial Port
2023-07-31 09:28:31,249 __main__ ERROR: [Characteristic] 00002101-0000-1000-8000-00805f9b34fb (Handle: 11): Vendor specific (read,notify), Error: Not connected
2023-07-31 09:28:31,250 __main__ ERROR: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 13): Client Characteristic Configuration, Error: Not connected
2023-07-31 09:28:31,251 __main__ ERROR: [Characteristic] 00002102-0000-1000-8000-00805f9b34fb (Handle: 14): Vendor specific (read,notify), Error: Not connected
2023-07-31 09:28:31,251 __main__ ERROR: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 16): Client Characteristic Configuration, Error: Not connected
2023-07-31 09:28:31,252 __main__ ERROR: [Characteristic] 00002103-0000-1000-8000-00805f9b34fb (Handle: 17): Vendor specific (read,notify), Error: Not connected
2023-07-31 09:28:31,252 __main__ ERROR: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 19): Client Characteristic Configuration, Error: Not connected
2023-07-31 09:28:31,253 __main__ INFO: disconnecting…
2023-07-31 09:28:31,261 __main__ INFO: disconnected
Al parecer no está leyendo la característica y por eso salen errores después en el proceso….