I try to reconnect my peripheral after rebooting the phone.
I'm using the location update callback to start the BLE connection process in background.
The ble stack is correctly initialized in background (CBCentralManagerStatePoweredOn event sent)
The "connect" on the peripheral is started but no connection is established...
Any idea to reconnect a device after reboot ?
When you say that the "connect" on the peripheral is started, do you mean that you manually connect using connectPeripheral: ? I have in the past struggled with connection on iOS and noticed that CoreBluetooth has a lot of race conditions internally that you have to avoid. Typically what I recommend is to always do a dispatch delay on all connect requests of at lest 20ms that happens after a CoreBluetooth callback. This will avoid most race conditions. You can test this out yourself for example by setting a pending connection and then at a later point calling cancelPeripheralConnection: before the peripheral has connected. When you get the didFailToConnect callback then immediately call connectPeripheral: again. Now the connection should be in a "limbo" mode where the connection status is "connecting" but in fact the peripheral will never connect. Calling connect again will do nothing either at this point.
I don't know if this is the cause of your issue, but it could be. Also, upon CentralManager state restoration I would recommend letting the framework "settle" for around a second before starting interacting with it.
One other important thing is that you don't re-initiate the manager until after you get the applicationDidFinishLaunchingWithOptions: callback of your AppDelegate. Doing this earlier can cause strange behaviors.
Please do some more investigations and if you are still having issues then let me know. I may have some further tips. /Anton
Related
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!
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:
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.
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.
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...
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.
we have a BLE peripheral that connects to the phone every hour and passes some data. Here is how the process works:
Upon launch with key UIApplicationLaunchOptionsBluetoothCentralsKey in
application(didFinishLaunchingWithOptions launchOptions) app re-initializes CBCentralManager with ID that was passed to it.
Then it goes through the regular restoration cycle and reads data off the BLE peripheral.
Performs REST request to the service in the cloud.
Assuming that app has been launched at least once after phone reboot everything works well for a few days (if app isnt running or been forced out of memory, iOS properly starts it up again, assuming user didnt do forced close manually).
However every few days iOS stops waking up the app when there is an incoming request from BLE device. If user relaunches app everything works properly for a few days and then stops agains. Given the nature of our product, it's critical to have our app / peripheral working together in the most reliable way possible.
Theories as to why it might be happening:
(upon closer examination all of them were dismissed)
Users restart the phone and forget to relaunch the app.
We've added logging of the uptime and it showed that phone didnt restart in between app launches.
Memory warnings lead to app being booted out.
Once again, added logging, they showed that there was no applicationDidReceiveMemoryWarning
Bad connection leads to app running for longer period than 10s when uploading results and iOS terminates it and gets upset
We artificially delayed server response by 15s to test this and everything continues to work properly during testing.
Any ideas on what is happening and why iOS stops notifying app about incoming BLE connection?
One of the problems is that we cannot figure out how to reliably reproduce the issue So any suggestions there will be much appreciated as well!
Thank you!
UPDATE 1:
Here is how we initialize CBCentralManager:
self.centralManager = CBCentralManager(delegate: self, queue: nil, options: [
CBCentralManagerOptionRestoreIdentifierKey : MyCentralManagerID,
CBCentralManagerOptionShowPowerAlertKey : 0])
I saw some suggestions that queue parameter should not be nil. Given that I'm unable to reliably reproduce issue I'm hesitant to make that change until I can confidently observe its effects.
I wanna start by saying that I have been working with CoreBluetooth for a long time now and from what I have noticed CoreBluetooth State Preservation and Restoration does not work reliably at all. You can get it working sort of "ok", but you will never get it to reconnect reliably unless Apple fixes it some day.
There are so many bugs that causes this to not work properly, but I will give you one that I believe is causing your problems:
State restoration will only relaunch your app due to bluetooth related activity if the event originates from a peripheral accessory that you are communicating with, such as connect/disconnect events and characteristics notifications. For other events, most importantly general bluetooth state change events, your app will not be relaunched and notified of this. The reason why this is so bad is because all bluetooth state change events will cancel all pending or current connections, meaning that pending connections will be dropped and your application will not be notified of it. This effectively means that your application will still believe that the connections are still pending when in fact they are not. Since your application is terminated at this time, the only way for it to wake up again is by having the user manually launch it again (or alternatively “hack” other background modes for this purpose, which does not work very reliably either).
This thing happens if the user toggles Flight Mode, toggles Bluetooth, power cycles the iOS device, or any other undefined reasons that many cause state changes…
But this is only one bug. Many other exists as well, such as the XPC connection being interrupted at different times for no apparent reason. I have also noticed that the pending connection can go into “limbo” mode where the peripheral state gets set to Connecting, but in fact it will never connect unless you cycle the connection state.
Anyhow, I am sad to say it, but if you are developing an app that must rely on the peripheral being reconnected in the background then I would not recommend doing it. You will be frustrated. I could probably write an essay about all the bugs in Core Bluetooth that Apple does not want to fix. Even more strange is that you can pretty easily ruin the bluetooth connectivity globally on the device from one single app so that no app can use bluetooth until the device is rebooted. This is pretty bad since it goes against Apple's own Sandboxing principle.
I have an issue with my centralManger object, its working great I can connect and disconnect to a peripheral. Once I pair 2 peripherals, after half an hour (more or less..) the centralManger state becomes "CentralManagerStateResetting" which causes my peripherals to disconnect automatically and not call delegate: didDisconnectPeripheral.
This is from apple's documentation:
#constant CBCentralManagerStateResetting The connection with the system service was momentarily lost, update imminent.
What does this state mean?
Why does the centralManger get to this state?
How can i prevent it from getting to this state?
How can i overcome this issue?
Seen in all iPhones and checked in version iOS 9+
Most propably this means that BTServer crashed. Normally it recovers within a few seconds and the state should change to CBCentralManagerStatePoweredOn again.
Check your device logs for output from BTServer.
This can happen if there is a lot of load on the BLE stack and the system has to manage a lot of simultaneous connections to different peripherals.
My application related to bluetooth communication with the peripheral device.Every functionality is working fine right from discovering to connecting .While coming to disconnecting the peripheral from the application i have written code like this
-(void) disconnect
{
if (_selectedPeripheral != nil &&
_selectedPeripheral.state != CBPeripheralStateDisconnected)
{
NSLog(#"Peripheral disconnecting");
[_centralManager cancelPeripheralConnection:_selectedPeripheral];
_selectedPeripheral = nil;
}
}
When i click button this above method is calling and app showing that peripheral is disconnected and when i came out of the application and look into settings /bluetooth/ .Peripheral is showing connected.How to stop connection the peripheral in the device level i.e in the settings .Please help me with the proper solution.
You are unable to guarantee a system level disconnect from the peripheral.
This is a link directly from the CBCentralManager documentation:
cancelPeripheralConnection:
Discussion
This method is nonblocking, and any CBPeripheral class
commands that are still pending to peripheral may or may not complete.
Because other apps may still have a connection to the peripheral,
canceling a local connection does not guarantee that the underlying
physical link is immediately disconnected. From the app’s perspective,
however, the peripheral is considered disconnected, and the central
manager object calls the centralManager:didDisconnectPeripheral:error:
method of its delegate object.
In my experience the physical link is disconnected quickly if you are the only application using the peripheral, but if you potentially are not as Apple clearly states there is a potential for other applications to be maintaining a persisted connection which would cause the physical link to not disconnect even though it is stating to you it has.
We were facing the same issue but we managed to workaround this nasty bug (or API design flaw) by using
Objective C
[peripheral writeValue:x forCharacteristic:y type:CBCharacteristicWriteWithResponse];
Swift
peripheral.writeValue(x, for: y, type: .withResponse)
It's strange that iOS doesn't cancel the physical connection to the peripheral we implemented in the peripheral to perform the disconnect, so we send a string indicating a disconnection should happen.
We used
Objective C
[peripheral writeValue:x forCharacteristic:y type:CBCharacteristicWriteWithoutResponse];
Swift
peripheral.writeValue(x, for: y, type: .withResponse)
and the peripheral disconnects as expected.
Hope this helps anyone facing the same issue by this CoreBluetooth API flaw.
x is the specific cmd supported in your peripheral device (i.e Firmware)
y is the specific characteristic you want to send the value
I know this is an old thread, but I figured I'd add a potential solution here for others.
What you could do is issue a command that causes the peripheral to reboot, resetting the Bluetooth connection. If your device has such a command that you can issue through Bluetooth, you're in luck, otherwise, you'll need access to the firmware for the peripheral in order to add a new command that does this. I'm not a firmware guy, so I can't tell you what exactly you need to do; all I know is the device I'm working with has such a command (it is a proprietary command specifically for our device, not part of the Bluetooth protocol) and that allowed the disconnect to be guaranteed as long as I issue it before calling cancelPeripheralConnection.
In Core Bluetooth, after connecting to a device, I turn off the device and the device is disconnected. But when I turn on the device again, there is no didDiscoverPeripheral called again. How can I reconnect to the device again?
When you disconnect a device with cancelPeripheralConnection the didDisconnectPeripheral delegate method will be invoked. However from iOS 6.0 the device remains connected for about 40-50 seconds (or more), so no didDiscoverPeripheral will be invoked in that timeframe. If you want to "discover" it again just call the retrieveConnectedPeripherals method and you will get the reference in didRetrieveConnectedPeripherals.
However, the best solution is to save the device's UUID and use that to reconnect with the retrievePeripherals method. This will invoke didRetrievePeripherals and you can reconnect with connectPeripheral. This is the fastest way to reconnect to a device, no scanning is required in this case.
When you do a scan with scanForPeripheralsWithServices, it will normally only notify you once for a particular device address. You can change this to report duplicates by specifying the option CBCentralManagerScanOptionAllowDuplicatesKey. Or you can have your app detect that the other device disconnected using a timeout, and restart your scan.
#Andras gave me the right path, but his answer is not complete anymore since iOS7.
The best way to reconnect to a previous device is to use the retrievePeripherals(withIdentifiers:) method.
This method does not call a delegate, but directly returns you a list of Peripherals, corresponding to the list of UUID passed in parameters.
if let peripheral = self.centralManager.retrievePeripherals(withIdentifiers: [uuid]).first {
self.peripheral = peripheral // <-- super important
self.centralManager.connect(peripheral, options: nil)
}
Please check the "super important" line of the above code: The method connect(_:option:) does not retain the peripheral, and if you don't do it yourself, the connection will always fail without any callback since the peripheral objet will be destroyed.
In CoreBluetooth all management is done by application layer.
In your case, what I would do it is to listen for disconnect event than in same event, reconnect the peripheral.
The connection method is an inexpensive one and assure you to reconnect to your device when it is back in range.
Note that if you explicitly disconnect the device, you received the same disconnect event, but you haven't to call the reconnect method.