|
Saludos, estamos vivos y en este tiempo es bueno poder decir
esto. Bien ha sido un periodo muy extraño que por suerte nos ha permitido
seguir trabajando desde casa en horarios no euclidianos.
Entre otras muchas cosas me llego un proyecto para trabajar
con un Arduino nano 33 sense con Ble. La placa por menos de 35 € ofrece una cantidad de sensores:
·
Sensor de inercia de 9 ejes.
·
Sensor de humedad y temperatura.
·
Sensor barométrico
·
Sensor de gestos, proximidad, color, luz
·
Micrófono
·
BLE: bluetooth de bajo consumo
Sin daros mucho la chapa, como comenzar a trabajar con el
esta muy bien explicado en el tutorial de arduino.
Quizá tengáis algo de trabajo en instalar todas las
librerías, porque no están agrupadas en una sola.
#include <ArduinoBLE.h> //Bluetooth ble #include <Arduino_LSM9DS1.h> //
IMU // #include <PDM.h> // Digital
microphone #include <Arduino_APDS9960.h> //
Gesture sensor // #include <Arduino_LPS22HB.h> //
Pressure // #include <Arduino_HTS221.h> // Relative humidity and temperature // |
Casi todas fácilmente instalables desde el administrador de
bibliotecas. |
La gracia es tratar de extraer la información de los
sensores y enviarlos por bluetooth. Pero para esto que con un bluetooth típico HC-06 el funcionamiento es muy similar al Serial.
Pero con el ble es otro mundo y este post es ayudar a solucionar algunos
problemas que me he topado.
Para explicarlo vamos a usar un simple código donde
enviaremos el valor de la IMU y recibiremos un char que nos variara el estado
del led (Pin 13). El Ble su uso esta muy extendido en un montón de periféricos
y su uso no debería ser problemático… Ahora bien, intentar capturar los datos
mencionados en el ejemplo anterior en un ordenador con Windows. Sin la aplicación específica del fabricante
es complicado.
|
Topología del modo connected |
Insisto no os voy a dar la chapa, aquí tenéis una buena
explicación de como funciona el bluetooth low energy BLE.
Resumiendo, nosotros en el Arduino vamos a tener que crear una serie de
servicios con unas propiedades, de lectura, escritura o notificación. Y desde
la central (programa Python en Windows) nos conectaremos cuando necesitemos y
obtendremos los datos.
Cabecera del programa |
#include <ArduinoBLE.h> #include <Arduino_LSM9DS1.h> //IMU const int BUFFER_SIZE = 64; char msgprint[BUFFER_SIZE]; const char*
uuid_string="00001143-0000-1000-8000-00805f9b34fb"; const char*
uuid_char="00001150-0000-1000-8000-00805f9b34fb"; BLEService customService(uuid_service); BLECharacteristic
Send_string(uuid_string,BLERead | BLENotify ,BUFFER_SIZE,false); BLECharCharacteristic led_control(uuid_char,
BLERead | BLEWrite);// 0,1 |
Las direcciones uuid tiene un formato de 16 bits o 128, en
principio pensé que podían ser inventadas, y pueden pero el primer valor 0x1101
o 0x1143, representa el GATT, que en función del valor que sean que se está
enviando, aquí
podéis ver una lista de perfiles existentes. Esto esta pensado para hacer el
protocolo más estable entre fabricantes. Si ponéis un perfil especifico, en
las aplicaciones de Android o ios os aparecerá ese nombre en el servicio. Si no
aparecerá toda la dirección.
Definimos tres, la principal es la que contendrá el resto de
los servicios vendría siendo el “profile”.
La segunda es la dirección donde enviaremos el string, como
características se puede hacer una orden de lectura o recibir notificaciones de
cambios.
Y la tercera es donde leeremos y escribiremos en un char que
emplearemos para activar el led.
|
Servicios y caracteristicas |
Setup del programa |
void setup() {
Serial.begin(9600); //to debug if (!IMU.begin()) { // init IMU
Serial.println("Failed to initialize IMU!");
while (1); }
if (!BLE.begin()) { //init BLE
Serial.println("starting BLE failed!");
while (1); }
BLE.setLocalName("IMU_test"); // name of Bluetooth BLE.setAdvertisedService(customService.uuid());
customService.addCharacteristic(Send_string); // add characteristic
customService.addCharacteristic(led_control); // add characteristic
BLE.addService(customService);
led_control.setValue(‘0’); // initial values 0x30 o 0 en Asccii
// Events
led_control.setEventHandler(BLEWritten,led_Update); // event to write
BLE.setEventHandler(BLEConnected, onBLEConnected); // event to connect
optional
BLE.setEventHandler(BLEDisconnected, onBLEDisconnected); // event to
disconned optional
BLE.advertise();
Serial.println(BLE.address());// Print the mac address
pinMode(13,OUTPUT); } |
Inicializamos el Serial, el IMU y la conexión BLE.
Añadimos el nombre al bluetooth, activamos el profile y le
añadimos las características que hemos defino anteriormente. Si uno de estos
servicios necesita inicializarlo “led_control.setValue(‘0’)”.
El valor inicial va en función del tipo de característica
definido, en nuestro caso un Char porque lo cambiaremos mediante la
introducción de teclado y para no andar con conversiones de char a int lo
dejamos como char.
Los eventos, “setEventHandler” definen funciones que
se llaman cuando se da la condición propiamente descrita, en este caso cuando
queramos escribir un char o cuando el bluetooth se conecte o se desconecte.
Funciones |
void led_Update(BLEDevice central,
BLECharacteristic characteristic) {
char aux=led_control.value();
if (aux=='1') digitalWrite(13,HIGH); // '1' o 0x31
else digitalWrite(13,LOW); } void onBLEConnected(BLEDevice central) {
Serial.print("Connected event, central: ");
Serial.println(central.address()); } void onBLEDisconnected(BLEDevice central)
{
Serial.print("Disconnected event, central: ");
Serial.println(central.address()); // util to know which mac address is your board } |
Y por último tenemos el loop, donde gestionamos que hacer
una vez nos conectemos a la central, en nuestro caso leeremos los valores de la
IMU, los agruparemos en un string que enviaremos. Podríamos haber hecho un
evento para la lectura de los valores, pero así podemos definir qué hacer.
Loop |
void
loop() { BLEDevice central = BLE.central(); if (central) { while (central.connected()) { if
(IMU.accelerationAvailable()&& IMU.gyroscopeAvailable() && IMU.magneticFieldAvailable())
{ IMU.readAcceleration(Ax, Ay, Az); IMU.readGyroscope(Gx, Gy, Gz); IMU.readMagneticField(Mx, My, Mz); sprintf(msgprint,
"%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,", Ax, Ay, Az, Gx,
Gy, Gz, Mx, My, Mz); Serial.println(msgprint); Send_string.writeValue(msgprint, sizeof(msgprint)); // guardamos en el servicio Send_string }
} } |
Mientras estemos conectados imprimirá por el serial el valor
de la IMU, y enviará estos valores al servicio.
El Ide de Arduino tarda en compilar y luego esta placa tiene
otra particularidad, tiene dos puertos un para monitorear y otro para
programar, pulsando 2 veces seguidas el reset entra en modo programar,
selecciona ese puerto para enviar el programa. Una vez hecho tendrás que
cambiar al otro puerto para mirar el serial.
Vamos al ordenador.
Con Python 3.7 he empleado la librería Bleak (“pip install bleak”) para
comunicación con el bluetooth low energy y aiconsole (“pip install
aioconsole” ), para introducir comandos en la consola
La lecturas que se van a realizar son asíncronas, y el
programa se detendrá para esperar la instrucción del control del led, si se le
envía ‘x’ romperá el bucle, se desconectara y saldrá del programa.
Inicio |
import logging import asyncio import datetime from aioconsole import ainput from bleak import BleakClient, BleakError read_string_uuid
= "00001143-0000-1000-8000-00805f9b34fb" #servicio de lectura de
string led_control_uuid
= "00001150-0000-1000-8000-00805f9b34fb" #servicio control del led #definimos
variable globales Ax=0.0;Ay=0.0;Az=0.0;Gx=0.0;Gy=0.0;Gz=0.0;Mx=0.0;My=0.0;Mz=0.0 |
Definimos los
servicios con el mismo valor que los
servicios del Arduino |
Funcion principal |
async with BleakClient(address, loop=loop) as
client:
while(1): x
= await client.is_connected() # Connect
to address log.info("Connected:
{0}".format(x))
log.info("Starting notifications/indications...") # Start
receiving notifications, which are sent to the `data_handler method`
await client.start_notify(indication_characteristic_uuid,
data_handler)# read the string #
wait for instruction to crontrol the led
keyboard_input = await ainput("Control Led [0,1]: ") if
keyboard_input== 'x':
break
bytes_to_send = bytearray(map(ord, keyboard_input)) await client.write_gatt_char(led_control_uuid, bytes_to_send, response=True) # Sino es True no lo envia #
Send a request to the peripheral to stop sending notifications.
await client.stop_notify(indication_characteristic_uuid)
log.info("Stopping notifications/indications...") |
Una vez conectado, la data contiene 64 bytes, que es como hemos definido
el buffer y en data_handler procesamos esos datos. Después el sistema se detiene
a la espera de una entrada de teclado. Con un 1 leído como un char se
enciende el led del Arduino, x cierra la aplicación y cualquier otra cosa
apaga el led. |
def data_handler |
def data_handler(sender, data): global
Ax,Ay,Az,Gx,Gy,Gz,Mx,My,Mz
#log.info("{0}: {1}".format(sender, data))
data_storage.append((datetime.datetime.utcnow().timestamp(), data)) aux =
bytearray(data) datos
= stringdata = aux.decode('utf-8') msg = datos.split(",")
Ax=float(msg[0])
Ay=float(msg[1])
Az=float(msg[2])
Gx=float(msg[3])
Gy=float(msg[4])
Gz=float(msg[5])
Mx=float(msg[6])
My=float(msg[7])
Mz=float(msg[8]) print("Accelerometro
[xyz]:"+str(Ax)+", "+str(Ay)+", "+str(Az))
print("Gyroscope [xyz]:"+str(Gx)+",
"+str(Gy)+", "+str(Gz))
print("Magnetometre [xyz]:"+str(Mx)+",
"+str(My)+", "+str(Mz)) |
Recibimos 64 bytes en un bytearray, salvo en MAC que hay que convertirlo a
bytearry de 64 bytes, mediante un Split separamos por las comas y el valor
msg[9] es descartable son todo cero. El resto lo convertimos a valores float
en este caso. Y visualizamos con el print |
Anakleto...