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  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:
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
to come back as:
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.