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.
Related
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.
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.
Is it possible to associate a regular Bluetooth Low Energy device (not an iBeacon!) with my iPhone app so that when the device sends data my app gets woken up by the iPhone even if the iPhone is locked and the app is terminated (not even in the background)?
As long as your app specifies Bluetooth Central background mode then it will be woken if
Your app has a current connection to the device and it sends data (i.e. the device is in range and it notifies or indicates on a characteristic)
Your app has a pending connection to the device and it comes into range(i.e. the device was out of range, but you have called connect to automatically reconnect when it comes into range)
Your app was scanning for specific service types and a device advertising one of these service types comes into range
The case where you app is terminated is slightly different. For these scenarios to work in this case your app must implement state preservation and restoration
Core Bluetooth supports state preservation and restoration for apps
that implement the central role, peripheral role, or both.
When your
app implements the central role and adds support for state
preservation and restoration, the system saves the state of your
central manager object when the system is about to terminate your app
to free up memory (if your app has multiple central managers, you can
choose which ones you want the system to keep track of). In
particular, for a given CBCentralManager object, the system keeps
track of:
The services the central manager was scanning for (and any scan
options specified when the scan started)
The peripherals the central
manager was trying to connect to or had already connected to
The
characteristics the central manager was subscribed to
The Apple guide talks about the situation where your App is terminated due to memory pressure. It doesn't specify what happens if the app is terminated by the user "swiping up" in the app switcher - In many cases iOS takes this as an indication that the user doesn't want the app to run at all and won't restore it in this case.
There are two states for a bluetooth device to interact with your app:
It has never interacted with your app before
It has already connected to the use's device once and to the app once
In either case, an iBeacon device will be able to interact with your app.
If the device hasn't connected with your app before, I'm not entirely certain if there is a way to make it work. I have tried and failed to get it to wake up the app.
However, if the bluetooth device has connected before, then you can use CBCentralManager and its delegate methods to communicate between the device and your app.
Core Bluetooth should wake up your app from the OS if registered. Once awake, it's running in the background like normal. You have up to 3 minutes to perform whatever tasks you need to.
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.
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.