17

Although undocumented, conventional wisdom using the Android BLE apis is that certain operations like reading / writing Characteristics & Descriptors should be done one at a time (although some devices are more lenient than others). However, I am not clear on whether this policy should apply only to a single connection, or across all active connections.

I've heard that its best to initiate connections to devices one at a time. That might be an example of operations (connect / connectGatt) which should be executed serially among all devices.

But for other operations, like reading and writing Characteristics, is it good enough if each connection executes operations serially, or do I need some global operation queue shared among all devices so that between all devices, only one operation is executing?

Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
sonorangoose
  • 201
  • 2
  • 5
  • My own testing confirms that reads must be serial per connection, at least, but I'm very interested to know the answer to your final paragraph in particular. – stkent Feb 22 '17 at 18:41

3 Answers3

9

On Android, per BluetoothGatt object you should only execute one operation at a time (request mtu, discover services, read/write characteristic/descriptor) otherwise things will go wrong. You have to wait until the corresponding callback gets called until you can execute the next operation.

Regarding having pending connections to multiple devices at the same time, if you use autoConnect=true then there is no problem but if you use autoConnect=false then Android's bluetooth stack will only attempt to connect to one device at a time, meaning it will enqueue the connection requests if there are more than one outstanding. There is one particular bug where it fails to cancel a pending connection that is still in the queue (when you call .disconnect() or .close()) however, that was recently fixed in Android.

Note that there is also a maximum number of connections/pending connections/gatt objects for which the behaviour is completely undocumented what happens when you exceed these limits. In the best cases you simply get a callback with error status but in some cases I've seen that the android bluetooth stack gets stuck in an endless loop where it in each iteration tells the bluetooth controller to connect to a device but the controller sends back the error code maximum connections reached.

Emil
  • 16,784
  • 2
  • 41
  • 52
  • Thanks! Regarding issues like "There is one particular bug where it fails to cancel a pending connection that is still in the queue (when you call .disconnect() or .close())" - do you have a reference to that issue? I'm curious what the recommended workaround is until that fix is released. – stkent Mar 02 '17 at 00:01
  • Sure. See https://code.google.com/p/android/issues/detail?id=228633. There are some different workarounds: use autoConnect=true, try to make sure you never have one outstanding pending gatt connect at a time (with autoConnect=false) if you sometimes cancel them, if you control firmware peripheral then you can have a disconnection timeout if nothing happens on the link for let's say 30 seconds. – Emil Mar 02 '17 at 00:33
  • When you say "you should only execute one operation at a time", does that also include the initial call to `BluetoothGatt.connect` and the final call(s) to `BluetoothGatt.disconnect` and `BluetoothGatt.close`? I'm currently attempting to connect to up to 6 devices simultaneously and the percentage of failures to connect seems to increase as the number of target devices increases. – stkent May 12 '17 at 12:56
  • Data point: I notice that the widely-cited puck central example does appear to queue [connect](https://github.com/NordicSemiconductor/puck-central-android/blob/3f5ead6d3/PuckCentral/app/src/main/java/no/nordicsemi/puckcentral/bluetooth/gatt/GattManager.java#L107) and [disconnect](https://github.com/NordicSemiconductor/puck-central-android/blob/3f5ead6d3/PuckCentral/app/src/main/java/no/nordicsemi/puckcentral/bluetooth/gatt/operations/GattDisconnectOperation.java) calls, but at least in the former case it's not clear if that was an explicit design decision or an incidental outcome. – stkent May 12 '17 at 13:00
  • 1
    The "one operation at a time" refers to read/write characteristic/descriptor and requestMtu. Regarding many connections, maybe you hit the ceiling? Which Android device do you have? Take a look at http://stackoverflow.com/questions/41365009/what-is-the-max-concurrent-ble-connections-android-m-can-have/41367864#41367864. For enqueued connect attempts, see http://stackoverflow.com/questions/40156699/which-correct-flag-of-autoconnect-in-connectgatt-of-ble. – Emil May 12 '17 at 19:37
  • Hmm, I do have fairly robust logic to close my GATT client instances out, but it's possible I'm releasing the reference somehow before trying to call close. I built out the queuing of connection and service discovery calls today to see whether it offered any improvement, and it seems slightly better. I also used that new mechanism to "group" connection/service discovery/initial reads/initial notification writes per-device, which seems to help stability also. – stkent May 12 '17 at 20:10
  • @Emil: Did you get answer to the question? I think the answers here are more concerned about simultaneous connections. What the original question is (and I too am) concerned about the order of read/write characteristics across multiple devices. If we were to purely talk about write characteristics with response, it is fairly accepted that one should not issue a writeCharacteristic before receiving onCharacteristicWrite from a device. But if we had say 4 devices to which I have to serially write, should I wait for onCharacteristicWrite of device 1 before issuing write on device 2? – Amruta Aug 04 '17 at 11:31
  • No it's only per BluetoothGatt object. So if you want to write to two devices at the same time you don't have you wait for the first write to complete before you issue another write, if it is for another device. – Emil Aug 04 '17 at 12:47
  • Thanks for confirmation! – Amruta Aug 07 '17 at 11:40
  • Sounds authoritative; I'll gladly skip testing this ;) – Kevin Sep 14 '18 at 17:03
2

While I cannot speak for the upper layer, I can relate to what will happen on lower hardware level and that might provide some insights for your design.

Which ever is the stack on upper layer doing, at the end the operation has to be handled by the transceiver chip.

BLE is operating over 40 channel band in which 3 are used for broadcast and other for data transmission. This is done to be able to have multitude of device communicating together limiting the collision by being on other frequency bands.

Those bands are selected based on the one with the lowest noise (or traffic).

The transceiver himself is only able to communicate (speak and listen) in one band at a time and has to switch between bands to reach other devices. This is done by very tight timing of the communication.

Another fact is that a wireless transceiver is basically some sort of half duplex communication with collision detection, it cannot send and listen at the same time, nor can two device emit at the same time on the same band. It is therefore by design (and laws of nature) serial or sequential.

If you implement some sort of operational queue or threaded implementation, at the end everything will have to be treated serially / sequentially by the transceiver.

If you access to it by different threads, the transceiver may have to jump all the time between channels or perhaps gets confused if it is not well handled on the upper level.

The only good reason I may see to treat that on thread would be that the processing time of the transceiver to be significantly lower than the upper stack you have to run, and you would take advantage of multi core processor.

But otherwise unless very specific software need or architecture, I do not believe you will have significant gain of having a different implementation than serial and I would also speak to the slaves one by one rather than all at the same time for the considerations explained above.

Damien
  • 1,492
  • 10
  • 32
  • Thanks, this was very interesting and helped me understand why the Android stack necessarily works the way it does. – stkent Mar 02 '17 at 00:01
-1

BLE is designed to be asynchronous and event driven. You can send the commands however you like and you will get the responses back in no particular order. If you send a command and expect the next packet to be the response, you're going to get into trouble.

This being said, I'm not sure how the Android library is structured around this.

Tim Tisdall
  • 9,914
  • 3
  • 52
  • 82
  • 2
    Unfortunately, while the APIs exposed by the Android Bluetooth stack suggest that asynchrony is supported, in reality it is not. Firing multiple characteristic read requests rapidly will result in a single read result only, for example. I've confirmed this by artificially slowing the rate of read requests, in which case all return a read result. – stkent Feb 22 '17 at 18:44