A primeira etapa na interação com um dispositivo BLE é a conexão com ele. Mais
especificamente, conectar-se ao servidor GATT no dispositivo. Para se conectar a um servidor GATT
em um dispositivo BLE, use o
método
connectGatt()
. Esse método usa três parâmetros: um objeto
Context
, autoConnect
(um booleano
que indica se a conexão com o dispositivo BLE será feita automaticamente assim que ele
estiver disponível) e uma referência a um
BluetoothGattCallback
:
Kotlin
var bluetoothGatt: BluetoothGatt? = null ... bluetoothGatt = device.connectGatt(this, false, gattCallback)
Java
bluetoothGatt = device.connectGatt(this, false, gattCallback);
Isso se conecta ao servidor GATT hospedado pelo dispositivo BLE e retorna uma instância BluetoothGatt
, que pode ser usada para conduzir operações do cliente GATT. O autor da chamada (o app Android)
é o cliente GATT. O
BluetoothGattCallback
é usado para entregar resultados ao cliente, como
o status da conexão e qualquer outra operação do cliente GATT.
Configurar um serviço vinculado
No exemplo a seguir, o app BLE fornece uma atividade (DeviceControlActivity
) para se conectar a dispositivos Bluetooth, exibir dados do dispositivo e mostrar os serviços GATT e as características compatíveis com o dispositivo. Com base
na entrada do usuário, essa atividade se comunica com um
Service
chamado BluetoothLeService
, que
interage com o dispositivo BLE pela API BLE. A comunicação é
realizada usando um serviço vinculado que permite
que a atividade se conecte ao BluetoothLeService
e chame funções para
se conectar aos dispositivos. O BluetoothLeService
precisa de uma implementação de
Binder
que forneça acesso ao
serviço para a atividade.
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; } } }
A atividade pode iniciar o serviço usando
bindService()
,
transmitindo um Intent
para iniciar o
serviço, uma implementação de ServiceConnection
para detectar os eventos de conexão e desconexão e uma flag
para especificar outras opções de conexão.
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); } }
Configurar o BluetoothAdapter
Depois que o serviço é vinculado, ele precisa acessar o
BluetoothAdapter
. Ela vai
verificar se o adaptador está disponível no dispositivo. Leia Configurar
o Bluetooth para mais informações sobre
o BluetoothAdapter
. O exemplo a seguir encapsula esse código de configuração em uma
função initialize()
que retorna um valor Boolean
indicando sucesso.
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; } ... }
A atividade chama essa função dentro da implementação de ServiceConnection
.
O processamento de um valor de retorno falso da função initialize()
depende do
aplicativo. Você pode mostrar uma mensagem de erro ao usuário indicando que o
dispositivo atual não é compatível com a operação Bluetooth ou desativar todos os recursos
que exigem o funcionamento do Bluetooth. No exemplo abaixo,
finish()
é chamado na atividade
para enviar o usuário de volta à tela anterior.
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; } }; ... }
Conectar a um dispositivo
Depois que a instância BluetoothLeService
é inicializada, ela pode se conectar ao dispositivo BLE. A atividade precisa enviar o endereço do dispositivo ao serviço para que ele possa
iniciar a conexão. O serviço vai chamar primeiro
getRemoteDevice()
no BluetoothAdapter
para acessar o dispositivo. Se o adaptador não conseguir encontrar
um dispositivo com esse endereço, o getRemoteDevice()
vai gerar uma
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 }
O DeviceControlActivity
chama essa função connect()
depois que o serviço é
inicializado. A atividade precisa transmitir o endereço do dispositivo BLE. No
exemplo abaixo, o endereço do dispositivo é transmitido para a atividade como um extra da
intent.
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; } };
Declarar callback GATT
Depois que a atividade informa ao serviço a que dispositivo se conectar e o serviço
se conecta ao dispositivo, o serviço precisa se conectar ao servidor GATT no
dispositivo BLE. Essa conexão requer um BluetoothGattCallback
para receber
notificações sobre o estado da conexão, a descoberta de serviço, as leituras
e as notificações características.
Este tópico se concentra nas notificações do estado da conexão. Consulte Transferir dados BLE para saber como executar a descoberta de serviços, as leituras de características e a solicitação de notificações de características.
A função onConnectionStateChange()
é acionada quando a conexão com o servidor GATT do dispositivo muda.
No exemplo abaixo, o callback é definido na classe Service
para que
possa ser usado com o
BluetoothDevice
quando o
serviço se conectar a ele.
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 } } };
Conectar-se ao serviço GATT
Depois que o BluetoothGattCallback
é declarado, o serviço pode usar o
objeto BluetoothDevice
da função connect()
para se conectar ao serviço GATT
no dispositivo.
A
função
connectGatt()
é usada. Isso requer um objeto Context
, uma sinalização booleana autoConnect
e o BluetoothGattCallback
. Neste exemplo, o app está se conectando diretamente
ao dispositivo BLE, então false
é transmitido para autoConnect
.
Uma propriedade BluetoothGatt
também foi adicionada. Isso permite que o serviço encerre a
conexão quando não for
mais necessária.
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 BluetoothLeService 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; } } }
Atualizações de transmissão
Quando o servidor se conecta ou desconecta do servidor GATT, ele precisa notificar a atividade do novo estado. Há várias maneiras de fazer isso. O exemplo abaixo usa transmissões para enviar as informações do serviço para a atividade.
O serviço declara uma função para transmitir o novo estado. Essa função usa
uma string de ação que é transmitida para um objeto Intent
antes de ser transmitida
ao sistema.
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); }
Quando a função de transmissão está ativa, ela é usada no
BluetoothGattCallback
para enviar informações sobre o estado da conexão com o
servidor GATT. As constantes e o estado atual da conexão do serviço são declarados
no serviço que representa as ações Intent
.
Kotlin
class BluetoothLeService : 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 BluetoothLeService 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); } } }; … }
Detectar atualizações na atividade
Depois que o serviço transmite as atualizações de conexão, a atividade precisa
implementar um BroadcastReceiver
.
Registre esse receptor ao configurar a atividade e cancele o registro quando a
atividade sair da tela. Ao detectar os eventos do serviço, a atividade pode atualizar a interface do usuário com base no estado atual da conexão com o dispositivo 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; } }
Em Transferir dados de BLE,
o BroadcastReceiver
também é usado para comunicar a descoberta de serviço,
bem como os dados característicos do dispositivo.
Fechar conexão GATT
Uma etapa importante ao lidar com conexões Bluetooth é encerrar a
conexão quando terminar de usá-la. Para fazer isso, chame a função close()
no objeto BluetoothGatt
. No exemplo abaixo, o serviço
mantém a referência ao BluetoothGatt
. Quando a atividade é desvinculada do
serviço, a conexão é encerrada para evitar o consumo da bateria do dispositivo.
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 BluetoothLeService extends Service { ... @Override public boolean onUnbind(Intent intent) { close(); return super.onUnbind(intent); } private void close() { if (bluetoothGatt == null) { Return; } bluetoothGatt.close(); bluetoothGatt = null; } }