Connettersi a un server GATT

Il primo passaggio per interagire con un dispositivo BLE è connettersi al dispositivo. Nello specifico, la connessione al server GATT sul dispositivo. Per connetterti a un server GATT su un dispositivo BLE, utilizza il metodo connectGatt(). Questo metodo richiede 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 BluetoothGattCallback:

Kotlin

var bluetoothGatt: BluetoothGatt? = null
...

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

Java

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

Questa operazione si connette al server GATT ospitato dal dispositivo BLE e restituisce un'istanza BluetoothGatt, che puoi utilizzare per eseguire le operazioni del client GATT. Il chiamante (l'app Android) è il client GATT. BluetoothGattCallback viene utilizzato per inviare risultati al client, come lo stato della connessione e qualsiasi ulteriore operazione 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 utente, questa attività comunica con un Service chiamato BluetoothLeService, che interagisce con il dispositivo BLE tramite l'API BLE. La comunicazione viene eseguita utilizzando un servizio associato che consente all'attività di connettersi a BluetoothLeService e di chiamare le funzioni per la connessione ai dispositivi. BluetoothLeService richiede un'implementazione Binder che fornisca l'accesso al servizio per l'attività.

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'attività può avviare il servizio utilizzando bindService(), trasmettendo un elemento Intent per avviare il servizio, un'implementazione di ServiceConnection per l'ascolto degli eventi di connessione e disconnessione e un flag per specificare ulteriori opzioni di connessione.

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

Configurazione dell'adattatore Bluetooth

Una volta associato il servizio, deve accedere a BluetoothAdapter. Deve controllare che l'adattatore sia disponibile sul dispositivo. Leggi Configurare il Bluetooth per ulteriori informazioni sulla BluetoothAdapter. L'esempio seguente aggrega questo codice di configurazione in una funzione initialize() che restituisce un valore Boolean che indica l'esito positivo.

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'attività chiama questa funzione all'interno della sua implementazione ServiceConnection. La gestione di un falso valore restituito dalla funzione initialize() dipende dall'applicazione. Potresti mostrare un messaggio di errore all'utente che indica che il dispositivo corrente non supporta l'operazione Bluetooth o disattiva funzionalità che richiedono il Bluetooth per il funzionamento. Nell'esempio seguente, finish() viene richiamato sull'attività per rimandare l'utente alla schermata precedente.

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

    ...
}

Collega a un dispositivo

Dopo aver inizializzato BluetoothService, può connettersi al dispositivo BLE. L'attività deve inviare l'indirizzo del dispositivo al servizio in modo che possa avviare la connessione. Il servizio chiamerà prima getRemoteDevice() sul BluetoothAdapter per accedere al dispositivo. Se l'adattatore non riesce a trovare un dispositivo con quell'indirizzo, getRemoteDevice() genera un 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 chiama questa funzione connect() dopo l'inizializzazione del servizio. L'attività deve essere trasmessa nell'indirizzo del dispositivo BLE. Nell'esempio seguente, l'indirizzo del dispositivo viene passato all'attività come intent aggiuntivo.

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

Dichiara callback GATT

Dopo che l'attività indica al servizio a quale dispositivo connettersi e che 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 rilevamento del servizio, sulle letture delle caratteristiche e sulle notifiche relative alle caratteristiche.

Questo argomento è incentrato sulle notifiche relative allo stato della connessione. Consulta Trasferire dati BLE per informazioni su come eseguire il rilevamento dei servizi, le letture delle caratteristiche e la richiesta di notifiche relative alle caratteristiche.

La funzione onConnectionStateChange() viene attivata quando la connessione al server GATT del dispositivo cambia. Nell'esempio seguente, il callback è definito nella classe Service, quindi può essere utilizzato con BluetoothDevice dopo la connessione del servizio.

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

Connettiti al servizio GATT

Una volta dichiarato BluetoothGattCallback, il servizio può utilizzare l'oggetto BluetoothDevice della funzione connect() per connettersi al servizio GATT sul dispositivo.

Viene utilizzata la funzione connectGatt(). Richiede un oggetto Context, un flag booleano autoConnect e BluetoothGattCallback. In questo esempio, l'app si connette direttamente al dispositivo BLE, quindi false viene trasmesso per autoConnect.

Viene aggiunta anche una proprietà BluetoothGatt. In questo modo il servizio può chiudere la connessione quando non è più necessaria.

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

Trasmetti aggiornamenti

Quando il server si connette o disconnette dal server GATT, deve notificare all'attività il nuovo stato. Esistono diversi modi per eseguire questa operazione. 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.

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

Una volta attivata, la funzione di trasmissione viene utilizzata all'interno di BluetoothGattCallback per inviare informazioni sullo stato della connessione con il server GATT. Le costanti e lo stato di connessione attuale del servizio vengono dichiarati nel servizio che rappresenta le azioni 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);
            }
        }
    };

    …
}

Ascolta gli aggiornamenti nell'attività

Dopo che il servizio trasmette l'aggiornamento della connessione, l'attività deve implementare BroadcastReceiver. Registra questo ricevitore durante la configurazione dell'attività e annulla la registrazione quando l'attività esce dalla schermata. Se ascolti gli eventi dal servizio, l'attività può aggiornare l'interfaccia utente in base all'attuale stato della connessione con il 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;
    }
}

In Trasferisci dati BLE, BroadcastReceiver viene utilizzato anche per comunicare il rilevamento del servizio e i dati relativi alle caratteristiche del dispositivo.

Chiudi la connessione GATT

Un passaggio importante da eseguire nelle connessioni Bluetooth è chiudere la connessione quando hai finito di usarla. Per farlo, chiama la funzione close() nell'oggetto BluetoothGatt. Nell'esempio seguente, il servizio contiene il riferimento a BluetoothGatt. Quando l'attività viene svincolata dal servizio, la connessione viene chiusa per evitare di consumare la batteria del 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 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;
      }
}