11.6. Servicios y características

Una vez que GAP ha puesto a dos dispositivos en una conexión abierta, la capa que está por encima – el Generic Attribute Profile, GATT – tiene que dar significado a los bytes que fluyen a través de esa conexión. La elección de BLE aquí es inusual. Mientras que TCP expone un flujo de bytes en bruto y deja que la aplicación invente su propio entramado, GATT expone una pequeña base de datos clave/valor que un lado aloja y el otro lee, escribe o a la que se suscribe.

Esa base de datos es en lo que los diseñadores de aplicaciones pasan la mayor parte de su tiempo de BLE pensando. Lo que la cámara publica a un teléfono, lo que observa en un sensor remoto, cómo un teclado Bluetooth indica a su anfitrión qué tecla se pulsó – todos son valores de características en alguna base de datos GATT en algún lugar.

11.6.1. Dos ejes de roles, no uno

Una fuente frecuente de confusión: peripheral / central y server / client son dos ejes independientes, no sinónimos.

  • Peripheral y central son roles de GAP, establecidos durante la conexión. El peripheral se anuncia y se conecta a él; el central escanea e inicia la conexión. Esto queda fijado en el momento en que se establece el enlace y no cambia.

  • Server y client son roles de GATT, establecidos por cada operación de característica. El server aloja la característica; el client la lee, escribe o se suscribe a ella.

Los dos ejes están desacoplados por la especificación. Un peripheral normalmente es el server (una banda de frecuencia cardíaca publica sus lecturas) y un central normalmente es el client (un teléfono las lee), pero BLE permite cualquier combinación – un peripheral puede descubrir una característica en el central al que acaba de conectarse, o una sola conexión puede alojar servicios en ambos lados a la vez.

La mayoría de las aplicaciones de cámara se ciñen al emparejamiento convencional (peripheral + server, o central + client), por lo que el resto de esta sección los trata como un único eje cuando lo que se describe es el caso convencional. Cuando la distinción importa, ambos términos se detallan explícitamente.

11.6.2. Dentro de la base de datos

Una base de datos GATT es un árbol. Las hojas llevan los bytes reales. Las ramas agrupan las hojas relacionadas en unidades con significado para las personas.

Un árbol con un nodo superior etiquetado como "GATT database". Debajo de él, tres nodos de servicio etiquetados como "Generic Access (0x1800)", "Battery (0x180F)" y "Environmental Sensing (0x181A)". Cada servicio tiene nodos de característica hijos; el servicio Battery tiene "Battery Level (0x2A19)" con un descriptor hijo "CCCD". El servicio Environmental Sensing tiene "Temperature (0x2A6E)" y "Humidity (0x2A6F)".

Una base de datos GATT. Los servicios agrupan características; las características llevan los bytes de la aplicación; los descriptores llevan metadatos sobre la característica.

Hay tres tipos de nodo:

  • Un servicio es un grupo lógico de valores relacionados. El Bluetooth SIG publica definiciones de servicios estándar para casos de uso comunes – Battery Service para el nivel de batería, Environmental Sensing para temperatura / humedad / presión, Heart Rate para monitores de frecuencia cardíaca – de modo que una aplicación genérica en un teléfono pueda reconocer un servicio que nunca antes ha visto. Una aplicación también es libre de definir sus propios servicios para cosas que el SIG no ha estandarizado.

  • Una característica es un valor con nombre dentro de un servicio. El servicio Battery tiene una única característica – Battery Level, un porcentaje de un byte. Environmental Sensing tiene características separadas para temperatura, humedad, presión, etc. Una característica es la unidad de las operaciones de GATT – se lee una característica, se escribe una característica, se suscribe a una característica.

  • Un descriptor es metadato adjunto a una característica. Algunos descriptores están estandarizados – el Client Characteristic Configuration Descriptor (CCCD) es el más famoso, porque escribir en él es la forma en que un client le dice al server «envíame notificaciones sobre esta característica». Otros están definidos por el usuario y llevan cosas como el formato de presentación o propiedades extendidas.

Un server GATT (normalmente el peripheral) declara su base de datos una vez al arrancar y la base de datos no cambia mientras se ejecuta. Un client GATT (normalmente el central) descubre lo que hay en la base de datos tras conectarse – recorriendo el árbol, leyendo los UUID de los servicios que encuentra, y luego las características dentro de cada uno.

11.6.3. UUID

Cada servicio, característica y descriptor tiene un UUID (Universally Unique IDentifier) que identifica qué tipo de cosa es. Los UUID vienen en tres anchos:

  • 16 bits. Reservado para estándares definidos por el Bluetooth SIG. Battery Service es 0x180F. Battery Level (una característica) es 0x2A19. La lista completa se publica en el sitio de números asignados del Bluetooth SIG, en https://www.bluetooth.com/specifications/assigned-numbers/.

  • 32 bits. Un punto intermedio que rara vez se usa.

  • 128 bits. Lo que usa todo el mundo – un fabricante o aplicación genera uno al azar y lo usa para su servicio o característica personalizados. Las cámaras que definen su propio protocolo viven aquí.

La clase bluetooth.UUID acepta cualquiera de los tres anchos:

import bluetooth

BATTERY_SERVICE = bluetooth.UUID(0x180F)
CUSTOM_SERVICE = bluetooth.UUID("12345678-1234-5678-9abc-def012345678")

Un UUID de 16 bits se codifica en una pequeña carga útil de publicidad, lo cual es una razón por la que los servicios estándar son preferibles cuando existe uno – una banda de frecuencia cardíaca que anuncia 0x180D (Heart Rate) cuesta dos bytes; un UUID personalizado cuesta dieciséis. Para aplicaciones que no necesitan interoperabilidad estándar, un UUID de 128 bits generado es la respuesta correcta.

11.6.4. Qué te aportan los servicios estandarizados por el SIG

El argumento para usar un servicio estándar es sencillo: las aplicaciones existentes ya saben cómo comunicarse con él. Un dispositivo que anuncia el servicio Heart Rate (0x180D) y expone la característica Heart Rate Measurement (0x2A37) funciona con todas las aplicaciones de fitness del planeta sin que nadie escriba código nuevo. Un dispositivo que reimplementa los mismos datos con UUID personalizados necesita su propia aplicación complementaria y su propio documento de protocolo.

Los estándares tienen un coste. Los diseños de bytes dentro de cada característica están especificados – el SIG decidió que Heart Rate Measurement es un campo de flags de un byte seguido de un valor de frecuencia cardíaca de 8 o 16 bits, seguido opcionalmente de intervalos R-R – y un dispositivo conforme tiene que seguir esos diseños. Los servicios personalizados están libres de esa restricción.

La respuesta pragmática para las cámaras: usa el servicio estándar cuando exista uno para el tipo de datos que tienes (Battery Service, Environmental Sensing), y define uno personalizado con un UUID de 128 bits para cualquier cosa específica de tu aplicación.

11.6.5. Objetos del lado del servidor y del lado del cliente

Para los mismos bloques conceptuales (servicio, característica, descriptor), cada biblioteca de GATT expone dos conjuntos paralelos de objetos: