Terhubung ke server GATT

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

var bluetoothGatt: BluetoothGatt? = null
// ...
bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)

Metode 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 mengirimkan hasil ke klien, seperti status koneksi, serta operasi klien GATT lebih lanjut.

Menyiapkan layanan terikat

Dalam contoh berikut, aplikasi BLE menyediakan aktivitas (DeviceControlActivity) untuk terhubung ke perangkat Bluetooth, menampilkan data perangkat, serta menampilkan layanan dan 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 untuk terhubung ke perangkat. BluetoothLeService memerlukan implementasi Binder yang memberikan akses ke layanan untuk aktivitas.

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

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

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

Menyiapkan BluetoothAdapter

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

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
    }

    // ...
}

Aktivitas memanggil fungsi ini dalam penerapan ServiceConnection-nya. Penanganan nilai yang 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. Dalam contoh berikut, finish() dipanggil pada aktivitas untuk mengembalikan pengguna ke layar sebelumnya.

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

    // ...
}

Hubungkan ke perangkat

Setelah instance BluetoothLeService diinisialisasi, instance tersebut dapat terhubung ke perangkat BLE. Aktivitas perlu mengirim alamat perangkat ke layanan agar layanan dapat memulai koneksi. Layanan ini akan memanggil getRemoteDevice() terlebih dahulu di BluetoothAdapter untuk mengakses perangkat. Jika adaptor tidak dapat menemukan perangkat dengan alamat tersebut, getRemoteDevice() akan menampilkan 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
    }
}

DeviceControlActivity memanggil fungsi connect() ini setelah layanan diinisialisasi. Aktivitas perlu meneruskan alamat perangkat BLE. Dalam contoh berikut, alamat perangkat diteruskan ke aktivitas sebagai ekstra intent.

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

Mendeklarasikan callback GATT

Setelah aktivitas memberi tahu layanan perangkat mana yang akan dihubungkan dan layanan terhubung ke perangkat, layanan perlu terhubung ke server GATT di 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 permintaan notifikasi karakteristik.

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

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

Menghubungkan ke layanan GATT

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

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

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

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

Info terbaru siaran

Saat 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 mengambil string tindakan yang diteruskan ke objek Intent sebelum disiarkan ke sistem.

private fun broadcastUpdate(action: String) {
    val intent = Intent(action)
    sendBroadcast(intent)
}

Setelah fungsi siaran diterapkan, fungsi ini digunakan dalam BluetoothGattCallback untuk mengirim informasi tentang status koneksi dengan server GATT. Konstanta dan status koneksi layanan saat ini dideklarasikan di layanan yang merepresentasikan tindakan Intent.

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

Memproses pembaruan dalam aktivitas

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

Di Transfer data BLE, BroadcastReceiver juga digunakan untuk mengomunikasikan penemuan layanan serta data karakteristik dari perangkat.

Menutup koneksi GATT

Salah satu langkah penting saat menangani koneksi Bluetooth adalah menutup koneksi setelah Anda selesai menggunakannya. Untuk melakukannya, panggil fungsi close() pada objek BluetoothGatt. Dalam contoh berikut, layanan memegang referensi ke BluetoothGatt. Saat aktivitas melepaskan binding dari layanan, koneksi ditutup untuk menghindari pengurasan daya baterai perangkat.

class BluetoothLeService : Service() {

    // ...
    override fun onUnbind(intent: Intent?): Boolean {
        close()
        return super.onUnbind(intent)
    }

    private fun close() {
        bluetoothGatt?.let { gatt ->
            gatt.close()
            bluetoothGatt = null
        }
    }
}