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