Nel precedente articolo ho introdotto la tecnologia Bluetooth Low Energy e il processo di advertising.
Abbiamo visto che un dispositivo BLE può sfruttare i pacchetti di advertising per inviare dati; in tal caso il dispositivo viene chiamato broadcaster, mentre i dispositivi che ricevono i dati sono chiamati observers.
Il payload di un pacchetto di advertising ha la seguente struttura:
ADV ADDR è l’indirizzo MAC del dispositivo (indirizzo che veniva visualizzato in console dal programma sviluppato nel precedente articolo), mentre ADV DATA è un campo, lungo un massimo di 31 bytes, organizzato in una o più strutture formate da 3 elementi:
- AD length è la lunghezza complessiva (in bytes) della singola struttura dati
- AD type è il tipo di informazione riportata in tale struttura
- AD data è l’informazione
Il sito ufficiale del Bluetooth Special Interest Group riporta l’elenco dei possibili AD types.
Un dispositivo può ad esempio trasmettere il proprio nome utilizzando l’AD type 0x09:
Durante lo scan, il driver Bluetooth mette a disposizione del programma i dati (ADV DATA) ricevuti nell’array scan_result->scan_rst.ble_adv. Tale array contiene valori uint8_t ed ha dimensione scan_result->scan_rst.adv_data_len.
La libreria Bluedroid contiene un metodo, esp_ble_resolve_adv_data(), che consente di ottenere il valore di un particolare AD type passando i dati grezzi (raw data). Il file di header esp_gap_ble_api.h contiene anche la definzione degli AD types più comuni:
Nel mio repository Github trovate una nuova versione del programma che effettua lo scan: utilizzando quanto spiegato sopra ora estrae anche – se disponibile – il nome del device e lo visualizza in console:
iBeacon
Una particolare famiglia di dispositivi broadcasters sono gli iBeacon. Tali dispositivi sono stati pensati da Apple per consentire interazioni con dispositivi IOS (iPhone…) basati sulla location awareness. Facciamo un esempio: un telefono iPhone può “accorgersi” di essere vicino ad un particolare iBeacon, associato ad una stanza di un museo, e proporre all’utente la visualizzazione di una breve guida delle opere esposte.
Le specifiche dei dispositivi iBeacon si trovano sul portale sviluppatore Apple. Gli iBeacon funzionano inserendo nei pacchetti di advertising un particolare payload (ADV DATA):
Il primo dato inviato è di tipo flags (0x01). Ogni bit ha un diverso significato, tipicamente gli iBeacon utilizzano il valore 0x0A.
Il secondo dato inviato è di tipo 0xFF, ovvero Manufacturer Specific Data. Lo standard Bluetooth lascia liberi i vari produttori di usare l’ID 0xFF per inviare dati custom. I dati inviati hanno lunghezza 25 bytes (0x1A – 0x01 che è la lunghezza del campo AD type).
Le specifiche Apple per gli iBeacon suddividono ulteriormente il campo AD data in diversi elementi:
Il primo campo indica il produttore; normalmente gli iBeacon utilizzano il codice 0x004C, assegnato ad Apple Inc. I successivi due indicano il tipo di iBeacon e hanno valore fisso (0x02 e 0x15). Il campo UUID, insieme con i campi Major e Minor (facoltativi, possono essere impostati a 0) identificano univocamente il singolo iBeacon. Infine il campo TX power contiene una misurazione, ad un metro di distanza dall’iBeacon, della potenza ricevuta ed è utile per rendere più accurata la stima della distanza tra il telefono e l’iBeacon stesso.
esp32
Ho sviluppato un programma per il chip esp32 che attiva un relay se rileva un particolare iBeacon. Tramite menuconfig è possibile configurare l’UUID dell’iBeacon da monitorare, il pin a cui è collegato il led e il timeout in secondi trascorsi i quali – se non viene nuovamente rilevato l’iBeacon – il programma spegne il led. E’ inoltre possibile impostare una soglia minima di potenza in modo da poter regolare la distanza di rilevamento dell’iBeacon.
Il programma verifica se il pacchetto ricevuto (evento ESP_GAP_SEARCH_INQ_RES_EVT) è stato inviato da un iBeacon verificando che tale pacchetto sia di 30 bytes e che contenga nel suo header i valori indicati sopra:
// iBeacon fixed header ibeacon_header_t ibeacon_fixed_header = { .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = 0x004C, .beacon_type = 0x1502 }; |
La verifica avviene utilizzando il comando memcmp che confronta due blocchi di memoria:
if(memcmp(adv_data, ibeacon_fixed_header, sizeof(ibeacon_fixed_header))) result = true; |
Il sorgente del programma si trova nel mio repository Github, ecco un video che mostra il suo funzionamento: