Beacon ranging vs BLE scanning - ios

I was trying to understand the difference between Beacon Ranging and BLE Scanning. From what I understand, beacon ranging uses BLE scanning to find the beacons. On top of that it calculates proximity of beacons using the signal strength(which I do not need). I just need to detect the beacons(similar to beacon monitoring). I am not using beacon monitoring because of the Always permission requirement in IOS. I know that beacon monitoring is highly optimised as compared to ranging, but I wanted to know how ranging compares to BLE scanning.
Can i use BLEModule.scanForDevices(UUID) in place of CLLocationManager.startRangingBeacons(region) to detect beacons? If yes, can I get callbacks in similar fashion when beacon is detected?
Are there any downsides of doing this in terms of battery performance or detection time, etc.?
Note that I am willing to make do without proximity information(distance from beacon).
Also, a related question:
Can beacon monitoring be used in foreground with WhenInUse permission in iOS?(I found mixed views on this in my initial investigation)

You cannot use Core Bluetooth scanning to detect an iBeacon. You must use Core Location and monitor a CLBeaconRegion to discover iBeacons.
Having discovered a beacon you do not need to range it.
As stated in Apple's documentation
Important
Apps must have always authorization to use region monitoring, and they must be configured with the Location updates background mode to be launched.
Even if you only want beacon notification when your app is in the foreground you must request always permission.

Related

CoreBluetooth to detect accessory proximity (whilst app in background)

I would like to use CoreBluetooth to detect the proximity to a hardware (emitting BLE signals) when the app runs in background.
The first step is activating BLE background mode in the capabilities tab. This will allow the app to receive BLE signals also when running in background. Now, the second step would be to write the code to detect the proximity to the BLE peripheral.
Looking at the iOS developer Bluetooth guide (at page 45/46) I found:
CBCentralManagerScanOptionAllowDuplicatesKey constant as a scan option
when calling the scanForPeripheralsWithServices:options: method. When
you do, a discovery event is generated each time the central receives
an advertising packet from the peripheral. Turning off the default
behavior can be useful for certain use cases, such as initiating a
connection to a peripheral based on the peripheral’s proximity (using
the peripheral received signal strength indicator (RSSI) value). T
Is this the correct direction for this?
In terms of iOS device battery usage, is this approach less efficient than using iBeacon?
Yes, it is a valid approach to use CoreBluetooth as you describe. You can get a callback for each packet you detect in the foreground (and in the background for non-manufacturer advertisements). You can then read the RSSI as an indicator of proximity.
Whether you want to use CoreBluetooth or use iBeacons with CoreLocation, the battery usage is similar in most foreground ranging cases.
If using CoreBluetooth, you probably don't want to keep getting callbacks for every packet indefinitely in the background, because it would drain the battery faster. CoreLocation iBeacon APIs limit you to 10 seconds of ranging in the background for each wakeup event to help save battery.
If you see your app ranging for longer periods in the background using CoreBluetooth you may want to add your own logic to protect against battery drain.

Detecting beacons via iBeacon Monitoring & Ranging vs CoreBluetooth scanForPeripheralsWithServices

There is a lot of confusion regarding the restrictions that are applied by the iOS on apps that want to scan BLE beacons\peripherals.
After reading several blogs and Stack Overflow answers, I want to see if I understand all the issues correctly. Please correct me if there is anything I misunderstood or missed. I refer only to iOS 7 and above, and focus on detection and not connection (Can you connect to a CLBeacon using the iBeacon Monitoring & Ranging API?).
The options for the beacons are clear - Use a general purpose BLE peripheral or use a BLE peripheral that advertises in the iBeacon format (Also, a non-standard peripheral can advertise in the iBeacon format in the adv-packet and a different format in the scan-response packet).
General Restrictions
iBeacon Ranging will let you know which beacons are around you. You must specify the ProximityUUID that the beacons advertise beforehand (no "general" scanning). didRangeBeacons will be called every second with an array of CLBeacon objects that were found recently. The distance from the beacon and its accuracy are calculated by the iOS using some confidential algorithm that only Apple's developers really know (The algorithm is based on the rssi values and the rssi-at-1-meter calibration byte that the beacon advertises). You can also use iBeacon Monitoring to call a delegate every time you enter or exit a region - again you must specify the ProximityUUID that you are looking for (you can also specify a major & minor). "Exiting a region" is defined by some time of not receiving any advertisement, and therefore cannot be immediate. The number of regions that can be ranged\monitored simultaneously per device is limited to 20 - This means that if other apps do monitoring\ranging at the same time, your app may not be able to monitor\range (right?).
CoreBluetooth - You can also detect other ad-structures in the beacon's advertisement. If the beacon advertises in iBeacon format too, you cannot see the iBeacon fields (ProximityUUID, major, minor...), despite the fact that they are sent under a standard "Manufacturer Specific" ad-structure that you can see in other cases.
Running in the Foreground - The less restricted use-case:
iBeacon Ranging and Monitoring - no further restrictions.
CoreBluetooth - Passing nil in the serviceUUIDs of scanForPeripheralsWithServices will scan for all peripherals. Passing CBCentralManagerScanOptionAllowDuplicatesKey as YES in the options will make the didDiscoverPeripheral to be called multiple times for the same peripheral\beacon (I assume that using a timer you detect the advertisement was not received for some time and assume that the user exited the "region").
Running in the Background - The more restricted use-case:
iBeacon Ranging will not work directly. iBeacon Monitoring will call didEnterRegion and give the app runtime of 6 seconds - in which you can start Ranging (for example, to detect major & minor). The detection may not be immediate since iOS turns scanning on and off to preserve the battery power. If you enter a region of multiple beacons with the same ProximityUUID, and you monitor this UUID without a specific major and\or minor, didEnterRegion will be called when you start receiving the signal from the first beacon - however, if you did not exit the region of the first beacon and you also entered the region of a second beacon the app will not be woken up again (didEnterRegion will not be called again) so you cannot start ranging to detect the second beacon's major & minor. The app cannot simply pop up to the foreground, but can create local notifications and other background operations.
CoreBluetooth - According to Core Bluetooth Background Processing scanForPeripheralsWithServices can run in the background using, but you must specify at least one serviceUUID. didDiscoverPeripheral will be given a runtime of 10 seconds. Using CBCentralManagerScanOptionAllowDuplicatesKey will not work - didDiscoverPeripheral will be called once for every peripheral. Therefore, you cannot detect "exit" from the region and "re-entry". I suppose you can use a non-standard BLE peripheral that changes its MAC address to overcome this issue. The app cannot simply pop up to the foreground, but can create local notifications and other background operations. The detection may not be immediate since iOS turns scanning on and off to preserve the battery power.
Running after the app is killed
iBeacon Monitoring - Works! Even if the user killed the app or the device was restarted.
CoreBluetooth - The app will be woken up if it was killed by the iOS (due to inactivity or memory constraints). However, if the user explicitly killed the app it won't be woken up (which makes the first case hard to test). I don't know what happens after a device restart...
Does anyone have more experience with these restrictions? Can scanForPeripheralsWithServices be used as a better alternative to iBeacon Monitoring in some use-cases?
Thanks!
You are mostly right with your description. Just two clarifications:
The 20 region limit is not per device, it is app-specific. No matter what other apps are doing on the mobile device, your app is still allowed to monitor up to 20 regions by iOS. That said, there are likely hardware limits that are device-specific on how many regions can be monitored in the background with hardware assistance. These limits are undocumented. If you surpass these undocumented limits, it will probably take a lot longer to detected beacons in the background. (Although that said, there is no OS guarantee of when the detections come, anyway.)
You cannot connect to a CLBeacon using Monitoring and Ranging APIs. These APIs only work with BLE advertising packets, which are connectionless.
Yes, it is possible to use scanForPeripheralsWithServices as an alternative. This is what Gimbal beacons do in order to implement a proprietary system. There are real disadvantages, however, in terms of background detection time and reliability.

