Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

My Question is: Can Android 4.3 (client) have active connections with multiple BLE devices (servers)? If so, how can I achieve it?

What I did so far

I try to evaluate what throughput you can achieve using BLE and Android 4.3 BLE API. In addition I also try to find out how many devices can be connected and active at the same time. I use a Nexus 7 (2013), Android 4.4 as master and TI CC2540 Keyfob as slaves.

I wrote a simple server software for the slaves, which transmits 10000 20Byte packets through BLE notifications. I based my Android App on the Application Accelerator from the Bluetooth SIG.

It works well for one device and I can achieve around 56 kBits payload throughput at a Connection Interval of 7.5 ms. To connect to multiple slaves I followed the advice of a Nordic Employee who wrote in the Nordic Developer Zone:

Yes it's possible to handle multiple slaves with a single app. You would need to handle each slave with one BluetoothGatt instance. You would also need specific BluetoothGattCallback for each slave you connect to.

So I tried that and it partly works. I can connect to multiple slaves. I can also register for notifications on multiple slaves. The problem begins when I start the test. I receive at first notifications from all slaves, but after a couple Connection Intervals just the notifications from one device come trough. After about 10 seconds the other slaves disconnect, because they seem to reach the connection time-out. Sometimes I receive right from the start of the test just notifications from one slave.

I also tried accessing the attribute over a read operation with the same result. After a couple of reads just the answers from one device came trough.

I am aware that there are a few similar questions on this forum: Does Android 4.3 support multiple BLE device connections?, Has native Android BLE GATT implementation synchronous nature? or Ble multiple connection. But none of this answers made it clear for me, if it is possible and how to do it.

I would be very grateful for advice.

like image 493
Andreas Mueller Avatar asked Jan 20 '14 14:01

Andreas Mueller


People also ask

Can Android Bluetooth connect to multiple devices?

Can Android Connect to Multiple Bluetooth Devices? Most Android devices can connect to two or five Bluetooth devices simultaneously, while others support up to seven devices. The number of supported connections depends on the Bluetooth module your device is equipped with.

Is Bluetooth low energy compatible with Bluetooth?

It is independent of classic Bluetooth and has no compatibility, but Bluetooth Basic Rate/Enhanced Data Rate (BR/EDR) and LE can coexist.


2 Answers

I suspect everyone adding delays is just allowing the BLE system to complete the action you have asked before you submit another one. Android's BLE system has no form of queueing. If you do

BluetoothGatt g; g.writeDescriptor(a); g.writeDescriptor(b); 

then the first write operation will immediately be overwritten with the second one. Yes it's really stupid and the documentation should probably actually mention this.

If you insert a wait, it allows the first operation to complete before doing the second. That is a huge ugly hack though. A better solution is to implement your own queue (like Google should have). Fortunately Nordic have released one for us.

https://github.com/NordicSemiconductor/puck-central-android/tree/master/PuckCentral/app/src/main/java/no/nordicsemi/puckcentral/bluetooth/gatt

Edit: By the way this is the universal behaviour for BLE APIs. WebBluetooth behaves the same way (but Javascript does make it easier to use), and I believe iOS's BLE API also behaves the same.

like image 186
Timmmm Avatar answered Sep 30 '22 06:09

Timmmm


Re visting the bluetooth-lowenergy problem on android: I am still using delays.

The concept: after every major action that provokes the BluetoothGattCallback (e.g. conenction, service discovery, write, read) a dealy is needed. P.S. have a look at Google example on BLE API level 19 sample for connectivity to understand how Broadcasts should be sent and get some general understanding etc...

Firstly, scan (or scan) for BluetoothDevices, populate the connectionQueue with desired devices and call initConnection().

Have a look on the following example.

