Połącz się z serwerem GATT

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