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 herstellen. Verwenden Sie die Methode connectGatt(), 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 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 GATT-Server hergestellt, der vom BLE-Gerät gehostet wird, und gibt eine BluetoothGatt-Instanz zurück, die Sie dann für GATT-Clientvorgänge verwenden können. Der Aufrufer (die Android-App) ist der GATT-Client. Die BluetoothGattCallback wird verwendet, um dem Client Ergebnisse wie den Verbindungsstatus und alle weiteren GATT-Clientvorgänge zu senden.

Gebundenen Dienst einrichten

Im folgenden Beispiel bietet die BLE-App eine Aktivität (DeviceControlActivity), 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, mit dem die Aktivität eine Verbindung zum BluetoothLeService herstellen und Funktionen zum Herstellen einer Verbindung zu den Geräten aufrufen kann. 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 ein Intent-Element zum Starten des Dienstes, eine ServiceConnection-Implementierung zur Überwachung der Verbindungs- und Trennereignisse sowie 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 gebunden ist, muss er auf die BluetoothAdapter zugreifen. Es sollte geprüft werden, ob der Adapter auf dem Gerät verfügbar ist. Weitere Informationen zu BluetoothAdapter findest du unter Bluetooth einrichten. Im folgenden Beispiel wird dieser Einrichtungscode in eine initialize()-Funktion eingebettet, die einen Boolean-Wert zurückgibt, der einen erfolgreichen Vorgang anzeigt.

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 ein falscher Rückgabewert von der initialize()-Funktion verarbeitet wird, hängt von Ihrer Anwendung ab. Sie können dem Nutzer in einer Fehlermeldung mitteilen, dass das aktuelle Gerät die Bluetooth-Funktion nicht unterstützt, oder 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ückzukehren.

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 das BluetoothService initialisiert ist, kann es eine Verbindung zum BLE-Gerät herstellen. Die Aktivität muss die Geräteadresse an den Dienst senden, damit die Verbindung initiiert werden kann. Der Dienst ruft zuerst getRemoteDevice() auf der BluetoothAdapter auf, um auf das Gerät zuzugreifen. Wenn der Adapter kein Gerät mit dieser Adresse findet, gibt getRemoteDevice() einen 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, nachdem der Dienst initialisiert wurde. Die Aktivität muss die Adresse des BLE-Geräts übergeben. Im folgenden Beispiel wird die Geräteadresse als zusätzliche Intent 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 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 Merkmalslesevorgängen und zu charakteristischen Benachrichtigungen zu erhalten.

In diesem Thema geht es um Benachrichtigungen zum Verbindungsstatus. Unter BLE-Daten übertragen erfahren Sie, wie Sie Diensterkennung, charakteristische Lesevorgänge und Benachrichtigungen zu Anfrageeigenschaften 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, damit er mit der BluetoothDevice verwendet werden kann, sobald der Dienst eine Verbindung zu ihr hergestellt hat.

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 connect()-Funktion 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 autoConnect-Flag und BluetoothGattCallback erforderlich. In diesem Beispiel stellt die App eine direkte Verbindung zum BLE-Gerät her. Daher wird false für autoConnect übergeben.

Das Attribut BluetoothGatt wurde ebenfalls 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 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;
        }
    }
}

Broadcast-Updates

Wenn der Server eine Verbindung zum GATT-Server herstellt oder trennt, muss er die Aktivität des neuen Bundesstaats 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 zum Übertragen des neuen Status. Diese Funktion verwendet einen Aktionsstring, 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, werden sie im BluetoothGattCallback verwendet, um Informationen zum 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 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);
            }
        }
    };

    …
}

Auf Updates in Aktivitäten warten

Sobald der Dienst die Verbindungsupdates überträgt, muss die Aktivität einen BroadcastReceiver implementieren. 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 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 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. Rufen Sie dazu die Funktion close() für das BluetoothGatt-Objekt auf. Im folgenden Beispiel enthält der Dienst den Verweis auf BluetoothGatt. Wenn die Bindung der Aktivität an den Dienst aufgehoben wird, wird die Verbindung geschlossen, um eine Entladung des Geräteakkus zu vermeiden.

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