← Índice del curso

Comunicación entre Dispositivos · Tema 38 · Comunicación inalámbrica

Bluetooth Low Energy

Bluetooth Low Energy (BLE) no es una versión más lenta del Bluetooth clásico: es otra tecnología, diseñada desde cero con una idea opuesta. En lugar de un flujo continuo de bytes, un dispositivo BLE despierta unos milisegundos, dice algo y vuelve a dormir, con un consumo medio de microamperios que permite años de funcionamiento con una pila de botón. Para lograrlo, cambia el modelo de comunicación: no hay un «puerto serie» sino una tabla de datos con nombre (el perfil GATT, con servicios y características), y no hay una conexión permanente sino publicidad para ser encontrado y conexiones breves y espaciadas. Es, además, la única forma de hablar con un iPhone desde un microcontrolador. En este tema se estudian la radio, la publicidad, la conexión y sus parámetros, GATT y las notificaciones, el consumo y la seguridad, y se construye un servidor GATT en un ESP32 y un cliente en Python.

01 · Objetivo

Qué es BLE

BLE se incorporó a Bluetooth 4.0 en 2010 (nació como «Wibree», de Nokia) y desde entonces ha crecido con cada versión. Comparte con el clásico la banda de 2,4 GHz y el nombre, pero no la radio ni los protocolos; un dispositivo solo BLE no habla con uno solo clásico (tema 37). Los teléfonos, las computadoras y los chips de doble modo hablan las dos.

Evolución de BLE.
VersiónAñoNovedades principales
4.02010Aparece BLE: publicidad, GATT, conexiones de bajo consumo, 1 Mbit/s
4.22014Paquetes de datos de hasta 251 bytes, LE Secure Connections, privacidad mejorada
5.02016PHY de 2 Mbit/s, PHY de largo alcance (Coded), publicidad extendida (hasta 255 bytes y más canales)
5.12019Localización por dirección de llegada y ángulo (direction finding)
5.2 a 5.42020 a 2023LE Audio, canales isócronos, mejoras de seguridad y de publicidad periódica con respuesta
Mesh2017Red de malla de muchos nodos (iluminación, domótica), sobre BLE

El estándar organiza la comunicación en dos niveles, y conviene no confundirlos:

  • GAP (Generic Access Profile): cómo se encuentran y se conectan los dispositivos. Define los roles: difusor (broadcaster, solo publica) y observador (solo escucha), o periférico (publica y acepta una conexión: el sensor) y central (escucha, inicia la conexión y manda: el teléfono). La palabra «maestro/esclavo» del tema anterior se corresponde aquí con central y periférico.
  • GATT (Generic Attribute Profile): cómo se organizan e intercambian los datos una vez conectados: servicios, características y descriptores.

Y hay dos formas de usar BLE, muy distintas. En la difusión (balizas, beacons), el dispositivo solo publica paquetes con datos y nadie se conecta: es unidireccional, simple y barata. En la conexión, el central abre un enlace, lee y escribe características y recibe notificaciones. Muchos productos usan las dos: publican para ser encontrados y se conectan para configurarlos o descargar datos.

02 · La capa física

La radio de BLE

BLE usa la banda de 2,4 GHz, pero la reparte distinto que el clásico: 40 canales de 2 MHz, de 2402 a 2480 MHz. Tres de ellos son canales de publicidad (los índices 37, 38 y 39, en 2402, 2426 y 2480 MHz), elegidos en los huecos y bordes de los canales 1, 6 y 11 de Wi-Fi (tema 34) para que la publicidad sobreviva a la interferencia. Los otros 37 son canales de datos, que usan los dispositivos conectados con un salto de frecuencia en cada evento y la posibilidad de excluir los canales ocupados (AFH).

Capas físicas (PHY) de BLE.
PHYVelocidad en el aireSensibilidad típicaPara qué
LE 1M (4.0)1 Mbit/s≈ −95 dBmLa de uso general: la única que entienden todos
LE 2M (5.0)2 Mbit/s≈ −92 dBmMás velocidad y menos tiempo en el aire (menos energía por byte)
LE Coded S=2 (5.0)500 kbit/s≈ −100 dBmMás alcance con un código de corrección de errores
LE Coded S=8 (5.0)125 kbit/s≈ −103 dBmEl mayor alcance: del orden de cuatro veces el de LE 1M

Es el mismo compromiso del tema 34: bajar la velocidad compra sensibilidad y, por lo tanto, alcance. La potencia de salida va de −20 a +10 dBm (hasta +20 dBm en algunos chips) y la modulación es GFSK, la misma del clásico básico. El alcance típico es de decenas de metros en interiores, y de cientos de metros al aire libre, con LE Coded hasta un kilómetro con visión directa.

El paquete de BLE es pequeño y simple. En el PHY de 1 Mbit/s consta de un preámbulo (1 byte), una dirección de acceso (4 bytes, que identifica el enlace), una PDU (cabecera de 2 bytes y hasta 37 bytes de carga) y un CRC de 24 bits (3 bytes). Un paquete de publicidad completo ocupa como máximo 47 bytes, o 376 µs en el aire: menos de medio milisegundo. A 1 Mbit/s, cada byte tarda 8 µs, de modo que la cuenta de la energía es simple: cada byte de más cuesta microsegundos con la radio encendida.

03 · Ser encontrado

Publicidad y escaneo

Dos eventos de publicidad Dos eventos de publicidad separados por el intervalo. En cada uno, el dispositivo envía el mismo paquete en los canales 37, 38 y 39, uno tras otro, con una pequeña pausa entre ellos. Entre los eventos duerme. Cada intervalo se alarga con un retardo aleatorio de hasta 10 milisegundos para evitar colisiones sistemáticas con otros dispositivos. canal 37canal 38canal 39 paquetepaquete evento (≈ 1 ms) intervalo de publicidad (20 ms a 10,24 s) + retardo aleatorio de 0 a 10 ms dormido: el procesador y la radio están apagados Cada evento repite el mismo paquete en los tres canales, con 150 µs de separación entre ellos.

Antes de conectarse, un dispositivo tiene que ser encontrado. El periférico lo logra con la publicidad (advertising): cada cierto tiempo, el intervalo de publicidad (de 20 ms a 10,24 s en pasos de 0,625 ms), envía su paquete uno tras otro en los tres canales de publicidad, y vuelve a dormir. A cada intervalo se le suma un retardo aleatorio de 0 a 10 ms para evitar que dos dispositivos con el mismo intervalo colisionen siempre. El otro extremo hace escaneo: abre su receptor durante una ventana dentro de cada intervalo de escaneo, y cuando coincide con un paquete, lo recibe. Un escáner puede ser pasivo (solo escucha) o activo (pide además una respuesta de escaneo con más datos).

Tipos de paquete de publicidad (BLE 4.x).
TipoAdmite conexiónAdmite respuesta de escaneoUso
ADV_INDSíSíEl normal: «aquí estoy; podés conectarte»
ADV_DIRECT_INDSolo a un central conocidoNoReconexión rápida con un dispositivo ya emparejado
ADV_SCAN_INDNoSíBaliza con más datos que caben en un paquete
ADV_NONCONN_INDNoNoBaliza pura: solo difunde

