Molti dispositivi Android integrano una versione a basso consumo del Bluetooth, nota come Bluetooth Low Energy (spesso abbreviato in BLE). Le API che ne trattano le funzionalità sono
parte del sistema operativo già dalla versione 4.3 (API di livello 18). Alcuni aspetti possono rendere somiglianti le due tecnologie per la connettività ravvicinata ma si tenga presente che sono totalmente distinte e differenti. Affinché BLE possa dissipare meno risorse, esso richiede un hardware apposito, un protocollo semplificato ed algoritmi più immediati che richiedono microcontrollori meno dispendiosi in termini energetici.
In questa lezione tratteremo gli aspetti fondamentali dell'uso di BLE su Android, da un punto di vista concettuale e pratico.
Permission e disponibilità di BLE
Per utilizzare BLE è necessario impostare le permission nel file AndroidManifest.xml (identiche a quelle del Bluetooth):
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
È importante anche saper verificare se BLE è disponibile nel dispositivo (cosa generalmente vera nei dispositivi più recenti, almeno nella versione classica di questa tecnologia). Lo si può fare in due modalità. La prima è via XML, tramite manifest:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
Con l'attributo required
impostato a true
l'app non risulterà nemmeno installabile su dispositivi senza BLE. Via Java, la verifica potrà essere condotta nel segente modo:
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
// BLE non disponibile
}
Glossario
I termini fondamentali di BLE non sono molti, ma di grande importanza. Pertanto vanno chiariti subito:
- il GATT (General Attribute Profile) è il profilo generale che viene attribuito al dispositivo che scambia informazioni mediante BLE, mediante il protocollo ATT (Attribute Protocol).
I dispositivi possono svolgere il ruolo di server o client GATT; - i dati scambiati sono indicati come attributi. Questi sono organizzati come caratteristiche e servizi;
- le caratteristiche sono costituite da valori accompagnati da un numero variabile di descrittori. Questi ultimi sono informazioni che possono assumere un qualsiasi significato ad essi attribuito dallo sviluppatore;
- i servizi sono collection di caratteristiche.
È importante rammentare sempre che la caratteristica fondamentale di BLE è il suo basso consumo energetico. Pertanto, tutte le informazioni da scambiare consistono di pochi byte.
Rilevare i dispositivi
Le due classi principali coinvolte nella ricerca di dispositivi sono il BluetoothManager
ed il BluetoothAdapter
. Questi vengono per lo più impiegati nella ricerca di dispositivi raggiungibili e nella verifica che il Bluetooth sia stato effettivamente attivato nel dispositivo:
// recuperiamo un riferimento al BluetoothManager
BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
// recuperiamo un riferimento all'adapter Bluetooth
bluetoothAdapter = bluetoothManager.getAdapter();
// verifichiamo che Bluetooth sia attivo nel dispositivo
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, REQUEST_ENABLE_BT);
}
I passaggi qui sopra riportati svolgono, rispettivamente, l'inizializzazione del BluetoothManager
(ne recuperiamo un riferimento come servizio di sistema), la richiesta di un adapter per il Bluetooth e la verifica dell'avvenuta attivazione di tale connettività.
Dopo le impostazioni preliminari, si può iniziare la scansione dei dispositivi. Per tale operazione viene impiegato un oggetto di classe BluetoothLeScanner
, reperibile direttamente mediante il BluetoothAdapter
, dotato degli appositi metodi startScan
e stopScan
finalizzati, rispettivamente, all'avvio della scansione e al suo arresto:
BluetoothLeScanner scanner;
...
...
scanner=mBluetoothAdapter.getBluetoothLeScanner();
Si tenga presente che per diverso tempo sono stati utilizzati i metodi startLeScan
e stopLeScan
della classe BluetoothAdapter
, analoghi a quelli citati in precedenza: sono stati deprecati a partire dalle API 21, pertanto è sconsigliabile il loro utilizzo.
I risultati della scansione verranno via via forniti ad un oggetto di tipo ScanCallback
, passato come argomento al metodo startScan
. Per svolgere una scansione efficiente, si tengano presenti le seguenti indicazioni:
- è bene che la ricerca di dispositivi termini dopo un certo lasso di tempo; pertanto, va fissato un timeout per interrompere la scansione. Tale operazione può essere svolta dal programmatore mediante uno dei vari espedienti a disposizione (pensiamo all'utilizzo di un Handler);
- un valore (possibilmente booleano) dovrebbe sempre indicare una scansione in corso, in modo da mettere in condizione l'interfaccia utente di segnalare lo stato della ricerca;
- l'oggetto di classe
ScanCallback
riceverà più volte l'indicazione dello stesso dispositivo durante la ricerca. Sarebbe il caso di predisporre apposite accortezze atte ad individuare subito dispositivi già segnalati. Si pensi al caso - piuttosto comune - in cui l'elenco dei terminali BLE rintracciati venga mostrato in una ListView, RecyclerView o altro AdapterView: si dovrebbe personalizzare l'adapter ad essa collegato in modo da fargli accettare solo gli elementi incontrati per la prima volta.
Vediamo sinteticamente l'applicazione di questi concetti. Supponiamo di predisporre nel nostro codice un metodo denominato startBleScan
che avvii una scansione BLE:
// variabili d'istanza della classe
private boolean scanning;
private BluetoothLeScanner scanner;
private Handler handler = new Handler();
private final static int SCAN_PERIOD = 10000;
...
...
public void startBleScan() {
// scanning a true significa "scansione in corso"
scanning = true;
// avviamo la scansione da un thread secondario
AsyncTask.execute(new Runnable() {
@Override
public void run() {
// avvio della scansione
scanner.startScan(scanCallback);
}
});
// l'oggetto Handler viene utilizzato per impostare un timeout
handler.postDelayed(new Runnable() {
@Override
public void run() {
// tempo scaduto per la scansione
// scansione interrotta
scanner.stopScan(scanCallback);
// scanning=false significa "Nessuna scansione in corso"
scanning = false;
}
}, SCAN_PERIOD);
// SCAN_PERIOD indica una durata in millisecondi
}
L'oggetto scanCallback
fa riferimento ad un listener che definiremo separatamente:
private ScanCallback leScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
// codice da eseguire
}
};
L'oggetto di tipo ScanResult
(il secondo argomento del metodo onScanResult
) durante la scansione porterà i dati relativi ad ogni dispositivo individuato. Mediante il suo metodo getDevice()
si ottiene un oggetto di tipo BluetoothDevice
che presenta metodi da cui trarre informazioni (getAddress()
per l'indirizzo e getName()
per il nome associato
all'apparecchio, tanto per fare un paio di esempi) ed altri dedicati alla connessione GATT.
Conclusioni
Quanto abbiamo visto sinora permette di iniziare ad utilizzare Bluetooth Low Energy ed individuare apparecchi disponibili al dialogo. Una volta appianati questi primi concetti e preso confidenza con le classi coinvolte, si potranno iniziare ad approfondire le tematiche più pratiche come connessioni e scambi di dati.