CoreBluetooth not caching discovered services and characteristics - ios

iOS 6 supposedly caches the discovered services and characteristics of peripherals so that reconnecting to known peripherals is faster. However, it doesn't seem to work in my app.
I save a peripheral after connecting to it, and on the reconnect I call retrievePeripherals with the saved peripheral's UUID. My didRetrievePeripherals gets called and I connect to the peripheral. My didConnectPeripheral gets called and I call discoverServices. I was expecting the services to be returned to my app from the cache. However, the service tables are read from my peripheral.
Because my app is busy reading the service tables, I miss the first measurement sent from the peripheral.
Does anyone know why the cache is not being read? Is there some option I need to set somewhere?

You should try to encrypt the connection. Encrypted connections require pairing and after that, the caching is truly turned on. Plain connections not necessarily trigger the caching mechanism and there is no official statement on this. You should also try iOS beta to see if this has changed there. ;)
The other thing you should consider is optimizing the service discovery. If measurement is very important, then discover the characteristics for that first and once that is done, continue on with the others.

Related

CoreBluetooth peripheral service becomes empty while reconnecting/restoring

In my iOS app I am able to successfully connect to BLE peripherals, discover service and subscribe to characteristics so that app gets notified whenever there is a change in characteristic value. All these happens with no issues as long as app runs in background.
But issue occurs when state restoration happens. In centralManager:willRestoreState: method, I am able to retrieve previously connected peripherals using the method retrieveConnectedPeripheralsWithServices:. But nothing happens when I call discoverServices: method on the retrieved peripheral. peripheral:didDiscoverServices: method never gets called. The value of retrieved peripheral's services property is also null. Does iOS not cache the services and characteristics ?
Note: Our BLE peripheral advertises service initially. When the app launches for first time, it reads value from peripheral and writes user specific data to a characteristic. Post writing, peripheral stops advertising services. But firmware engineer claims that even though service is stopped by peripheral, peripheral retrieved from state restoration should have the cached service. Is it true ?
Have you checked the connection state of the peripherals that you have retrieved via retrieveConnectedPeripheralsWithServices:? The CoreBluetooth framework has some quirks that you need to be aware of, one being the behaviour of the retrieveConnectedPeripheralsWithServices: method:
When you obtain the peripherals via the aforementioned method they might only be connected on the system level (iOS) but not within your app (see the Discussion section in the API documentation). Therefore, you still have to call connect on the peripherals before you can use them properly. Here is the corresponding part from the API documentation:
The list of connected peripherals can include those that are connected by other apps and that will need to be connected locally using the connectPeripheral:options: method before they can be used.
Regarding the caching of services: iOS caches any discovered service and corresponding characteristic. There are only to ways to force iOS to update the cache:
a BLE power cycle, e.g., turning off and on BLE in the Settings App or restarting your iOS device
send a Services Changed notification via the GAP service from the peripheral side (requires an active connection)
Note: You can also activate log messages from the Bluetooth Stack via the Bluetooth Configuration Profile. They can be quite helpful when debugging BLE related issues especially with custom hardware involved (even though the logs are a bit cumbersome to use).

CoreBluetooth not discovering hear-trate monitor if other app is connected first

