Search code examples
android-studiobluetoothbluetooth-lowenergyandroid-bluetoothandroid-ble

Handling Notifications on Android with Multiple BLE Peripheral Connections


So, I am new to Android development and I am trying to connect my device to multiple BLE devices (T-Wristband) to receive frequent notifications (IMU sensor data less than 20 bytes at 50Hz).

When connecting to multiple devices, I miss data from one or more devices. I suspect the reason is that my BluetoothGattCallback method, onCharacteristicChanged, is working on the same thread for all devices (Note: I have checked that by logging Thread.currentThread.getName()).

What I have tried:

1. Android 4.3: How to connect to multiple Bluetooth Low Energy devices

I suspect everyone adding delays is just allowing the BLE system to complete the action you have asked before you submit another one.

Problem: I cannot add delays when receiving notifications since it would interfere with my sampling rate, and I am not sure when I might receive new notifications from another device, or even the same device.

2. Android BLE multiple connections

To achieve multiple BLE connections you have to store multiple BluetoothGatt objects and use those objects for a different device.

In this regard: I have tried writing a custom class for my device, TTGODevice, which saves an instance of the corresponding BluetoothGatt upon connection:

public class TTGODevice {
  /* Bunch of psf constants */
  private BluetoothDevice device;
  private Context context;
  private Accelerometer acc;
  private BluetoothGatt server;
  private int deviceStatus;

  private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { ...
  };

  public TTGODevice(Context context, BluetoothDevice device) {
    this.device = device;
    this.context = context;
    this.acc = new Accelerometer(); // My custom class for accelerometer on the T-Wristband device(s)
  }

  public void connect(boolean autoConnect) {
    server = device.connectGatt(context, false, bluetoothGattCallback);
  }

  /*Getters for device, server, and deviceStatus*/
}

In the BluetoothGattCallback, I override my onCharacteristicChanged as below:

@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {

  byte[] bytes = characteristic.getValue();

  // updating the Accelerometer
  acc.update(bytes);
  Log.d(TAG, "onCharacteristicChanged: " + Thread.currentThread().getName());
}

I have also tried using the exact instance of my server in the callback above, but I am out of luck.

byte[] bytes = server.getService(SERVICE_UUID).getCharacteristic(CHARACTERISTIC_UUID).getValue();

Long story short, I have been unsuccessful. I would appreciate any other relevant threads.

[Note: I have been suggested to use Fragments and/or ViewModels. I am not sure how they are relevant.]


Solution

  • It seems that BLE on Android cannot handle sampling rates at 50Hz and higher. I am not sure if this is a BLE-related problem or if it has something to do with the onCharacteristicCHanged method being called too frequently. I solved this by sending larger packets of data every 50ms, contrary to my original case at every 20ms. Larger BLE data packets are possible starting from Bluetooth 4.2, which is controlled using the MTU, Maximum Throughput Unit. This blog helped me a lot in understanding the underlying mechanism.

    When receiving large packets of data over BLE at high sampling rates, it is best to use gatt.requestMtu(MY_DESIRED_MTU) with MY_DESIRED_MTU > 20Bytes inside the corresponding BluetoothGattCallback on Android. It should be noted that larger MTU means it takes longer for the data to be written on the BLE characteristic for the server device.

    In short, I am using larger data packets and a larger delay to make up for the limitations in the sampling rate. For a sampling rate of 100Hz on the IMU in the T-Wristband, I am using an MTU of 95 Bytes, requested by the Android device, and a 50ms delay on the timer on the T-Wristband. This allows me to receive five consecutive data samples every 50ms.