Der erste Schritt bei der Interaktion mit einem BLE-Gerät besteht darin, eine Verbindung zu diesem Gerät herzustellen. Genauer gesagt, die Verbindung zum GATT-Server auf dem Gerät. Verwenden Sie die Methode connectGatt()
, um eine Verbindung zu einem GATT-Server auf einem BLE-Gerät herzustellen. Diese Methode benötigt 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 ein BluetoothGattCallback
:
Kotlin
var bluetoothGatt: BluetoothGatt? = null ... bluetoothGatt = device.connectGatt(this, false, gattCallback)
Java
bluetoothGatt = device.connectGatt(this, false, gattCallback);
Dadurch wird eine Verbindung zum vom BLE-Gerät gehosteten GATT-Server hergestellt 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. Die BluetoothGattCallback
wird verwendet, um Ergebnisse an den Client zu liefern, z. B. den Verbindungsstatus sowie alle weiteren GATT-Clientvorgänge.
Gebundenen Dienst einrichten
Im folgenden Beispiel stellt die BLE-App eine Aktivität (DeviceControlActivity
) zur Verfügung, um eine Verbindung zu Bluetooth-Geräten herzustellen, Gerätedaten anzuzeigen und die vom Gerät unterstützten GATT-Dienste und -Eigenschaften anzuzeigen. Basierend auf Nutzereingaben 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, der es der Aktivität ermöglicht, eine Verbindung zu BluetoothLeService
herzustellen und Funktionen aufzurufen, um eine Verbindung zu den Geräten herzustellen. Für BluetoothLeService
ist eine Binder
-Implementierung erforderlich, die Zugriff auf den Dienst für die Aktivität bietet.
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; } } }
Die Aktivität kann den Dienst mit bindService()
starten und dabei eine Intent
zum Starten des Dienstes, eine ServiceConnection
-Implementierung zum Überwachen von Verbindungs- und Verbindungsabbrüchen und ein Flag zum Angeben zusätzlicher Verbindungsoptionen übergeben.
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); } }
Bluetooth-Adapter einrichten
Sobald der Dienst an ihn gebunden ist, muss er auf BluetoothAdapter
zugreifen. Es sollte geprüft werden, ob der Adapter auf dem Gerät verfügbar ist. Weitere Informationen zu BluetoothAdapter
finden Sie unter Bluetooth einrichten. Im folgenden Beispiel ist dieser Einrichtungscode in einer initialize()
-Funktion eingebunden, die einen Boolean
-Wert zurückgibt, der auf eine erfolgreiche Ausführung hinweist.
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; } ... }
Die Aktivität ruft diese Funktion innerhalb ihrer ServiceConnection
-Implementierung auf.
Ob der Rückgabewert der Funktion initialize()
falsch ist, hängt von der Anwendung ab. Sie können dem Nutzer eine Fehlermeldung anzeigen, dass das aktuelle Gerät die Bluetooth-Funktion nicht unterstützt, oder Funktionen deaktivieren, die Bluetooth erfordern. Im folgenden Beispiel wird finish()
für die Aktivität aufgerufen, um den Nutzer zurück zum vorherigen Bildschirm zu leiten.
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; } }; ... }
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 er die Verbindung initiieren kann. Der Dienst ruft zuerst getRemoteDevice()
auf BluetoothAdapter
auf, um auf das Gerät zuzugreifen. Wenn der Adapter kein Gerät mit dieser Adresse findet, löst getRemoteDevice()
den Fehler IllegalArgumentException
aus.
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
ruft diese connect()
-Funktion auf, sobald der Dienst initialisiert wurde. 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.
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; } };
GATT-Callback deklarieren
Sobald dem Dienst mitgeteilt wurde, zu welchem Gerät er eine Verbindung herstellen soll, und er eine Verbindung zu dem Gerät herstellt, muss der Dienst eine Verbindung zum GATT-Server auf dem BLE-Gerät herstellen. Bei dieser Verbindung muss ein BluetoothGattCallback
Benachrichtigungen zum Verbindungsstatus, zur Diensterkennung, zu charakteristischen Lesevorgängen und zu charakteristischen Benachrichtigungen erhalten.
In diesem Thema geht es um Benachrichtigungen zum Verbindungsstatus. Unter BLE-Daten übertragen erfahren Sie, wie Sie Diensterkennungen, charakteristische Lesevorgänge und Benachrichtigungen zu charakteristischen Anforderungen durchführen.
Die Funktion onConnectionStateChange()
wird ausgelöst, wenn sich die Verbindung zum GATT-Server des Geräts ändert.
Im folgenden Beispiel ist der Callback in der Klasse Service
definiert, sodass er mit dem BluetoothDevice
verwendet werden kann, sobald der Dienst eine Verbindung herstellt.
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 } } };
Mit GATT-Dienst verbinden
Sobald 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 Funktion connectGatt()
wird verwendet. Dafür sind ein Context
-Objekt, ein boolesches Flag autoConnect
und das BluetoothGattCallback
erforderlich. In diesem Beispiel stellt die App eine direkte Verbindung zum BLE-Gerät her. Daher wird false
für autoConnect
übergeben.
Außerdem wird die Property BluetoothGatt
hinzugefügt. Dadurch kann der Dienst die Verbindung trennen, wenn sie nicht mehr benötigt wird.
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; } } }
Broadcast-Updates
Wenn der Server eine Verbindung zum GATT-Server herstellt oder trennt, muss er die Aktivität über den neuen Status benachrichtigen. Dafür 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 zur Übertragung des neuen Status. Diese Funktion nimmt einen Aktionsstring an, der an ein Intent
-Objekt übergeben wird, bevor er an das System gesendet wird.
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); }
Sobald die Broadcast-Funktion eingerichtet ist, wird sie innerhalb von BluetoothGattCallback
verwendet, um Informationen über den Verbindungsstatus an den GATT-Server zu senden. Konstanten und der aktuelle Verbindungsstatus des Dienstes werden im Dienst deklariert, der die Intent
-Aktionen darstellt.
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); } } }; … }
In Aktivitäten auf Updates warten
Sobald der Dienst die Verbindungsaktualisierungen sendet, muss durch die Aktivität ein BroadcastReceiver
implementiert werden.
Registrieren Sie diesen Empfänger, wenn Sie die Aktivität einrichten, und heben Sie die Registrierung auf, wenn die Aktivität den Bildschirm verlässt. Durch das Warten auf Ereignisse des Dienstes kann die Aktivität die Benutzeroberfläche basierend auf dem aktuellen Verbindungsstatus mit dem BLE-Gerät aktualisieren.
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; } }
Bei BLE-Daten übertragen wird die BroadcastReceiver
auch verwendet, um die Diensterkennung sowie die charakteristischen Daten vom Gerät zu kommunizieren.
GATT-Verbindung trennen
Ein wichtiger Schritt bei Bluetooth-Verbindungen besteht darin, die Verbindung zu trennen, wenn Sie sie nicht mehr benötigen. Dazu rufen Sie die Funktion close()
für das Objekt BluetoothGatt
auf. Im folgenden Beispiel enthält der Dienst den Verweis auf BluetoothGatt
. Wenn die Aktivität vom Dienst getrennt wird, wird die Verbindung getrennt, damit der Geräteakku nicht belastet wird.
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; } }