I want my users to be able to track their heart-rate with my app. So I use CBCentralManager for that. Everything works fine if no other app is connected to the heart-rate sensor yet. The problem I have is if I start f.e. Strava or Endomondo first. Then I just can't find any devices any more. The other way round everything works fine, so I guess I am missing an options somewhere?
What I currently do:
I instantiate my CBCentralManager like so
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
which will cause the delegate method for connection to be triggered
func centralManagerDidUpdateState(_ central: CBCentralManager) {
let heartRateServiceUUID = CBUUID(string: "180D")
let services = [heartRateServiceUUID]
switch central.state {
case .poweredOn:
centralManager.scanForPeripherals(withServices: services, options: nil)
and from there on no peripheral are found.
But again, when I force quit other apps like Endomondo or Strava and then start my app, everything works fine.
Ok, I just found the solution to my main problem myself. So anybody who might have the same issue:
Once you are connected to a device remember it's UUID
When you want to reconnect to it you don't need to scan, just use centralManager.retrievePeripherals(withIdentifiers: [the id's of the devices you know])
This will give you back a list of CBPeripherals and you simple connect to them like you would have after searching for them.
The only thing that I still don't know:
Even when i uninstall Endomondo, start my app first, they still can discover my heartrate monitor... And I suppose even if they remember the UUID it will be in UserDefaults i suppose, so they can't reconnect using centralManager.retrievePeripherals(...). So I still like to know what I'd need to change in order for that to work...
See Best Practices For Interacting With A Remote Peripheral Device for full details on how to connect to devices you already know about.
The main issue you're encountering is that most BLE devices stop advertising once they have a connection. Since they aren't advertising, you can't see them in a scan. In this particular case, the BLE device is connected to the iPhone you're running on, but that doesn't change anything. It's still not advertising.
To deal with this, you want to ask the iPhone for connected devices that have the service you want, using retrieveConnectedPeripherals(withServices:). This is a very fast, synchronous call, and you generally should do it before calling scan​For​Peripherals(with​Services:​options:​).
There are several other steps that you generally should do. The precise order and logic depends a little on your situation, but the linked flowchart above walks you through one approach. Basically it will look something like this:
Call retrieve​Peripherals(with​Identifiers:​) to find a peripheral you already know the identifier for. Note that this just tells you the system knows about the peripheral; it doesn't mean it's currently nearby. Calling connect on it may never succeed.
Call retrieveConnectedPeripherals(withServices:) to find a peripheral that is already connected to this iPhone and advertises your service. You still need to call connect on it for your process, but it should succeed.
If all the rest fails, then call scan​For​Peripherals(with​Services:​options:​).
Update:
It appears that BLE4.0 chipset did only support one connection so when a centralmanager connected to a BLE4.0 device it stopped advertising and established a connected layer.
With BLE4.1 chipset it added support for multi-role connection. It is possible that the heart-rate sensor use newer BLE technique that support more than one connection.
https://e2e.ti.com/blogs_/b/connecting_wirelessly/archive/2016/11/30/bluetooth-low-energy-multi-role-demystified
It is still unclear why your app wont connect if the other apps are connected. Can you post all your code?
My old answer:
"Core bluetooth and Bluetooth Low Energy can only handle one connection for each peripheral which means that if your app(CBCentralManager) establish a connection with a peripheral(UUID) it establish a message layer between the central and peripheral so other apps cant interfere the connection to that peripheral unless you disconnect the first app. This is how Bluetooth Low Energy works."
Apple Guides and Samples:
https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/AboutCoreBluetooth/Introduction.html#//apple_ref/doc/uid/TP40013257-CH1-SW1
Apple Core Bluetooth API:
https://developer.apple.com/reference/corebluetooth

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!
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.

Trying to retrieve previously paired bluetooth device in IOS app will not respond with failure if device is off

