La première étape pour interagir avec un appareil BLE consiste à s’y connecter. Plus précisément, en se connectant
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
(une valeur booléenne indiquant s'il faut se connecter automatiquement à l'appareil BLE dès qu'il devient disponible) et une référence à un BluetoothGattCallback
:
Kotlin
var bluetoothGatt: BluetoothGatt? = null ... bluetoothGatt = device.connectGatt(this, false, gattCallback)
Java
bluetoothGatt = device.connectGatt(this, false, gattCallback);
Elle se connecte au serveur GATT hébergé par l'appareil BLE et renvoie une instance BluetoothGatt
, que vous pouvez ensuite utiliser pour effectuer des opérations client GATT. L'appelant (l'application Android)
est le client GATT. Le BluetoothGattCallback
permet de fournir des résultats au client, tels que l'état de la connexion, ainsi que toute autre opération GATT du client.
Configurer un service lié
Dans l'exemple suivant, l'application BLE fournit une activité (DeviceControlActivity
) permettant de se connecter aux appareils Bluetooth, d'afficher les données de l'appareil, et d'afficher les services et les caractéristiques GATT compatibles avec l'appareil. En fonction de l'entrée 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
nécessite une implémentation Binder
qui fournit un accès au service pour l'activité.
Kotlin
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 } } }
Java
class BluetoothLeService extends Service { private Binder binder = new LocalBinder(); @Nullable @Override public IBinder onBind(Intent intent) { return binder; } class LocalBinder extends Binder { public BluetoothLeService getService() { return BluetoothLeService.this; } } }
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, ainsi qu'un indicateur permettant de spécifier des options de connexion supplémentaires.
Kotlin
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) } }
Java
class DeviceControlActivity extends AppCompatActivity { private BluetoothLeService bluetoothService; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { // call functions on service to check connection and connect to devices } } @Override public void onServiceDisconnected(ComponentName name) { bluetoothService = null; } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.gatt_services_characteristics); Intent gattServiceIntent = new Intent(this, BluetoothLeService.class); bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE); } }
Configurer l'adaptateur Bluetooth
Une fois que le service est lié, il doit accéder au BluetoothAdapter
. Il doit vérifier que l'adaptateur est disponible sur l'appareil. Consultez la section Configurer le Bluetooth pour plus d'informations sur le BluetoothAdapter
. L'exemple suivant encapsule ce code de configuration dans une fonction initialize()
qui renvoie une valeur Boolean
indiquant la réussite de l'opération.
Kotlin
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 } ... }
Java
class BluetoothLeService extends Service { public static final String TAG = "BluetoothLeService"; private BluetoothAdapter bluetoothAdapter; public boolean initialize() { 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
.
Le traitement d'une valeur renvoyée fausse à partir 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 le Bluetooth ou désactiver les fonctionnalités qui nécessitent le Bluetooth pour fonctionner. Dans l'exemple suivant, finish()
est appelé sur l'activité pour renvoyer l'utilisateur à l'écran précédent.
Kotlin
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 } } ... }
Java
class DeviceControlsActivity extends AppCompatActivity { private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { if (!bluetoothService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); finish(); } // perform device connection } } @Override public void onServiceDisconnected(ComponentName name) { bluetoothService = null; } }; ... }
Connexion à un appareil
Une fois le BluetoothService
initialisé, il 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 appelle d'abord getRemoteDevice()
sur le BluetoothAdapter
pour accéder à l'appareil. Si l'adaptateur ne parvient pas à trouver un appareil doté de cette adresse, getRemoteDevice()
génère une erreur IllegalArgumentException
.
Kotlin
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 } ?: run { Log.w(TAG, "BluetoothAdapter not initialized") return false } }
Java
public boolean connect(final String address) { if (bluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } try { final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); } catch (IllegalArgumentException exception) { Log.w(TAG, "Device not found with provided address."); return false; } // connect to the GATT server on the device }
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'intent supplémentaire.
Kotlin
// 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 bluetooth.connect(deviceAddress) } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } }
Java
private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { if (!bluetoothService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); finish(); } // perform device connection bluetoothService.connect(deviceAddress); } } @Override public void onServiceDisconnected(ComponentName name) { bluetoothService = null; } };
Déclarer un rappel GATT
Une fois que l'activité indique au service à quel appareil se connecter et que le service s'y connecte, 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étection 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 la section Transférer des données BLE pour découvrir comment effectuer la détection de service, les lectures de caractéristiques et les notifications des caractéristiques de requête.
La fonction onConnectionStateChange()
est déclenchée lorsque la connexion au serveur GATT de l'appareil est modifiée.
Dans l'exemple suivant, le rappel est défini dans la classe Service
. Il peut donc être utilisé avec BluetoothDevice
une fois que le service s'y connecte.
Kotlin
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 } } }
Java
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server } } };
Se connecter au 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. false
est donc 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.
Kotlin
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 } } }
Java
class BluetoothService extends Service { ... private BluetoothGatt bluetoothGatt; ... public boolean connect(final String address) { if (bluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } try { final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); // connect to the GATT server on the device bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback); return true; } catch (IllegalArgumentException exception) { Log.w(TAG, "Device not found with provided address. Unable to connect."); return false; } } }
Mises à jour de diffusion
Lorsque le serveur se connecte ou se déconnecte du serveur GATT, il doit notifier l'activité du nouvel état. Il y a plusieurs façons d'y parvenir. 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.
Kotlin
private fun broadcastUpdate(action: String) { val intent = Intent(action) sendBroadcast(intent) }
Java
private void broadcastUpdate(final String action) { final Intent intent = new 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 au serveur GATT. Les constantes et l'état de connexion actuel du service sont déclarés dans le service représentant les actions Intent
.
Kotlin
class BluetoothService : Service() { private var connectionState = STATE_DISCONNECTED private val bluetoothGattCallback: 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 } }
Java
class BluetoothService extends Service { public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTED = 2; private int connectionState; ... private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 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); } } }; … }
Écouter les mises à jour dans l'activité
Une fois que le service a diffusé 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 annulez l'enregistrement lorsque l'activité quitte l'écran. En écoutant les événements du service, l'activité est en mesure de mettre à jour l'interface utilisateur en fonction de l'état actuel de la connexion à l'appareil BLE.
Kotlin
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()) if (bluetoothService != null) { val result = bluetoothService!!.connect(deviceAddress) Log.d(DeviceControlsActivity.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) } } }
Java
class DeviceControlsActivity extends AppCompatActivity { ... private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { connected = true; updateConnectionState(R.string.connected); } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { connected = false; updateConnectionState(R.string.disconnected); } } }; @Override protected void onResume() { super.onResume(); registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter()); if (bluetoothService != null) { final boolean result = bluetoothService.connect(deviceAddress); Log.d(TAG, "Connect request result=" + result); } } @Override protected void onPause() { super.onPause(); unregisterReceiver(gattUpdateReceiver); } private static IntentFilter makeGattUpdateIntentFilter() { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED); intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED); return intentFilter; } }
Dans Transférer des données BLE, BroadcastReceiver
est également utilisé pour communiquer la détection de services 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 n'en avez plus besoin. Pour ce faire, appelez la fonction close()
sur l'objet BluetoothGatt
. Dans l'exemple suivant, le service contient la référence à BluetoothGatt
. Lorsque l'activité est dissociée du service, la connexion est fermée pour éviter de décharger la batterie de l'appareil.
Kotlin
class BluetoothLeService : Service() { ... override fun onUnbind(intent: Intent?): Boolean { close() return super.onUnbind(intent) } private fun close() { bluetoothGatt?.let { gatt -> gatt.close() bluetoothGatt = null } } }
Java
class BluetoothService extends Service { ... @Override public boolean onUnbind(Intent intent) { close(); return super.onUnbind(intent); } private void close() { if (bluetoothGatt == null) { Return; } bluetoothGatt.close(); bluetoothGatt = null; } }