Qué lleva un paquete

La carga de datos es una secuencia de estructuras AD con la forma [largo][tipo][contenido], donde el largo cuenta el byte del tipo y el contenido. En total hay 31 bytes disponibles (la publicidad extendida de 5.0 llega a 255). Las estructuras más usadas:

Estructuras AD habituales.
TipoContenidoEjemplo
0x01Banderas (descubrible, sin Bluetooth clásico)02 01 06
0x03Lista completa de UUID de servicios de 16 bits (little-endian)03 03 1A 18 (el servicio 0x181A)
0x08 y 0x09Nombre abreviado o completo, en ASCII o UTF-809 09 53 65 6E 73 6F 72 30 31 («Sensor01»)
0x0APotencia de transmisión (dBm, con signo)02 0A 00
0x16Datos de un servicioEddystone, la temperatura de un sensor
0xFFDatos del fabricante, con el identificador de empresa de 2 bytes05 FF 4C 00 02 15 (inicio de un iBeacon)

El paquete del sensor del tema: 02 01 06 (3 bytes) + 03 03 1A 18 (4) + 09 09 53 65 6E 73 6F 72 30 31 (10) = 17 bytes de los 31, con 14 libres. Ocupa 264 µs en el aire por canal. Si el nombre fuera «Sensor-de-temperatura-01» y se agregaran 7 bytes de datos del fabricante, serían 42: no cabe, y habría que acortar el nombre o ponerlo en la respuesta de escaneo (la herramienta 1 del laboratorio lo calcula). La regla de diseño es poner en el paquete lo que el escáner necesita para decidir si le interesa (el UUID del servicio, un nombre corto) y dejar el resto para después de conectar.

Balizas

Una baliza (beacon) es un dispositivo que solo publica, sin aceptar conexiones, y cuyo paquete es el mensaje. Los formatos más conocidos son iBeacon de Apple (datos del fabricante con el identificador de empresa 0x004C, un UUID de 16 bytes, major y minor de 2 bytes y la potencia medida a 1 m: 30 bytes en total con las banderas) y Eddystone de Google (servicio 0xFEAA). Sirven para localización en interiores, avisos por proximidad y, con datos de un sensor en el paquete, para enviar una lectura a cualquier receptor cercano sin conexión: un termómetro puede difundir su temperatura en cada evento, y un teléfono, una puerta de enlace o un ESP32 la recibe sin emparejarse con nadie. Es la forma más barata de energía y de complejidad, a cambio de que no hay confirmación ni cifrado ni garantía de que alguien la reciba.

Direcciones y privacidad

Cada dispositivo anuncia una dirección de 48 bits que puede ser pública (registrada en el IEEE, como la MAC de Ethernet), aleatoria estática (fija hasta el reinicio) o aleatoria privada. La última es la que usan los teléfonos: cambia cada unos 15 minutos y solo la reconocen quienes comparten una clave (IRK) con el dispositivo, de modo que un tercero no puede seguirlo. Un sensor con dirección fija es rastreable, y un iPhone ni siquiera muestra la dirección: ve un identificador propio de cada teléfono.

04 · Hablar

La conexión

Eventos de conexión con latencia 0 y con latencia 4 Dos líneas de tiempo con seis eventos de conexión separados por el intervalo. Con latencia 0, el periférico atiende los seis eventos. Con latencia 4, atiende el primero y el sexto y se saltea los cuatro intermedios porque no tiene datos que enviar, lo que baja el consumo pero aumenta el tiempo de reacción hasta cinco intervalos. Latencia 0Latencia 4 evento 012345eventos saltados (el periférico duerme) intervalo hasta 5 intervalos para reaccionar: (1 + latencia) × intervalo En cada evento, el central transmite y el periférico responde. Sin datos que enviar, el periférico puede saltearse hasta «latencia» eventos.

Cuando un central quiere conectarse a un periférico que publica con ADV_IND, le envía una petición de conexión (CONNECT_IND) con todos los parámetros del enlace: la dirección de acceso, la secuencia de saltos de canal y los tiempos. A partir de ahí, central y periférico se encuentran en eventos de conexión, separados por el intervalo de conexión: en cada evento, el central transmite y el periférico responde (aunque sea un paquete vacío), y pueden intercambiar varios paquetes seguidos si hay datos. Tres parámetros, que negocian y puede proponer el periférico, definen el comportamiento:

Parámetros de una conexión.
ParámetroRangoQué controla
Intervalo de conexión7,5 ms a 4 s, en pasos de 1,25 msCada cuánto se encuentran. Corto: poca demora y más velocidad, pero más consumo. Largo: ahorro de energía y demora
Latencia del periférico0 a 499 eventosCuántos eventos puede saltear el periférico si no tiene datos. El central no se entera de la diferencia
Tiempo de supervisión100 ms a 32 s, en pasos de 10 msCuánto tiempo sin oír al otro antes de dar la conexión por perdida. Debe ser mayor que (1 + latencia) × intervalo × 2

La latencia es la herramienta de ahorro más importante de BLE: un periférico que casi nunca tiene datos y permite 499 eventos de salto con un intervalo de 30 ms se «despierta» solo cada 15 segundos, pero cuando tiene algo que decir, lo envía en el próximo evento (sin esperar a ese tiempo). A cambio, si es el central el que necesita algo del periférico, puede tardar hasta (1 + latencia) × intervalo en tener respuesta. La herramienta 3 calcula la corriente media, la reacción y la velocidad.

Velocidad real

La velocidad útil depende de cuántos bytes se mueven por evento. Un paquete de datos lleva, además de los datos, 4 bytes de L2CAP, 3 de ATT (código de operación y manejador) y las cabeceras del enlace. Por omisión, el MTU de ATT (el tamaño máximo de un mensaje) es de 23 bytes: quedan 20 bytes de datos por notificación. Con BLE 4.2 se puede ampliar el paquete del enlace a 251 bytes (Data Length Extension) y con una negociación de MTU subirlo hasta 517, con lo que llegan a unos 244 bytes por paquete. Con paquetes de 20 bytes y un intervalo de 100 ms, la velocidad máxima es de unos kbit/s; con 244 bytes, un intervalo de 7,5 ms y tres paquetes por evento, de unos 780 kbit/s. En la práctica, la velocidad útil de un enlace BLE de 1 Mbit/s se queda en unos 700 kbit/s en condiciones ideales, y bastante menos con teléfonos y pilas reales.

05 · Datos con nombre

ATT y GATT: servicios y características

