11.7. Operaciones GATT¶
Una característica simplemente reside en la base de datos GATT como un valor con nombre. Lo que la hace útil es el conjunto pequeño y bien definido de operaciones que un cliente puede ejecutar sobre ella. Cada característica declara qué operaciones admite mediante una máscara de bits de propiedades – un servidor que no tiene nada que exponer puede publicar un valor de solo lectura, un registro de control podría ser de solo escritura, un sensor que transmite actualizaciones activaría el bit de notificación. El cliente descubre la máscara de bits durante el descubrimiento y la respeta.
Las cinco operaciones son read, write, write without response, notify e indicate. Se dividen en dos grupos – extracción (el cliente pide) y empuje (el servidor envía).
11.7.1. Extracción: read y write¶
Estas dos son las más simples y se parecen exactamente a llamadas a funciones.
Read. El cliente pide el valor actual, el servidor se lo devuelve. Una ida y vuelta, el cliente obtiene los bytes que el servidor haya establecido para esa característica, y el servidor no obtiene nada sobre quién hizo la lectura.
Write. El cliente envía nuevos bytes, el servidor los almacena (y opcionalmente ejecuta lógica de aplicación sobre el nuevo valor). Existen dos variantes:
Write with response – el servidor confirma, lanzando cualquier error de aplicación con un estado distinto de cero. Fiable, una ida y vuelta.
Write without response – el servidor almacena los bytes en silencio; el cliente no recibe confirmación alguna. Más rápido (sin esperar la ida y vuelta de la confirmación) y útil para la transmisión continua, a costa de enterarse de los errores solo mediante una relectura por canal lateral.
En aioble, la API del lado del cliente oculta la elección tras un único método aioble.ClientCharacteristic.write() con una palabra clave response (True / False / None para seleccionar automáticamente según lo que anuncie el par).
11.7.2. Empuje: notify e indicate¶
El modelo de extracción es inadecuado para los datos de sensores. Una banda de frecuencia cardiaca que el teléfono tuviera que sondear cada segundo gastaría batería en cien eventos de radio innecesarios; una que envía un valor solo cuando tiene una nueva lectura es, en primer lugar, el sentido de BLE.
GATT resuelve esto con operaciones iniciadas por el servidor. El cliente se suscribe a una característica; a partir de ese momento, cada vez que el servidor actualiza el valor, el nuevo valor se envía a través del enlace al cliente. Dos variantes:
Notify. Disparar y olvidar. El servidor pone en cola una notificación, la capa de enlace la transmite durante el siguiente evento de conexión, el cliente la recibe. No hay confirmación a nivel de GATT; la retransmisión normal de la capa de enlace gestiona la pérdida en el lado de la radio, pero la aplicación no ve ninguna confirmación de que el valor se procesó.
Indicate. El servidor envía una notificación y espera la confirmación a nivel de GATT del cliente antes de enviar la siguiente. Una indicación a la vez. Se usa cuando el servidor necesita saber que el cliente realmente vio el valor – una característica de alarma crítica, una confirmación de configuración.
Extracción (read) frente a empuje (notify). Con las notificaciones, el cliente se suscribe una vez y el servidor envía nuevos valores cada vez que cambian.¶
La suscripción se realiza escribiendo en un descriptor adjunto a la característica – el Client Characteristic Configuration Descriptor (CCCD, 0x2902). Escribir 0x0001 habilita las notificaciones, 0x0002 habilita las indicaciones y 0x0000 deshabilita ambas. El método aioble.ClientCharacteristic.subscribe() realiza la escritura por ti, con las palabras clave notify=True e indicate=True.
Una vez suscrito, el cliente espera los empujes entrantes con notified() e indicated() – ambas corrutinas asíncronas que se suspenden hasta que llega el siguiente empuje.
11.7.3. La MTU rige el tamaño de la carga útil¶
Cada operación está limitada por la MTU negociada en la que se asentó la conexión en el momento del enlace. La MTU por defecto es de 23 bytes, lo que deja 20 bytes para los bytes del valor de la característica tras la cabecera GATT. Cualquier cosa mayor que eso tiene que caber en una MTU más grande (negociada al alza mediante aioble.DeviceConnection.exchange_mtu(), hasta 512 bytes en la cámara) o dividirse en varias características o varias notificaciones.
Las lecturas y escrituras iniciadas por el cliente de valores mayores que la MTU se gestionan entre bastidores mediante los procedimientos long de GATT (Read Long / Prepare-Write + Execute-Write); aioble los ejecuta de forma transparente, de modo que llamar a read() / write() con un valor sobredimensionado solo cuesta más idas y vueltas. Las notificaciones e indicaciones iniciadas por el servidor no se fragmentan – cada empuje está acotado por la MTU, y la aplicación divide cualquier cosa más grande en varias notificaciones o se sale por completo de GATT.
Para transferencias genuinamente grandes – un fotograma capturado, un lote de mediciones, un blob de firmware – la respuesta correcta suele ser salirse por completo de GATT y usar en su lugar un canal L2CAP (consulta Canales L2CAP).
11.7.4. Los dos lados de un vistazo¶
Las cinco operaciones se exponen de forma diferente en cada lado de la conexión:
En el servidor (el periférico, en la disposición habitual):
aioble.Characteristic.read()– lee el valor local actual de la base de datos GATT (el lado del servidor de «lo que vería el cliente»).aioble.Characteristic.write()– actualiza el valor local, opcionalmente enviando la actualización a cada cliente suscrito.aioble.Characteristic.notify()/indicate()– envía un empuje a un cliente concreto.aioble.Characteristic.written()– espera la siguiente escritura entrante de cualquier cliente.aioble.Characteristic.on_read()– función de retorno (callback) invocada de forma síncrona cuando un cliente lee, útil para calcular un valor bajo demanda.
En el cliente (el central, en la disposición habitual):
aioble.ClientCharacteristic.read()– pide al servidor el valor actual.aioble.ClientCharacteristic.write()– envía un nuevo valor, con o sin respuesta.aioble.ClientCharacteristic.subscribe()– habilita / deshabilita notificaciones e indicaciones.aioble.ClientCharacteristic.notified()/indicated()– espera el siguiente empuje.