11.6. Servizi e caratteristiche

Una volta che il GAP ha portato due dispositivi in una connessione aperta, il livello superiore – il Generic Attribute Profile, GATT – deve dare un significato ai byte che scorrono attraverso quella connessione. La scelta del BLE in questo caso è insolita. Mentre il TCP espone un flusso di byte grezzo e lascia all’applicazione il compito di inventare il proprio framing, il GATT espone un piccolo database chiave/valore che un lato ospita e l’altro legge, scrive o a cui si sottoscrive.

Quel database è ciò a cui i progettisti di applicazioni dedicano la maggior parte del loro tempo dedicato al BLE. Ciò che la camera pubblica verso un telefono, ciò che osserva su un sensore remoto, il modo in cui una tastiera Bluetooth comunica al proprio host quale tasto è stato premuto – sono tutti valori di caratteristiche in qualche database GATT da qualche parte.

11.6.1. Due assi di ruolo, non uno

Una frequente fonte di confusione: peripheral / central e server / client sono due assi indipendenti, non sinonimi.

  • Peripheral e central sono ruoli GAP, impostati durante la connessione. Il peripheral si annuncia e viene connesso; il central effettua la scansione e avvia la connessione. Questo viene stabilito nel momento in cui il collegamento si attiva e non cambia.

  • Server e client sono ruoli GATT, impostati per ogni operazione su una caratteristica. Il server ospita la caratteristica; il client la legge, la scrive o vi si sottoscrive.

I due assi sono disaccoppiati dalla specifica. Un peripheral è di solito il server (una fascia cardiaca pubblica le proprie letture) e un central è di solito il client (un telefono le legge), ma il BLE consente qualsiasi combinazione – un peripheral può scoprire una caratteristica sul central a cui si è appena connesso, oppure una singola connessione può ospitare servizi su entrambi i lati contemporaneamente.

La maggior parte delle applicazioni per camera si attiene all’abbinamento convenzionale (peripheral + server, oppure central + client), quindi il resto di questa sezione li tratta come un unico asse quando si descrive il caso convenzionale. Quando la distinzione è importante, entrambi i termini vengono indicati esplicitamente.

11.6.2. All’interno del database

Un database GATT è un albero. Le foglie contengono i byte effettivi. I rami raggruppano foglie correlate in unità comprensibili per l’uomo.

Un albero con un nodo superiore etichettato "GATT database". Al di sotto, tre nodi Service etichettati "Generic Access (0x1800)", "Battery (0x180F)" ed "Environmental Sensing (0x181A)". Ogni Service ha nodi figli Characteristic; il servizio Battery ha "Battery Level (0x2A19)" con un Descriptor figlio "CCCD". Il servizio Environmental Sensing ha "Temperature (0x2A6E)" e "Humidity (0x2A6F)".

Un database GATT. I servizi raggruppano le caratteristiche; le caratteristiche contengono i byte dell’applicazione; i descrittori contengono i metadati relativi alla caratteristica.

Esistono tre tipi di nodo:

  • Un service è un gruppo logico di valori correlati. Il Bluetooth SIG pubblica definizioni di servizio standard per casi d’uso comuni – Battery Service per il livello della batteria, Environmental Sensing per temperatura / umidità / pressione, Heart Rate per i cardiofrequenzimetri – in modo che un’app generica su un telefono possa riconoscere un servizio che non ha mai visto prima. Un’applicazione è inoltre libera di definire i propri servizi per cose che il SIG non ha standardizzato.

  • Una characteristic è un valore con nome all’interno di un servizio. Il servizio Battery ha una singola caratteristica – Battery Level, una percentuale di un byte. Environmental Sensing ha caratteristiche separate per temperatura, umidità, pressione e così via. Una caratteristica è l’unità delle operazioni GATT – si legge una caratteristica, si scrive una caratteristica, ci si sottoscrive a una caratteristica.

  • Un descriptor è un metadato associato a una caratteristica. Alcuni descrittori sono standardizzati – il Client Characteristic Configuration Descriptor (CCCD) è il più famoso, perché scrivere su di esso è il modo in cui un client comunica al server «inviami le notifiche su questa caratteristica». Altri sono definiti dall’utente e contengono cose come il formato di presentazione o le proprietà estese.

Un server GATT (tipicamente il peripheral) dichiara il proprio database una sola volta all’avvio e il database non cambia durante l’esecuzione. Un client GATT (tipicamente il central) scopre cosa contiene il database dopo la connessione – percorrendo l’albero, leggendo gli UUID dei servizi che trova, poi le caratteristiche all’interno di ciascuno.

11.6.3. UUID

Ogni servizio, caratteristica e descrittore ha un UUID (Universally Unique IDentifier) che identifica di che tipo di cosa si tratta. Gli UUID hanno tre larghezze:

  • 16 bit. Riservati agli standard definiti dal Bluetooth SIG. Battery Service è 0x180F. Battery Level (una caratteristica) è 0x2A19. L’elenco completo è pubblicato sul sito degli assigned-numbers del Bluetooth SIG all’indirizzo https://www.bluetooth.com/specifications/assigned-numbers/.

  • 32 bit. Una via di mezzo usata raramente.

  • 128 bit. Ciò che usano tutti gli altri – un produttore o un’applicazione ne genera uno in modo casuale e lo utilizza per il proprio servizio o caratteristica personalizzati. Le camere che definiscono il proprio protocollo rientrano in questa categoria.

La classe bluetooth.UUID accetta una qualsiasi delle tre larghezze:

import bluetooth

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

Un UUID a 16 bit viene codificato in un piccolo payload di advertising, il che è uno dei motivi per cui i servizi standard sono preferibili quando ne esiste uno – una fascia cardiaca che annuncia 0x180D (Heart Rate) costa due byte; un UUID personalizzato ne costa sedici. Per le applicazioni che non necessitano di interoperabilità standard, un UUID a 128 bit generato è la risposta giusta.

11.6.4. Cosa offrono i servizi standardizzati dal SIG

Il motivo per usare un servizio standard è semplice: le app esistenti sanno già come comunicare con esso. Un dispositivo che annuncia il servizio Heart Rate (0x180D) ed espone la caratteristica Heart Rate Measurement (0x2A37) funziona con ogni app di fitness del pianeta senza che nessuno scriva nuovo codice. Un dispositivo che reimplementa gli stessi dati con UUID personalizzati necessita di una propria app companion e di un proprio documento di protocollo.

Gli standard hanno comunque un costo. I layout dei byte all’interno di ogni caratteristica sono specificati – il SIG ha deciso che Heart Rate Measurement è un campo flags di un singolo byte seguito da un valore di frequenza cardiaca a 8 bit o a 16 bit, opzionalmente seguito da intervalli R-R – e un dispositivo conforme deve rispettare quei layout. I servizi personalizzati sono liberi da questo vincolo.

La risposta pragmatica per le camere: usare il servizio standard quando ne esiste uno per il tipo di dati che si possiede (Battery Service, Environmental Sensing) e definirne uno personalizzato con un UUID a 128 bit per qualsiasi cosa specifica della propria applicazione.

11.6.5. Oggetti lato server e lato client

Per gli stessi elementi concettuali di base (servizio, caratteristica, descrittore), ogni libreria GATT espone due insiemi paralleli di oggetti: