iOS Core Bluetooth state Preservation and Restoration issues - ios

I have a question regarding state Preservation and Restoration for Core Bluetooth on iOS 7.
I can't seem to get it to work properly. I have followed every guidline that apple mentions in their documentation for core bluetooth as well as the general documentation for state preservation.
For example here: Core Bluetooth Background Processing for iOS Apps and here: iOS State Preservation and Restoration
I can get the general state preservation to work on the device (for view controllers and objects etc.), but not for the bluetooth manager.
As far as I know the cheklist look like this:
Opt in preservation and restoration when you allocate and initialize a central manager object by assigning a restoration identifier in the options dictionary for the key CBCentralManagerOptionRestoreIdentifierKey.
Reinstantiate any central manager objects after your app is relaunched by the system. This is done in app delegate when the application:didFinishLaunchingWithOptions: is called. Here I am supposed to look for the UIApplicationLaunchOptionsBluetoothCentralsKey in the options dictionary and then reinstatiate the CBManager with that key. It is here where things go wrong since there are never any identifiers for that key, and thus I can not reinstatitate it.
Implement the appropriate restoration delegate method. I have done this step also, but since the manager is never re-instantiated I never receive this delegate callback.
The app is working fine in the background and I have followed all the steps for that part as well.
Now, having said all this, I am not entierly sure how to test this and that might be a part of the broblem. The way I do it now is to press the home button on the iOS device (actuall device) so that it puts the app into background and goes back to the home screen. While doing this I can tell that all the regular state preservation calls are acheived by looking at my log output. After this i quit the app by pressing the stop button in Xcode to kill the background process. I now restart the app through Xcode and once again I can now see all the regular state preservation code being executed and the state gets restored on everything except the bluetooth manager.
If this is wrong, then please let me know. But in all, I am very confused about this since it says in the Core Bluetooth docs that the preservation occurs only when "your app is relaunched by the system". What does that really mean? I also read a post on the Apple developer forum that since iOS 7 the OS will now never relaunch the app for any reason if the user kills the app manually which I am doing..
Any help regarding this would be much appreciated!
/A

When you click home button to send app to background, it it suspended, and can handle Bluetooth delegates and run in background for 10s, this feature could be realize by " add bluetooth central in background mode in info.plist", and do not use State Preservation & Restoration.
If your app is terminated by IOS, due to memory pressure, it can't handle bluetooth delegates anymore. In this case, if you used State Preservation & Restoration, your app can be relaunched to background to run again, also for only 10s. After 10s, it would move to suspended state.
Only in this situation, CBCentralManager's willRestoreState can be triggered.
You can add code
[kill(getpid(), SIGKILL);]
to a button action, when you click the button, your app will be terminated by IOS just like killed by memory pressure, and then "willRestoreState" will be triggered.
Good luck.

First, note that state preservation of view controllers has nothing to do with restoration of Core Bluetooth managers.
Important: Restoration doesn't work for scanning, static characteristics and generally any use cases that do not generate connection related events.
Now the steps:
Make sure any of the following on the tested app:
peripheral manager is advertising
peripheral manager has connected centrals
central is trying to connect to a peripheral
central is connected to some peripheral
Use this app to kill your app: https://github.com/ddaddy/BackgroundKill (kudos to ddaddy, give a star on the repo)
Switch to the killer app
Start the killing process and wait until it is terminated by the system
You app is now killed
Do some connection event
(Tested peripheral) subscribe to characteristics on the peripheral
(Tested peripheral) start read requests on dynamic characteristics
(Tested central) make the connection request succeed
(Tested central) update subscribed characteristics on the peripheral
Depending on what you want to test, consider the applicable points in the list. Use logging in your tested app and watch the logs in the organizer to see what happens over time.

