Pierwszym krokiem w interakcji z urządzeniem BLE jest połączenie się z nim. W szczególności chodzi o połączenie z serwerem GATT na urządzeniu. Aby połączyć się z serwerem GATT na urządzeniu BLE, użyj metody connectGatt(). Ta metoda przyjmuje 3 parametry: obiekt Context, autoConnect (wartość logiczną wskazującą, czy należy automatycznie połączyć się z urządzeniem BLE, gdy tylko stanie się dostępne) i odwołanie do BluetoothGattCallback:
var bluetoothGatt: BluetoothGatt? = null // ... bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
Łączy się z serwerem GATT hostowanym przez urządzenie BLE i zwraca instancję BluetoothGatt, której możesz użyć do przeprowadzania operacji klienta GATT. Dzwoniący (aplikacja na Androida) jest klientem GATT. BluetoothGattCallback służy do dostarczania klientowi wyników, takich jak stan połączenia, a także wszelkich dalszych operacji klienta GATT.
Konfigurowanie powiązanej usługi
W poniższym przykładzie aplikacja BLE udostępnia aktywność (DeviceControlActivity), która umożliwia łączenie się z urządzeniami Bluetooth, wyświetlanie danych urządzenia oraz wyświetlanie usług i charakterystyk GATT obsługiwanych przez urządzenie. Na podstawie danych wejściowych użytkownika to działanie komunikuje się z Service o nazwie BluetoothLeService, które wchodzi w interakcję z urządzeniem BLE za pomocą interfejsu BLE API. Komunikacja odbywa się za pomocą usługi powiązanej, która umożliwia aktywności łączenie się z BluetoothLeService i wywoływanie funkcji w celu łączenia się z urządzeniami. BluetoothLeService musi mieć wdrożoną Binder, która zapewnia dostęp do usługi w przypadku aktywności.
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 } } }
Aktywność może uruchomić usługę za pomocą bindService(), przekazując Intent, aby uruchomić usługę, implementację ServiceConnection, aby nasłuchiwać zdarzenia połączenia i rozłączenia, oraz flagę, aby określić dodatkowe opcje połączenia.
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) } }
Konfigurowanie obiektu BluetoothAdapter
Po powiązaniu usługi musi ona mieć dostęp do BluetoothAdapter. Powinien sprawdzić, czy adapter jest dostępny na urządzeniu. Więcej informacji o BluetoothAdapter znajdziesz w artykule Konfigurowanie Bluetootha. W poniższym przykładzie ten kod konfiguracji jest zawarty w funkcji initialize(), która zwraca wartość Boolean wskazującą powodzenie.
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 } // ... }
Aktywność wywołuje tę funkcję w ramach implementacji ServiceConnection.
Obsługa wartości „false” zwracanej przez funkcję initialize() zależy od aplikacji. Możesz wyświetlić użytkownikowi komunikat o błędzie informujący, że bieżące urządzenie nie obsługuje Bluetootha, lub wyłączyć funkcje, które wymagają Bluetootha do działania. W tym przykładzie funkcja
finish() jest wywoływana w aktywności, aby cofnąć użytkownika do poprzedniego ekranu.
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 } } // ... }
Połącz się z urządzeniem
Po zainicjowaniu instancji BluetoothLeService może ona połączyć się z urządzeniem BLE. Aktywność musi wysłać adres urządzenia do usługi, aby mogła ona zainicjować połączenie. Usługa najpierw zadzwoni
getRemoteDevice()
na BluetoothAdapter, aby uzyskać dostęp do urządzenia. Jeśli adapter nie może znaleźć urządzenia o tym adresie, getRemoteDevice() zgłasza błąd IllegalArgumentException.
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 } }
Funkcja DeviceControlActivity wywołuje funkcję connect() po zainicjowaniu usługi. Aktywność musi przekazywać adres urządzenia BLE. W tym przykładzie adres urządzenia jest przekazywany do aktywności jako dodatkowa intencja.
// 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 } }
Deklarowanie wywołania zwrotnego GATT
Gdy aktywność poinformuje usługę, z którym urządzeniem ma się połączyć, a usługa połączy się z urządzeniem, musi połączyć się z serwerem GATT na urządzeniu BLE. To połączenie wymaga BluetoothGattCallback, aby otrzymywać powiadomienia o stanie połączenia, wykrywaniu usług, odczytach charakterystyki i powiadomieniach o charakterystyce.
Ten temat dotyczy powiadomień o stanie połączenia. Więcej informacji o wykrywaniu usług, odczytywaniu charakterystyk i żądaniu powiadomień o charakterystykach znajdziesz w artykule Przesyłanie danych BLE.
Funkcja
onConnectionStateChange()
jest wywoływana, gdy zmienia się połączenie z serwerem GATT urządzenia.
W przykładzie poniżej wywołanie zwrotne jest zdefiniowane w klasie Service, dzięki czemu można go używać z BluetoothDevice po połączeniu się z nim usługi.
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 } } }
Łączenie z usługą GATT
Po zadeklarowaniu BluetoothGattCallback usługa może użyć obiektu BluetoothDevice z funkcji connect(), aby połączyć się z usługą GATT na urządzeniu.
Używana jest funkcja
connectGatt(). Wymaga to obiektu Context, flagi logicznej autoConnect i BluetoothGattCallback. W tym przykładzie aplikacja łączy się bezpośrednio z urządzeniem BLE, więc w przypadku parametru autoConnect przekazywana jest wartość false.
Dodawana jest też usługa BluetoothGatt. Dzięki temu usługa może zamknąć połączenie, gdy nie jest już potrzebne.
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 } } }
Aktualizacje dotyczące transmisji
Gdy serwer łączy się z serwerem GATT lub odłącza się od niego, musi powiadomić o nowym stanie. Możesz to zrobić na kilka sposobów. W tym przykładzie do wysyłania informacji z usługi do aktywności używane są transmisje.
Usługa deklaruje funkcję do rozpowszechniania nowego stanu. Ta funkcja przyjmuje ciąg działania, który jest przekazywany do obiektu Intent przed rozgłoszeniem go w systemie.
private fun broadcastUpdate(action: String) { val intent = Intent(action) sendBroadcast(intent) }
Gdy funkcja transmisji jest już dostępna, jest używana w BluetoothGattCallback do wysyłania informacji o stanie połączenia z serwerem GATT. Stałe i bieżący stan połączenia usługi są deklarowane w usłudze reprezentującej Intent działania.
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 } }
Nasłuchiwanie aktualizacji aktywności
Gdy usługa wyśle aktualizacje połączenia, aktywność musi zaimplementować BroadcastReceiver.
Zarejestruj ten odbiornik podczas konfigurowania aktywności i wyrejestruj go, gdy aktywność opuszcza ekran. Nasłuchując zdarzeń z usługi, aktywność może aktualizować interfejs użytkownika na podstawie bieżącego stanu połączenia z urządzeniem BLE.
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) } } }
W sekcji Transfer BLE data (Przesyłanie danych BLE) symbol BroadcastReceiver jest też używany do komunikowania wykrywania usługi oraz danych charakterystycznych z urządzenia.
Zamknij połączenie GATT
Ważnym krokiem w przypadku połączeń Bluetooth jest zamknięcie połączenia po zakończeniu korzystania z niego. Aby to zrobić, wywołaj funkcję close() na obiekcie BluetoothGatt. W tym przykładzie usługa zawiera odwołanie do BluetoothGatt. Gdy aktywność odłączy się od usługi, połączenie zostanie zamknięte, aby uniknąć wyczerpania baterii urządzenia.
class BluetoothLeService : Service() { // ... override fun onUnbind(intent: Intent?): Boolean { close() return super.onUnbind(intent) } private fun close() { bluetoothGatt?.let { gatt -> gatt.close() bluetoothGatt = null } } }