Monitoring the Closest Beacon with Proximity Kit

By Scott Newman

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:

  1. CLProximityUnknown

  2. CLProximityImmediate

  3. CLProximityNear

  4. 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 immediatenear, 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:

  1. The didRangeBeacons:inRegion: method is called in your delegate

  2. Iterate over each beacon from step #1 and get its accuracy property

  3. Store the accuracy value from step #2 in an array along with its timestamp

  4. Iterate over the array from step #3, purging any readings older than five seconds

  5. Compute the average accuracy value from the array in step #4

  6. Compare the average accuracy value from step #5 for each beacon and determine which is the closest

The 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_secondsvalues 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:

  1. If you go out of range of all beacons, the beacon value will be nil.

  2. The closest beacon will not change more than once every five seconds

  3. When the closest beacon calculation happens behind the scenes, the new closest beacon must be 10% closer than the current closest beacon

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!