I was able to get this working with scanning in the background (along with connecting and transmitting data). There are important difference between how your app was terminated and if iOS will preserve and restore your Core Bluetooth Manager.
Adding "App communicates using CoreBluetooth" to "Required background modes" in Info.plist covers most situations. Your central manager will continue to work in the background — either when the user presses the home button or locks the phone (or both). However you will never get the "willRestoreState" delegate call in these scenarios as your central manager is still being handled by your application (it's still in memory).
Preservation and restoration only comes into effect in one scenario — your app was terminated by iOS due to memory constraints. The easiest way to force this for testing it to load 3-4 memory intensive games while your app is in the background. If you review the device console, you're waiting for this message:
"Apr 4 13:16:47 Michaels-iPhone SpringBoard[58] <Warning>: Application 'UIKitApplication:com.oculeve.TearBud[0x6df4]' was killed by jetsam.”
iOS will take over you central manager at this point. If it was scanning, it will continue scanning, if it was connect to a peripheral, it will continue to be connected. In the event that you receive a central manager delegate call (didDiscoverPeripheral, didUpdateValueForCharacteristic) iOS will launch your application again in the background. At this point you will get the willRestoreState delegate call. At this point your app is back in memory and will work as describe above. Note that you need may need to do some restoration in willRestoreState if you were connected to a device. This is all covered in the WWDC 2013 Core Bluetooth video demo.
The kicker seems to be that restoration/preservation does not work if you manually close the app from the system tray (swiping up on your app). I'm assuming Apple's reasoning for that is in this case the user is explicitly closing the app and all Bluetooth communication should cease. This is also true is the user restarts their phone. I'm assuming this is because a restart is basically equal to swiping up to close all the apps in the system tray. If you get to this point, you can only reconnect once the user opens your application again.
Something to point out is that just because your app is in the system tray doesn't mean it's in memory.
Why Apple doesn't just tell you this in the documentation is beyond me.

Related

corebluetooth and ios states

Successfully connected my BLE device using corebluetooth library and set notify values for certain service from it on my ios app. Works fine in foreground and background modes.
I currently save the identifiers of the device to UserDefaults and need the ios device to be always connected to the device at all times.
Question: What are the best practices for corebluetooth implementation with the states of the ios app? Specifically:
What should be implemented when user opens the app? (currently: I use the saved identifiers to reconnect to the ble device.. but is this necessary as corebluetooth should automatically stay connected to the device)
What should be implemented when the user backgrounds the app?
What should be implemented when the user reopens the app from background? (should i reconnect to the device?)
What should be implemented when the user kills the app? Does the corebluetooth connection get disconnected?
What should be implemented when battery is low and bluetooth is powered down or turned off by the device?
and lastly:
Is it necessary to implement the corebluetooth library in a singleton class to ensure that only one bluetooth manager is used by the app to connect to the devices?
Here are some advises and best (as I can see them) practices. These are not craved in stone:
When the user opens the app, it doesn't necessarily reconnects to the device. So your approach is good. However, you should check if the app wants to reconnect.
When the user backgrounds the app and the device is connected, the device stays connected. So nothing to implement here unless some special cases. We will get to them later.
When the user brings the app to the foreground (reopens it from background) and the device was connected before the app was sent to background, it still remains connected. So nothing to implement here.
When user kills the app, the device is disconnected, and there's nothing you can do. You can try to reconnect to the device when the user opens the app again.
I would monitor the device battery (check it once in a while) and when the battery reaches the critical level, let's say 5%, disconnect from the device.
And lastly: it is not necessary to implement it as a singleton. It is very convenient though when you have only one object that handles all the bluetooth connection. So, I'd say, singleton is a good choice.
In general you have to take into account two cases:
The app is killed by the system (probably because of memory pressure or crash). When the app is killed while in background, the system will relaunch it and the func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) function will be called. In this function you should try to reconnect to the bluetooth device. When the app is killed by the system while in foreground, sometimes (from my experience) the same function will be called. So you should do the same thing. When it's not called - see the following paragraph.
The app is killed by the user. In this case you cannot relaunch the app. But when the user opens it again you should check the latest connection status, and try to reconnect to the device if needed. Since you save the connected device in UserDefaults (which is the right thing), you should have all the needed properties.
For any other cases - disconnections because the Bluetooth turns off (on the device or on the phone) or because the device is out of range, the system handles the reconnection. So basically, nothing to do here.
Once again, all this is not carved in stone. That's how I implemented it, and it works fine.

Core Bluetooth - Performing Long-Term Actions in the Background

