La première étape pour interagir avec un appareil BLE consiste à s'y connecter. Plus précisément, la connexion au serveur GATT sur l'appareil. Pour vous connecter à un serveur GATT sur un appareil BLE, utilisez la méthode connectGatt(). Cette méthode utilise trois paramètres : un objet Context, autoConnect (un booléen indiquant s'il faut se connecter automatiquement à l'appareil BLE dès qu'il est disponible) et une référence à un BluetoothGattCallback :
var bluetoothGatt: BluetoothGatt? = null // ... bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
Cela permet de se connecter au serveur GATT hébergé par l'appareil BLE et renvoie une instance BluetoothGatt, que vous pouvez ensuite utiliser pour effectuer des opérations de client GATT. L'appelant (l'application Android) est le client GATT. BluetoothGattCallback permet de fournir des résultats au client, tels que l'état de la connexion, ainsi que d'autres opérations du client GATT.
Configurer un service lié
Dans l'exemple suivant, l'application BLE fournit une activité (DeviceControlActivity) pour se connecter aux appareils Bluetooth, afficher les données de l'appareil et afficher les services et caractéristiques GATT pris en charge par l'appareil. En fonction de la saisie de l'utilisateur, cette activité communique avec un Service appelé BluetoothLeService, qui interagit avec l'appareil BLE via l'API BLE. La communication est effectuée à l'aide d'un service lié qui permet à l'activité de se connecter à BluetoothLeService et d'appeler des fonctions pour se connecter aux appareils. BluetoothLeService a besoin d'une implémentation Binder qui fournit l'accès au service pour l'activité.
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'activité peut démarrer le service à l'aide de bindService(), en transmettant un Intent pour démarrer le service, une implémentation ServiceConnection pour écouter les événements de connexion et de déconnexion, et un indicateur pour spécifier des options de connexion supplémentaires.
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) } }
Configurer le BluetoothAdapter
Une fois le service lié, il doit accéder à BluetoothAdapter. Il doit vérifier que l'adaptateur est disponible sur l'appareil. Pour en savoir plus sur BluetoothAdapter, consultez Configurer le Bluetooth. L'exemple suivant encapsule ce code de configuration dans une fonction initialize() qui renvoie une valeur Boolean indiquant la réussite.
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'activité appelle cette fonction dans son implémentation ServiceConnection.
La gestion d'une valeur de retour "false" de la fonction initialize() dépend de votre application. Vous pouvez afficher un message d'erreur indiquant à l'utilisateur que l'appareil actuel n'est pas compatible avec l'opération Bluetooth ou désactiver les fonctionnalités qui nécessitent le fonctionnement du Bluetooth. Dans l'exemple suivant, finish() est appelé sur l'activité pour renvoyer l'utilisateur à l'écran précédent.
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 } } // ... }
Connexion à un appareil
Une fois l'instance BluetoothLeService initialisée, elle peut se connecter à l'appareil BLE. L'activité doit envoyer l'adresse de l'appareil au service pour qu'il puisse établir la connexion. Le service appellera d'abord getRemoteDevice() sur BluetoothAdapter pour accéder à l'appareil. Si l'adaptateur ne parvient pas à trouver un appareil avec cette adresse, getRemoteDevice() génère une 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 appelle cette fonction connect() une fois le service initialisé. L'activité doit transmettre l'adresse de l'appareil BLE. Dans l'exemple suivant, l'adresse de l'appareil est transmise à l'activité en tant qu'extra d'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 } }
Déclarer le rappel GATT
Une fois que l'activité indique au service à quel appareil se connecter et que le service se connecte à l'appareil, le service doit se connecter au serveur GATT sur l'appareil BLE. Cette connexion nécessite un BluetoothGattCallback pour recevoir des notifications sur l'état de la connexion, la découverte de services, les lectures de caractéristiques et les notifications de caractéristiques.
Cet article porte sur les notifications d'état de la connexion. Consultez Transférer des données BLE pour savoir comment effectuer la découverte de services, la lecture de caractéristiques et la demande de notifications de caractéristiques.
La fonction onConnectionStateChange() est déclenchée lorsque la connexion au serveur GATT de l'appareil change.
Dans l'exemple suivant, le rappel est défini dans la classe Service afin qu'il puisse être utilisé avec BluetoothDevice une fois que le service s'y connecte.
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 } } }
Se connecter à un service GATT
Une fois le BluetoothGattCallback déclaré, le service peut utiliser l'objet BluetoothDevice de la fonction connect() pour se connecter au service GATT sur l'appareil.
La fonction connectGatt() est utilisée. Cela nécessite un objet Context, un indicateur booléen autoConnect et le BluetoothGattCallback. Dans cet exemple, l'application se connecte directement à l'appareil BLE, donc false est transmis pour autoConnect.
Une propriété BluetoothGatt est également ajoutée. Cela permet au service de fermer la connexion lorsqu'elle n'est plus nécessaire.
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 } } }
Mises à jour sur les diffusions
Lorsque le serveur se connecte ou se déconnecte du serveur GATT, il doit notifier l'activité du nouvel état. Plusieurs solutions s'offrent à vous. L'exemple suivant utilise des diffusions pour envoyer les informations du service à l'activité.
Le service déclare une fonction pour diffuser le nouvel état. Cette fonction accepte une chaîne d'action qui est transmise à un objet Intent avant d'être diffusée au système.
private fun broadcastUpdate(action: String) { val intent = Intent(action) sendBroadcast(intent) }
Une fois la fonction de diffusion en place, elle est utilisée dans BluetoothGattCallback pour envoyer des informations sur l'état de la connexion avec le serveur GATT. Les constantes et l'état de connexion actuel du service sont déclarés dans le service représentant les actions Intent.
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 } }
Écouter les mises à jour de l'activité
Une fois que le service diffuse les mises à jour de la connexion, l'activité doit implémenter un BroadcastReceiver.
Enregistrez ce récepteur lors de la configuration de l'activité et désenregistrez-le lorsque l'activité quitte l'écran. En écoutant les événements du service, l'activité peut mettre à jour l'interface utilisateur en fonction de l'état de connexion actuel avec l'appareil 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) } } }
Dans Transférer des données BLE, le BroadcastReceiver est également utilisé pour communiquer la découverte du service ainsi que les données caractéristiques de l'appareil.
Fermer la connexion GATT
Une étape importante lors de la gestion des connexions Bluetooth consiste à fermer la connexion lorsque vous avez terminé de l'utiliser. Pour ce faire, appelez la fonction close() sur l'objet BluetoothGatt. Dans l'exemple suivant, le service détient la référence à BluetoothGatt. Lorsque l'activité se dissocie du service, la connexion est fermée pour éviter de décharger la batterie de l'appareil.
class BluetoothLeService : Service() { // ... override fun onUnbind(intent: Intent?): Boolean { close() return super.onUnbind(intent) } private fun close() { bluetoothGatt?.let { gatt -> gatt.close() bluetoothGatt = null } } }