เมื่อคุณเชื่อมต่อกับ BLE GATT คุณสามารถใช้ การเชื่อมต่อเพื่อค้นหาบริการที่พร้อมใช้งานในอุปกรณ์ ค้นหาข้อมูล จากอุปกรณ์ และส่งคำขอการแจ้งเตือนเมื่อคุณลักษณะ GATT บางอย่าง การเปลี่ยนแปลง
สำรวจบริการ
สิ่งแรกที่ต้องทำเมื่อคุณเชื่อมต่อกับเซิร์ฟเวอร์ GATT ในอุปกรณ์ BLE คือ
เพื่อค้นหาบริการ ส่วนนี้ให้ข้อมูลเกี่ยวกับบริการ
บนอุปกรณ์ระยะไกล ตลอดจนลักษณะของบริการและ
ข้อบ่งชี้ ในตัวอย่างต่อไปนี้ เมื่อเชื่อมต่อกับบริการสำเร็จแล้ว
อุปกรณ์ (ระบุโดยการเรียกที่เหมาะสมไปยัง
onConnectionStateChange()
ของฟังก์ชัน
BluetoothGattCallback
),
เวลา
discoverServices()
จะค้นหาข้อมูลจากอุปกรณ์ BLE
บริการจำเป็นต้องลบล้าง
onServicesDiscovered()
ในส่วน
BluetoothGattCallback
ระบบจะเรียกฟังก์ชันนี้เมื่ออุปกรณ์รายงานเกี่ยวกับบริการที่ใช้งานได้
class BluetoothLeService : Service() { ... 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 broadcastUpdate(ACTION_GATT_CONNECTED) connectionState = STATE_CONNECTED // Attempts to discover services after successful connection. bluetoothGatt?.discoverServices() } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server broadcastUpdate(ACTION_GATT_DISCONNECTED) connectionState = STATE_DISCONNECTED } } override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED) } else { Log.w(BluetoothLeService.TAG, "onServicesDiscovered received: $status") } } } ... 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" const val ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED" private const val STATE_DISCONNECTED = 0 private const val STATE_CONNECTED = 2 }
class BluetoothLeService extends Service { public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; ... 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); // Attempts to discover services after successful connection. bluetoothGatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server connectionState = STATE_DISCONNECTED; broadcastUpdate(ACTION_GATT_DISCONNECTED); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); } else { Log.w(TAG, "onServicesDiscovered received: " + status); } } }; }
บริการใช้การประกาศเพื่อแจ้ง
กิจกรรม เมื่อพบบริการแล้ว บริการจะสามารถเรียกใช้
getServices()
ถึง
รับข้อมูลที่รายงาน
class BluetoothLeService : Service() { ... fun getSupportedGattServices(): List<BluetoothGattService?>? { return bluetoothGatt?.services } }
class BluetoothLeService extends Service { ... public List<BluetoothGattService> getSupportedGattServices() { if (bluetoothGatt == null) return null; return bluetoothGatt.getServices(); } }
กิจกรรมจะสามารถเรียกใช้ฟังก์ชันนี้เมื่อได้รับ Intent ในการออกอากาศ เพื่อบ่งบอกว่าการค้นหาบริการเสร็จสิ้นแล้ว
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) } BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED -> { // Show all the supported services and characteristics on the user interface. displayGattServices(bluetoothService?.getSupportedGattServices()) } } } } }
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); } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { // Show all the supported services and characteristics on the user interface. displayGattServices(bluetoothService.getSupportedGattServices()); } } }; }
อ่านลักษณะเฉพาะ BLE
เมื่อแอปของคุณเชื่อมต่อกับเซิร์ฟเวอร์ GATT และพบบริการแล้ว สามารถอ่านและเขียนแอตทริบิวต์ได้หากรองรับ ตัวอย่างเช่น URL ต่อไปนี้ ข้อมูลโค้ดจะทำซ้ำผ่านบริการและลักษณะเฉพาะของเซิร์ฟเวอร์ รวมถึงการแสดงผล ใน UI ดังนี้
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 } } }
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); } ... } ... }
บริการ GATT จะแสดงรายการลักษณะเฉพาะที่คุณอ่านได้จาก
อุปกรณ์ ในการค้นหาข้อมูล ให้เรียกเมธอด
readCharacteristic()
ใน
BluetoothGatt
ผ่านใน
BluetoothGattCharacteristic
ที่คุณต้องการอ่าน
class BluetoothLeService : Service() { ... fun readCharacteristic(characteristic: BluetoothGattCharacteristic) { bluetoothGatt?.let { gatt -> gatt.readCharacteristic(characteristic) } ?: run { Log.w(TAG, "BluetoothGatt not initialized") Return } } }
class BluetoothLeService extends Service { ... public void readCharacteristic(BluetoothGattCharacteristic characteristic) { if (bluetoothGatt == null) { Log.w(TAG, "BluetoothGatt not initialized"); return; } bluetoothGatt.readCharacteristic(characteristic); } }
ในตัวอย่างนี้ บริการจะใช้ฟังก์ชันเพื่อเรียก
readCharacteristic()
การโทรนี้ทำงานไม่พร้อมกัน ระบบจะส่งผลลัพธ์ไปยัง
BluetoothGattCallback
ฟังก์ชัน
onCharacteristicRead()
class BluetoothLeService : Service() { ... private val bluetoothGattCallback = object : BluetoothGattCallback() { ... override fun onCharacteristicRead( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int ) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(BluetoothLeService.ACTION_DATA_AVAILABLE, characteristic) } } } }
class BluetoothLeService extends Service { ... private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { ... @Override public void onCharacteristicRead( BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status ) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } } }; }
เมื่อมีการเรียก Callback ที่เจาะจง จะมีการเรียกตัวเลือก
วิธีการของตัวช่วย broadcastUpdate()
และส่งต่อการดำเนินการ โปรดทราบว่าข้อมูล
การแยกวิเคราะห์ในส่วนนี้เป็นไปตามอัตราการเต้นของหัวใจบลูทูธ
ข้อกำหนดโปรไฟล์การวัด
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) }
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); }
รับการแจ้งเตือน GATT
การที่แอป BLE จะขอให้แจ้งเตือนเมื่อมีลักษณะเฉพาะบางอย่างเกิดขึ้นเป็นเรื่องปกติ
การเปลี่ยนแปลงบนอุปกรณ์ ในตัวอย่างต่อไปนี้ บริการมีการใช้
เพื่อเรียกใช้ฟังก์ชัน
setCharacteristicNotification()
วิธีการ:
class BluetoothLeService : Service() { ... fun setCharacteristicNotification( characteristic: BluetoothGattCharacteristic, enabled: Boolean ) { bluetoothGatt?.let { gatt -> gatt.setCharacteristicNotification(characteristic, enabled) // This is specific to Heart Rate Measurement. if (BluetoothLeService.UUID_HEART_RATE_MEASUREMENT == characteristic.uuid) { val descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)) descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(descriptor) } } ?: run { Log.w(BluetoothLeService.TAG, "BluetoothGatt not initialized") } } }
class BluetoothLeService extends Service { ... public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enabled) { if (bluetoothGatt == null) { Log.w(TAG, "BluetoothGatt not initialized"); Return; } bluetoothGatt.setCharacteristicNotification(characteristic, enabled); // This is specific to Heart Rate Measurement. if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); bluetoothGatt.writeDescriptor(descriptor); } } }
เมื่อเปิดใช้การแจ้งเตือนสำหรับลักษณะเฉพาะหนึ่งๆ แล้ว
onCharacteristicChanged()
Callback จะทริกเกอร์หากลักษณะเฉพาะในอุปกรณ์ระยะไกลมีการเปลี่ยนแปลง
class BluetoothLeService : Service() { ... private val bluetoothGattCallback = object : BluetoothGattCallback() { ... override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic) } } }
class BluetoothLeService extends Service { ... private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { ... @Override public void onCharacteristicChanged( BluetoothGatt gatt, BluetoothGattCharacteristic characteristic ) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } }; }