Передача данных BLE

После подключения к серверу 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();
   
}
}

Затем действие может вызвать эту функцию, когда оно получит широковещательное намерение, указывающее, что обнаружение службы завершено.

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 и обнаружит службы, оно сможет читать и записывать атрибуты, если это поддерживается. Например, следующий фрагмент перебирает службы и характеристики сервера и отображает их в пользовательском интерфейсе:

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

Когда запускается конкретный обратный вызов, он вызывает соответствующий вспомогательный метод broadcastUpdate() и передает ему действие. Обратите внимание, что анализ данных в этом разделе выполняется в соответствии со спецификациями профиля измерения сердечного ритма Bluetooth.

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

Получать уведомления ГАТТ

Приложения 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() запускается, если характеристика изменяется на удаленном устройстве:

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