Verbindung zu einem GATT-Server herstellen

Der erste Schritt bei der Interaktion mit einem BLE-Gerät besteht darin, eine Verbindung zu ihm herzustellen. Genauer gesagt: eine Verbindung zum GATT-Server auf dem Gerät. Verwenden Sie die connectGatt() Methode, um eine Verbindung zu einem GATT Server auf einem BLE-Gerät herzustellen. Diese Methode verwendet drei Parameter: ein Context-Objekt, autoConnect (ein boolescher Wert, der angibt, ob automatisch eine Verbindung zum BLE-Gerät hergestellt werden soll, sobald es verfügbar ist) und einen Verweis auf einen BluetoothGattCallback:

var bluetoothGatt: BluetoothGatt? = null
// ...
bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)

Dadurch wird eine Verbindung zum GATT-Server hergestellt, der vom BLE-Gerät gehostet wird, und eine BluetoothGatt Instanz zurückgegeben, mit der Sie dann GATT-Clientvorgänge ausführen können. Der Aufrufer (die Android-App) ist der GATT-Client. Der BluetoothGattCallback wird verwendet, um Ergebnisse an den Client zu senden, z. B. den Verbindungsstatus sowie alle weiteren GATT-Clientvorgänge.

Gebundenen Dienst einrichten

Im folgenden Beispiel stellt die BLE-App eine Aktivität (DeviceControlActivity) bereit, um eine Verbindung zu Bluetooth-Geräten herzustellen, Gerätedaten anzuzeigen und die vom Gerät unterstützten GATT-Dienste und -Merkmale anzuzeigen. Basierend auf der Nutzereingabe kommuniziert diese Aktivität mit einem Service namens BluetoothLeService, der über die BLE API mit dem BLE-Gerät interagiert. Die Kommunikation erfolgt über einen gebundenen Dienst, über den die Aktivität eine Verbindung zum BluetoothLeService herstellen und Funktionen aufrufen kann, um eine Verbindung zu den Geräten herzustellen. Der BluetoothLeService benötigt eine Binder-Implementierung, die der Aktivität Zugriff auf den Dienst ermöglicht.

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

Die Aktivität kann den Dienst mit bindService()starten, wobei ein Intent übergeben wird, um den Dienst zu starten, eine ServiceConnection -Implementierung, um auf Verbindungs- und Trennungsereignisse zu warten, und ein Flag, um zusätzliche Verbindungsoptionen anzugeben.

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

BluetoothAdapter einrichten

Sobald der Dienst gebunden ist, muss er auf den BluetoothAdapter zugreifen. Er sollte prüfen, ob der Adapter auf dem Gerät verfügbar ist. Weitere Informationen zum BluetoothAdapter finden Sie unter Bluetooth einrichten. Im folgenden Beispiel wird dieser Einrichtungscode in eine initialize()-Funktion eingebunden, die einen Boolean-Wert zurückgibt, der den Erfolg angibt.

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
    }

    // ...
}

Die Aktivität ruft diese Funktion in ihrer ServiceConnection-Implementierung auf. Wie Sie mit einem falschen Rückgabewert der Funktion initialize() umgehen, hängt von Ihrer Anwendung ab. Sie können dem Nutzer eine Fehlermeldung anzeigen, die darauf hinweist, dass das aktuelle Gerät den Bluetooth-Vorgang nicht unterstützt, oder alle Funktionen deaktivieren, für die Bluetooth erforderlich ist. Im folgenden Beispiel wird finish() für die Aktivität aufgerufen, um den Nutzer zum vorherigen Bildschirm zurückzuleiten.

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

    // ...
}

Mit einem Gerät verbinden

Sobald die BluetoothLeService-Instanz initialisiert ist, kann sie eine Verbindung zum BLE-Gerät herstellen. Die Aktivität muss die Geräteadresse an den Dienst senden, damit dieser die Verbindung initiieren kann. Der Dienst ruft zuerst getRemoteDevice() für den BluetoothAdapter auf, um auf das Gerät zuzugreifen. Wenn der Adapter kein Gerät mit dieser Adresse findet, löst getRemoteDevice() eine IllegalArgumentException aus.

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

Die DeviceControlActivity ruft diese connect()-Funktion auf, sobald der Dienst initialisiert ist. Die Aktivität muss die Adresse des BLE-Geräts übergeben. Im folgenden Beispiel wird die Geräteadresse als Intent-Extra an die Aktivität übergeben.

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