Tabla de atributos del sensor El servidor tiene dos servicios. El servicio de Environmental Sensing, con UUID 0x181A, contiene la característica Temperature, 0x2A6E, con propiedades de lectura y notificación; esta tiene un valor, con los bytes 2E 09, y un descriptor CCCD, 0x2902, con el valor 0001 que significa notificaciones activadas. El segundo servicio, de configuración, tiene un UUID propio de 128 bits y contiene la característica Intervalo, con lectura y escritura, y el valor 05 00. Servicio · Environmental SensingUUID 0x181A · handle 0x000E Servicio · Configuración (propio)UUID de 128 bits · handle 0x0014 Característica · TemperatureUUID 0x2A6E · propiedades: leer, notificar · handle 0x000F Característica · IntervaloUUID propio · propiedades: leer, escribir · handle 0x0015 Valor · 2E 0923,50 °C · int16 little-endian · 0x0010 Descriptor · CCCD 0x2902 = 01 00el cliente pidió notificaciones · 0x0011 Valor · 05 005 s · uint16 little-endian · 0x0016 Todo es una tabla plana de atributos con un número de identificación (handle), un tipo (UUID), un valor y permisos. Un cliente la recorre una vez y guarda los handles; después trabaja con ellos.

Si el clásico ofrece un puerto serie, BLE ofrece una tabla de datos con estructura y nombre. El protocolo ATT (Attribute Protocol) define el mecanismo: un servidor guarda una tabla de atributos, cada uno con un manejador (handle, un número de 16 bits), un tipo (un UUID), un valor de hasta 512 bytes y unos permisos; un cliente puede leerlos y escribirlos por su manejador. GATT es la convención que da estructura a esa tabla en tres niveles:

  • Servicio: un grupo de funciones relacionadas (la batería, el ambiente, la información del fabricante). Se identifica por un UUID.
  • Característica: un dato dentro de un servicio (el nivel de batería, la temperatura). Tiene un UUID, unas propiedades (qué se puede hacer con ella) y un valor.
  • Descriptor: información adicional sobre una característica (una descripción en texto, las unidades, el interruptor de las notificaciones).

Los UUID son de dos clases. Los de 16 bits los asigna la Bluetooth SIG para servicios y características estándar (son abreviaturas de un UUID de 128 bits con la base 0000xxxx-0000-1000-8000-00805F9B34FB): 0x180F es el servicio de batería, 0x2A19 su característica «nivel de batería» (1 byte, de 0 a 100 %), 0x181A el de sensado ambiental y 0x2A6E la temperatura. Las funciones propias llevan UUID de 128 bits generados al azar, sin riesgo de colisión. Usar los estándares cuando existen hace que cualquier aplicación genérica (como nRF Connect) muestre «Temperature» en lugar de un número misterioso. La herramienta 5 del laboratorio expande los UUID cortos.

Operaciones de GATT.
PropiedadQuién iniciaQué haceConfirmación
ReadClientePide el valor actual y el servidor respondeLa respuesta es la confirmación
WriteClienteEscribe un valor; el servidor lo valida y contestaSí (puede responder con un error)
Write without responseClienteEscribe sin esperar respuesta: más rápido, sin garantíaNo
NotifyServidorEnvía el valor cada vez que cambia, sin que se lo pidanNo (ordenada por el enlace, pero sin confirmación de la aplicación)
IndicateServidorComo notify, pero el cliente confirma cada mensajeSí

Las notificaciones son la pieza clave del bajo consumo: en lugar de que el cliente pregunte cada segundo (sondeo, como en Wi-Fi), el servidor avisa solo cuando hay un cambio, y entre cambios no gasta nada. Pero no se envían solas: el cliente tiene que activarlas escribiendo en el descriptor CCCD (Client Characteristic Configuration, UUID 0x2902) de esa característica el valor 0x0001 (notificaciones) o 0x0002 (indicaciones). Hasta entonces, el servidor no manda nada. Se desactivan escribiendo 0x0000. Es una de las causas más comunes de «mi dispositivo no envía datos».

Los valores son bytes: orden y formato

GATT transporta bytes, y cada característica define su formato. Para la temperatura (0x2A6E), el estándar establece un entero de 16 bits con signo en unidades de 0,01 °C, con el byte menos significativo primero (little-endian). Entonces 23,50 °C son 2350 = 0x092E, y se envía como 2E 09. Una aplicación que los lea al revés obtendrá 117,85 °C; −5,25 °C son −525 = 0xFDF3 y viajan como F3 FD. El valor 0x8000 significa «sin dato». Todos los números de BLE son little-endian, y los errores de orden, tamaño y signo son los más frecuentes entre un dispositivo y su aplicación (la herramienta 4 y el tema 46 lo trabajan). Para datos propios conviene el mismo criterio: enteros de tamaño fijo y una unidad entera, sin decimales de coma flotante.

06 · Dormir casi siempre

Consumo y diseño

Toda la arquitectura de BLE se puede leer como una estrategia para minimizar el tiempo con la radio encendida: paquetes pequeños, eventos breves, intervalos largos, latencia, notificaciones en lugar de sondeos. El consumo medio es el mismo cálculo del tema 34: la carga de cada evento por la cantidad de eventos por segundo, más el sueño. Para una baliza que publica con un evento de 3 ms a 8 mA (24 µC) y duerme con 3 µA, con una pila de 220 mAh:

Duración de una baliza según el intervalo de publicidad (herramienta 2).
IntervaloEventos por segundoCorriente mediaDuración con 220 mAh
20 ms501203 µA7,6 días
100 ms10243 µA37,7 días
1 s127 µA340 días
5 s0,27,8 µA3,2 años

El intervalo es el compromiso entre energía y latencia de descubrimiento: un teléfono que escanea tarda, en promedio, más cuanto menos frecuente es la publicidad. Las aplicaciones de proximidad usan unos 100 a 300 ms; una baliza de inventario, de 1 a 5 s. Con una conexión, el papel del intervalo lo toma el de conexión, con la latencia como ayuda. El diseño típico de un sensor es uno de estos tres patrones:

Patrones de uso de BLE en un sensor.
PatrónCómo funcionaCuándo conviene
Difusión de datosLa lectura va en el paquete de publicidad; cualquiera la escuchaMuchos receptores, datos no sensibles, mínima energía y complejidad
Conectar, leer y desconectarUna puerta de enlace o un teléfono se conecta cada tanto, lee o descarga el historial y cortaUn sensor que acumula datos y se descarga de a ratos
Conexión mantenida con notificacionesEl enlace queda abierto con intervalo largo y latencia; el sensor notifica cuando hay un cambioWearables y accesorios con un teléfono cerca, y control en vivo

Dos aclaraciones realistas. Primero, no todo chip BLE es de bajo consumo. Un SoC pensado para esto (nRF52, EFR32, CC26xx) consume alrededor de 5 mA transmitiendo y unos 2 µA dormido; un ESP32 usa BLE con un procesador y una memoria que gastan mucho más y, aunque puede dormir, un evento de publicidad le cuesta varias veces más energía, así que sirve para prototipos, productos alimentados y dispositivos con puerta de enlace, pero no para años con una pila de botón. Segundo, la corriente pico importa: una pila CR2032 sufre con picos altos y pierde capacidad, y un evento de unos 8 a 15 mA ya es exigente; un condensador cerca del chip ayuda.

BLE frente a las otras tecnologías inalámbricas del curso (órdenes de magnitud).
AspectoBLEBluetooth clásicoWi-Fi
Consumo medio de un sensorDe µA a decenas de µADe 5 a 40 mA conectadoDe 20 mA conectado; con deep sleep, cientos de µA de media
Tiempo de conexiónDe milisegundos a cientos de milisegundosSegundosDe 0,5 a 3 segundos
Velocidad útilHasta unos 700 kbit/s (1M)Hasta 2 Mbit/sDecenas de Mbit/s
Internet directoNo (con puerta de enlace)NoSí
Con un iPhoneSíNo (salvo MFi)Sí

