Pierwszym krokiem podczas interakcji z urządzeniem BLE jest nawiązanie z nim połączenia. Chodzi o łączenie się 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ść logiczna wskazująca, czy automatycznie połączyć się z urządzeniem BLE, gdy tylko stanie się dostępne) oraz odwołanie do BluetoothGattCallback
:
Kotlin
var bluetoothGatt: BluetoothGatt? = null ... bluetoothGatt = device.connectGatt(this, false, gattCallback)
Java
bluetoothGatt = device.connectGatt(this, false, gattCallback);
Łączy się z serwerem GATT hostowanym przez urządzenie BLE i zwraca instancję BluetoothGatt
, której możesz potem używać do wykonywania operacji na kliencie GATT. Element wywołujący (aplikacja na Androida)
to klient GATT. BluetoothGattCallback
służy do dostarczania klientowi wyników, takich jak stan połączenia, a także wszelkie dalsze operacje klienta GATT.
Konfigurowanie powiązanej usługi
W poniższym przykładzie aplikacja BLE udostępnia działanie (DeviceControlActivity
) służące do łączenia się z urządzeniami Bluetooth, wyświetlania danych z urządzenia oraz wyświetlania usług i cech GATT obsługiwanych przez urządzenie. Na podstawie danych wejściowych użytkownika ta aktywność komunikuje się z obiektem Service
o nazwie BluetoothLeService
, który wchodzi w interakcję z urządzeniem BLE za pomocą interfejsu BLE API. Komunikacja jest realizowana z użyciem powiązanej usługi, która pozwala aktywności na nawiązanie połączenia z BluetoothLeService
i wywołanie funkcji w celu nawiązania połączenia z urządzeniami. BluetoothLeService
wymaga implementacji Binder
, która zapewnia dostęp do usługi dla działania.
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; } } }
Działanie może uruchamiać usługę za pomocą polecenia bindService()
, przekazać w Intent
w celu uruchomienia usługi, implementacji ServiceConnection
wykrywającej zdarzenia połączenia i rozłączenia oraz flagi określającej dodatkowe opcje połączenia.
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); } }
Skonfiguruj adapter Bluetooth
Po powiązaniu usługi musi ona mieć dostęp do BluetoothAdapter
. Powinien sprawdzić, czy przejściówka jest dostępna w urządzeniu. Przeczytaj artykuł Konfigurowanie Bluetootha, aby dowiedzieć się więcej o urządzeniu BluetoothAdapter
. Poniższy przykład umieszcza ten kod konfiguracji w funkcji initialize()
, która zwraca wartość Boolean
oznaczającą powodzenie.
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; } ... }
Ta aktywność wywołuje tę funkcję w ramach implementacji ServiceConnection
.
Obsługa wartości zwróconej przez funkcję initialize()
zależy od Twojej aplikacji. Możesz wyświetlić użytkownikowi komunikat o błędzie z informacją, że bieżące urządzenie nie obsługuje Bluetootha, a także wyłączyć funkcje, które wymagają Bluetootha do działania. W poniższym przykładzie wywołanie finish()
jest wywoływane dla działania, aby przekierować użytkownika z powrotem na poprzedni ekran.
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; } }; ... }
Połącz się z urządzeniem
Po zainicjowaniu instancji BluetoothLeService
może się ona połączyć z urządzeniem BLE. Aktywność musi wysłać adres urządzenia do usługi, aby móc zainicjować połączenie. Aby uzyskać dostęp do urządzenia, usługa najpierw wywoła metodę getRemoteDevice()
na urządzeniu BluetoothAdapter
. Jeśli przejściówka nie może znaleźć urządzenia o tym adresie, getRemoteDevice()
wysyła IllegalArgumentException
.
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
wywołuje tę funkcję connect()
po zainicjowaniu usługi. Aktywność musi być przekazywana w adresie urządzenia BLE. W poniższym przykładzie adres urządzenia jest przekazywany do aktywności jako dodatkowa intencja.
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; } };
Zadeklarowanie wywołania zwrotnego GATT
Gdy aktywność informuje usługę, z którym urządzeniem ma się połączyć, a usługa połączy się z urządzeniem, usługa musi połączyć się z serwerem GATT na urządzeniu z BLE. To połączenie wymaga BluetoothGattCallback
do otrzymywania powiadomień o stanie połączenia, wykrywaniu usługi, odczytach cech i powiadomieniach o cechach.
Ten temat dotyczy powiadomień o stanie połączenia. Informacje o tym, jak przeprowadzać wykrywanie usług, odczyty cech i żądania powiadomień o cechach, znajdziesz w artykule Przenoszenie danych BLE.
Funkcja onConnectionStateChange()
jest wyzwalana po zmianie połączenia z serwerem GATT urządzenia.
W poniższym przykładzie wywołanie zwrotne jest zdefiniowane w klasie Service
, więc można go użyć z BluetoothDevice
, gdy usługa się z nią połączy.
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 } } };
Połącz z usługą GATT
Po zadeklarowaniu BluetoothGattCallback
usługa może używać obiektu BluetoothDevice
z funkcji connect()
do łączenia się z usługą GATT na urządzeniu.
Używana jest funkcja connectGatt()
. Wymaga to obiektu Context
, flagi wartości logicznej autoConnect
i BluetoothGattCallback
. W tym przykładzie aplikacja łączy się bezpośrednio z urządzeniem BLE, więc w polu autoConnect
przekazywana jest wartość false
.
Dodano także właściwość BluetoothGatt
. Dzięki temu usługa może zamknąć połączenie, gdy nie jest już potrzebne.
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; } } }
Aktualne informacje o transmisjach
Gdy serwer łączy się z serwerem GATT lub się od niego rozłącza, musi powiadomić o aktywności o nowym stanie. Istnieje kilka sposobów wykonania tej czynności. W przykładzie poniżej użyto transmisji do wysłania informacji z usługi do aktywności.
Usługa deklaruje funkcję rozgłaszającą nowy stan. Ta funkcja przyjmuje ciąg znaków działania, który jest przekazywany do obiektu Intent
przed przekazaniem do systemu.
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); }
Po utworzeniu funkcji transmisji jest ona używana w obrębie BluetoothGattCallback
do wysyłania informacji o stanie połączenia do serwera GATT. Stałe i bieżący stan połączenia usługi są deklarowane w usłudze reprezentujące działania Intent
.
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); } } }; … }
Wykrywaj aktualizacje dotyczące aktywności
Gdy usługa rozpowszechni aktualizację połączenia, aktywność musi zaimplementować BroadcastReceiver
.
Zarejestruj tego odbiorcę podczas konfigurowania działania i wyrejestruj go, gdy działanie znika z ekranu. Nasłuchiwanie zdarzeń z usługi umożliwia aktualizowanie interfejsu użytkownika na podstawie bieżącego stanu połączenia z urządzeniem BLE.
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; } }
W sekcji Przenoszenie danych BLE BroadcastReceiver
służy też do przekazywania informacji o wykryciu usługi, a także do przekazywania danych charakterystycznych z urządzenia.
Zamknij połączenie GATT
Jednym z ważnych kroków podczas obsługi połączeń Bluetooth jest zamknięcie połączenia po jego zakończeniu. Aby to zrobić, wywołaj funkcję close()
w obiekcie BluetoothGatt
. W poniższym przykładzie usługa zawiera odniesienie do obiektu BluetoothGatt
. Gdy to działanie zostanie usunięte z usługi, połączenie zostanie zamknięte, aby uniknąć rozładowania baterii urządzenia.
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; } }