Android 4.3 (API level 18) memperkenalkan dukungan platform bawaan untuk Bluetooth Hemat Energi (BLE) pada peran sentral dan menyediakan API yang dapat digunakan aplikasi untuk menemukan perangkat, mengirimkan kueri layanan, dan mengirim informasi.
Kasus penggunaan umum meliputi:
- Mentransfer sejumlah kecil data antar-perangkat terdekat.
- Berinteraksi dengan sensor kedekatan seperti Google Beacons untuk memberi pengguna pengalaman khusus berdasarkan lokasi mereka saat ini.
Berbeda dengan Bluetooth Klasik, Bluetooth Hemat Energi (BLE) dirancang untuk memberikan konsumsi daya yang jauh lebih rendah. Hal ini memungkinkan aplikasi Android berkomunikasi dengan perangkat BLE yang memiliki persyaratan daya yang lebih ketat, seperti sensor kedekatan, pemantau denyut jantung, dan perangkat kebugaran.
Perhatian: Jika seorang pengguna memasangkan perangkat mereka dengan perangkat lain menggunakan BLE, data yang dikomunikasikan antara kedua perangkat dapat diakses oleh semua aplikasi pada perangkat pengguna.
Karena alasan ini, jika aplikasi Anda mengambil data sensitif, Anda harus menerapkan keamanan layer aplikasi untuk melindungi privasi data tersebut.
Konsep dan istilah kunci
Berikut ini ringkasan konsep dan istilah BLE kunci:
- Profil Atribut Generik (GATT)—Profil GATT adalah spesifikasi umum untuk mengirim dan menerima potongan data pendek yang dikenal sebagai "atribut" melalui link BLE. Semua profil aplikasi Hemat Energi saat ini didasarkan pada GATT.
- Bluetooth SIG mendefinisikan banyak profil untuk perangkat Hemat Energi. Profil adalah spesifikasi untuk cara kerja perangkat dalam aplikasi tertentu. Perhatikan bahwa perangkat dapat menerapkan lebih dari satu profil. Misalnya, perangkat dapat berisi monitor denyut jantung dan detektor level baterai.
- Protokol Atribut (ATT)—GATT dibuat di atas Protokol Atribut (ATT). Ini juga disebut sebagai GATT/ATT. ATT dioptimalkan untuk dijalankan pada perangkat BLE. Untuk tujuan ini, ATT menggunakan sesedikit mungkin byte. Setiap atribut diidentifikasi secara unik oleh Universally Unique Identifier (UUID), yang merupakan format 128-bit terstandardisasi untuk ID string yang digunakan untuk mengidentifikasi informasi secara unik. Atribut yang diangkut oleh ATT diformat sebagai karakteristik dan layanan.
- Karakteristik—Karakteristik berisi nilai tunggal dan deskriptor 0-n yang menjelaskan nilai karakteristik. Karakteristik dapat dianggap sebagai jenis, analog dengan class.
- Deskriptor—Deskriptor adalah atribut yang didefinisikan yang menjelaskan nilai karakteristik. Misalnya, deskriptor dapat menentukan deskripsi yang dapat dibaca manusia, rentang yang dapat diterima untuk nilai karakteristik, atau unit ukuran yang khusus untuk nilai karakteristik.
- Layanan—Layanan adalah kumpulan karakteristik. Misalnya, Anda dapat memiliki layanan yang disebut "Pemantau Denyut Jantung" yang mencakup karakteristik seperti "pengukuran denyut jantung." Anda dapat menemukan daftar profil dan layanan berbasis GATT yang ada di bluetooth.org.
Peran dan tanggung jawab
Berikut adalah peran dan tanggung jawab yang berlaku ketika perangkat Android berinteraksi dengan perangkat BLE:
- Sentral vs. periferal. Ini berlaku untuk koneksi BLE itu sendiri. Perangkat dalam peran sentral memindai, mencari iklan, dan perangkat dalam peran periferal membuat iklan.
- Server GATT vs. klien GATT. Ini menentukan bagaimana dua perangkat berkomunikasi satu sama lain begitu berhasil membuat koneksi.
Untuk memahami perbedaannya, bayangkan Anda memiliki ponsel Android dan pelacak aktivitas yang merupakan perangkat BLE. Telepon mendukung peran sentral; pelacak aktivitas mendukung peran periferal (untuk membuat koneksi BLE, Anda perlu salah satu dari masing-masing—dua hal yang hanya mendukung periferal tidak dapat saling berkomunikasi, juga dua hal yang hanya mendukung sentral).
Setelah ponsel dan pelacak aktivitas membuat koneksi, mereka mulai mentransfer metadata GATT satu sama lain. Bergantung pada jenis data yang ditransfer, satu atau yang lain mungkin bertindak sebagai server. Misalnya, jika pelacak aktivitas ingin melaporkan data sensor ke ponsel, mungkin masuk akal bagi pelacak aktivitas untuk bertindak sebagai server. Jika pelacak aktivitas ingin menerima pembaruan dari telepon, maka masuk akal jika telepon bertindak sebagai server.
Pada contoh yang digunakan dalam dokumen ini, aplikasi Android (berjalan pada perangkat Android) adalah klien GATT. Aplikasi ini mendapatkan data dari server GATT, yang merupakan monitor denyut jantung BLE yang mendukung Profil Denyut Jantung. Namun Anda bisa juga mendesain aplikasi Android untuk memainkan peran server GATT. Lihat BluetoothGattServer
untuk informasi selengkapnya.
Izin BLE
Untuk menggunakan fitur Bluetooth dalam aplikasi, Anda harus mendeklarasikan dua izin BLUETOOTH
. Anda memerlukan izin ini untuk melakukan komunikasi Bluetooth, seperti meminta koneksi, menerima koneksi, dan mentransfer data.
Anda juga harus mendeklarasikan izin ACCESS_FINE_LOCATION
, mengingat beacon LE sering dikaitkan dengan lokasi. Tanpa izin ini, pemindaian tidak akan menampilkan hasil apa pun.
Catatan: Jika aplikasi Anda menargetkan Android 9 (API level 28) atau yang lebih rendah, Anda dapat mendeklarasikan izin ACCESS_COARSE_LOCATION
alih-alih izin ACCESS_FINE_LOCATION
.
Jika ingin aplikasi memulai pencarian perangkat atau memanipulasi setelan Bluetooth, Anda juga harus mendeklarasikan izin BLUETOOTH_ADMIN
. Catatan: Jika Anda menggunakan izin BLUETOOTH_ADMIN
, maka Anda juga harus memiliki izin BLUETOOTH
.
Mendeklarasikan izin dalam file manifes aplikasi Anda. Sebagai contoh:
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <!-- If your app targets Android 9 or lower, you can declare ACCESS_COARSE_LOCATION instead. --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Jika ingin mendeklarasikan bahwa aplikasi Anda hanya tersedia untuk perangkat yang memiliki kemampuan BLE, sertakan yang berikut ini dalam manifes aplikasi Anda:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
Namun, jika ingin membuat aplikasi Anda tersedia untuk perangkat yang tidak mendukung BLE, Anda harus tetap memasukkan elemen ini dalam manifes aplikasi Anda, tetapi tetapkan required="false"
. Kemudian pada saat run-time Anda dapat menentukan ketersediaan BLE dengan menggunakan PackageManager.hasSystemFeature()
:
Kotlin
private fun PackageManager.missingSystemFeature(name: String): Boolean = !hasSystemFeature(name) ... packageManager.takeIf { it.missingSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) }?.also { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show() finish() }
Java
// Use this check to determine whether BLE is supported on the device. Then // you can selectively disable BLE-related features. if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish(); }
Menyiapkan BLE
Sebelum aplikasi Anda dapat berkomunikasi melalui BLE, Anda perlu memverifikasi bahwa BLE didukung pada perangkat, dan jika demikian, pastikan BLE diaktifkan. Ingat bahwa pemeriksaan ini hanya diperlukan jika <uses-feature.../>
disetel ke false.
Jika BLE tidak didukung, maka Anda harus menonaktifkan fitur BLE dengan baik. Jika BLE didukung, tetapi dinonaktifkan, maka Anda dapat meminta pengguna mengaktifkan Bluetooth tanpa keluar dari aplikasi Anda. Penyiapan ini dilakukan dalam dua langkah, menggunakan BluetoothAdapter
.
- Dapatkan
BluetoothAdapter
Diperlukan
BluetoothAdapter
untuk setiap dan semua aktivitas Bluetooth.BluetoothAdapter
merepresentasikan adaptor Bluetooth perangkat itu sendiri (radio Bluetooth). Ada satu adaptor Bluetooth untuk seluruh sistem, dan aplikasi Anda bisa berinteraksi dengannya menggunakan objek ini. Cuplikan di bawah ini menunjukkan cara mendapatkan adaptor. Perhatikan bahwa pendekatan ini menggunakangetSystemService()
untuk mengembalikan instanceBluetoothManager
, yang kemudian digunakan untuk mendapatkan adaptor. Android 4.3 (API Level 18) memperkenalkanBluetoothManager
:Kotlin
private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) { val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothManager.adapter }
Java
private BluetoothAdapter bluetoothAdapter; ... // Initializes Bluetooth adapter. final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter();
- Mengaktifkan Bluetooth
Berikutnya, Anda harus memastikan bahwa Bluetooth diaktifkan. Panggil
isEnabled()
untuk memeriksa apakah Bluetooth saat ini diaktifkan. Jika metode ini kembali ke false, maka Bluetooth dinonaktifkan. Cuplikan berikut memeriksa apakah Bluetooth diaktifkan. Jika tidak, cuplikan menampilkan error yang meminta pengguna untuk membuka Setelan untuk mengaktifkan Bluetooth:Kotlin
private val BluetoothAdapter.isDisabled: Boolean get() = !isEnabled ... // Ensures Bluetooth is available on the device and it is enabled. If not, // displays a dialog requesting user permission to enable Bluetooth. bluetoothAdapter?.takeIf { it.isDisabled }?.apply { val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT) }
Java
// Ensures Bluetooth is available on the device and it is enabled. If not, // displays a dialog requesting user permission to enable Bluetooth. if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
Catatan: Konstanta
REQUEST_ENABLE_BT
diteruskan kestartActivityForResult(android.content.Intent, int)
adalah integer yang ditentukan secara lokal (yang harus lebih besar dari 0) yang dikembalikan sistem kepada Anda dalam implementasionActivityResult(int, int, android.content.Intent)
sebagai parameterrequestCode
.
Mencari perangkat BLE
Untuk menemukan perangkat BLE, Anda menggunakan metode startLeScan()
. Metode ini menggunakan BluetoothAdapter.LeScanCallback
sebagai parameter. Anda harus menerapkan callback ini, karena itulah cara hasil pemindaian ditampilkan. Karena pemindaian intensif baterai, Anda harus mematuhi panduan berikut:
- Begitu Anda menemukan perangkat yang diinginkan, hentikan pemindaian.
- Jangan pernah memindai dalam loop, dan menetapkan batas waktu pada pemindaian Anda. Perangkat yang sebelumnya tersedia mungkin telah bergerak keluar dari jangkauan, dan terus memindai yang menguras baterai.
Cuplikan berikut ini menunjukkan cara memulai dan menghentikan pemindaian:
Kotlin
private const val SCAN_PERIOD: Long = 10000 /** * Activity for scanning and displaying available BLE devices. */ class DeviceScanActivity( private val bluetoothAdapter: BluetoothAdapter, private val handler: Handler ) : ListActivity() { private var mScanning: Boolean = false private fun scanLeDevice(enable: Boolean) { when (enable) { true -> { // Stops scanning after a pre-defined scan period. handler.postDelayed({ mScanning = false bluetoothAdapter.stopLeScan(leScanCallback) }, SCAN_PERIOD) mScanning = true bluetoothAdapter.startLeScan(leScanCallback) } else -> { mScanning = false bluetoothAdapter.stopLeScan(leScanCallback) } } } }
Java
/** * Activity for scanning and displaying available BLE devices. */ public class DeviceScanActivity extends ListActivity { private BluetoothAdapter bluetoothAdapter; private boolean mScanning; private Handler handler; // Stops scanning after 10 seconds. private static final long SCAN_PERIOD = 10000; ... private void scanLeDevice(final boolean enable) { if (enable) { // Stops scanning after a pre-defined scan period. handler.postDelayed(new Runnable() { @Override public void run() { mScanning = false; bluetoothAdapter.stopLeScan(leScanCallback); } }, SCAN_PERIOD); mScanning = true; bluetoothAdapter.startLeScan(leScanCallback); } else { mScanning = false; bluetoothAdapter.stopLeScan(leScanCallback); } ... } ... }
Jika Anda ingin memindai hanya untuk jenis periferal tertentu, Anda bisa memanggil startLeScan(UUID[], BluetoothAdapter.LeScanCallback)
, memberikan array objek UUID
yang menentukan layanan GATT yang didukung aplikasi Anda.
Berikut ini adalah implementasi dari BluetoothAdapter.LeScanCallback
, yang merupakan antarmuka yang digunakan untuk memberikan hasil pemindaian BLE:
Kotlin
val leDeviceListAdapter: LeDeviceListAdapter = ... private val leScanCallback = BluetoothAdapter.LeScanCallback { device, rssi, scanRecord -> runOnUiThread { leDeviceListAdapter.addDevice(device) leDeviceListAdapter.notifyDataSetChanged() } }
Java
private LeDeviceListAdapter leDeviceListAdapter; ... // Device scan callback. private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { runOnUiThread(new Runnable() { @Override public void run() { leDeviceListAdapter.addDevice(device); leDeviceListAdapter.notifyDataSetChanged(); } }); } };
Catatan: Anda hanya dapat memindai perangkat Bluetooth LE atau memindai perangkat Bluetooth Klasik, seperti yang dijelaskan dalam Bluetooth. Anda tidak dapat memindai Bluetooth LE dan perangkat klasik secara bersamaan.
Terhubung ke server GATT
Langkah pertama dalam berinteraksi dengan perangkat BLE adalah menghubungkannya— lebih khusus, menghubungkan ke server GATT pada perangkat. Untuk terhubung ke server GATT pada perangkat BLE, Anda menggunakan metode connectGatt()
. Metode ini mengambil tiga parameter: objek Context
, autoConnect
(boolean menunjukkan apakah akan terhubung secara otomatis ke perangkat BLE begitu tersedia), dan referensi ke a BluetoothGattCallback
:
Kotlin
var bluetoothGatt: BluetoothGatt? = null ... bluetoothGatt = device.connectGatt(this, false, gattCallback)
Java
bluetoothGatt = device.connectGatt(this, false, gattCallback);
Ini terhubung ke server GATT yang dihosting oleh perangkat BLE, dan mengembalikan instance BluetoothGatt
, yang 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.
Dalam contoh ini, aplikasi BLE menyediakan aktivitas (DeviceControlActivity
) untuk menghubungkan, menampilkan data, dan menampilkan layanan dan karakteristik GATT yang didukung oleh perangkat. Berdasarkan input pengguna, aktivitas ini berkomunikasi dengan Service
bernama BluetoothLeService
, yang berinteraksi dengan perangkat BLE melalui Android BLE API:
Kotlin
private val TAG = BluetoothLeService::class.java.simpleName private const val STATE_DISCONNECTED = 0 private const val STATE_CONNECTING = 1 private const val STATE_CONNECTED = 2 const val ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED" const val ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED" const val ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED" const val ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE" const val EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA" val UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT) // A service that interacts with the BLE device via the Android BLE API. class BluetoothLeService(private var bluetoothGatt: BluetoothGatt?) : Service() { private var connectionState = STATE_DISCONNECTED // Various callback methods defined by the BLE API. private val gattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int ) { val intentAction: String when (newState) { BluetoothProfile.STATE_CONNECTED -> { intentAction = ACTION_GATT_CONNECTED connectionState = STATE_CONNECTED broadcastUpdate(intentAction) Log.i(TAG, "Connected to GATT server.") Log.i(TAG, "Attempting to start service discovery: " + bluetoothGatt?.discoverServices()) } BluetoothProfile.STATE_DISCONNECTED -> { intentAction = ACTION_GATT_DISCONNECTED connectionState = STATE_DISCONNECTED Log.i(TAG, "Disconnected from GATT server.") broadcastUpdate(intentAction) } } } // New services discovered override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { when (status) { BluetoothGatt.GATT_SUCCESS -> broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED) else -> Log.w(TAG, "onServicesDiscovered received: $status") } } // Result of a characteristic read operation override fun onCharacteristicRead( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int ) { when (status) { BluetoothGatt.GATT_SUCCESS -> { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic) } } } } }
Java
// A service that interacts with the BLE device via the Android BLE API. public class BluetoothLeService extends Service { private final static String TAG = BluetoothLeService.class.getSimpleName(); private BluetoothManager bluetoothManager; private BluetoothAdapter bluetoothAdapter; private String bluetoothDeviceAddress; private BluetoothGatt bluetoothGatt; private int connectionState = STATE_DISCONNECTED; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTING = 1; private static final int STATE_CONNECTED = 2; 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"; public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; public final static String EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA"; public final static UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT); // Various callback methods defined by the BLE API. private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { String intentAction; if (newState == BluetoothProfile.STATE_CONNECTED) { intentAction = ACTION_GATT_CONNECTED; connectionState = STATE_CONNECTED; broadcastUpdate(intentAction); Log.i(TAG, "Connected to GATT server."); Log.i(TAG, "Attempting to start service discovery:" + bluetoothGatt.discoverServices()); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { intentAction = ACTION_GATT_DISCONNECTED; connectionState = STATE_DISCONNECTED; Log.i(TAG, "Disconnected from GATT server."); broadcastUpdate(intentAction); } } @Override // New services discovered public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); } else { Log.w(TAG, "onServicesDiscovered received: " + status); } } @Override // Result of a characteristic read operation public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } } ... }; ... }
Setelah suatu panggilan balik terpicu, ia memanggil metode bantuan broadcastUpdate()
yang sesuai dan memberikannya suatu tindakan. Perhatikan bahwa penguraian data di bagian ini dilakukan sesuai dengan spesifikasi profil Pengukuran Denyut Jantung Bluetooth:
Kotlin
private fun broadcastUpdate(action: String) { val intent = Intent(action) sendBroadcast(intent) } private fun broadcastUpdate(action: String, characteristic: BluetoothGattCharacteristic) { val intent = Intent(action) // This is special handling for the Heart Rate Measurement profile. Data // parsing is carried out as per profile specifications. when (characteristic.uuid) { UUID_HEART_RATE_MEASUREMENT -> { val flag = characteristic.properties val format = when (flag and 0x01) { 0x01 -> { Log.d(TAG, "Heart rate format UINT16.") BluetoothGattCharacteristic.FORMAT_UINT16 } else -> { Log.d(TAG, "Heart rate format UINT8.") BluetoothGattCharacteristic.FORMAT_UINT8 } } val heartRate = characteristic.getIntValue(format, 1) Log.d(TAG, String.format("Received heart rate: %d", heartRate)) intent.putExtra(EXTRA_DATA, (heartRate).toString()) } else -> { // For all other profiles, writes the data formatted in HEX. val data: ByteArray? = characteristic.value if (data?.isNotEmpty() == true) { val hexString: String = data.joinToString(separator = " ") { String.format("%02X", it) } intent.putExtra(EXTRA_DATA, "$data\n$hexString") } } } sendBroadcast(intent) }
Java
private void broadcastUpdate(final String action) { final Intent intent = new Intent(action); sendBroadcast(intent); } private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) { final Intent intent = new Intent(action); // This is special handling for the Heart Rate Measurement profile. Data // parsing is carried out as per profile specifications. if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { int flag = characteristic.getProperties(); int format = -1; if ((flag & 0x01) != 0) { format = BluetoothGattCharacteristic.FORMAT_UINT16; Log.d(TAG, "Heart rate format UINT16."); } else { format = BluetoothGattCharacteristic.FORMAT_UINT8; Log.d(TAG, "Heart rate format UINT8."); } final int heartRate = characteristic.getIntValue(format, 1); Log.d(TAG, String.format("Received heart rate: %d", heartRate)); intent.putExtra(EXTRA_DATA, String.valueOf(heartRate)); } else { // For all other profiles, writes the data formatted in HEX. final byte[] data = characteristic.getValue(); if (data != null && data.length > 0) { final StringBuilder stringBuilder = new StringBuilder(data.length); for(byte byteChar : data) stringBuilder.append(String.format("%02X ", byteChar)); intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString()); } } sendBroadcast(intent); }
Kembali ke DeviceControlActivity
, peristiwa ini ditangani oleh BroadcastReceiver
:
Kotlin
// Handles various events fired by the Service. // ACTION_GATT_CONNECTED: connected to a GATT server. // ACTION_GATT_DISCONNECTED: disconnected from a GATT server. // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services. // ACTION_DATA_AVAILABLE: received data from the device. This can be a // result of read or notification operations. private val gattUpdateReceiver = object : BroadcastReceiver() { private lateinit var bluetoothLeService: BluetoothLeService override fun onReceive(context: Context, intent: Intent) { val action = intent.action when (action){ ACTION_GATT_CONNECTED -> { connected = true updateConnectionState(R.string.connected) (context as? Activity)?.invalidateOptionsMenu() } ACTION_GATT_DISCONNECTED -> { connected = false updateConnectionState(R.string.disconnected) (context as? Activity)?.invalidateOptionsMenu() clearUI() } ACTION_GATT_SERVICES_DISCOVERED -> { // Show all the supported services and characteristics on the // user interface. displayGattServices(bluetoothLeService.getSupportedGattServices()) } ACTION_DATA_AVAILABLE -> { displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)) } } } }
Java
// Handles various events fired by the Service. // ACTION_GATT_CONNECTED: connected to a GATT server. // ACTION_GATT_DISCONNECTED: disconnected from a GATT server. // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services. // ACTION_DATA_AVAILABLE: received data from the device. This can be a // result of read or notification operations. 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); invalidateOptionsMenu(); } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { connected = false; updateConnectionState(R.string.disconnected); invalidateOptionsMenu(); clearUI(); } else if (BluetoothLeService. ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { // Show all the supported services and characteristics on the // user interface. displayGattServices(bluetoothLeService.getSupportedGattServices()); } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); } } };
Membaca atribut BLE
Setelah aplikasi Android Anda terhubung ke server GATT dan menemukan layanan, aplikasi tersebut dapat membaca dan menulis atribut, jika didukung. Misalnya, cuplikan ini berulang melalui layanan dan karakteristik server dan menampilkannya di UI:
Kotlin
class DeviceControlActivity : Activity() { // Demonstrates how to iterate through the supported GATT // Services/Characteristics. // In this sample, we populate the data structure that is bound to the // ExpandableListView on the UI. private fun displayGattServices(gattServices: List<BluetoothGattService>?) { if (gattServices == null) return var uuid: String? val unknownServiceString: String = resources.getString(R.string.unknown_service) val unknownCharaString: String = resources.getString(R.string.unknown_characteristic) val gattServiceData: MutableList<HashMap<String, String>> = mutableListOf() val gattCharacteristicData: MutableList<ArrayList<HashMap<String, String>>> = mutableListOf() mGattCharacteristics = mutableListOf() // Loops through available GATT Services. gattServices.forEach { gattService -> val currentServiceData = HashMap<String, String>() uuid = gattService.uuid.toString() currentServiceData[LIST_NAME] = SampleGattAttributes.lookup(uuid, unknownServiceString) currentServiceData[LIST_UUID] = uuid gattServiceData += currentServiceData val gattCharacteristicGroupData: ArrayList<HashMap<String, String>> = arrayListOf() val gattCharacteristics = gattService.characteristics val charas: MutableList<BluetoothGattCharacteristic> = mutableListOf() // Loops through available Characteristics. gattCharacteristics.forEach { gattCharacteristic -> charas += gattCharacteristic val currentCharaData: HashMap<String, String> = hashMapOf() uuid = gattCharacteristic.uuid.toString() currentCharaData[LIST_NAME] = SampleGattAttributes.lookup(uuid, unknownCharaString) currentCharaData[LIST_UUID] = uuid gattCharacteristicGroupData += currentCharaData } mGattCharacteristics += charas gattCharacteristicData += gattCharacteristicGroupData } } }
Java
public class DeviceControlActivity extends Activity { ... // Demonstrates how to iterate through the supported GATT // Services/Characteristics. // In this sample, we populate the data structure that is bound to the // ExpandableListView on the UI. private void displayGattServices(List<BluetoothGattService> gattServices) { if (gattServices == null) return; String uuid = null; String unknownServiceString = getResources(). getString(R.string.unknown_service); String unknownCharaString = getResources(). getString(R.string.unknown_characteristic); ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>(); ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData = new ArrayList<ArrayList<HashMap<String, String>>>(); mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>(); // Loops through available GATT Services. for (BluetoothGattService gattService : gattServices) { HashMap<String, String> currentServiceData = new HashMap<String, String>(); uuid = gattService.getUuid().toString(); currentServiceData.put( LIST_NAME, SampleGattAttributes. lookup(uuid, unknownServiceString)); currentServiceData.put(LIST_UUID, uuid); gattServiceData.add(currentServiceData); ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String, String>>(); List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>(); // Loops through available Characteristics. for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { charas.add(gattCharacteristic); HashMap<String, String> currentCharaData = new HashMap<String, String>(); uuid = gattCharacteristic.getUuid().toString(); currentCharaData.put( LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString)); currentCharaData.put(LIST_UUID, uuid); gattCharacteristicGroupData.add(currentCharaData); } mGattCharacteristics.add(charas); gattCharacteristicData.add(gattCharacteristicGroupData); } ... } ... }
Menerima notifikasi GATT
Sudah umum bagi aplikasi BLE untuk meminta agar diberi tahu ketika karakteristik tertentu berubah pada perangkat. Cuplikan ini menunjukkan cara mengatur pemberitahuan untuk karakteristik, menggunakan metode setCharacteristicNotification()
:
Kotlin
lateinit var bluetoothGatt: BluetoothGatt lateinit var characteristic: BluetoothGattCharacteristic var enabled: Boolean = true ... bluetoothGatt.setCharacteristicNotification(characteristic, enabled) val uuid: UUID = UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG) val descriptor = characteristic.getDescriptor(uuid).apply { value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE } bluetoothGatt.writeDescriptor(descriptor)
Java
private BluetoothGatt bluetoothGatt; BluetoothGattCharacteristic characteristic; boolean enabled; ... bluetoothGatt.setCharacteristicNotification(characteristic, enabled); ... BluetoothGattDescriptor descriptor = characteristic.getDescriptor( UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); bluetoothGatt.writeDescriptor(descriptor);
Setelah notifikasi diaktifkan untuk karakteristik, callback onCharacteristicChanged()
dipicu jika karakteristik berubah pada perangkat jarak jauh:
Kotlin
// Characteristic notification override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic) }
Java
@Override // Characteristic notification public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); }
Menutup aplikasi klien
Setelah aplikasi Anda selesai menggunakan perangkat BLE, maka harus memanggil close()
sehingga sistem dapat merilis sumber daya dengan tepat:
Kotlin
fun close() { bluetoothGatt?.close() bluetoothGatt = null }
Java
public void close() { if (bluetoothGatt == null) { return; } bluetoothGatt.close(); bluetoothGatt = null; }