11.14. Resumen¶
Ha recorrido Bluetooth Low Energy desde la radio hasta la API de Python que se usa para controlarlo:
La motivación – BLE es la respuesta cuando la cámara quiere hablar con algo cercano sin ninguna infraestructura entre ellos. Un teléfono en la misma sala, un dispositivo ponible en una muñeca, una baliza en una pared. Corto alcance, sin red a la que unirse, casi sin consumo de energía.
La radio – 2,4 GHz, 40 canales: tres para anuncio, 37 para datos de conexión, saltando en una secuencia pseudoaleatoria con evitación adaptativa de los canales ruidosos. Paquetes breves, radios casi siempre dormidas.
La capa de enlace – entramado de paquetes, direccionamiento, planificación de conexiones, retransmisión y cifrado de la capa de enlace. Nada de esto se configura desde Python; todo ello se refleja en los parámetros de conexión y en la MTU.
Generic Access Profile (GAP) – descubrimiento y gestión de conexiones. Cuatro roles: periférico y difusor (anuncian), central y observador (escanean). Las cargas útiles de anuncio transportan el nombre local, los UUID de servicio, la apariencia y datos específicos del fabricante – 31 bytes más una respuesta de escaneo opcional de 31 bytes. El intervalo de conexión, la latencia del periférico y el tiempo de espera de supervisión rigen cómo se percibe una conexión abierta.
Generic Attribute Profile (GATT) – un árbol de servicios, cada uno con características, cada una con descriptores opcionales, identificados por UUID (de 16 bits para los estándares de Bluetooth-SIG, de 128 bits para los personalizados). Cinco operaciones: read y write (extracción, iniciadas por el cliente), notify e indicate (envío, iniciadas por el servidor, suscritas a través del Client Characteristic Configuration Descriptor). El tamaño de la carga útil está limitado por la MTU negociada.
La API de Python –
aiobleconvierte cada patrón de BLE en una corrutina de asyncio. Un periférico esaioble.advertise()iterando sobre conexiones, con objetosService/Characteristicconstruidos una vez y confirmados poraioble.register_services(). Un central esaioble.scan()para encontrar un par,connect()para abrir el enlace,service()ycharacteristic()para recorrer el árbol GATT remoto, y luegoread()/write()/subscribe()/notified()para los datos reales. Las desconexiones aparecen comoaioble.DeviceDisconnectedErrordentro de la corrutina que estaba esperando.Canales L2CAP – la vía de escape para flujos de bytes masivos que no encajan en el modelo de clave/valor de GATT.
aioble.DeviceConnection.l2cap_accept()/l2cap_connect()abren un canal por aplicación sobre la conexión GAP, con envío / recepción con control de flujo por créditos y una MTU mayor de la que GATT puede transportar.Emparejamiento y cifrado – los enlaces BLE son públicos de forma predeterminada.
aioble.DeviceConnection.pair()inicia un intercambio de claves que produce un enlace cifrado;bond=True(el valor predeterminado) persiste las claves para que las conexiones posteriores se salten el protocolo de enlace. Sinmitm=Truey una capacidad de E/S utilizable, el cifrado protege frente a espías pasivos pero no frente a una redirección activa durante el emparejamiento original.
Eso es suficiente para escribir aplicaciones de cámara que publiquen el estado como periférico, lean datos de sensores como central, envíen valores en vivo a un teléfono por BLE, aseguren el enlace con un paso de emparejamiento y vinculación y – para el raro caso de transferencia masiva – salgan de GATT hacia un canal L2CAP.
11.14.1. Resolución de problemas¶
Los fallos de BLE son en su mayoría discrepancias entre lo que esperan los dos lados, y un inspector del lado del teléfono es la forma más rápida de ver de quién son las expectativas erróneas. La herramienta estándar es nRF Connect for Mobile (Nordic Semiconductor, gratuita en Android e iOS): escanea, conecta, recorre la base de datos GATT, lee y escribe características y se suscribe a notificaciones – de modo que el comportamiento del lado de la cámara puede probarse de forma aislada, sin necesidad de escribir una aplicación complementaria en absoluto.
Los modos de fallo comunes:
«Mi dispositivo aparece en el escáner pero no se conecta.» Lo más frecuente es que el paquete de anuncio tenga
connectable=False(modo difusor), o que una conexión anterior siga abierta y la cámara ya haya pasado deaioble.advertise(). Añada sentencias print alrededor de la llamada de anuncio para confirmarlo.«exchange_mtu(512) se ejecutó pero mis notificaciones siguen limitadas a 20 bytes.» La MTU negociada es
min(local, peer)– el teléfono o la biblioteca central puede no haber solicitado una MTU mayor por su parte, en cuyo caso la conexión se queda en 23. Inspeccionemtudespués de queexchange_mtu()retorne. Tenga en cuenta también queexchange_mtu()solo funciona una vez por conexión; llámelo antes de la primera operación grande.«El emparejamiento falla con un error genérico.» Dos culpables habituales: la discrepancia de capacidad de E/S (pedir
mitm=Trueen una cámara que declaraio=3/ sin entrada ni salida – no hay forma de confirmar el código numérico, así que el motor de emparejamiento se rinde), y una hora de reloj completamente errónea en la cámara cuando el par la requiere. Ajuste el reloj conntptime.settime()antes del primer intento de emparejamiento.«Las notificaciones nunca llegan al cliente.» Dos cosas que comprobar, en orden: (a) ¿se declaró la característica con
notify=True? – el bit de propiedad debe estar establecido en el lado del servidor; (b) ¿llamó el cliente asubscribe()? – sin escribir el Client Characteristic Configuration Descriptor (CCCD), al servidor se le indica que ningún cliente quiere notificaciones y las descarta silenciosamente.«El nombre anunciado está truncado o falta.» La carga útil de anuncio es de 31 bytes, y los campos de flags + UUID de servicio + apariencia consumen bytes desde el principio. Un
name=largo más varios UUID de servicio provoca un desbordamiento. O bien acorte el nombre o bien use escaneo activo para que la respuesta de escaneo (otros 31 bytes) transporte el desbordamiento. nRF Connect muestra ambas mitades por separado, lo que hace que la división sea obvia.«La conexión L2CAP lanza una excepción inmediatamente.» Normalmente una discrepancia de PSM – ambos lados tienen que ponerse de acuerdo en el mismo número de PSM fuera de banda. Una
L2CAPConnectionErrorlleva el código de estado de Bluetooth como su primer argumento; el estado2(«PSM not supported») es la pista delatora.«Las conexiones vinculadas siguen desencadenando un protocolo de emparejamiento completo en cada reconexión.» No se llamó a
aioble.security.load_secrets()al inicio. Sin ella, las claves guardadas están en la memoria flash pero nunca se cargan en memoria, así que la identidad del par es desconocida y el emparejamiento se ejecuta desde cero cada vez.
Cuando todo lo demás falla, el módulo de más bajo nivel bluetooth expone una función de retorno (callback) de IRQ que se dispara para cada evento subyacente; suscribirse a ella brevemente e imprimir los eventos es el equivalente a una traza de Wireshark para el lado de la cámara.
11.14.2. Usar esta referencia más adelante¶
Trate los capítulos de Bluetooth como material de referencia; volver para consultar la disposición exacta de la carga útil de anuncio de un periférico o el flujo de escaneo y suscripción de un central es el uso previsto. Las páginas de referencia aioble — BLE asíncrono y bluetooth — Bluetooth de bajo nivel enumeran cada método, flag y constante en un solo lugar cuando la pregunta es simplemente «cuál es el nombre exacto de esta llamada».