Sorry for the long title, but we are having a pretty interesting issue with using corebluetooth for ios. We are issuing a call to retrievePeripherals in CBCentralManager and are able to find the previously paired device.
This happens though regardless if the device is on or off though. I can't find anything in apple's documentation as to why it's able to find the device when it is off though and it isn't showing up in Settings -> Bluetooth -> Devices. I'm suspecting that Apple is caching this information but can't find any documentation to confirm this. Also, when the device is off and we issue the connect call, the program continues to execute as normal but the delegate for didFailToConnect never gets called. When the device is turned on, it will connect immediately.
Is there a way to pass a timeout parameter when trying to connect to a device? If not, what would the best solution be to handling reconnecting to a previously used device for an application (we're storing the last connected device within the app).
Two points you need to know about retrievePeripherals: and connectPeripheral:
1.) retrievePeripherals: attempts to retrieve the CBPeripheral object associated with the uuid you supply. Even if the ble device is off (or on the other side of the country) retrievePeripherals: will still return an instance of CBPeripheral that you can call connectPeripheral: on. This is done intentionally so that you can issue a call to a peripheral that is not even around and still automatically connect to it when it comes back into range. It basically creates a marker inside the system bluetooth so that when the device is actually seen, it will know it should connect to it.
2.)connectPeripheral: will not time out unless the communication channel is broken with the actual device. If the iOS device has not seen the device, it will not fail and should not time out (unless some error occurs inside the system bluetooth).
And as for the timeout parameter, there is no documented way inside the CoreBluetooth framework. You can create your own implementation for it, however I believe you'd be better off keeping a list of which peripheral uuid's you've actually called connectPeripheral: on and then just pop them from the list when they connect. If you no longer want to connect to a peripheral in the list call cancelPeripheral: on that UUID, call connectPeripheral: on the other, and swap entries. Good to go.

Peripheral and central at the same time on iOS

I've looked everywhere and tried everything, but nothing seems to work :(
On iOS, I'm making an app (for iOS 6 and above) in which iOS devices need to exchange data. Therefore, both devices need to be peripheral and central at the same time. I've done exactly as specified in the WWDC video, but the devices can't connect successfully with each other.
When I make one device only central and the other only peripheral, the central connects seamlessly to the peripheral.
However, when both devices are peripheral and central at the same time, I get random errors: at any stage (discovering services/characteristics or setting notify value to YES) errors sometimes happen, and sometimes discoverServices doesn't even call didDiscoverServices
Is there something different I should be doing? I simply merged the peripheral and central code into one view controller. I've noticed that if device "a" connects to device "b", and then device "b" connects to device "a", it works more often than not. I manage this by using NSThread sleepForTimeInterval: manually for different amounts of time on each device, but how could I get one device to connect first (and then the other) in a reliable (and not manually pre-defined) way?
If I do get errors, usually they're simply Unknown error
Please let me know if you need any code or any other information :)
Yes, it can be in both roles at the same time. You just have to initialize a CBPeripheralManager and a CBCentralManager. As soon as the peripheral manager is initialized and you receive the POWER ON state the device starts acting as a peripheral. You can add your services at this point and receive connections from other devices. At the same time you can use the central manager to scan and initiate connections to other peripherals.
Note that you cannot connect to your own device even if it acts as a peripheral.
For your errors, I suggest:
Turn off scanning before initiating a connection. That is, scan, find peripheral, stop scan, connect. Connection and scanning do not like each other.
Use a dedicated queue for handling bluetooth events, not the main queue. [[CBCentralManager alloc] initWithDelegate:self queue:my_dedicated_bluetooth_q]
Unfortunately, the stack sometimes become unstable. Even restarts are possible. But this usually happens only under heavy loads or several simultaneous connections. Hopefully, this will be improved in iOS7.
The unfamous Unknown error started to appear for several developers recently. Judging from your description there are probably a number of reasons why your setup may fail and it would require much more info that what fits well into a SO question.
For more info I suggest you search the bluetooth-dev mailing list archives https://lists.apple.com/archives/Bluetooth-dev or send a mail Bluetooth-dev#lists.apple.com. The community provides great help if you approach with reasonable questions like this.
As per my understanding one device can work with one mode at a time . That is if the device is working in the peripheral mode then it you cant work it as a central mode .If you see some standard examples like BTLE transfer or lilke Light Blue those are working in one mode at a time .
Firstly, what do you mean "the same time"?
If you mean the device advertising to other devices while it scanning for other devices, it can not.
But you can create two threads which share same lock to advertising and scanning.
Before scanning, stop advertising, before advertising, stop scanning.
I tested on my iPhone 4s and iPad air, worked well.

Resources