Scott Newman 15 Feb 2016 Permalink
Calculating the closest beacon to a device can be a challenge, but our latest release of ProximityKit makes it very easy to do. We’ve added a new delegate callback method to our library that will tell you when the closest beacon has changed. It’s easy to use right out of the box and it can be configured as needed.
Let’s look at how we solved the challenge so you can focus on writing world-class applications.
Background
If you read through Apple’s iBeacon documentation, you’ll find their recommended approach to distance ranging is to use the proximity
property on the CLBeacon
class and they discourage developers from trying to detect specific distances. While this is a very good strategy, there are times when it is appropriate to determine the beacon’s distance.
The proximity
property of CLBeacon
returns a CLProximity
enum with one of four values:
CLProximityUnknown
CLProximityImmediate
CLProximityNear
CLProximityFar
These values are calculated by Core Location automatically and take into account factors such as RF signal interference, fluctuation, etc.
Note: In our ProximityKit iOS library, we expose the proximity
and accuracy
properties from Core Location’s CLBeacon
class in our RPKBeacon
class. Any tips you find around the web for working with CLBeacon
also apply to our RPKBeacon
implementation.
When is estimated proximity good enough?
For many use cases, knowing if a device is immediate
, near
, or far
from a beacon is good enough. Ideally, beacons are physically placed far enough apart so that it is not possible to be in immediate
or near
proximity of two beacons at the same time.
If this is the case for your application, the code can simply use the didRangeBeacons:inRegion:
callback from ProximityKit or Core Location to see which beacons, if any, are immediate
or near
.
In practice, the immediate
proximity is approximately 1-2 meters from the beacon, and this might be too close for your needs. You probably don’t want users to crowd around a podium in the middle of a museum exhibit to trigger an immediate
proximity event.
In this scenario, it would make more sense to trigger when the proximity is near
, but what if two beacons are both reporting near at the same time because they are about ten meters apart?
When to use the accuracy measurement
ProximityKit (and Core Location) also returns an accuracy measurement derived from a calculation using the beacon’s signal strength (RSSI) and the measured power calibration. While it is not strictly a distance measurement, it can be used as a proxy for distance calculations. Apple recommends this property be used “to differentiate between beacons with the same proximity value”.
From the CLBeacon reference discussion for accuracy
:
Indicates the one sigma horizontal accuracy in meters. Use this property to differentiate between beacons with the same proximity value. Do not use it to identify a precise location for the beacon. Accuracy values may fluctuate due to RF interference.
Let’s imagine a scenario where three beacons are placed in a room approximately fifteen meters apart. Your user is standing in the room and all three beacons are reporting a proximity of near
. We want to tell the user which beacon is the closest, so we can differentiate the three beacons by looking at the accuracy
property.
Challenges using accuracy
The challenge of using the accuracy
property is that the value can fluctuate wildly in a short amount of time. Perhaps the room is busy and people are walking between the device and the beacon, causing the signal to fluctuate and sporadically disappear.
To combat this, we can create some logic that averages the readings over a few seconds and reports back a running average of the accuracy
reading.
The logic would work something like this:
didRangeBeacons:inRegion:
method is called in your delegateaccuracy
propertyThe logic is straightforward, but there are subtle details that must be taken into consideration to conserve battery life and processing.
We wrote it so you don’t have to!
We felt this was enough of a common use case that we should write an implementation for the developers using our beacons and ProximityKit library. This functionality has been released with version 1.2.1 of our ProximityKit framework.
Using it is very simple. When instantiating RPKManager
, provide the monitor_closest_beacon
and averaging_seconds
values in the configuration dictionary:
RPKManager *manager = [RPKManager managerWithDelegate:self andConfig:@{
@"kit_url": @"<your kit url>",
@"api_token": @"<your api token>",
@"monitor_closest_beacon": @"true",
@"averaging_seconds": @(10.0),
}];
Note: averaging_seconds
has a default threshold of five seconds; you can omit the key/value in the configuration if that value works for you.
How to use it
When you configure RPKManager
this way, you’ll get a closestBeaconDidChange:forRegion:
delegate callback:
- (void) proximityKit:(RPKManager *)manager
closestBeaconDidChange:(RPKBeacon *)beacon
forRegion:(RPKBeaconRegion *)region
Here’s an example implementation of the delegate method:
- (void)proximityKit:(RPKManager *)manager closestBeaconDidChange:(RPKBeacon *)beacon forRegion:(RPKBeaconRegion *)region {
if (beacon != nil) {
NSLog(@"New closest beacon: %@-%@", beacon.major, beacon.minor);
}
}
There are three things to know about the callback:
beacon
value will be nil
.Steps #2 and #3 are designed to prevent “flapping” behavior where two beacons in very close proximity keep fighting each other to become the closest. This is part of the ‘smoothing’ behavior discussed above.
Summary
We hope that this new functionality cuts down the amount of code you need to write as a developer when you need to calculate the closest beacon. Give it a shot, and let us know what you think!