Is there a mechanism in iOS CoreBluetooth so that an event will be triggered when a discovered (but not connected) peripheral is "lost" i.e. a peripheral that was advertising is no longer advertising .
You could specify the CBCentralManagerScanOptionAllowDuplicatesKey:YES in the options to CBCentralManager's scanForPeripheralsWithServices: and then keep a table of observed devices, ageing the entries periodically, but this will impact battery life and you will not be able to operate once your application is no longer in the foreground. Connecting is a better option - is there some reason why you don't want to connect to the device?
You can look in AltBeacon, an open source project we released https://github.com/CharruaLabs/AltBeacon how we do this. Check the method reportDelegates of the class AltBeacon.m
A bit more in detail. What we do it to keep reporting in an preestablished interval and then age the signal (RSSI) of the device. After a while if it is too aged you can trigger the no longer advertising callback. What we do is just to change an enum status distance to unknown.
Related
I am currently using CoreBluetooth to scan for peripherals. Every 30 seconds it sends its packet of information to the cloud by using a Timer. This is working good in foreground. I would like this exact operation to function seamlessly in the background too.
I have declared a CBUUID ahead of time for it seek out the designated peripheral in the background. Upon entering background mode, the scan stops functioning after 10~ seconds. How do I continually make the scan operate continue in the background?
I was looking into Bluetooth State Preservation, would this alleviate the issue? Should it also not be on the main thread?
After the packets are advertised I would like them stored in memory. I am aware the DiscoverPeripherals logs the peripherals... is it possible to log the RSSI and additional peripheral data in chronological order and have it an operation take affect per an interval? I was looking at BGProcessingTask to fire off a function in the background. Would a better approach be to use CoreData to store the memory and clear it after? Best/easy suggestion is appreicated.
I have changed the CBUUID to the right peripheral for centralManager.scanForPeripherals(withServices: [uuid], options: nil)
with uuid being the CBUUID. That seem to be allowing the peripheral to be detected in the background. It stopped logging the scan after a short period of time. Right when I put the app in background, it functions properly for a bit. I theorize it could just be calling it on the same thread and it may not be operating in the background all together. I did test this by changing the the withServices to nil, the result was that the operating was not being logged at all once I closed to the background.
I was expecting the operation to continually be scanning on the basis of the Timer every period, after the period is up it would send it the cloud just how it was doing it in the foreground.
There are lots of restrictions on iOS apps when they are not in the foreground. Once an app moves from the foreground it is suspended and can only execute in the background for specific reasons and for limited durations.
In general, anything based on a Timer will not fire when the app is not in the foreground.
Some Core Bluetooth events are delivered while your app is in the background:
Pending connect operations can complete with a corresponding delivery to your app.
Peripheral disconnections will be delivered to your app
GATT Notify/Indicate operations from a connected peripheral will be delivered to your app.
Discovery of new peripherals advertising a service that you are specifically scanning for will be delivered to your app.
It is this last behaviour that you are relying on.
While you have done the right thing by specifying the specific service you are interested in, your plans are being thwarted by the fact that Core Bluetooth will not deliver repeated discovery notifications for a particular peripheral.
When your app is in the foreground you can use the CBCentralManagerAllowDuplicatesKey option to request a discovery notification each time a peripheral advertisement is seen, even if an advertisement from that peripheral has been seen before. This option has no effect when your app is not in the background.
The best way to gather data on a periodic basis from a peripheral (whether in the background or foreground) is for that peripheral to send its data via Notify/Indicate, however you seem to be trying to scan for the existence of peripherals rather than gather specific data from them, so this may not work for you.
When one of the supported Core Bluetooth background events occurs and your app has been jettisoned, State restoration allows your app to respond after iOS relaunches your app. It will not help you in this case.
It probably isn't possible to do what you want, at least not without changing the behaviour of your peripheral.
I've noticed that when you disconnect for a bluetooth device in an application the iOS device will continue to hold that connection for around 10 seconds. I've attempted to get around this by writing to a characteristic that causes the bluetooth module to cancel the connection with the iOS device instead but that isn't working (mostly because I changed the module and the iOS doesn't see the change because I assume the device is cached somewhere). Is there a way to make it disconnect instantly in code? I am using the swift command
manager.cancelPeripheralConnection(peripheral) currently.
Unfortunately, there isn't. The only way to tell the system to disconnect a peripheral is via the cancelPeripheralConnection method that you are already using. Yet, if you call this method it doesn't necessarily mean that the peripheral will be disconnect.
Background
On iOS the whole BLE connection management is maintained by the operating system. That means that no single application "owns" a connection. All BLE functionality is multiplexed to allow more than one application to gain access to centrals and peripherals.
For example, if you have installed a fitness application that tracks data from your heart rate sensor in the background then you can also "connect" to the heart rate sensor in your app but you will be unable to trigger a real disconnect as long as the fitness app maintains a connection.
The disconnect delay you have noticed is basically an optimization of the operating system. If no application holds a connection to the peripheral anymore it will wait for some time (to avoid unnecessary connect/disconnect cycles) and then trigger the disconnect on the bluetooth chip.
Hope that helps.
In iOS when you call the CBCentralManager method cancelPeripheralConnection(CBPeripheral), it does not always immediately terminate the connection. As Apple's documentation states:
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.
If you have a need to immediately terminate a connection programmatically, say to free up the peripheral to be connected from another central device or to reset one's own security layer, then you need to follow the procedure described in the following StackOverflow thread:
iOS 6 - Bluetooth LE disconnect
which is for the app to send your own proprietary command to the peripheral that tells the peripheral to disconnect through normal means (e.g. "GAPRole_TerminateConnection" or "GAP_TerminateLinkReq" or "LL_Disconnect" or "HCI_Disconnect[_*]" with reason HCI_DISCONNECT_REMOTE_USER_TERM). This always works and is not delayed by the connection supervision timeout because it is a formal disconnection notifying the central device (i.e. iOS). The supervision timeout (up to 6 seconds in iOS; on Android the default is 20 seconds) only comes into play if the disconnection is unplanned as with going out of range or if the peripheral does a disconnect without notifying the remote device as with "LL_EXT_DisconnectImmed" (only available in some BLE peripheral implementations).
The basic problem I'm trying to solve is as follows. I have two iOS devices, one configured as a central, and the other as a peripheral. I would like the peripheral to know if the central moves away or becomes inactive for some reason (say the device running the central is turned off).
Under normal conditions, I have it set up so that as the central moves close to the peripheral, the central can use beacon regions and ranging to inform the peripheral via a characteristic-write when it is in the immediate proximity (CLProximityImmediate), and then again when it is still in range, but far off (CLProximityFar). This works well.
However, to catch a corner condition when the central device goes from being CLProximityImmediate to some unknown state, I was planning to used periodic indications from the peripheral to which the central can respond. If there is no response to the indication, then the peripheral can assume that the central is no longer in immediate proximity. However, I am not able to find a callback or delegate method that informs the peripheral manager that the indication failed.
The method updateValue:forCharacteristic:onSubscribedCentrals: returns NO when the underlying transmit queue is full-- and NOT because the central did not respond, the way I've understood it.
Am I missing something obvious here? Is there a way for the peripheral manager to tell that the central did not receive an indication? Or is there a missing callback in CoreBluetooth for this case?
Thanks for your help!
There are two issues to let the peripheral know the location maneager lost contact with the CLBeacon:
The iBeacons are passive, advertise and transmit only devices. As in the Apple implementation there are no connection made to them from the Core Location framework. Therefore the peripheral newer knows anyone listened to their broadcast.
The CLBeacon will not expose the underlying BTLE peripheral as a CBPeripheral, so you would not be able to let the BTLE peripheral know your location monitor was in range.
Issue #2 can be resolved a rather complicated way:
Start scanning for CBPeripherals where the advertisementData contains the proximityUUID.
Make connections to that CBPeripheral and get notified of a characteristic value change.
Change a characteristic value on the peripheral frequently, so when the Central sees no more notifications after a timeout period it indicates the peripheral is out of range.
If you want to make it work for multiple iOS devices this gets way to complicated to switch between advertising and iBeacon and working as a GATT peripheral.
Although this was not explicitly stated in the question, implicitly, I was stating that the peripheral was advertising a service with a couple of characteristics. This is in addition to the explicit iBeacon advertisement, which I agree is "passive". When the central is notified of the beacon region, it discovers and writes to one of the characteristics, and the peripheral can know that a central is in range.
My fundamental question is not regarding the passive nature of the beacon, but more about how "indications" work with CoreBluetooth. I think it is fairly safe to say that if a peripheral uses updateValue:forCharacteristic:onSubscribedCentrals: to inform a central subscribed to a characteristic defined as CBCharacteristicPropertyIndicate, there is no callback defined to capture the confirmation to the indication that should come back from the central (per GAP).
One workaround to this missing API is to define a third characteristic as a "confirmation" characteristic, and impose a protocol that the central should do a dummy read on this characteristic whenever the central receives peripheral:didUpdateValueForCharacteristic: to the indication which was subscribed. That should produce a call to peripheralManager:didReceiveReadRequest: on the peripheral to confirm that the indication was received.
This is more work, and not the most elegant way to do it, but it may be the best thing to do given that there is no API for indication confirmations.
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.
my app using scanForPeripheralsWithServices: to scan BLE device,the argument is specified servicesUUIDs array, sometimes could discover peripheral quickly,but sometimes need to wait for a moment.And my app is session backgrounding.Then,how can I discoverPeripherals as quickly as possible on background.
When the app is in the background iOS is enabling Bluetooth from time to time and it is not always listening. This is to save battery and because the WiFi and Bluetooth share the same antenna and cannot send at the same time. To make it discover your peripheral as fast as possible you should if you have access to peripheral implementation, make sure the peripheral advertises itself as often as possible. I believe Apple recommends at least once each 20th ms.