6 Comments

Managing Multiple BLE Devices in iOS

With the onset of the Internet of Things, Bluetooth Low Energy (BLE) has become a popular choice for connecting interesting new devices to the smart phones we all carry in our pockets.

In dealing with BLE on iOS devices, I’ve had great success with iBeacons and single devices. However, when dealing with multiple BLE devices in iOS, things get tricky.

The Problem with Managing Multiple BLE Devices in iOS

Why many connections? Throughput. I need to read kilobytes of data from each device as quickly as possible. Given the nature of BLE’s sparse use of the radio, running multiple connections greatly increases our throughput.

Apple has also added extra limitations to its implementation of the BLE spec. Connection intervals (the time between devices connecting and exchanging data) cannot be faster than 20ms on iOS. The BLE spec allows for 7.5ms. That’s more than twice as slow; we need to use multiple connections.

Unfortunately, the current version of iOS has some major Bluetooth bugs when dealing with many connections. On iOS, Bluetooth is managed by a process called BTServer. When loaded up with devices, it likes to crash. It crashes worse than my six-year-old after eating an entire bag of Halloween candy.

...
Hardware Model:      iPhone7,2
Process:             BTServer [1743]
Path:                /usr/sbin/BTServer
Identifier:          BTServer
...

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  3

Filtered syslog:
None found

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0:
0   libsystem_kernel.dylib        	0x00000001995d8a40 mach_msg_trap + 8
1   libsystem_kernel.dylib        	0x00000001995d88bc mach_msg + 72
2   CoreFoundation                	0x000000018408c108 __CFRunLoopServiceMachPort + 196
3   CoreFoundation                	0x0000000184089e0c __CFRunLoopRun + 1032
4   CoreFoundation                	0x0000000183fb8ca0 CFRunLoopRunSpecific + 384
5   CoreFoundation                	0x000000018400644c CFRunLoopRun + 112
6   BTServer                      	0x00000001000a0fc4 0x100098000 + 36804
7   libdyld.dylib                 	0x00000001994d68b8 start + 4

Thread 1 name:  Dispatch queue: com.apple.libdispatch-manager
Thread 1:
0   libsystem_kernel.dylib        	0x00000001995f44fc kevent_qos + 8
1   libdispatch.dylib             	0x00000001994b8a04 _dispatch_mgr_invoke + 232
2   libdispatch.dylib             	0x00000001994a7874 _dispatch_source_invoke + 0

Thread 2 name:  StackLoop
Thread 2:
0   libsystem_kernel.dylib        	0x00000001995f2f48 __psynch_cvwait + 8
1   libsystem_pthread.dylib       	0x00000001996bad20 _pthread_cond_wait + 704
2   BTServer                      	0x00000001000bd044 0x100098000 + 151620
3   libsystem_pthread.dylib       	0x00000001996bbb28 _pthread_body + 156
4   libsystem_pthread.dylib       	0x00000001996bba8c _pthread_body + 0
5   libsystem_pthread.dylib       	0x00000001996b9028 thread_start + 4

Thread 3 name:  RxLoop
Thread 3 Crashed:
0   libsystem_kernel.dylib        	0x00000001995f3140 __pthread_kill + 8
1   libsystem_pthread.dylib       	0x00000001996bcef8 pthread_kill + 112
2   libsystem_c.dylib             	0x0000000199566b78 abort + 140
3   BTServer                      	0x00000001000b0ea8 0x100098000 + 102056
4   BTServer                      	0x00000001000ff944 0x100098000 + 424260
5   BTServer                      	0x00000001000ff5dc 0x100098000 + 423388
6   BTServer                      	0x00000001000b4560 0x100098000 + 116064
7   BTServer                      	0x00000001000b3c58 0x100098000 + 113752
8   BTServer                      	0x00000001000b0b7c 0x100098000 + 101244
9   libsystem_pthread.dylib       	0x00000001996bbb28 _pthread_body + 156
10  libsystem_pthread.dylib       	0x00000001996bba8c _pthread_body + 0
11  libsystem_pthread.dylib       	0x00000001996b9028 thread_start + 4

...

Thread 3 crashed with ARM Thread State (64-bit):
    x0: 0x0000000000000000   x1: 0x0000000000000000   x2: 0x0000000000000000   x3: 0x000000014c6402d0
    x4: 0x000000014c640300   x5: 0x00000001a269f040   x6: 0x0000000120048348   x7: 0x0000000000000000
    x8: 0x0000000008000000   x9: 0x0000000004000000  x10: 0x0000000000003c96  x11: 0x00000001a2cadbaa
   x12: 0x00000001a2cadbaa  x13: 0x0000000000000018  x14: 0x000000008000001f  x15: 0x0000000080000023
   x16: 0x0000000000000148  x17: 0x00000001002b4550  x18: 0x0000000000000000  x19: 0x0000000000000006
   x20: 0x000000016e733000  x21: 0x000000000000003e  x22: 0x09005a330fcd5075  x23: 0x00000001002d9958
   x24: 0x09005a330fcd5075  x25: 0x0000000000000002  x26: 0x0000000000000000  x27: 0x00000001002d3000
   x28: 0x000000000000ffff  fp: 0x000000016e732a70   lr: 0x00000001996bcef8
    sp: 0x000000016e732a50   pc: 0x00000001995f3140 cpsr: 0x00000000
...

In my testing, I’ve found that connecting 15 devices crashes all Bluetooth on the phone. Just don’t connect 15 devices at once, you say? No problem. Except, iOS keeps connections open for a short period of time after calling:

cancelPeripheralConnection

This allows connections to stack up while waiting for disconnect. Now, I’m not sure of the exact cause of the crash but I see it often when working with more than 10 devices.

How to Handle Multiple Connections

My advice: if you control the firmware, implement a disconnect command to terminate the connection from the peripheral. This prevents connections from hanging on longer than needed. By allowing the peripheral to hang-up, I was able to stair step many connections. Only when trying to keep 7 or more active connections did I see BTServer crashes rear their ugly head. Also, be prepared for the BLE state of your

CBCentralManager

to come back as:

CBCentralManagerStateResetting

You will need to scan for your devices, services, and characteristics all over again but you should be able to recover gracefully. Using multiple connections can definitely help with throughput on iOS.

Apple has some work to do to clean up their handling of many BLE devices in their Bluetooth stack. If you are encountering these same issues, please send the bug report to Apple to expedite work on these features.