07 · Quién puede leer y escribir

Seguridad

Por defecto, un servidor GATT sin protección deja leer y escribir sus características a cualquier central cercano, y la publicidad es pública por naturaleza. BLE ofrece mecanismos para cerrar las dos cosas, pero hay que activarlos y elegirlos bien:

  • Emparejamiento y vinculación. Como en el clásico, dos dispositivos intercambian una clave (la LTK) y pueden guardarla (bonding) para cifrar las siguientes conexiones. Los métodos son Just Works (sin interacción), comparación numérica de 6 dígitos, ingreso de clave (passkey) y fuera de banda (NFC).
  • Emparejamiento heredado y LE Secure Connections. El heredado (BLE 4.0 y 4.1) deriva la clave a partir de una clave temporal que, en Just Works, vale cero: quien escucha el emparejamiento descifra todo. LE Secure Connections (4.2 en adelante) usa curvas elípticas P-256 y protege contra escucha pasiva, y con comparación numérica o clave, también contra un atacante interpuesto.
  • Permisos por característica. Cada atributo puede exigir que el enlace esté cifrado, o cifrado y autenticado (con un método que proteja contra el atacante interpuesto), para leer o para escribir. Una característica que enciende un actuador debe requerir al menos enlace cifrado.
  • Privacidad. Direcciones privadas resolubles que cambian (sección 3), para que no se rastree al dispositivo; y no poner identificadores personales o secretos en la publicidad.
  • Lista de permitidos. Aceptar conexiones solo de centrales conocidos.

Los riesgos prácticos son los de siempre. Publicidad en claro: cualquier receptor ve los datos de una baliza; si importan, se cifran en la aplicación. Reproducción: un atacante graba un paquete de «abrir» y lo repite; las cerraduras necesitan un contador o un desafío con respuesta (tema 49). Falta de validación: cada escritura es una entrada externa (como en el tema 33): se comprueba el tamaño, el rango y el formato, y se devuelve un error de ATT si no es válida (en el ejemplo del ESP32, se ignora el valor incorrecto y se republica el que quedó). Firmware: la actualización por BLE (DFU) es cómoda y, sin firma ni autenticación, un camino para instalar código ajeno. Conviene pensar con el mismo criterio de la sección de seguridad del tema 36: qué pasa si alguien dentro del alcance es hostil.

08 · Herramientas

Módulos serie BLE y herramientas

Para quien viene del HC-05, la forma más corta de pasar a BLE (y de funcionar con un iPhone) es un módulo BLE con interfaz serie, que oculta GATT detrás de una UART. El más conocido es el HM-10 (con un chip CC2541 de BLE 4.0): se conecta igual que el HC-05, con las señales de 3,3 V (y el divisor en su RXD, tema 37), y publica un servicio propio de UUID 0xFFE0 con una característica 0xFFE1 que permite leer, escribir y notificar: todo lo que se escribe en ella sale por la UART y lo que llega por la UART se notifica al central. Se configura con comandos AT sin terminador de línea y a 9600 baudios: AT (responde OK), AT+NAMEnombre, AT+BAUD con un código (0 = 9600, 1 = 19200, 2 = 38400, 3 = 57600, 4 = 115200), AT+ROLE0 o AT+ROLE1 y AT+RESET. El protocolo de órdenes por líneas del tema 37 funciona sin cambios. Hay que tener presente que se envía en trozos de 20 bytes (el MTU por omisión) y que abundan los clones del CC2541 con firmware distinto. Es una solución simple y universal, pero no aprovecha GATT: el teléfono ve un «puerto serie» y no una temperatura.

Existe una convención para eso: el Nordic UART Service (UUID 6E400001-B5A3-F393-E0A9-E50E24DCCA9E), con dos características (…0002 para escribir hacia el dispositivo y …0003 para notificar hacia el cliente), que muchas aplicaciones de terminal reconocen. Si el producto necesita una consola serie por BLE, es la elección más compatible; si publica datos con significado, GATT con servicios estándar y propios es mejor.

Para probar sin escribir una aplicación

  • nRF Connect for Mobile (Nordic, gratuita en Android y en iOS) es la herramienta de referencia: escanea, muestra los paquetes de publicidad desglosados por estructuras AD, se conecta, lista los servicios y las características, lee, escribe y activa las notificaciones (la campana, que escribe el CCCD). Reemplaza al «terminal Bluetooth» del tema 37. Es la primera prueba de cualquier dispositivo BLE.
  • La computadora. Con Python y la biblioteca bleak (Windows, macOS y Linux) se escribe un cliente en pocas líneas (sección 9). En Chrome y Edge, la API Web Bluetooth permite conectarse desde JavaScript en una página con HTTPS, sin instalar nada (no está disponible en iOS).
  • Un analizador de paquetes. Wireshark con un dongle de captura de BLE (por ejemplo, un nRF52840) muestra la publicidad, la conexión y los paquetes de ATT, con los manejadores y los valores.

Dos particularidades de los teléfonos. En Android 12 o posterior las aplicaciones piden los permisos BLUETOOTH_SCAN y BLUETOOTH_CONNECT; en versiones anteriores, el permiso de ubicación (porque el escaneo permite localizar a una persona). En iOS no se ve la dirección del dispositivo sino un identificador, y la aplicación debe declarar para qué usa Bluetooth. Es normal que la lista de dispositivos no muestre un sensor que está publicitando si la aplicación filtra por un UUID de servicio que no está en el paquete.

09 · Con el microcontrolador

Un servidor GATT en un ESP32 y un cliente en Python

El sensor del tema ofrece la temperatura como característica estándar (0x2A6E) con notificaciones, y un intervalo configurable en un servicio propio. Se carga en cualquier ESP32 con BLE (clásico, S3, C3 o C6), con las bibliotecas BLE incluidas en el paquete de placas (en proyectos que necesitan ahorrar memoria se usa la alternativa NimBLE-Arduino, con una API muy parecida y menos flash y RAM).

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

// ---------- Identificadores ----------
const char NOMBRE[] = "Sensor01";
#define UUID_AMBIENTE  ((uint16_t)0x181A)                      // Environmental Sensing (asignado por la SIG)
#define UUID_TEMP      ((uint16_t)0x2A6E)                      // Temperature: entero de 16 bits con signo, 0,01 °C
#define UUID_CONFIG    "6e3f0001-4a1b-4f64-9c6d-0d5a5b1c2e01"   // servicio propio (128 bits aleatorios)
#define UUID_INTERVALO "6e3f0002-4a1b-4f64-9c6d-0d5a5b1c2e01"   // intervalo en segundos: entero de 16 bits sin signo

const uint16_t INTERVALO_MIN = 1, INTERVALO_MAX = 3600;

// ---------- Estado ----------
BLEServer *servidor;
BLECharacteristic *cTemp, *cIntervalo;
bool conectado = false;
uint16_t intervalo = 5;                                        // segundos entre mediciones

