BLE 데이터 전송

BLE GATT에 연결되면 서버의 경우 기기에서 어떤 서비스를 사용할 수 있는지 확인하고, 데이터를 쿼리하기 위해 특정 GATT 특성이 발견될 때 알림을 있습니다.

서비스 검색

BLE 기기에서 GATT 서버에 연결한 후 가장 먼저 할 일은 서비스 검색을 수행할 수 있습니다 서비스에 대한 정보를 제공합니다. 서비스 특성 및 서비스 특성 및 액세스 제어의 설명어입니다. 다음 예시에서는 서비스가 해당 기기( onConnectionStateChange() 드림 함수 BluetoothGattCallback), discoverServices() 함수가 BLE 기기에서 정보를 쿼리합니다.

서비스는 onServicesDiscovered() 드림 함수를 BluetoothGattCallback 이 함수는 기기가 사용 가능한 서비스를 보고할 때 호출됩니다.

Kotlin

 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()(으)로 보고된 데이터를 가져옵니다.

Kotlin

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

그러면 액티비티는 브로드캐스트 인텐트를 수신할 때 이 함수를 호출할 수 있습니다. 서비스 검색이 완료되었음을 나타냅니다.

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)                         }                 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 서버에 연결되어 서비스를 찾으면 는 지원되는 경우 속성을 읽고 쓸 수 있습니다. 예를 들어 스니펫이 서버의 서비스 및 특성 및 디스플레이를 통해 반복됩니다. 다음과 같습니다.

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

자바

 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 선택할 수 있습니다

Kotlin

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

Kotlin

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

특정 콜백이 트리거되면 적절한 broadcastUpdate() 도우미 메서드를 호출하고 작업을 전달합니다. 참고: 데이터는 블루투스 심박수 모니터링, 측정 프로필 사양.

Kotlin

 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 알림 수신

저전력 블루투스 앱은 일반적으로 변경할 수 있습니다 다음 예에서 서비스는 함수를 사용하여 setCharacteristicNotification() 드림 메서드를 사용하여 축소하도록 요청합니다.

Kotlin

 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() 드림 콜백은 원격 기기에서 특성이 변경되면 트리거됩니다.

Kotlin

 class BluetoothLeService : Service() {  ...      private val bluetoothGattCallback = object : BluetoothGattCallback() {         ...          override fun onCharacteristicChanged(         gatt: BluetoothGatt,         characteristic: BluetoothGattCharacteristic         ) {             broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic)         }     } } 

Java

 class BluetoothLeService extends Service {  ...      private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {     ...          @Override         public void onCharacteristicChanged(         BluetoothGatt gatt,         BluetoothGattCharacteristic characteristic         ) {             broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);         }     }; }