Połącz się z serwerem GATT

Pierwszym krokiem w interakcji z urządzeniem BLE jest nawiązanie do niego połączenia. Chodzi konkretnie 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 należy 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ę to z serwerem GATT hostowanym przez urządzenie BLE i zwraca instancję BluetoothGatt, której możesz użyć do wykonywania operacji klienta GATT. Element wywołujący (aplikacja na Androida) to klient GATT. Pole BluetoothGattCallback służy do dostarczania klientowi wyników, takich jak stan połączenia, oraz wszelkich dalszych operacji klienckich w GATT.

Skonfiguruj powiązaną usługę

W poniższym przykładzie aplikacja BLE udostępnia działanie (DeviceControlActivity) umożliwiające połączenie z urządzeniami Bluetooth, wyświetlanie danych z urządzenia oraz wyświetlanie usług i parametrów 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 przez interfejs BLE API. Komunikacja jest realizowana za pomocą powiązanej usługi, która umożliwia aktywności na nawiązanie połączenia z interfejsem BluetoothLeService i wywołanie funkcji w celu połączenia się z urządzeniami. Element BluetoothLeService wymaga implementacji Binder, która zapewnia dostęp do usługi w ramach tego 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ą kodu bindService(), który może przekazywać kod Intent w celu uruchomienia usługi, implementacji ServiceConnection do nasłuchiwania zdarzeń 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 BluetoothAdapter

Po powiązaniu usługa musi mieć dostęp do BluetoothAdapter. Sprawdź, czy przejściówka jest dostępna na urządzeniu. Przeczytaj Skonfiguruj Bluetooth, aby dowiedzieć się więcej na temat BluetoothAdapter. W poniższym przykładzie ten kod konfiguracji jest ujęty w funkcję initialize(), która zwraca wartość Boolean wskazującą na sukces.

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

    ...
}

Aktywność wywołuje tę funkcję w ramach swojej implementacji ServiceConnection. Obsługa fałszywej wartości zwracanej przez funkcję initialize() zależy od aplikacji. Możesz wyświetlić użytkownikowi komunikat o błędzie informujący, że obecne urządzenie nie obsługuje operacji Bluetooth lub wyłączyć funkcje, które wymagają Bluetootha do działania. W poniższym przykładzie działanie finish() jest wywoływane przez działanie w celu przekierowania użytkownika 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 BluetoothService może połączyć się z urządzeniem BLE. Aktywność musi wysłać adres urządzenia do usługi, aby mogło zainicjować połączenie. Usługa najpierw wywoła metodę getRemoteDevice() na urządzeniu BluetoothAdapter, aby uzyskać dostęp do urządzenia. Jeśli adapter nie może znaleźć urządzenia o tym adresie, getRemoteDevice() zgłasza 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 przekazywać adres urządzenia BLE. W poniższym przykładzie adres urządzenia jest przekazywany do działania 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;
    }
};

Zadeklaruj wywołanie zwrotne GATT

Gdy aktywność informuje usługę, z którym urządzeniem ma się połączyć, a usługa łączy się z urządzeniem, usługa 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ługi, przeczytaniu informacji o cechach i powiadomieniach o charakterze.

Ten temat dotyczy powiadomień o stanie połączenia. Informacje o tym, jak wykrywać usługi, odczytywać dane i prosić o powiadomienia o cechach, znajdziesz w sekcji Przesyłanie danych BLE.

Funkcja onConnectionStateChange() jest aktywowana po zmianie połączenia z serwerem GATT na urządzeniu. W poniższym przykładzie wywołanie zwrotne jest zdefiniowane w klasie Service, więc można go używać z BluetoothDevice, gdy usługa się z nim 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(), aby łączyć 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 przekazywana jest metoda false dla autoConnect.

Zostanie też dodana właściwość BluetoothGatt. Dzięki temu usługa może zamknąć połączenie, gdy nie będzie 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 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;
        }
    }
}

Aktualne informacje o transmisjach

Gdy serwer łączy się z serwerem GATT lub się od niego rozłącza, musi powiadamiać o aktywności o nowym stanie. Można to osiągnąć na kilka sposobów. W tym przykładzie użyto transmisji do wysyłania informacji z usługi do działania.

Usługa deklaruje funkcję rozgłaszania nowego stanu. Ta funkcja pobiera ciąg znaków działania, który jest przekazywany do obiektu Intent, zanim zostanie przesłany 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 skonfigurowaniu funkcji przesyłania 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ą zadeklarowane w usłudze reprezentującej działania Intent.

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

    …
}

Wysłuchaj powiadomień dotyczących aktywności

Gdy usługa rozgłasza aktualizacje połączenia, działanie musi zaimplementować BroadcastReceiver. Zarejestruj ten odbiornik podczas konfigurowania aktywności i wyrejestruj go, gdy aktywność zniknie z ekranu. Nasłuchując zdarzeń z usługi, może ona aktualizować interfejs 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 przypadku przesyłania danych BLE obiekt BroadcastReceiver służy też do przekazywania informacji o wykryciu usługi oraz danych charakterystycznych z urządzenia.

Zamknij połączenie z GATT

Ważnym etapem obsługi połączeń Bluetooth jest zamknięcie połączenia na koniec. Aby to zrobić, wywołaj w obiekcie BluetoothGatt funkcję close(). W poniższym przykładzie usługa przechowuje odniesienie do obiektu BluetoothGatt. Gdy aktywność usunie powiązanie z usługą, połączenie jest zamykane, aby uniknąć rozładowywania 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 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;
      }
}