GATT-Callback deklarieren

Sobald die Aktivität dem Dienst mitteilt, mit welchem Gerät eine Verbindung hergestellt werden soll, und der Dienst eine Verbindung zum Gerät herstellt, muss der Dienst eine Verbindung zum GATT-Server auf dem BLE-Gerät herstellen. Für diese Verbindung ist ein BluetoothGattCallback erforderlich, um Benachrichtigungen zum Verbindungsstatus, zur Diensterkennung, zu Merkmalen und zu Merkmalsbenachrichtigungen zu erhalten.

In diesem Thema geht es um die Benachrichtigungen zum Verbindungsstatus. Unter BLE-Daten übertragen erfahren Sie, wie Sie die Diensterkennung durchführen, Merkmale lesen und Merkmalsbenachrichtigungen anfordern.

Die onConnectionStateChange() Funktion wird ausgelöst, wenn sich die Verbindung zum GATT-Server des Geräts ändert. Im folgenden Beispiel wird der Callback in der Service Klasse definiert, damit er mit dem BluetoothDevice verwendet werden kann, sobald der Dienst eine Verbindung zu ihm herstellt.

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

Verbindung zum GATT-Dienst herstellen

Sobald der BluetoothGattCallback deklariert ist, kann der Dienst das BluetoothDevice-Objekt aus der Funktion connect() verwenden, um eine Verbindung zum GATT-Dienst auf dem Gerät herzustellen.

Die connectGatt() Funktion wird verwendet. Dazu sind ein Context-Objekt, ein boolesches Flag autoConnect und der BluetoothGattCallback erforderlich. In diesem Beispiel stellt die App direkt eine Verbindung zum BLE-Gerät her, daher wird false für autoConnect übergeben.

Außerdem wird eine BluetoothGatt-Property hinzugefügt. So kann der Dienst die Verbindung schließen, wenn sie nicht mehr benötigt wird.

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

Updates senden

Wenn der Server eine Verbindung zum GATT-Server herstellt oder die Verbindung trennt, muss er die Aktivität über den neuen Status informieren. Dazu gibt es mehrere Möglichkeiten. Im folgenden Beispiel werden Broadcasts verwendet, um die Informationen vom Dienst an die Aktivität zu senden.

Der Dienst deklariert eine Funktion, um den neuen Status zu senden. Diese Funktion verwendet einen Aktionsstring, der an ein Intent-Objekt übergeben wird, bevor er an das System gesendet wird.

private fun broadcastUpdate(action: String) {
    val intent = Intent(action)
    sendBroadcast(intent)
}

Sobald die Broadcast-Funktion vorhanden ist, wird sie im BluetoothGattCallback verwendet, um Informationen zum Verbindungsstatus mit dem GATT-Server zu senden. Konstanten und der aktuelle Verbindungsstatus des Dienstes werden im Dienst deklariert und stellen die Intent-Aktionen dar.

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

Auf Updates in der Aktivität warten

Sobald der Dienst die Verbindungsupdates sendet, muss die Aktivität einen BroadcastReceiverimplementieren. Registrieren Sie diesen Empfänger beim Einrichten der Aktivität und heben Sie die Registrierung auf, wenn die Aktivität den Bildschirm verlässt. Durch das Warten auf die Ereignisse des Dienstes kann die Aktivität die Benutzeroberfläche basierend auf dem aktuellen Verbindungsstatus mit dem BLE-Gerät aktualisieren.

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

Unter BLE-Daten übertragen, der BroadcastReceiver wird auch verwendet, um die Diensterkennung sowie die Merkmalsdaten vom Gerät zu kommunizieren.

GATT-Verbindung schließen

Ein wichtiger Schritt beim Umgang mit Bluetooth-Verbindungen ist das Schließen der Verbindung, wenn Sie sie nicht mehr benötigen. Rufen Sie dazu die Funktion close() für das BluetoothGatt-Objekt auf. Im folgenden Beispiel enthält der Dienst den Verweis auf das BluetoothGatt. Wenn die Aktivität die Bindung an den Dienst aufhebt, wird die Verbindung geschlossen, um den Akku des Geräts zu schonen.

class BluetoothLeService : Service() {

    // ...
    override fun onUnbind(intent: Intent?): Boolean {
        close()
        return super.onUnbind(intent)
    }

    private fun close() {
        bluetoothGatt?.let { gatt ->
            gatt.close()
            bluetoothGatt = null
        }
    }
}