void publicarIntervalo() {                                     // el valor se guarda siempre en little-endian
    uint8_t b[2] = { (uint8_t)(intervalo & 0xFF), (uint8_t)(intervalo >> 8) };
    cIntervalo->setValue(b, 2);
}

// ---------- Eventos ----------
class AlServidor : public BLEServerCallbacks {
    void onConnect(BLEServer *s) { conectado = true; Serial.println("Cliente conectado"); }
    void onDisconnect(BLEServer *s) {
        conectado = false;
        Serial.println("Cliente desconectado");
        s->startAdvertising();                                 // vuelve a publicitarse para el próximo cliente
    }
};

class AlEscribir : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *c) {
        size_t n = c->getLength();
        uint8_t *d = c->getData();
        if (n == 2) {                                          // lo que llega por la red no es confiable: tamaño y rango
            uint16_t v = d[0] | (d[1] << 8);
            if (v >= INTERVALO_MIN && v <= INTERVALO_MAX) { intervalo = v; Serial.printf("Intervalo = %u s\n", v); }
        }
        publicarIntervalo();                                   // deja visible el valor que realmente quedó
    }
};

// ---------- Medición ----------
void servicioMedicion() {
    static unsigned long ultimo = 0;
    if (millis() - ultimo < (unsigned long)intervalo * 1000UL) return;
    ultimo = millis();
    float t = temperatureRead();                               // temperatura del chip: reemplazar por el sensor real
    int16_t v = (int16_t)lroundf(t * 100);                     // centésimas de grado
    uint16_t u = (uint16_t)v;
    uint8_t b[2] = { (uint8_t)(u & 0xFF), (uint8_t)(u >> 8) }; // byte menos significativo primero
    cTemp->setValue(b, 2);
    if (conectado) cTemp->notify();                            // solo llega si el cliente activó las notificaciones
}

