ioscore-bluetoothbtle

Does CBCentralManager connect ever time out?


I know the answer is nominally "no", but I mean really—what if the app goes into the background (with BTLE background processing enabled)? For 24 hours? Across an app update?

Under the heading "Reconnecting to Peripherals", this Apple documentation describes a reconnection workflow that first tries to reconnect to previously paired peripherals found via retrievePeripheralsWithIdentifiers: but then starts scanning again if you fail to connect. How do you know when to give up on connect-ing to a previously found peripheral if there is no formal timeout? How do you know when to start/keep scanning if the idea is to re-connect to a previously found BTLE device whenever you move back into proximity to it, without the user necessarily interacting with your app?

Also, a note further down that page says that some BTLE devices might invent a random identifier for themselves every time they're powered on, so even though you find some previously paired peripherals from retrievePeripheralsWithIdentifiers: you might not be able to connect to them as their names have changed. Do any BTLE devices do that in practice? That's nuts!


Solution

  • This is a tricky one to answer. The CoreBluetooth framework itself does not have an official timeout on connect requests. In fact it will try to connect the peripheral for as long as possible. But how long is that?

    Well, unfortunately this is not something that is very well defined. You can be pretty confident that the connection will not time out while the app is in the foreground, but as soon as you involve connections in the background then things are not so funny any more. Obviously, like you mention, the pending connection will not remain after a phone reboot, etc.. which is fine since no user would expect the app to still be running after a reboot anyway. Regarding long running pending connections, you will find in Apple’s documentation that they tell you to opt-in for State Preservation and Restoration in order to make sure that the pending connections are properly kept while the app is suspended and eventually terminated. This would be good if it worked as advertised, but unfortunately it does not. After many years of working with this I have found that it is nearly impossible to get a reliable background pending connection on iOS. I have reported many bugs on this topic but so far none have been resolved.

    There are a few issues in particular that I think you should pay extra attention to:

    1. State Preservation and Restoration will completely stop working if a Bluetooth-state-change event happens while your app is in the terminated state. This essentially means that if the bluetooth chip gets reset for any reason (ex by toggling bluetooth/flight mode/etc..) then your app will never be relaunched again by Core Bluetooth whenever the peripheral is advertising within range. The reason for this is because all pending connections that have been set by your app will be cleared whenever the bluetooth chip is restarted. The problem with this is that your app will not be relaunched to be notified of this change, so the pending connections will never be recovered. So your app will think that the peripherals will connect, while in fact they will not. To me this one is the most serious issue and it alone makes CoreBluetooth extremely unreliable.

    2. Sometimes the framework gets ”stuck” in a bad state (possibly by an internal race-condition or similar). This can happen randomly, but you can pretty easily reproduce this by calling connectPeripheral immediately in the didFailToConnect or didDisconnect callback. When this happen the ”connection state” property is set to “connecting” when a pending connection is in fact not set. To avoid this I have found that you should wait at least around 20ms before connecting, for example using a dispatch_after or something.

    3. The framework internally uses XPC connections for interprocess communication in order to deliver bluetooth event. On some occasions this will break for whatever reason and the connection will be lost. I don’t know why this happens, but whenever it happens state preservation will stop working and you will manually have to relaunch the app to recover from it. Sometimes I manages to catch this in the device sysdiagnose logs...

    4. Using an iPhone 7 and at the same time having an Apple Watch (paired the phone) will completely break all reconnects from behind the lock screen in case the Watch is not currently connected (out of range/flight mode/low battery/or any other reason). This is particularly bad since it was introduced recently! But it looks like the Apple Watch for some reason has "priority" over other bluetooth peripherals.

    These are from the top of my head, but there are other issues as well. Regarding random addresses, most often these peripheral use so called ”random resolvable” addresses. This means that they appear random but in fact they can be resolved using an IRK (Identity Resolving Key) which is usually shared during initial bluetooth bonding. Devices that use completely random addresses are to my knowledge not very common.