38

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.

Community
  • 1
  • 1
Andreas Mueller
  • 383
  • 1
  • 4
  • 6
  • Do you necessarily need to connect? If you are concerned about concealing the data and reliable-or-total-fail, perhaps you could simply put it in broadcast packets and scan for those. – Chris Stratton Mar 16 '14 at 12:59
  • Thank you for your replay. I have to use connected mode for reliability reasons – Andreas Mueller Mar 17 '14 at 06:47
  • I have search all over the net to find examples how to do what you did in you test program @Andreas Mueller. Could you be so kind to help me, and show me how to send notifications from Android to the CC2540 chip with the modified "Application accelerator"-code? (link to your project maybe?) – HenrikS May 26 '14 at 06:32

5 Answers5

25

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.

stkent
  • 19,772
  • 14
  • 85
  • 111
Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • does all Android Smart Phone supports multiple gatt connection simultaneously?May I know the phone you tested this and the Android API version.I suppose this is the chractersitics of the underlying Bluetooth Chip in the Phone as well. – Raulp Dec 21 '16 at 04:38
  • Yes all phones support multiple gatt connections. See [this question](https://stackoverflow.com/questions/34400182/android-limit-of-simultaneous-ble-connections). – Timmmm Dec 21 '16 at 10:26
  • You can make a descriptor queue and just call the first g.writeDescriptor(a); then in onDescriptorWrite you continue with the remaining descriptors until all are written. – Pedro Antonio Feb 04 '21 at 17:27
12

Re visting the problem on : 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.

Rain
  • 315
  • 4
  • 11
  • Thnks for sharing the link of android example. Sometimes, we move around the world but forget to have a look on android examples..:) – Rahul Rastogi Feb 16 '15 at 07:48
  • 8
    Using the Gatt callback is the ONLY correct way to handle BLE operations. I'm very surprised to see so many posts talking about random delays. These methods are asynchronous - who knows how long they might take. That is the entire purpose of the Gatt callback. Yes, its very annoying to effectively implement that callback so that it plays nicely with the rest of your system, but its the only way to go. – SuperDeclarative May 29 '15 at 06:25
  • 1
    I am trying to handle two ble connections. I have implemented most of this on my own already. However, when I set each device to receive notifications it seems to happen twice on the last device I connect. Has anyone verified this above answer works? Thanks, I know this is an old post – luckyging3r Mar 21 '18 at 14:11
6

I am developing an app with BLE features myself. The way I managed to connect to multiple devices and turn on notifications was to implement delays.

So I make a new thread (in order not to block UI thread) and in the new thread connect and turn on notifications.

For example, after BluetoothDevice.connectGatt(); call Thread.sleep();

And add the same delay for read/write and enable/dissable notifications.

EDIT

Use wait like this so that Android dindn't reaise ANR

public static boolean waitIdle() {
        int i = 300;
        i /= 10;
        while (--i > 0) {
            if (true)
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

        }

        return i > 0;
    }
Marian Paździoch
  • 8,813
  • 10
  • 58
  • 103
Rain
  • 315
  • 4
  • 11
  • I changed my code so that it does now connect to and register for notifications on each slave sequentially. And that fixed all the problems. So thank you very much for your help. – Andreas Mueller Mar 17 '14 at 14:41
  • 1
    Delays are a hack that workaround the real problem. See my answer. – Timmmm May 26 '15 at 10:15
0

Unfortunately notifications in the current Android BLE stack are a bit buggy. There are some hardcoded limits and I've found some stability issues even with a single device. (I read at one point that you could only have 4 notifications... not sure if that's across all devices or per device. Trying to find the source for that info now.)

I would try switching to a polling loop (say, poll the items in question 1/sec) and seeing if you find your stability increases. I would also consider switching to a different slave device (say a HRM or the TI SensorTag) to see if there is perhaps an issue with the slave-side code (unless you can test that against iOS or another platform and confirm it isn't part of the issue).

Edit: Reference for notification limitation

Ben Von Handorf
  • 2,326
  • 1
  • 15
  • 17
  • Thank you for your answer. I think the code on the slave side is ok, because the test it is running under iOS with up to 8 slaves. I also tried accessing the attribute over a read operation with the same result. – Andreas Mueller Jan 20 '14 at 15:22
  • Then I would try a quick change to a polling loop instead of notifications and see if that stabilizes the connection at all. I'm trying to get an equivalent setup put together for testing, but that may not be done today. – Ben Von Handorf Jan 20 '14 at 15:24
0

Rain is right in his answer, you need delays for pretty much everything when you work with BLE in Android. I developed several apps with it and it is really necessary. By using them you avoid a lot of crashes.

In my case, I use delays after every read/write command. Doing so, you ensure you receive the response from the BLE device almost always. I do something like this: (of course everything is done in a separate thread to avoid to much work on the main thread)

 readCharacteristic(myChar);
 try {
    Thread.sleep(100);
 } catch (InterruptedException e) {
    e.printStackTrace();
 }
 myChar.getValue();

or:

 myChar.setValue(myByte);
 writeCharacteristic(myChar);
 try {
    Thread.sleep(100);
 } catch (InterruptedException e) {
    e.printStackTrace();
 }

This is really useful when you read/write several characteristics in a row... As Android is enough fast to execute the commands almost instantly, if you don't use a delay between them you may get errors or incoherent values...

Hope it helps even if it is not exactly the answer to your question.

kodartcha
  • 1,063
  • 12
  • 23
  • For example, I needed 500ms of delay to make everything work 100%. – Rain Aug 13 '14 at 17:44
  • 1
    This is a very bad "solution". The callbacks are there to deliver the responses. Waiting a random delay instead is really bad, since you don't know if the operation really is complete (radio interference can add unpredictable slowdowns). It also doesn't deliver the answer as soon as possible, obviously. – Emil Dec 05 '20 at 10:44
  • @Emil, thanks for your comment in a 6 years old answer, which is not even the accepted one! Many things have changed since then in the BLE framwork... – kodartcha Dec 06 '20 at 11:13
  • Not really. This part of the API looks exactly the same today as it did 6 years ago. – Emil Dec 06 '20 at 13:11