void setup() {
    Serial.begin(115200);
    BLEDevice::init(NOMBRE);
    servidor = BLEDevice::createServer();
    servidor->setCallbacks(new AlServidor());

    BLEService *ambiente = servidor->createService(BLEUUID(UUID_AMBIENTE));
    cTemp = ambiente->createCharacteristic(BLEUUID(UUID_TEMP), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
    cTemp->addDescriptor(new BLE2902());                       // el CCCD: el cliente escribe aquí 0x0001 para pedir notificaciones

    BLEService *config = servidor->createService(UUID_CONFIG);
    cIntervalo = config->createCharacteristic(UUID_INTERVALO, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
    cIntervalo->setCallbacks(new AlEscribir());
    publicarIntervalo();

    ambiente->start();
    config->start();

    BLEAdvertising *publicidad = BLEDevice::getAdvertising();
    publicidad->addServiceUUID(BLEUUID(UUID_AMBIENTE));        // el UUID viaja en el paquete: se puede buscar por servicio
    publicidad->setScanResponse(true);                         // el nombre completo va en la respuesta de escaneo
    publicidad->start();
    Serial.println("Publicitando como Sensor01");
}

void loop() {
    servicioMedicion();
}

Qué hay que notar:

  • El nombre del servicio estándar va como número de 16 bits y el propio, como cadena de 128 bits. El paquete de publicidad lleva el UUID del servicio de ambiente (para poder buscarlo por servicio) y, como el nombre completo ya no cabe junto con otros datos, setScanResponse(true) lo manda en la respuesta de escaneo.
  • El CCCD lo agrega BLE2902: sin ese descriptor, el cliente no puede activar las notificaciones. notify() solo envía si el cliente las activó, y el sketch lo hace únicamente cuando hay un cliente conectado.
  • Los números viajan en little-endian y con tamaño fijo: la temperatura como entero de 16 bits con signo en centésimas de grado, y el intervalo como entero de 16 bits sin signo. Se arman byte a byte para no depender de cómo se almacenan los números en cada placa.
  • La escritura se valida (largo de 2 bytes y rango de 1 a 3600), y, sea válida o no, se vuelve a publicar el valor vigente: así el cliente ve lo que realmente quedó. Una biblioteca que lo permita (como NimBLE-Arduino) puede devolver además un código de error de ATT.
  • Al desconectarse el cliente se vuelve a publicitar: si no, el dispositivo queda invisible para el siguiente. Es un error muy común.
  • La API cambia entre versiones del núcleo ESP32 (por ejemplo, el tipo que devuelven algunas funciones). Si algo no compila, revisá los ejemplos de la propia biblioteca (BLE_server y BLE_notify).

El cliente en Python

Con pip install bleak, el siguiente programa (que se ejecuta en una computadora, no en el navegador) busca el sensor por su nombre, lee la temperatura, escribe el intervalo y escucha las notificaciones durante 30 segundos:

import asyncio
from bleak import BleakClient, BleakScanner

TEMPERATURA = "00002a6e-0000-1000-8000-00805f9b34fb"
INTERVALO = "6e3f0002-4a1b-4f64-9c6d-0d5a5b1c2e01"

def a_grados(datos):
    return int.from_bytes(datos, "little", signed=True) / 100      # centésimas de grado, little-endian

async def main():
    dispositivo = await BleakScanner.find_device_by_name("Sensor01", timeout=10)
    if dispositivo is None:
        print("No se encontró el sensor")
        return
    async with BleakClient(dispositivo) as cliente:
        print("Temperatura:", a_grados(await cliente.read_gatt_char(TEMPERATURA)), "°C")
        await cliente.write_gatt_char(INTERVALO, (10).to_bytes(2, "little"))     # intervalo de 10 s
        await cliente.start_notify(TEMPERATURA, lambda _, datos: print("Notificación:", a_grados(datos), "°C"))
        await asyncio.sleep(30)                                                # escuchar 30 s

asyncio.run(main())

El programa del cliente no sabe nada de la radio, de los canales ni de las ranuras: lee y escribe por UUID. start_notify escribe el CCCD por debajo. La conversión int.from_bytes(datos, "little", signed=True) es el contrato de la característica: si se omite el orden o el signo, el resultado es incorrecto sin ningún aviso.

10 · Analogía con programación

BLE en Python

La parte lógica de BLE (armar y leer estructuras AD, codificar valores de GATT, expandir UUID, calcular tiempos en el aire, corrientes medias, validar parámetros de conexión y estimar velocidades) se prueba sin hardware. El modelo siguiente lo reproduce con las mismas reglas del sketch y de la página.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

BASE_UUID = "0000{:04X}-0000-1000-8000-00805F9B34FB"         # los UUID de 16 bits son abreviaturas de este


def uuid128(corto):
    return BASE_UUID.format(corto)


def estructura(tipo, datos):
    return bytes([len(datos) + 1, tipo]) + datos             # [largo][tipo][contenido]; el largo cuenta el tipo


def publicidad(nombre, servicio=None, fabricante=b""):
    datos = estructura(0x01, b"\x06")                        # banderas: descubrible, sin Bluetooth clásico
    if servicio is not None:
        datos += estructura(0x03, servicio.to_bytes(2, "little"))
    datos += estructura(0x09, nombre.encode("ascii"))
    if fabricante:
        datos += estructura(0xFF, fabricante)
    return datos


def leer_publicidad(datos):
    i, campos = 0, []
    while i < len(datos) and datos[i] != 0:
        largo = datos[i]
        campos.append((datos[i + 1], datos[i + 2:i + 1 + largo]))
        i += largo + 1
    return campos


def codificar_temperatura(grados):                           # característica 0x2A6E: entero de 16 bits, 0,01 °C
    return round(grados * 100).to_bytes(2, "little", signed=True)


def decodificar_temperatura(datos):
    return int.from_bytes(datos, "little", signed=True) / 100


def tiempo_aire_us(bytes_datos):
    return (1 + 4 + 2 + 6 + bytes_datos + 3) * 8             # preámbulo, dirección de acceso, cabecera, AdvA, datos, CRC


def corriente_media_ua(intervalo_ms, i_evento_ma, t_evento_ms, i_sueno_ua):
    q = i_evento_ma * t_evento_ms                            # µC por evento
    return (q + i_sueno_ua * (intervalo_ms - t_evento_ms) / 1000) / (intervalo_ms / 1000)


def conexion_valida(intervalo_ms, latencia, supervision_ms):
    return supervision_ms > (1 + latencia) * intervalo_ms * 2


def velocidad_bps(intervalo_ms, datos, paquetes):
    par_us = (datos + 17) * 8 + 380                          # paquete, 150 µs, ACK vacío, 150 µs
    caben = int(intervalo_ms * 1000 // par_us)
    return datos * 8 * min(paquetes, caben) * 1000 / intervalo_ms, caben


pub = publicidad("Sensor01", 0x181A)
print("publicidad:", pub.hex(" ").upper(), f"({len(pub)} de 31 bytes, {tiempo_aire_us(len(pub))} µs en el aire)")
print("campos:", [(hex(t), d) for t, d in leer_publicidad(pub)])
largo = publicidad("Sensor-de-temperatura-01", 0x181A, bytes.fromhex("FFFF0102030405"))
print("con nombre largo y fabricante:", len(largo), "bytes", "(no cabe)" if len(largo) > 31 else "")
ibeacon = estructura(0x01, b"") + estructura(0xFF, bytes.fromhex("4C000215") + bytes(16) + bytes([0, 1, 0, 2, 0xC5]))
print("iBeacon:", len(ibeacon), "bytes en total (3 de banderas y 27 de datos del fabricante)")

for t in (23.5, -5.25, 0.0, 41.07):
    b = codificar_temperatura(t)
    print(f"{t:>6} °C -> {b.hex(' ').upper()} -> {decodificar_temperatura(b)} °C; al revés: {int.from_bytes(b, 'big', signed=True) / 100}")
print("UUID 0x181A:", uuid128(0x181A))
print("UUID 0x2A6E:", uuid128(0x2A6E))

for intervalo in (20, 100, 1000, 5000):
    ua = corriente_media_ua(intervalo, 8, 3, 3)
    print(f"publicidad cada {intervalo:>4} ms: {ua:7.1f} µA, {220 * 1000 / ua / 24:7.1f} días con 220 mAh")

for intervalo, lat, to in ((100, 0, 2000), (100, 4, 2000), (100, 9, 2000), (100, 9, 2010), (30, 0, 100)):
    print(f"intervalo {intervalo} ms, latencia {lat}, supervisión {to} ms: {'válido' if conexion_valida(intervalo, lat, to) else 'inválido'}")
for intervalo, datos, paquetes in ((100, 20, 4), (30, 20, 4), (7.5, 244, 6), (50, 244, 6)):
    v, caben = velocidad_bps(intervalo, datos, paquetes)
    print(f"intervalo {intervalo} ms, {datos} bytes x {paquetes}: caben {caben}, {v / 1000:.1f} kbit/s")

Resultado: el paquete de publicidad es 02 01 06 03 03 1A 18 09 09 53 65 6E 73 6F 72 30 31, 17 de 31 bytes y 264 µs en el aire; al leerlo salen las banderas (06), el UUID 1A18 (el 0x181A en little-endian) y el nombre «Sensor01». Con un nombre largo y 7 bytes de datos del fabricante suma 42 bytes: no cabe; el iBeacon mide 30 bytes (3 de banderas y 27 del fabricante). Las temperaturas codifican así: 23,5 °C es 2E 09 (y leído al revés daría 117,85); −5,25 °C es F3 FD (al revés, −30,75); 0 °C es 00 00; 41,07 °C es 0B 10 (al revés, 28,32). Los UUID de 128 bits son 0000181A-0000-1000-8000-00805F9B34FB y 00002A6E-0000-1000-8000-00805F9B34FB. La corriente media de la publicidad es 1202,5 µA cada 20 ms (7,6 días con 220 mAh), 242,9 µA cada 100 ms (37,7 días), 27,0 µA cada segundo (339,6 días) y 7,8 µA cada 5 s (1175,5 días, unos 3,2 años). Las conexiones de 100 ms con latencia 0 y supervisión de 2000 ms, y con latencia 4, son válidas; con latencia 9 y 2000 ms, inválida (hacen falta más de 2000 ms), y con 2010 ms, válida. Con 100 ms y 20 bytes por paquete caben 147 paquetes y la velocidad con cuatro es de 6,4 kbit/s; con 30 ms, caben 44 y son 21,3 kbit/s; con 7,5 ms y 244 bytes caben 3 y llegan a 780,8 kbit/s; con 50 ms y 244 bytes, caben 20 y hay 234,2 kbit/s con seis paquetes pedidos.

Una lección que sale del modelo: casi todos los errores de integración en BLE no son de radio sino de contrato de datos (orden de bytes, tamaño, signo, unidad). Un par de funciones de codificar y decodificar probadas en la computadora los evita.

11 · Exploración interactiva

Laboratorio: publicidad, batería, conexión, valores GATT y UUID

El laboratorio tiene cinco herramientas. La primera arma un paquete de publicidad y dice cuánto ocupa. La segunda estima la duración de la batería de un dispositivo que solo publica. La tercera valida los parámetros de una conexión y estima su consumo y su velocidad. La cuarta codifica y decodifica el valor de una característica de temperatura. La quinta expande un UUID corto y explica el descriptor de notificaciones.

1. Paquete de publicidad

Bytes del paquete—
Espacio usado—
Espacio libre—
Tiempo en el aire (1 Mbit/s)—
Veredicto—

Escribí el nombre y el servicio.

2. Batería de una baliza

Carga por evento—
Frecuencia—
Corriente media—
Duración—
Veredicto—

Elegí el intervalo de publicidad.

3. Parámetros de la conexión

Validación—
Tiempo de reacción máximo—
Corriente media—
Paquetes que caben—
Velocidad máxima—
Veredicto—

Elegí los parámetros de la conexión.

4. Valor de la característica «Temperature»

Valor entero—
Bytes que se envían—
En el orden equivocado—
Lo recibido, decodificado—
Lo recibido, leído al revés—

Escribí una temperatura y dos bytes.

5. UUID y descriptor de notificaciones

UUID de 128 bits—
Nombre—
Tipo—
Efecto del CCCD—
Bytes que escribe el cliente—

Escribí un UUID corto.

En la herramienta 1, el paquete incluye siempre las banderas (3 bytes) y el tiempo en el aire cuenta el preámbulo, la dirección de acceso, la cabecera, la dirección del dispositivo y el CRC. En la 2, la corriente es la de un SoC de bajo consumo, no la de un ESP32. En la 3, cada evento atendido cuesta 7 µC y el sueño 3 µA, y el paquete incluye las cabeceras de L2CAP y ATT. En la 4 se usa la codificación de la característica 0x2A6E.

Recorrido guiado

  1. En la herramienta 1, con «Sensor01» y 0x181A: 02 01 06 03 03 1A 18 09 09 53 65 6E 73 6F 72 30 31, 17 de 31 bytes, 14 libres, 264 µs por paquete y 792 µs en los tres canales. Probá el nombre Sensor-de-temperatura-01 con los datos del fabricante FFFF0102030405: 42 bytes, faltan 11: no cabe. Sin UUID y con 4C000215 (el principio de un iBeacon): 19 bytes.
  2. En la herramienta 2, con los valores iniciales: 24 µC por evento, 1 evento por segundo, 27 µA y 340 días. Con 100 ms: 243 µA y 37,7 días. Con 5000 ms: 7,8 µA y 3,2 años. Con 20 ms: 1203 µA y 7,6 días. Un chip menos eficiente (100 mA, 10 ms, 100 µA dormido): 1000 µC, 1099 µA y 8,3 días.
  3. En la herramienta 3, con los valores iniciales (100 ms, latencia 0, supervisión 2000 ms, 20 bytes por paquete, 4 por evento): válido, reacción de 100 ms, 73 µA (10 eventos por segundo), 147 paquetes caben y 6,4 kbit/s. Con latencia 4: reacción de 500 ms y 17 µA, casi cuatro veces menos. Con latencia 9: inválido, la supervisión debe superar 2000 ms. Con 101 ms: inválido (no es múltiplo de 1,25).
  4. Probá 7,5 ms, 244 bytes, 6 paquetes y supervisión de 500: solo caben 3, y se llega a 780,8 kbit/s, pero con 936 µA de media: la velocidad se paga en energía. Con 30 ms y 20 bytes: 21,3 kbit/s y 236 µA.
  5. En la herramienta 4, con 23,5 °C: 2350 (0x092E), se envía 2E 09 y al revés sería 09 2E; los bytes recibidos 2E09 son 23,50 °C y leídos al revés, 117,85 °C. Con −5,25 °C y F3FD: −525 (0xFDF3), F3 FD y −5,25 °C, o −30,75 °C si se leen al revés. Con los bytes 0080: 0x8000, «desconocido».
  6. En la herramienta 5, con 181A y 1: 0000181A-0000-1000-8000-00805F9B34FB, «Environmental Sensing», servicio; el valor 1 activa las notificaciones y el cliente escribe 01 00. Con 2A19 y 2: «Battery Level», una característica, indicaciones (02 00). Con FFE1 y 7: un UUID que no figura en la tabla (el del HM-10) y un valor reservado.
Desafío: la cerradura de un casillero

Un casillero se abre desde el teléfono. El diseño inicial: un servicio propio con una característica «Abrir» que acepta la escritura del byte 01, publicidad cada 100 ms con el nombre «Casillero 12», alimentado por 2 pilas AA (unos 2500 mAh). Problemas: (1) Seguridad: cualquier teléfono cercano puede escribir 01 y abrirlo; la característica debe exigir un enlace cifrado y autenticado (LE Secure Connections con clave o comparación numérica), y la aplicación debe enviar un desafío firmado o un contador contra la repetición. (2) Energía: con la herramienta 2 (unos 8 mA y 3 ms por evento), cada 100 ms son 243 µA de media: con 2500 mAh, unos 430 días; con 500 ms, unos 50 µA y casi 6 años en el cálculo. Se elegiría un intervalo de 500 ms a 1 s, y como el usuario espera unos segundos, el escaneo del teléfono lo encuentra en menos de uno o dos segundos. (3) Privacidad: el nombre «Casillero 12» y una dirección fija permiten rastrear y atacar un casillero concreto; mejor un identificador no obvio y una dirección privada. (4) Datos: «01» es una orden sin significado propio: un formato con un código de operación y un contador es más robusto. Son las mismas reglas que se vieron en los temas 33 y 36, aplicadas a otra radio.

12 · Comprensión

Confusiones frecuentes

«BLE es Bluetooth clásico con menos consumo»

Son tecnologías distintas, con radios y protocolos distintos. Un teléfono las atiende como dos cosas diferentes, y un HC-05 no se entiende con un módulo BLE.

«Con BLE tengo un puerto serie inalámbrico»

BLE no tiene un puerto serie: tiene una tabla de atributos. Los módulos serie BLE (HM-10) lo simulan con una característica de lectura, escritura y notificación. Para datos con significado, conviene usar características con UUID y formato propios.

«Mi dispositivo no manda notificaciones: está roto»

Casi siempre es que el cliente no activó el CCCD. Sin escribir 01 00 en el descriptor, el servidor no envía nada. En nRF Connect es el botón de la campana; en bleak, start_notify.

«Un intervalo de conexión corto siempre es mejor»

Reduce la demora y aumenta la velocidad, pero multiplica el consumo (eventos por segundo). La latencia y un intervalo largo son lo que permite años de batería. Se elige el más largo que la aplicación tolere.

«Los números de GATT se mandan en el orden que sea»

El estándar define el orden: little-endian, el byte menos significativo primero. Un valor mal ordenado no da error: da un número equivocado (23,50 °C se lee como 117,85 °C).

«Si el dispositivo está emparejado, es seguro»

Depende del método. Just Works del emparejamiento heredado no protege de quien escucha el emparejamiento. Y el cifrado del enlace no valida lo que el otro extremo envía: la aplicación valida cada escritura y se defiende de la repetición.

«Mi sensor tiene el nombre en el paquete, así que cualquier aplicación lo ve»

Si el nombre no cabe junto con el resto, va en la respuesta de escaneo, y los escáneres pasivos no la piden. Por eso a veces el dispositivo aparece sin nombre. Y los filtros por UUID de servicio solo funcionan si el UUID está en el paquete.

«Un ESP32 con BLE puede funcionar años con una pila de botón»

No: su procesador y su memoria consumen demasiado, aun con el modo de ahorro, y la pila de botón no soporta los picos. Para eso están los SoC pensados para bajo consumo. El ESP32 sirve cuando hay una fuente de energía mayor o duerme mucho entre eventos con una batería más grande.

«No veo mi dispositivo en el teléfono: no está publicitando»

Puede estar conectado a otro central (muchos periféricos dejan de publicitar al conectarse, o solo aceptan un cliente), o el teléfono no tiene el permiso de escaneo, o el dispositivo no volvió a publicitar tras la última desconexión.

13 · Práctica guiada

Predecí antes de comprobar

1. Paquete de publicidad

Armá el paquete de un dispositivo llamado «Garaje» que anuncia el servicio de batería (0x180F). ¿Cuántos bytes ocupa y cuántos quedan?

Ver solución

Banderas: 02 01 06 (3). Servicio: 03 03 0F 18 (4, el UUID en little-endian). Nombre: largo 7 (tipo más 6 letras), tipo 09 y «Garaje» (47 61 72 61 6A 65): 07 09 47 61 72 61 6A 65 (8). Total 15 bytes; quedan 16.

2. La baliza del invernadero

Una baliza publica cada 5 s con eventos de 3 ms a 8 mA, duerme con 3 µA y usa una pila de 220 mAh. ¿Cuánto dura?

Ver solución

Cada evento cuesta 24 µC; 0,2 eventos por segundo dan 4,8 µA, más el sueño: ≈ 7,8 µA. Con 220 mAh = 220 000 µAh, dura unas 28 200 h, o 3,2 años. La autodescarga de la pila ya empieza a pesar.

3. Parámetros válidos

Una conexión de 50 ms con latencia 9. ¿Es válida con 1000 ms de supervisión? ¿Cuál es el mínimo, y cuánto tarda el periférico en reaccionar como máximo?

Ver solución

El tiempo debe superar (1 + 9) × 50 × 2 = 1000 ms. Con 1000 ms no es válido (tiene que ser mayor), y el mínimo múltiplo de 10 ms es 1010 ms. El periférico puede tardar hasta (1 + 9) × 50 = 500 ms en atender.

4. Velocidad

Con un intervalo de 30 ms, 244 bytes por paquete y tres paquetes por evento, ¿cuál es la velocidad? ¿Y con 20 bytes por paquete?

Ver solución

244 · 8 · 3 = 5856 bits por evento; 1000 / 30 = 33,3 eventos por segundo: 195,2 kbit/s (caben 12 paquetes de ese tamaño por evento, de modo que los tres entran). Con 20 bytes: 20 · 8 · 3 · 33,3 = 16 kbit/s. Por eso conviene ampliar el MTU si se mueven muchos datos.

5. Orden de bytes

Un sensor envía los bytes F3 FD en la característica 0x2A6E. ¿Qué temperatura es? ¿Y qué bytes se envían para 41,07 °C?

Ver solución

Little-endian: 0xFDF3 como entero de 16 bits con signo es −525; −5,25 °C. Para 41,07 °C: 4107 = 0x100B, y se envía 0B 10.

6. UUID y CCCD

¿Cuál es el UUID de 128 bits de «Battery Level» (0x2A19)? ¿Qué bytes escribe el cliente en el CCCD para recibir notificaciones, y para indicaciones?

Ver solución

00002A19-0000-1000-8000-00805F9B34FB. Para notificaciones, 01 00 (el valor 0x0001 en little-endian); para indicaciones, 02 00. Con 00 00 se desactivan.

7. Una cerradura en BLE

Una cerradura abre al recibir 01 en una característica con permisos abiertos y emparejamiento Just Works. ¿Qué problemas tiene y cómo se corrigen?

Ver solución

Cualquier central cercano puede escribir 01, y con Just Works no se autentica al otro extremo. Además, un atacante que capture una orden puede repetirla. Se corrige con permisos de escritura que exijan cifrado y autenticación (LE Secure Connections con clave o comparación numérica), con una lista de dispositivos permitidos y con un desafío con respuesta o un contador en la aplicación para evitar la repetición; y con direcciones privadas para que no se pueda rastrear.

14 · Síntesis

Ideas para recordar

  • BLE es otra tecnología que el Bluetooth clásico, diseñada para despertar poco y dormir mucho: paquetes de menos de medio milisegundo, eventos de pocos milisegundos y consumos medios de microamperios. Funciona con iPhone y Android, y es el Bluetooth de los sensores y los accesorios.
  • La radio usa 40 canales de 2 MHz: 3 de publicidad (37, 38 y 39, que esquivan los canales 1, 6 y 11 de Wi-Fi) y 37 de datos con salto. Los PHY son LE 1M, 2M y Coded (largo alcance): bajar la velocidad compra alcance. Un paquete de publicidad ocupa como máximo 47 bytes y 376 µs.
  • Para ser encontrado, el periférico publica con un intervalo de 20 ms a 10,24 s, con un retardo aleatorio, en los tres canales; el central escanea. El paquete lleva hasta 31 bytes en estructuras [largo][tipo][contenido] (banderas, UUID, nombre, datos del fabricante). Las balizas (iBeacon, Eddystone) solo difunden, sin conexión.
  • Una conexión se define por el intervalo (7,5 ms a 4 s), la latencia (0 a 499 eventos que el periférico puede saltearse) y el tiempo de supervisión (mayor que (1 + latencia) × intervalo × 2). Intervalo y latencia largos ahorran energía y suben el tiempo de reacción. El MTU por omisión es de 23 bytes (20 de datos); con la ampliación de 4.2 llega a unos 244.
  • GATT organiza los datos en servicios, características y descriptores, sobre una tabla de atributos con manejadores y UUID. Los UUID de 16 bits son abreviaturas de uno de 128 con una base fija. Las propiedades son leer, escribir, notificar e indicar; las notificaciones exigen que el cliente escriba el CCCD (0x2902).
  • Los valores son bytes con un contrato: números en little-endian, tamaño fijo y signo definido (la temperatura 0x2A6E es un entero de 16 bits con signo en centésimas de grado: 23,50 °C es 2E 09). Los errores de formato no producen avisos, producen números equivocados.
  • La energía se gasta en despertar: la corriente media es la carga de cada evento por los eventos por segundo. Una baliza con eventos de 24 µC dura 340 días con 1 s de intervalo y 3,2 años con 5 s en una pila de 220 mAh. Un ESP32 no es un chip de bajo consumo en ese sentido: sirve para prototipos y productos con más energía.
  • La seguridad de BLE hay que activarla: LE Secure Connections en lugar del emparejamiento heredado, permisos cifrados y autenticados en las características que actúan, direcciones privadas, listas de permitidos, validación de cada escritura y protección contra la repetición. La publicidad es siempre pública.
  • Se prueba con nRF Connect, con bleak en Python o con Web Bluetooth. Para quien quiere un puerto serie, el HM-10 y el Nordic UART Service lo simulan; para datos con significado, GATT con características estándar o propias.

¿Listo para el siguiente tema?

Antes de avanzar, explicá por qué conviene que un sensor BLE use notificaciones en lugar de que el teléfono pregunte cada segundo; calculá cuántos bytes y qué orden de bytes se envían para 23,5 °C en la característica 0x2A6E; y decidí cómo reducirías el consumo de un periférico que casi nunca tiene datos (intervalo de conexión largo y latencia alta). Si podés justificar las tres respuestas, entendés cómo funciona BLE.

En el próximo tema se recorren otras tecnologías inalámbricas, cada una con su punto de equilibrio entre alcance, velocidad y consumo: el módulo nRF24L01, LoRa, Zigbee, ESP-NOW, RFID y NFC, e infrarrojo. Con lo visto en esta parte del curso (espectro, modulación, presupuesto de enlace y consumo), vas a poder compararlas con criterio.