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.
| Versión | Año | Novedades principales |
|---|---|---|
| 4.0 | 2010 | Aparece BLE: publicidad, GATT, conexiones de bajo consumo, 1 Mbit/s |
| 4.2 | 2014 | Paquetes de datos de hasta 251 bytes, LE Secure Connections, privacidad mejorada |
| 5.0 | 2016 | PHY de 2 Mbit/s, PHY de largo alcance (Coded), publicidad extendida (hasta 255 bytes y más canales) |
| 5.1 | 2019 | Localización por dirección de llegada y ángulo (direction finding) |
| 5.2 a 5.4 | 2020 a 2023 | LE Audio, canales isócronos, mejoras de seguridad y de publicidad periódica con respuesta |
| Mesh | 2017 | Red 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).
| PHY | Velocidad en el aire | Sensibilidad típica | Para qué |
|---|---|---|---|
| LE 1M (4.0) | 1 Mbit/s | ≈ −95 dBm | La de uso general: la única que entienden todos |
| LE 2M (5.0) | 2 Mbit/s | ≈ −92 dBm | Más velocidad y menos tiempo en el aire (menos energía por byte) |
| LE Coded S=2 (5.0) | 500 kbit/s | ≈ −100 dBm | Más alcance con un código de corrección de errores |
| LE Coded S=8 (5.0) | 125 kbit/s | ≈ −103 dBm | El 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
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).
| Tipo | Admite conexión | Admite respuesta de escaneo | Uso |
|---|---|---|---|
ADV_IND | Sí | Sí | El normal: «aquí estoy; podés conectarte» |
ADV_DIRECT_IND | Solo a un central conocido | No | Reconexión rápida con un dispositivo ya emparejado |
ADV_SCAN_IND | No | Sí | Baliza con más datos que caben en un paquete |
ADV_NONCONN_IND | No | No | Baliza 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:
| Tipo | Contenido | Ejemplo |
|---|---|---|
0x01 | Banderas (descubrible, sin Bluetooth clásico) | 02 01 06 |
0x03 | Lista completa de UUID de servicios de 16 bits (little-endian) | 03 03 1A 18 (el servicio 0x181A) |
0x08 y 0x09 | Nombre abreviado o completo, en ASCII o UTF-8 | 09 09 53 65 6E 73 6F 72 30 31 («Sensor01») |
0x0A | Potencia de transmisión (dBm, con signo) | 02 0A 00 |
0x16 | Datos de un servicio | Eddystone, la temperatura de un sensor |
0xFF | Datos del fabricante, con el identificador de empresa de 2 bytes | 05 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
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ámetro | Rango | Qué controla |
|---|---|---|
| Intervalo de conexión | 7,5 ms a 4 s, en pasos de 1,25 ms | Cada 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érico | 0 a 499 eventos | Cuántos eventos puede saltear el periférico si no tiene datos. El central no se entera de la diferencia |
| Tiempo de supervisión | 100 ms a 32 s, en pasos de 10 ms | Cuá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
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.
| Propiedad | Quién inicia | Qué hace | Confirmación |
|---|---|---|---|
| Read | Cliente | Pide el valor actual y el servidor responde | La respuesta es la confirmación |
| Write | Cliente | Escribe un valor; el servidor lo valida y contesta | Sí (puede responder con un error) |
| Write without response | Cliente | Escribe sin esperar respuesta: más rápido, sin garantía | No |
| Notify | Servidor | Envía el valor cada vez que cambia, sin que se lo pidan | No (ordenada por el enlace, pero sin confirmación de la aplicación) |
| Indicate | Servidor | Como notify, pero el cliente confirma cada mensaje | Sí |
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:
| Intervalo | Eventos por segundo | Corriente media | Duración con 220 mAh |
|---|---|---|---|
| 20 ms | 50 | 1203 µA | 7,6 días |
| 100 ms | 10 | 243 µA | 37,7 días |
| 1 s | 1 | 27 µA | 340 días |
| 5 s | 0,2 | 7,8 µA | 3,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:
| Patrón | Cómo funciona | Cuándo conviene |
|---|---|---|
| Difusión de datos | La lectura va en el paquete de publicidad; cualquiera la escucha | Muchos receptores, datos no sensibles, mínima energía y complejidad |
| Conectar, leer y desconectar | Una puerta de enlace o un teléfono se conecta cada tanto, lee o descarga el historial y corta | Un sensor que acumula datos y se descarga de a ratos |
| Conexión mantenida con notificaciones | El enlace queda abierto con intervalo largo y latencia; el sensor notifica cuando hay un cambio | Wearables 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.
| Aspecto | BLE | Bluetooth clásico | Wi-Fi |
|---|---|---|---|
| Consumo medio de un sensor | De µA a decenas de µA | De 5 a 40 mA conectado | De 20 mA conectado; con deep sleep, cientos de µA de media |
| Tiempo de conexión | De milisegundos a cientos de milisegundos | Segundos | De 0,5 a 3 segundos |
| Velocidad útil | Hasta unos 700 kbit/s (1M) | Hasta 2 Mbit/s | Decenas de Mbit/s |
| Internet directo | No (con puerta de enlace) | No | Sí |
| Con un iPhone | Sí | 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
Escribí el nombre y el servicio.
2. Batería de una baliza
Elegí el intervalo de publicidad.
3. Parámetros de la conexión
Elegí los parámetros de la conexión.
4. Valor de la característica «Temperature»
Escribí una temperatura y dos bytes.
5. UUID y descriptor de notificaciones
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
- 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. - 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.
- 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).
- 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.
- En la herramienta 4, con 23,5 °C: 2350 (0x092E), se envía
2E 09y al revés sería09 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 FDy −5,25 °C, o −30,75 °C si se leen al revés. Con los bytes 0080: 0x8000, «desconocido». - 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 escribe01 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.