Conectar-se ao servidor GATT

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 GATT servidor em um dispositivo BLE, use o connectGatt() . Esse método usa três parâmetros: Objeto Context, autoConnect (um booleano indicando se ele deve se conectar automaticamente ao dispositivo BLE assim que estiver disponível) e uma referência a um BluetoothGattCallback:

var bluetoothGatt: BluetoothGatt? = null
...

bluetoothGatt
= device.connectGatt(this, false, gattCallback)
bluetoothGatt = device.connectGatt(this, false, gattCallback);

Ela se conecta ao servidor GATT hospedado pelo dispositivo BLE e retorna uma a instância BluetoothGatt, que que podem ser usados para conduzir operações do cliente GATT. O autor da chamada (o app Android) é o cliente GATT. A BluetoothGattCallback é usado para entregar resultados ao cliente, como o status da conexão, bem como 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 exibe os serviços e as características do GATT compatíveis com o dispositivo. Baseado em na entrada do usuário, essa atividade se comunica Service chamado BluetoothLeService, que interage com o dispositivo BLE por meio da API BLE. A comunicação é realizada usando um serviço vinculado que permite a atividade para se conectar ao BluetoothLeService e chamar funções para e se conectar aos dispositivos. O BluetoothLeService precisa de um implementação do Binder que fornece acesso a o serviço para a atividade.

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
       
}
   
}
}
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, um ServiceConnection para detectar eventos de conexão e desconexão, além de uma sinalização para especificar outras opções de conexão.

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)
   
}
}
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 Ele deveria verifique se o adaptador está disponível no dispositivo. Leia Configuração Bluetooth para mais informações sobre o BluetoothAdapter. O exemplo a seguir encapsula esse código de configuração Função initialize() que retorna um valor Boolean indicando sucesso.

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
   
}

   
...
}
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 das suas para o aplicativo. Você pode mostrar uma mensagem de erro ao usuário indicando que o o dispositivo atual não é compatível com a operação de Bluetooth nem desativa recursos que precisam do Bluetooth para funcionar. No exemplo a seguir, finish() é chamado na atividade. para retornar o usuário à tela anterior.

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
       
}
   
}

   
...
}
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 for inicializada, ela poderá se conectar ao BLE dispositivo. A atividade precisa enviar o endereço do dispositivo ao serviço para que ele possa inicie a conexão. O serviço vai chamar pela primeira vez getRemoteDevice() no BluetoothAdapter para acessar o dispositivo. Se o adaptador não conseguir encontrar um dispositivo com esse endereço, getRemoteDevice() gera uma 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
   
} ?: run {
       
Log.w(TAG, "BluetoothAdapter not initialized")
       
return false
   
}
}
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() quando o serviço é inicializado. A atividade precisa transmitir o endereço do dispositivo BLE. Em no exemplo a seguir, o endereço do dispositivo é transmitido para a atividade como uma intent extra.

// 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
   
}
}
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 conecta ao dispositivo, o serviço precisa se conectar ao servidor GATT na dispositivo BLE. Esta conexão exige um BluetoothGattCallback para receber notificações sobre o estado da conexão, descoberta de serviços, leituras e notificações características.

Este tópico se concentra nas notificações do estado da conexão. Consulte Transferir BLE dados sobre como realizar descoberta de serviços, leituras de características e característica da solicitação notificações.

A onConnectionStateChange() é acionada quando a conexão com o servidor GATT do dispositivo é alterada. No exemplo a seguir, o callback é definido na classe Service pode ser usada com o BluetoothDevice assim que o ou serviço se conecta a ele.

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
       
}
   
}
}
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 a BluetoothGattCallback for declarada, o serviço poderá usar a Objeto BluetoothDevice da função connect() para conexão com o GATT serviço no dispositivo.

A connectGatt() é usada. Isso exige um objeto Context, um booleano autoConnect e a sinalização BluetoothGattCallback. Neste exemplo, o app é usado diretamente conectando-se ao dispositivo BLE, então false é transmitido para autoConnect.

Uma propriedade BluetoothGatt também foi adicionada. Isso permite que o serviço feche o padrão quando não há mais necessário.

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
       
}
   
}
}
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. A exemplo a seguir 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 em uma string de ação transmitida para um objeto Intent antes de ser transmitida ao sistema.

private fun broadcastUpdate(action: String) {
   
val intent = Intent(action)
    sendBroadcast
(intent)
}
private void broadcastUpdate(final String action) {
   
final Intent intent = new Intent(action);
    sendBroadcast
(intent);
}

Depois que a função de transmissão está no lugar, 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.

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

   
}
}

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 transmitir as atualizações da conexão, a atividade precisará implementar um BroadcastReceiver. Registre este receptor ao configurar a atividade e cancele o registro quando o a atividade está saindo da tela. Ao detectar eventos no serviço, a atividade poderá atualizar a interface do usuário com base o estado da conexão com o 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())
       
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)
       
}
   
}
}
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, faça o seguinte: BroadcastReceiver também é usado para comunicar a descoberta do serviço, e os dados característicos do dispositivo.

Fechar conexão GATT

Um passo importante ao lidar com conexões Bluetooth é fechar o quando você terminar de usá-lo. Para fazer isso, chame o método close() no objeto BluetoothGatt. No exemplo a seguir, o serviço contém a referência ao BluetoothGatt. Quando a atividade se desvincula do serviço, a conexão será encerrada para evitar o descarregamento da bateria do 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
       
}
   
}
}
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;
     
}
}