private Queue<BluetoothDevice> connectionQueue = new LinkedList<BluetoothDevice>();  public void initConnection(){     if(connectionThread == null){         connectionThread = new Thread(new Runnable() {             @Override             public void run() {                 connectionLoop();                 connectionThread.interrupt();                 connectionThread = null;             }         });          connectionThread.start();     } }  private void connectionLoop(){     while(!connectionQueue.isEmpty()){         connectionQueue.poll().connectGatt(context, false, bleInterface.mGattCallback);         try {             Thread.sleep(250);         } catch (InterruptedException e) {}     } } 

Now if all is good, you have made connections and BluetoothGattCallback.onConnectionStateChange(BluetoothGatt gatt, int status, int newState) has been called.

public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {         switch(status){             case BluetoothGatt.GATT_SUCCESS:                 if (newState == BluetoothProfile.STATE_CONNECTED) {                     broadcastUpdate(BluetoothConstants.ACTION_GATT_CONNECTED, gatt);                 }else if(newState == BluetoothProfile.STATE_DISCONNECTED){                     broadcastUpdate(BluetoothConstants.ACTION_GATT_DISCONNECTED, gatt);                 }                 break;         }      } protected void broadcastUpdate(String action, BluetoothGatt gatt) {     final Intent intent = new Intent(action);      intent.putExtra(BluetoothConstants.EXTRA_MAC, gatt.getDevice().getAddress());      sendBroadcast(intent); } 

P.S. sendBroadcast(intent) might need to be done like this:

Context context = activity.getBaseContext(); context.sendBroadcast(intent); 

Then the broadcast is received by BroadcastReceiver.onReceive(...)

public BroadcastReceiver myUpdateReceiver = new BroadcastReceiver(){      @Override     public void onReceive(Context context, Intent intent) {         final String action = intent.getAction();         if(BluetoothConstants.ACTION_GATT_CONNECTED.equals(action)){             //Connection made, here you can make a decision: do you want to initiate service discovery.             // P.S. If you are working with multiple devices,              // make sure that you start the service discovery              // after all desired connections are made         }         ....     } } 

After doing whatever you want in the broadcast receiver, here is how I continue:

private Queue<BluetoothGatt> serviceDiscoveryQueue = new LinkedList<BluetoothGatt>();  private void initServiceDiscovery(){     if(serviceDiscoveryThread == null){         serviceDiscoveryThread = new Thread(new Runnable() {             @Override             public void run() {                 serviceDiscovery();                  serviceDiscoveryThread.interrupt();                 serviceDiscoveryThread = null;             }         });          serviceDiscoveryThread.start();     } }  private void serviceDiscovery(){     while(!serviceDiscoveryQueue.isEmpty()){         serviceDiscoveryQueue.poll().discoverServices();         try {             Thread.sleep(250);         } catch (InterruptedException e){}     } } 

Again, after a successful service discovery, BluetoothGattCallback.onServicesDiscovered(...) is called. Again, I send an intent to the BroadcastReceiver (this time with different action String) and it is now that you can start reading, writing and enabling notifications/indications... P.S. If you are working with multiple devices, make sure that you start the reading, writing etc... stuff after all devices have reported that their services have been discovered.

private Queue<BluetoothGattCharacteristic> characteristicReadQueue = new LinkedList<BluetoothGattCharacteristic>();  private void startThread(){      if(initialisationThread == null){         initialisationThread = new Thread(new Runnable() {             @Override             public void run() {                 loopQueues();                  initialisationThread.interrupt();                 initialisationThread = null;             }         });          initialisationThread.start();     }  }  private void loopQueues() {      while(!characteristicReadQueue.isEmpty()){         readCharacteristic(characteristicReadQueue.poll());         try {             Thread.sleep(BluetoothConstants.DELAY);         } catch (InterruptedException e) {}     }     // A loop for starting indications and all other stuff goes here! } 

BluetoothGattCallback will have all your incoming data from the BLE sensor. A good practice is to send a broadcast with the data to your BroadcastReceiver and handle it over there.

like image 29
Rain Avatar answered Sep 30 '22 06:09

Rain