This is from the Core Bluetooth Background Processing for iOS Apps section found in official docs:
Performing Long-Term Actions in the Background
Some apps may need to use the Core Bluetooth framework to perform
long-term actions in the background. As an example, imagine you are
developing a home security app for an iOS device that communicates
with a door lock (equipped with Bluetooth low energy technology). The
app and the lock interact to automatically lock the door when the user
leaves home and unlock the door when the user returns—all while the
app is in the background. When the user leaves home, the iOS device
may eventually become out of range of the lock, causing the connection
to the lock to be lost. At this point, the app can simply call the
connectPeripheral:options: method of the CBCentralManager class, and
because connection requests do not time out, the iOS device will
reconnect when the user returns home.
Okay, we have an app which locks/unlocks doors as appropriate... So as pointed out, this works when app is in the background (most likely in the suspended mode). Now, lets move on (with quoting docs):
Now imagine that the user is away from home for a few days. If the app
is terminated by the system while the user is away, the app will not
be able to reconnect to the lock when the user returns home, and the
user may not be able to unlock the door. For apps like these, it is
critical to be able to continue using Core Bluetooth to perform
long-term actions, such as monitoring active and pending connections.
So, if the user was away from home for a few days, and app has been terminated by the iOS, we will have to implement state preservation and restoration, so that iOS relaunch the app when connection request is detected, and let the app to unlock the door. Related quotes:
In the case of the home security app described above, the system
would monitor the connection request, and re-relaunch the app to
handle the centralManager:didConnectPeripheral: delegate callback when
the user returned home and the connection request completed.
This all make sense, but pay attention to this part again:
Now imagine that the user is away from home for a few days. If the app
is terminated by the system while the user is away, the app will not be able to reconnect to the lock when the user returns home, and
the user may not be able to unlock the door. For apps like these, it
is critical to be able to continue using Core Bluetooth to perform
long-term actions...
Does this means, if the app is forcefully killed by the user at some moment while he was away from home, that this will work as well ? Means when user comes home, the door will unlock anyway, or he must manually relaunch the app to unlock the door?
I am asking this, because of how relaunch of terminated apps works. It is not the same when user kills the app, and when iOS kills the app which supports background execution:
Apps that support background execution may be relaunched by the system
to handle incoming events. If an app is terminated for any reason
other than the user force quitting it, the system launches the app
when one of the following events happens...
Source
So once again, if the user was away for a few days and he has closed the app by double tapping Home button and dragging up, will he be able to enter his home without manually relaunching the app?
No. If the app is forcefully killed by the user then it will not be woken up again. The only scenario where it will be woken up is if the app was terminated by iOS itself, which will happen sooner or later when the app has not been it the foreground for a while. It will also not be relaunched if the device is rebooted.
Having said that, from my experience with Core Bluetooth I have come to the conclusion that State Preservation is way too unreliable. I would believe that the use-case that you are trying to implement will not work well enough, which is ironic since it is exactly the use-case that Apple is promoting it their documentation.
For example, you will have issues with the following:
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 any bluetooth-state-change events will cause all pending connections to be tossed, meaning that your pending connections to the door lock will be lost. However, since your app is not relaunched to be notified of this, then it 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 will NOT work very reliably either).
This scenario happens if the user toggles Flight Mode, toggles Bluetooth, power cycles the iOS device, or any other undefined reasons that many cause state changes… And it is very unlikely that a bluetooth-state-change will not happen if "... the user is away from home for a few days.".
This "issue" has been reported by me and others multiple times, but Apple does not appear to want to fix it for some reason.
Apart from this, many other issues 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. Etc, etc, ...
/A

Syncing data over BLE via iOS without App Open (or in the background)

I'm working on a device that keeps count when a door is closed. What I'd like to do is when I walk through the door, my iPhone automatically syncs the data on the device and sends that count to the server (via iPhone) without me opening the app or having it in the background. Is this possible, if so how?
Here's a diagram of what I'm thinking:
Door closes -> BLE notifies iPhone -> BLE sends count to iPhone ->
iPhone sends that value to server
All without the user (me) touching my device or opening the installed iOS application.
Your app has to be running in the background to do something, but that is OK, because Core Bluetooth background mode will take care of that for you.
First, you need to select "Uses Bluetooth accessories" background mode in your project.
Now, your program flow will be something like this:
Your user runs your app which scans for available doors and displays them to the user
Your user selects a door that they want to connect to
You save the identifier of the selected peripheral somewhere like NSUserDefaults
You connect to the peripheral
Once you get a call to the didConnectPeripheral delegate method you can read the count and update your server once you get the value
The user can now suspend your app and do something else
Eventually the peripheral will go out of range and you will get a call to didDisconnectPeripheral. In this method you immediately re-issue the connect to the peripheral.
Since you have Core Bluetooth background mode, when the peripheral is eventually seen again you will get another call to didConnectPeripheral in the background, and you can proceed as per step 5 (In this case your app is already in the background so it will just go back to suspended state after you have read the data without the user doing anything).
You update the server in step 5. This step executes regardless of whether the app is in the foreground or background. The user doesn't need to open your app.
Now eventually iOS may remove your app from the suspended state, say due to memory pressure. In order to still be able to connect to the peripheral when it is seen you need to opt in to state restoration as described in the Core Bluetooth Programming Guide
If you are up for building your own circuit board and Bluetooth LE firmware, this is pretty straightforward:
Add a contact switch that sends a voltage level change to the circuit board whenever the door opens.
Increment a counter on the microcontroller when the level changes.
Write firmware that advertises an iBeacon packet with the counter as the least significant part of the iBeacon identifier (32 bit major and minor).
A phone can then pick up this counter by using CoreLocation APIs to both monitor for the beacon (for fast background wakeups) and range for it (to read the specific identifier), then sending the counter value to the server based on the identifier read.
The advantage of using CoreLocation instead of CoreBluetooth as #paulw11 suggests in his very good answer is faster background wakeups of the app, allowing an app to reliably read the counter in the background. With CoreBluetooth, this background wakeup can be much slower, and door open events are more likely to be missed.

iOS Bluetooth monitoring without iBeacon monitoring

