Il primo passaggio per interagire con un dispositivo BLE è connettersi. Più precisamente, la connessione al server GATT sul dispositivo. Per connetterti a un server GATT su un dispositivo BLE, utilizza il metodo connectGatt(). Questo metodo accetta tre parametri: un oggetto Context, autoConnect (un valore booleano che indica se connettersi automaticamente al dispositivo BLE non appena diventa disponibile) e un riferimento a un BluetoothGattCallback:
var bluetoothGatt: BluetoothGatt? = null // ... bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
In questo modo, si connette al server GATT ospitato dal dispositivo BLE e restituisce un'istanza BluetoothGatt, che puoi utilizzare per eseguire operazioni client GATT. Il chiamante (l'app per Android) è il client GATT. Il
BluetoothGattCallback viene utilizzato per fornire risultati al client, ad esempio
lo stato della connessione, nonché eventuali altre operazioni del client GATT.
Configura un servizio associato
Nell'esempio seguente, l'app BLE fornisce un'attività
(DeviceControlActivity) per connettersi ai dispositivi Bluetooth, visualizzare i dati del dispositivo
e visualizzare i servizi e le caratteristiche GATT supportati dal dispositivo. In base
all'input dell'utente, questa attività comunica con un Service chiamato
BluetoothLeService, che interagisce con il dispositivo BLE utilizzando l'API BLE. Questa comunicazione viene gestita da un servizio associato, che consente all'attività di connettersi a BluetoothLeService e di chiamare le funzioni per connettersi ai dispositivi. BluetoothLeService richiede un'implementazione di Binder che fornisca l'accesso al servizio per l'attività.
class BluetoothLeService : Service() { private val binder = LocalBinder() override fun onBind(intent: Intent): IBinder? { return binder } inner class LocalBinder : Binder() { fun getService(): BluetoothLeService { return this@BluetoothLeService } } }
L'attività può avviare il servizio utilizzando bindService(), passando un
Intent per avviare il servizio, un'implementazione
ServiceConnection per ascoltare gli
eventi di connessione e disconnessione e un flag per specificare opzioni di connessione
aggiuntive.
class DeviceControlActivity : AppCompatActivity() { private var bluetoothService: BluetoothLeService? = null // Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> // call functions on service to check connection and connect to devices } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.gatt_services_characteristics) val gattServiceIntent = Intent(this, BluetoothLeService::class.java) bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE) } }
Configura BluetoothAdapter
Dopo che l'attività si è associata al servizio, quest'ultimo deve accedere a
BluetoothAdapter. Deve verificare che l'adattatore sia disponibile sul dispositivo. Per ulteriori informazioni su BluetoothAdapter, vedi
Configura il Bluetooth. L'esempio seguente racchiude questo codice di configurazione in una funzione initialize() che restituisce un valore Boolean che indica l'esito positivo.
private const val TAG = "BluetoothLeService" class BluetoothLeService : Service() { private var bluetoothAdapter: BluetoothAdapter? = null fun initialize(): Boolean { bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() if (bluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter.") return false } return true } // ... }
L'attività chiama questa funzione all'interno della sua implementazione ServiceConnection.
La gestione di un valore restituito false dalla funzione initialize() dipende dalla tua applicazione. Puoi mostrare all'utente un messaggio di errore che indica che il dispositivo corrente non supporta l'operazione Bluetooth o disattivare le funzionalità che richiedono il Bluetooth per funzionare. Nell'esempio seguente, l'attività chiama finish() per rimandare l'utente alla schermata precedente.
class DeviceControlActivity : AppCompatActivity() { // Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> if (!bluetooth.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth") finish() } // perform device connection } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } } // ... }
Collega a un dispositivo
Dopo l'inizializzazione dell'istanza BluetoothLeService, questa può connettersi al dispositivo BLE. L'attività deve inviare l'indirizzo del dispositivo al servizio in modo che possa avviare la connessione. Il servizio chiama prima
getRemoteDevice() su BluetoothAdapter per accedere al dispositivo. Se l'adattatore non riesce a trovare un dispositivo con quell'indirizzo, getRemoteDevice() genera un IllegalArgumentException.
fun connect(address: String): Boolean { bluetoothAdapter?.let { adapter -> try { val device = adapter.getRemoteDevice(address) } catch (exception: IllegalArgumentException) { Log.w(TAG, "Device not found with provided address.") return false } // connect to the GATT server on the device return true } ?: run { Log.w(TAG, "BluetoothAdapter not initialized") return false } }
DeviceControlActivity chiama questa funzione connect() dopo l'inizializzazione del servizio. L'attività deve trasmettere l'indirizzo del dispositivo BLE. Nell'esempio seguente, l'indirizzo del dispositivo viene passato all'attività come extra dell'intent.
// Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> if (!bluetooth.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth") finish() } // perform device connection deviceAddress?.let { bluetooth.connect(it) } } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } }
Dichiara il callback GATT
Dopo che l'attività ha comunicato al servizio a quale dispositivo connettersi e il servizio si connette al dispositivo, il servizio deve connettersi al server GATT sul dispositivo BLE. Questa connessione richiede un BluetoothGattCallback per ricevere notifiche sullo stato della connessione, sul Service Discovery, sulle letture delle caratteristiche e sulle notifiche delle caratteristiche.
Questo documento si concentra sulle notifiche relative allo stato della connessione. Per scoprire come rilevare i servizi, leggere le caratteristiche e richiedere le notifiche delle caratteristiche, consulta Trasferisci dati BLE.
La funzione onConnectionStateChange() viene attivata quando la connessione al server GATT del dispositivo cambia. Nell'esempio seguente, il callback è definito nella classe Service in modo che possa essere utilizzato con BluetoothDevice dopo che il servizio si connette.
private val bluetoothGattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server } } }
Connettiti al servizio GATT
Dopo aver dichiarato BluetoothGattCallback, il servizio può utilizzare l'oggetto BluetoothDevice della funzione connect() per connettersi al servizio GATT sul dispositivo.
Il servizio chiama la funzione connectGatt(). Ciò richiede un oggetto Context, un flag booleano autoConnect e BluetoothGattCallback. In questo esempio, l'app si connette direttamente al dispositivo BLE, quindi viene passato false per autoConnect.
Il servizio aggiunge anche una proprietà BluetoothGatt per poter chiudere la connessione quando non è più necessaria.
class BluetoothLeService : Service() { // ... private var bluetoothGatt: BluetoothGatt? = null // ... fun connect(address: String): Boolean { bluetoothAdapter?.let { adapter -> try { val device = adapter.getRemoteDevice(address) // connect to the GATT server on the device bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback) return true } catch (exception: IllegalArgumentException) { Log.w(TAG, "Device not found with provided address. Unable to connect.") return false } } ?: run { Log.w(TAG, "BluetoothAdapter not initialized") return false } } }
Aggiornamenti sulle trasmissioni
Quando il servizio si connette o si disconnette dal server GATT, deve notificare l'attività del nuovo stato. Esistono diversi modi per farlo. L'esempio seguente utilizza le trasmissioni per inviare le informazioni dal servizio all'attività.
Il servizio dichiara una funzione per trasmettere il nuovo stato. Questa funzione accetta una stringa di azione che viene passata a un oggetto Intent prima di essere trasmessa al sistema.
private fun broadcastUpdate(action: String) { val intent = Intent(action) sendBroadcast(intent) }
Una volta implementata la funzione di trasmissione, il servizio la utilizza all'interno di BluetoothGattCallback per inviare informazioni sullo stato della connessione con il server GATT. Il servizio dichiara costanti che rappresentano le azioni Intent
e lo stato di connessione corrente del servizio.
class BluetoothLeService : Service() { private var connectionState = STATE_DISCONNECTED private val bluetoothGattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server connectionState = STATE_CONNECTED broadcastUpdate(ACTION_GATT_CONNECTED) } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server connectionState = STATE_DISCONNECTED broadcastUpdate(ACTION_GATT_DISCONNECTED) } } } // ... companion object { const val ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED" const val ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED" private const val STATE_DISCONNECTED = 0 private const val STATE_CONNECTED = 2 } }
Ascolta gli aggiornamenti sull'attività
Dopo che il servizio ha trasmesso gli aggiornamenti della connessione, l'attività deve implementare un BroadcastReceiver. Registra questo ricevitore durante la configurazione dell'attività e annulla la registrazione quando l'attività non è più visibile sullo schermo. Ascoltando gli eventi del servizio, l'attività è in grado di aggiornare l'interfaccia utente in base allo stato di connessione corrente con il dispositivo BLE.
class DeviceControlActivity : AppCompatActivity() { private val gattUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { BluetoothLeService.ACTION_GATT_CONNECTED -> { connected = true updateConnectionState(R.string.connected) } BluetoothLeService.ACTION_GATT_DISCONNECTED -> { connected = false updateConnectionState(R.string.disconnected) } } } } override fun onResume() { super.onResume() registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter()) bluetoothService?.let { service -> deviceAddress?.let { address -> val result = service.connect(address) Log.d(TAG, "Connect request result=$result") } } } override fun onPause() { super.onPause() unregisterReceiver(gattUpdateReceiver) } private fun makeGattUpdateIntentFilter(): IntentFilter { return IntentFilter().apply { addAction(BluetoothLeService.ACTION_GATT_CONNECTED) addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED) } } }
In Trasferimento di dati BLE, BroadcastReceiver viene utilizzato anche per comunicare il Service Discovery e i dati delle caratteristiche dal dispositivo.
Chiudi connessione GATT
Un passaggio importante quando si gestiscono le connessioni Bluetooth è chiudere la connessione quando non è più necessaria. Per farlo, chiama la funzione close() sull'oggetto BluetoothGatt. Nell'esempio seguente, il servizio contiene il riferimento a BluetoothGatt. Quando l'attività viene scollegata dal servizio, la connessione viene chiusa per evitare di consumare la batteria del dispositivo.
class BluetoothLeService : Service() { // ... override fun onUnbind(intent: Intent?): Boolean { close() return super.onUnbind(intent) } private fun close() { bluetoothGatt?.let { gatt -> gatt.close() bluetoothGatt = null } } }