Waking up a killed app using didEnterRegion and then scanning for peripherals using scanForPeripheralsWithServices

I know that the common way to use iBeacons even if the app was killed is to use Monitoring (for example, monitor a region defined by a UUID) and when didEnterRegion is called start Ranging for iBeacons (to fetch each beacon's major and minor).
Did anyone tried running a Corebluetooth scanForPeripheralsWithServices instead of Ranging?
This won't give the major & minor - iOS hides the iBeacon identifiers (uuid, major & minor) from the Corebluetooth API.
However, it can give other information that the non-standard iBeacon advertises (or other BLE peripherals around), and may even let you connect to them (in the 5-seconds "wakeup").
Thanks
Yes, what you say is absolutely possible. As you point out, since you cannot access the beacon identifiers, you will not know which peripheral in the callback from scanForPeripheralsWithServices is the beacon that triggered the region entry event. But you can certainly scan for these devices for a brief time after your app is launched into the background by this event.

Time spend around a beacon

What should be the correct approach to identify time spend around a beacon[not within a region] in background. I am able to do this when app is in foreground using didRangeBeacons and some business logic.I read on few forums that ranging does work when app has registered for location updates in background, but i am having no success. I have added the location updates key for UIBackgroundModes in plist.
I am using estimote beacons and their sdk.
I see two possible solutions here:
Listen for enter and exit region events, store the timestamps and then use them to calculate the time span on exit. If you define your region so that it encompasses only one beacon, monitoring the region will be equivalent to monitoring the beacon. The only thing to keep in mind is that iOS imposes a limit of 20 regions to be monitored at the same time - so this solution doesn't scale above 20 beacons.
Use ranging in the background. Apart from the UIBackgroundModes, you also need to start regular location services, i.e. startUpdatingLocation.
Beacon ranging delivers events normally while an app is running in the foreground. When your app is in the background, this service delivers events only when the location-updates background mode is enabled for the app and the standard location service is running.
(this is from CLLocationManager class reference, section "Using Location Services in the Background")
Note that ranging in the background will be draining the battery life more than usual, and Apple also requires justification for using the background modes. Unless there's some value for the user of your app coming from the background modes, they might choose to reject it. All in all, use the background ranging wisely! (:

How can I tell if CLLocationManager is actively scanning for a beacon?

When you set up an iOS device as a beacon (peripheral role), you can query its state by calling CBPeripheralManager.isAdvertising. I can't find the equivalent to query whether a device is scanning for a beacon (central role) on CLLocationManager. Any ideas?
Update:
Given David's answer, I encapsulated the code setting up the CLBeaconRegion with the specific UUID and added a boolean variable which is changed when calling startMonitoringRegion and stopMonitoringRegion on CLLocationManager.
I do not believe there is any way this is possible. Even though CoreLocation's iBeacon APIs use Bluetooth LE and CoreBluetooth under the hood, Apple appears to have gone to some lengths to hide this implementation. There is no obvious way to see whether a Bluetooth LE scan is going on at a specific point in time.
Generally speaking, a Bluetooth LE scan is always going on when an app is ranging for iBeacons in the foreground. When an app is monitoring for iBeacons (either in the foreground or background) or ranging in the background, indirect evidence suggests that scans for beacons take place every few minutes, with the exact number ranging from 1-15 depending on phone model and state. I know of no way to programmatically detect the exact times when this starts and stops, although it can be inferred by iBeacon monitoring entry/exit times. If you look at the graph below, the blue dots show the inferred scan times for one particular test case. Details of how I inferred this are described in this blog post.

Resources