I am trying to create an app that wakes up to background mode whenever it detects a new peripheral that advertises a pre-defined service. I want the wakeup to happen even if the user minimized the app, the app was suspended, the app was killed by the user or the app was killed by the system. Obviously I also want the app to be accepted to the app store (so don't suggest solutions like adding all possible UIBackgroundModes and run in the background forever...).
I can add bluetooth-central to the UIBackgroundModes because my app fits the description here - it needs to communicate with a peripheral in the background.
I found some documentation here about State Preservation and Restoration. It describes a scenario where an app connects to a bluetooth door lock, and the connection breaks because the user went far away from the door. From what I understand, when the user returns to the door and the iPhone detects the door lock (by its MAC address, I suppose), it reconnects to the door lock and wakes up the app in Background mode (please correct me if I am wrong).
I don't want to preserve a connection to the peripheral (and waste its battery). I want the app to run scanForPeripheralWithServices that will wake up the app when a new peripheral is detected, even if the app was killed\suspended by the user\system.
I found a relevant answer here to a different question, saying I could simulate an app-termination-by-the-OS using kill(getpid(), SIGKILL); and then, if my app uses State Preservation & Restoration, it should wake up (right?).
When my app detects a new\old peripheral, I want it to create a quick connection, read some characteristics and disconnect. Unforetunately this is not possible with iBeacon Monitoring that does not give a Peripheral object (only a region). I know I can detect the peripheral by running scanForPeripheralsWithServices after my app enters the iBeacon region, but this feels strange - a direct continuous scanForPeripheralsWithServices that survives suspension & kill, would make much more sense.
Any ideas?
If you know for sure that this is not possible, and depending on iBeacon Monitoring is the only way, please let me know.
The key part of the question is:
I want the app to run scanForPeripheralWithServices that will wake up the app when a new peripheral is detected, even if the app was killed\suspended by the user\system.
Unfortunately, this is not possible with CoreBluetooth alone. If the user kills an app, it will not get new OS launch events from CoreBluetooth unless the user manually launches the app again.
Fortunately, CoreLocation does not have this restriction. Since iOS 7.1, it will launch a killed app to notify it of a Bluetooth beacon detection. This would allow you a few seconds of background running time to scan for peripherals.

How to wake up iOS app with bluetooth signal (BLE)

When using the BLE with CoreBluetooth (no iBeacon), is there a way to wake an app that is not running when the device receives a Bluetooth signal?
I'm simulating a beacon with the RedBearlab's BLE Shield (http://redbearlab.com/bleshield/).
Thanks,
DAN
* UPDATE 03/05/14 *
It looks like Apple has introduced a major update with iOS 7.1: now iOS will open your app for you if it detects a UUID that matches your app. The app only needs to be installed, it doesn't have to be running (logic in AppDelegate needed to answer the wake-up call).
If the app was running in the background and then closed (I mean here terminated - and you do not see it anymore in the list of background apps when you double click the home button) due to memory pressure from other apps, then the iOS will take care of waking it up again when a delegate is called (assuming you have background modes listed in .plist).
If the app was running in the background and then closed by the user (again I mean here terminated. So the user double clicked to get the list of apps running in the background and then clicked on your app in the background list until it wiggled and then pressed the 'x' next to the app to kill it) then this is a clear indication that the user does not want the app running in the background and the app will not be re-launched. The user has to relaunch the app for its delegates to start functioning again in the background (new in iOS7).
Update:
I am not sure if this is in the documentation but it is in CoreBluetooth WWDC 2013 Video. I recommend you watch it. They spent a good portion of the video on how CoreBluetooth behaves in the background.
From what I understand, if your app has not previously connected with the BLE Peripheral, then no.
If your app has previously connected with the BLE Peripheral, then use:
-connectPeripheral:options
Connection requests don't time out. The best place to call this method is when your app loses connectivity with the BLE peripheral. You will get notified when you lose connection to the peripheral in the CBCentralManagerDelegate Protocol:
-centralManager: didDisconnectPeripheral: error
So the next time your App comes in range of the BLE Peripheral, it will trigger this method. Also note that you will need to set up State Preservation and Restoration when you initialize a CBCentralManager.
https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html
the system wakes up your app when any of the CBCentralManagerDelegate or CBPeripheralDelegate delegate methods are
invoked, allowing your app to handle important central role events,
such as when a connection is established or torn down, when a
peripheral sends updated characteristic values, and when a central
manager’s state changes.
To perform certain peripheral role tasks while in the background, you
must include the UIBackgroundModes key with the bluetooth-peripheral
value in your app’s Info.plist file. When this key-value pair is
included in the app’s Info.plist file, the system wakes up your app to
process read, write, and subscription events.
You could try to declare voip in info.plist. For the time being, my application was automatically relaunched after a time, even user terminated it.

Resources