Terhubung ke server GATT

Langkah pertama dalam berinteraksi dengan perangkat BLE adalah menghubungkan ke perangkat tersebut. Lebih khususnya, menghubungkan ke server GATT di perangkat. Untuk terhubung ke server GATT pada perangkat BLE, gunakan metode connectGatt(). Metode ini memerlukan tiga parameter: objek Context, autoConnect (boolean yang menunjukkan apakah akan terhubung secara otomatis ke perangkat BLE segera setelah tersedia), dan referensi ke BluetoothGattCallback:

Kotlin

var bluetoothGatt: BluetoothGatt? = null
...

bluetoothGatt = device.connectGatt(this, false, gattCallback)

Java

bluetoothGatt = device.connectGatt(this, false, gattCallback);

Objek ini terhubung ke server GATT yang dihosting oleh perangkat BLE, dan menampilkan instance BluetoothGatt, yang kemudian dapat Anda gunakan untuk melakukan operasi klien GATT. Pemanggil (aplikasi Android) adalah klien GATT. BluetoothGattCallback digunakan untuk memberikan hasil kepada klien, seperti status koneksi, serta operasi klien GATT lebih lanjut.

Menyiapkan layanan terikat

Pada contoh berikut, aplikasi BLE menyediakan aktivitas (DeviceControlActivity) untuk terhubung ke perangkat Bluetooth, menampilkan data perangkat, dan menampilkan layanan serta karakteristik GATT yang didukung oleh perangkat. Berdasarkan input pengguna, aktivitas ini berkomunikasi dengan Service yang disebut BluetoothLeService, yang berinteraksi dengan perangkat BLE melalui BLE API. Komunikasi dilakukan menggunakan layanan terikat yang memungkinkan aktivitas terhubung ke BluetoothLeService dan memanggil fungsi agar terhubung ke perangkat. BluetoothLeService memerlukan implementasi Binder yang memberikan akses ke layanan untuk aktivitas tersebut.

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

Aktivitas dapat memulai layanan menggunakan bindService(), yang meneruskan Intent untuk memulai layanan, implementasi ServiceConnection untuk memproses peristiwa koneksi dan pemutusan koneksi, serta flag untuk menentukan opsi koneksi tambahan.

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

Menyiapkan BluetoothAdapter

Setelah terikat, layanan perlu mengakses BluetoothAdapter. Anda harus memeriksa apakah adaptor tersedia di perangkat. Baca Menyiapkan Bluetooth untuk mengetahui informasi selengkapnya tentang BluetoothAdapter. Contoh berikut menggabungkan kode penyiapan ini dalam fungsi initialize() yang menampilkan nilai Boolean yang menunjukkan keberhasilan.

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

    ...
}

Aktivitas memanggil fungsi ini dalam implementasi ServiceConnection-nya. Menangani nilai yang ditampilkan salah dari fungsi initialize() bergantung pada aplikasi Anda. Anda dapat menampilkan pesan error kepada pengguna yang menunjukkan bahwa perangkat saat ini tidak mendukung operasi Bluetooth atau menonaktifkan fitur apa pun yang memerlukan Bluetooth agar berfungsi. Pada contoh berikut, finish() dipanggil pada aktivitas untuk mengirim pengguna kembali ke layar sebelumnya.

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

    ...
}

Hubungkan ke perangkat

Setelah diinisialisasi, BluetoothService dapat terhubung ke perangkat BLE. Aktivitas perlu mengirimkan alamat perangkat ke layanan agar dapat memulai koneksi. Layanan akan memanggil getRemoteDevice() terlebih dahulu pada BluetoothAdapter untuk mengakses perangkat. Jika adaptor tidak dapat menemukan perangkat dengan alamat tersebut, getRemoteDevice() akan menampilkan 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 memanggil fungsi connect() ini setelah layanan diinisialisasi. Aktivitas harus meneruskan alamat perangkat BLE. Pada contoh berikut, alamat perangkat diteruskan ke aktivitas sebagai tambahan intent.

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

Mendeklarasikan callback GATT

Setelah aktivitas memberi tahu layanan perangkat mana yang akan dihubungkan dan layanan terhubung ke perangkat, layanan tersebut harus terhubung ke server GATT pada perangkat BLE. Koneksi ini memerlukan BluetoothGattCallback untuk menerima notifikasi tentang status koneksi, penemuan layanan, pembacaan karakteristik, dan notifikasi karakteristik.

Topik ini berfokus pada notifikasi status koneksi. Lihat Mentransfer data BLE untuk mengetahui cara melakukan penemuan layanan, pembacaan karakteristik, dan meminta notifikasi karakteristik.

Fungsi onConnectionStateChange() terpicu saat koneksi ke server GATT perangkat berubah. Pada contoh berikut, callback ditentukan di class Service sehingga dapat digunakan dengan BluetoothDevice setelah layanan terhubung kenya.

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

Terhubung ke layanan GATT

Setelah BluetoothGattCallback dideklarasikan, layanan dapat menggunakan objek BluetoothDevice dari fungsi connect() untuk terhubung ke layanan GATT di perangkat.

Fungsi connectGatt() digunakan. Tindakan ini memerlukan objek Context, flag boolean autoConnect, dan BluetoothGattCallback. Dalam contoh ini, aplikasi langsung terhubung ke perangkat BLE, sehingga false diteruskan untuk autoConnect.

Properti BluetoothGatt juga ditambahkan. Hal ini memungkinkan layanan menutup koneksi saat tidak diperlukan lagi.

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

Siaran pembaruan

Ketika server terhubung atau terputus dari server GATT, server perlu memberi tahu aktivitas status baru. Ada beberapa cara untuk melakukannya. Contoh berikut menggunakan siaran untuk mengirim informasi dari layanan ke aktivitas.

Layanan mendeklarasikan fungsi untuk menyiarkan status baru. Fungsi ini menggunakan string tindakan yang diteruskan ke objek Intent sebelum disiarkan ke sistem.

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

Setelah diterapkan, fungsi siaran akan digunakan dalam BluetoothGattCallback untuk mengirim informasi tentang status koneksi dengan server GATT. Konstanta dan status koneksi layanan saat ini dideklarasikan dalam layanan yang mewakili tindakan 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);
            }
        }
    };

    …
}

Memproses update dalam aktivitas

Setelah layanan menyiarkan update koneksi, aktivitas harus mengimplementasikan BroadcastReceiver. Daftarkan penerima ini saat menyiapkan aktivitas, dan batalkan pendaftarannya saat aktivitas meninggalkan layar. Dengan memproses peristiwa dari layanan, aktivitas dapat memperbarui antarmuka pengguna berdasarkan status koneksi saat ini dengan perangkat 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;
    }
}

Dalam Mentransfer data BLE, BroadcastReceiver juga digunakan untuk mengomunikasikan penemuan layanan serta data karakteristik dari perangkat.

Tutup koneksi GATT

Salah satu langkah penting saat menangani koneksi Bluetooth adalah menutup koneksi setelah Anda selesai melakukannya. Untuk melakukannya, panggil fungsi close() pada objek BluetoothGatt. Pada contoh berikut, layanan menyimpan referensi ke BluetoothGatt. Jika aktivitas terlepas dari layanan, koneksi akan ditutup untuk menghindari kehabisan baterai perangkat.

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