Integrating iBeacon With Your Apps – The BeaconDemo Project

Welcome to the world of beacons! We’re glad you’re here.

iBeacon_Logo_RGB

This tutorial is a walkthrough of a simple App we’ve created to demonstrate integration of iBeacon Technology with Core Location and iOS features such as notifications and persistence.

In simpler terms – the tutorial shows you how to integrate iBeacon with an App in a simple manner. We also point out some of the pitfalls and considerations when dealing with Bluetooth Low Energy devices in the “real world”. There are a number of things to consider to provide the best possible user experience!

 

Objectives

Here’s what we’re going to cover:

  1. Core Location Concepts - How the features of Core Location on iOS can be used to detect and range beacons in the environment.
  2. Practical Considerations - What are the ‘gotchas’ of dealing with bluetooth low energy (BLE) devices in the real world, and what do you need to keep in mind when designing for beacon-enabled Apps?
  3. The BeaconDemo App - A walkthrough of a simple example app, BeaconDemo, which deals with the basic implementation, as well as a discussion of next steps to take the system from demo to production-ready App code.

Pre-requisites

You’ll need a few things to get going with this tutorial and demo:

  1. A valid iOS Developer Account, Provisioning Profile, iOS Device, and Xcode – Basically, a typical iOS development environment. If you’re not yet a registered developer, head over to Apple and get signed up! We’re not going to cover the basics here. We’ll assume you can build and deploy to an iOS7 device. Also, note that you will need to do this on a device – the iOS Simulator does not have Bluetooth LE.
  2. A beacon with iBeacon Technology - We’d love it if you were using an IoT Design Shop Beacon, but any compatible beacon will do. If you don’t have a beacon - The AirLocate sample code from WWDC will allow you to use a second iOS device as a beacon. We cover that code in another tutorial in this series: Core Location at WWDC 2013. You can grab the source to AirLocate from there as well as a link to the video.
  3. A basic understanding of Bluetooth Low Energy and iBeacon - Again, we’ve got another post in this series that covers the parts that you really need to know. If you haven’t done so already, check out our Bluetooth LE and iBeacon Primer now.

 

Core Location Concepts

 

First things first, we need to talk a bit about Core Location and how it has been extended to handle beacons. As you may recall, Core Location has been around for a while now, mostly to manage GPS activities like geo-fencing and determining user position. Accordingly, it was a very fitting place to add the extensions for managing beacons when Apple made the announcement that iBeacon was coming.

CL BeaconRegion and Management

Core Location Hierarchy

 

 

CLBeaconRegion is a key class for managing our beacons. It holds the information about beacons that we’re interested in tracking, as well as a set of flags that indicate what types of notifications we’d like to receive for the region:

  • proximityUUID - This is the UUID for the beacon, or series of beacons that we want to be notified about. There are a lot of different options for identification schemes. We discussed these concepts and the use of hierarchical of ID’s in another post – Bluetooth LE and iBeacon Primer.
  • Major / Minor ID - It’s important to note that these are optional fields. You can set up a region based on just a UUID, or UUID+Major, or UUID+Major+Minor depending on how specific you want the notification to be tied to a particular beacon or set of beacons. Most of the time, when you’re starting out, you have one or two beacons, so this is less of a concern. For a more complex environment, like retail, or commercial, the ID scheme becomes a lot more relevant.
  • notifyEntryOnStateDisplay - This is a powerful, and somewhat confusing flag. It’s important to understand though, because it’s important for certain applications. In simple terms: if this flag is set, and the user turns the screen on when they are inside a beacon region, your application will receive a locationManagerDidDetermineState:forRegion: event. If this flag is not set, then your application only receives that event if the user crosses the boundary from outside a region to inside a region, or vice versa. To illustrate,  consider a retail environment: it’s one thing if the user crosses into the store’s beacon region with the phone in their pocket. However, if they actually pull the phone out and turn on the screen, it’s a more relevant time to capture their attention.
  • notifyOnEntry/notifyOnExit - These flags indicate when you want the locationManagerDidDetermineState:forRegion: event to be triggered. Relatively self-explanatory, but you can set the flags to be notified when the device enters the beacon region, exits the beacon region, or both. There are some considerations related to leaving a region, which we cover in our next section.

On the SDK side, registering and unregistering beacon regions is pretty straight forward. The  corresponding calls in the CLLocationManager class are as follows:

- (void)startMonitoringForRegion:(CLRegion*) region;
- (void)stopMonitoringForRegion:(CLRegion*))region;

Note: Currently, you are only able to register 20 regions per application. This can complicate some really large beacon installations but is normally not a major consideration for most deployments.

Region Notifications

Once you’ve registered regions to be monitored, you will receive CLLocationManagerDelegate callbacks which report back the entry and exit events for our CLBeaconRegions via the didDetermineState: method.

- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region

You will see state reported back with one of three values:

  • CLRegionStateUnknown - The CLLocationManager hasn’t determined what state the region is in yet.
  • CLRegionStateInside -  The device is inside the specified CLBeaconRegion.
  • CLRegionStateOutside - The device is outside the specified CLBeaconRegion

Simple enough. It’s usually a good idea to keep track of previous and current states so that you can filter out any duplicate events and noise that may happen at the edge of the Bluetooth LE range (more about that in the next section). These methods fire quite reliably under most conditions.

Note: Although the didDetermineState: method returns a CLRegion object in the prototype (it also works for GPS-based CLRegions) the regions returned in the didDetermineState: callbacks are CLBeaconRegions and can be cast accordingly if you are receiving updates for beacon regions:

CLBeaconRegion* beaconRegion = (CLBeaconRegion*)region

Ranging Beacons

When inside beacon regions (CLRegionStateInside), it is possible to engage a higher resolution sampling of the beacons in that region. This is called ranging.

Managing ranging for a CLBeaconRegion is relatively straight-forward and follows the same convention as most other operations in Core Location:

- (void)startRangingBeaconsInRegion:(CLBeaconRegion *)region
- (void)stopRangingBeaconsInRegion:(CLBeaconRegion *)region

To keep things clean, it’s not a bad idea to tie registering for ranging into the CLLocationManager:didDetermineState: callback. When you receive a notification that you are inside a beacon region (CLRegionStateInside), call startRangingBeaconsInRegion: and when you receive the notification that you are outside a beacon region (CLRegionStateOutside), call stopRangingBeaconsInRegion: to end ranging. You can see an example of this in the BeaconDemo project.

After you’ve registered to range inside a beacon region, you will receive regular ranging callbacks (at approximately 1 second intervals) when your App is in the foreground:

- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region

Of particular interest is the contents of the beacons array passed down in the callback (we’re already familiar with the other two parameters). This NSArray contains CLBeacon objects for each beacon detected inside the region:

CLBeacon

 

As you can see, a CLBeacon contains many of the same fields as the CLBeaconRegion (except on a per-beacon level) but it also contains additional info about proximity and range for the beacon. Here’s what those fields contain:

  • proximity - Can be CLProximityUnknown, CLProximityImmediate, CLProximityNear, or CLProximityFar. In other words, proximity is a rough approximation of distance to the beacon. This may seem pretty broad, but in practical terms, these buckets are about as accurate as you can be on Bluetooth LE range without a bunch of fancy tricks.
  • accuracy - An approximation of horizontal accuracy (in meters). Again, this isn’t super useful in an “absolute” sense because it does fluctuate. However, if you need to decide which is the closer of two beacons in the same proximity bucket, then this might cast the deciding vote.
  • rssi - RSSI is shorthand for “received signal strength” which is a very common concept in radio transmission. It will show you the power level (in dB) of the received strength. These are typically always negative, with -35 or so being a very strong signal (immediate) and losing a lot of accuracy and predictability at around -80-90dB where things really start to fall off. RSSI and propagation of 2.4ghz radio is a very interesting study, but beyond the scope of this tutorial. The folks at IoT Design Shop spend a ton of time tuning BLE radios, so if you have questions, please contact us.

Beacon Region Zones

 

You can set up some very interesting behaviour to trigger inside the particular proximities and use ranging to do very clever things. In our source code for the BeaconDemo project, we have added a call to NSLog to dump out these ranging transitions to the debugger console log. It can be very interesting to observe how they are handled, particularly where a beacon straddles the edge of a boundary or edge of the reception range altogether.  We’ll talk about that next.

 

Practical Considerations for iBeacon Apps

So far, we’ve given a lot of good news about how Core Location was designed and built to make our life easy when tracking beacons. Apple did a great job of integrating beacons alongside other Core Location features. 

However, there is some bad news - interacting with the physical world is complex!

Many of us that are interested in iBeacon come from the software and App world, where there are some strange things on occasion but typically it’s a workaround or a few lines of code to fix. In the case of physical limitations like radio interference, propagation, and power measurements, the workarounds aren’t quite as simple!

At IoT Design Shop, we spend a lot of time tweaking and tuning antennas, bluetooth firmware, and the interaction with smartphones, their BLE antennas and SDK’s. We have solutions for a lot of problems, advice for some of the others, and then there are simply some problems where physics wins!

We’re going to give you some of the most important points to consider for real-world deployment, and hopefully this will help you to understand the limits, or compromises you may need to make to ensure you have the best possible user experience.

Fundamental Issues

  • The 2.4 ghz band is busy - Bluetooth shares the 2.4 ghz band with a lot of other modern wireless devices like WiFi, Cordless Phones, Remote Controls, and more. Although frequency-hopping and channel selection are automated, interference is still a factor.
  • Bluetooth Low Energy is “Low Energy” - Amongst other devices that may be transmitting, BLE is probably the “quietest” in the sense that it was designed for efficiency and to sip power. This means that it doesn’t have a lot of power to propagate through obstructions like walls and floors the same way that some higher power radios do.
  • Reflection and Scatter Complicate Signal Strength Readings - An indoor environment with lots of objects, including metal and reflective surfaces will scatter and reflect BLE signals. This may manifest in different ways. You could be close to a beacon and see a lower signal than you expect if something is in the way, or far from a beacon and see a stronger signal due to reflections.
  • A Single Point Source is Hard to Range - Most of the time, when looking at proximity and accuracy, we’re looking at a single point source (a beacon) and trying to make some intelligent decisions about how far away we are. This is hard – with a single source, all of the previous bullets (interference, power, and reflection) all contribute to some degree of inaccuracy. Sometimes things are pretty good, sometimes they are not.

Practical Approach

Back to the good news – we’ve got all the bad news out of the way for now!

While there are challenges, there are also some really practical and exciting things you can do with beacons! If you understand the limits and technology, you can design really cool beacon-enabled Apps that delight users.

Here are some rules of thumb that we use when thinking about BLE and beacon strategies:

  •  Don’t Design Features That Require Precise Distance - The Far, Near, and Immediate buckets are pretty reliable. Knowing that you’re 14 feet from a beacon is not nearly as easy to ascertain in a consistent manner. If you design your features around the loose proximity buckets, you will be able to deliver a more consistent user experience.
  • Account for “Noise” and Some Uncertainty - The variation in radiation patterns of Bluetooth Low Energy will generate some uncertainty on the device. You may see oscillations in proximity between CLProximityNear and CLProximityFar during ranging, and you may see CLRegions pop in and out of range if you’re on the fringe of the BLE broadcast range. This is a reflection of the physical realities of the Bluetooth Low Energy signals in the environment. It’s a bad idea to tie a user notification or something interruptive directly to a state change, because if there is some noise, it may generate repetitive events. (Many demos, including ours, do this for the sake of simplicity, but it’s not an ideal mechanic for the real world).
  • Entering a Region is More Immediate Than Leaving a Region - This is fundamental to how entry and exit events are triggered for a CLBeaconRegion. Moving from outside to inside a beacon region causes didDidDetermineState: to fire quite quickly (usually) because the device encounters a new signal. However, leaving a region is more tricky to determine – it can either be that you’ve left the region, or that you’re momentarily in a spot with low signal strength. Core Location attempts to buffer this on your behalf so that events are not continuously oscillating between inside/outside a region. However, this means that it takes a while for a CLRegionStateOutside to fire if a device moves out of the range of a beacon. You need to keep this in mind when designing features that respond to leaving a CLBeaconRegion.

The BeaconDemo App

beacon_notification

You’ve made it this far. Thanks for sticking with us!

Next up, we’re going to walk through a simple tutorial App that we’ve provided for our IoT Design Shop Beacon Development Kits. It shows the basics around using Core Location, and the Regioning and Ranging operations we discussed earlier in the post in a hands-on App demo.

First, grab the project source:

download Download the BeaconDemo Project Here.

Walkthrough

The App is incredibly simple – there are two views, along with systems to manage Core Location and the regions you set up with the UI.

 

BeaconDemo_1 The App is based on the master-detail template from Xcode. We wanted to keep things simple and familiar so we can focus on the iBeacon integration instead of UI. We know your Apps will be much more beautiful!You click the + at the top right of the main screen in order to add a beacon region to your notification list (the table is currently empty because we haven’t set up any notifications yet).
BeaconDemo_2 We happen to have our own beacon right in the middle of the IoT Design Shop, with Major ID = 2, and Minor ID = 3. The UUID is the default one used by our IoT Design Shop beacons.We set up a reasonable hello and goodbye message to greet our guests who come in and out of the IoT Design Shop.
BeaconDemo_3 After registering the region, we back out to the Main View, and can see that we’re now tracking one beacon region.In this case, because the beacon is on and right next to me, there is proximity, accuracy, and RSSI displayed for the zone as well.

Go ahead and experiment a bit – note that the App supports the hierarchical notification system we discussed earlier in the article. You can register notifications on UUID, UUID+Major, or UUID+Major+Minor in order to build up a hierarchical system of notifications for different scenarios.

You will also note that we are automatically enabling ranging for beacons when we are inside regions – you can see the proximityaccuracy, and RSSI displayed in the cells when you inside of a registered beacon region. It’s interesting to walk around observing how these signals change as you move through your environment.

 

A Closer Look at the Code

Let’s take a look at how this is done – we’ll start with the views and then move through the mechanics of how we manage the beacon regions via Core Location in our App Delegate.

View Controllers

Both the BeaconDemoMasterViewController and BeaconDemoDetailController originally come from the boilerplate code created by Xcode when you create a master-detail application. They’re super simple (and a little less than exciting) so we’ll just cover the highlights here!

BeaconDemoMasterViewController

The master view is basically just a table view that loads BeaconNotificationRegion objects (stored by the App Delegate) into a table.

It has a timer that is scheduled to fire once a second to refresh the cells in the table so that the proximity, accuracy, and RSSI values update on the cells:

-(void)viewWillAppear:(BOOL)animated
{
    // Reload the data in our table in case records were added or changed
    [self.tableView reloadData];

    // We start a timer to update the cells in the view every second
    self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(updateTable:) userInfo:nil repeats:YES];
}

As the cells are loaded, updated values are pulled from the BeaconNotificationRegions by the cellForRowAtIndexPath: method:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    BeaconCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

    // Our app delegate owns all of the notification regions, so we retrieve the record from it
    BeaconDemoAppDelegate* delegate = (BeaconDemoAppDelegate*)[[UIApplication sharedApplication] delegate];

    BeaconNotificationRegion* region = [delegate notificationRegionAtIndex:indexPath.row];

    // Convert beacon proximity to an English description
    if (region.lastState == CLRegionStateInside)
    {
        // We're in the region, so use proximity values from the last ranging
        // operation to update the view
        NSArray* proximityDescriptions = @[ @"Unknown", @"Immediate", @"Near", @"Far"];
        cell.beaconProximity.text = proximityDescriptions[region.lastProximity];
        cell.beaconRange.text = [NSString stringWithFormat:@"%.2fm", region.lastAccuracy];
        cell.beaconRSSI.text = [NSString stringWithFormat:@"%lddB", region.lastRSSI];
    }
    else
    {
        // We're outside the region, let the user know
        cell.beaconProximity.text = @"Not In Range";
        cell.beaconRange.text = @"0.0m";
        cell.beaconRSSI.text = @"0dB";
    }

    // ID labels so we can pick out which cell is which
    cell.beaconUUID.text = region.beaconUUID;
    cell.beaconID.text = [NSString stringWithFormat:@"Major: %@ Minor: %@", region.beaconMajor?[region.beaconMajor stringValue]:@"All", region.beaconMinor?[region.beaconMinor stringValue]:@"All"];
    return cell;
}

We have a bit of code in there to convert enum values to meaningful text, but otherwise, very straight forward code for cataloguing our beacon regions and adding them to our table.

 

BeaconDemoDetailViewController

Once again, this is a very simple view. It has the necessary fields for setting up notifications for a region, but nothing fancy.

One little trick here that we’d like to point out is a technique we use for validating UUID strings. Often, in beacon applications, you must get users or developers to manage (and manually enter UUIDs).  Because the format is specific, and involves entering a bunch of hex values, it is prone to error.

To validate a string as a UUID, you can use the NSUUID initWithUUIDString: initializer. If the NSUUID object is nil after this initialization, the user has entered an invalid string that does not comprise a UUID:

if (textField.text.length)
        {
            NSUUID* valid = [[NSUUID alloc] initWithUUIDString:textField.text];

            if (!valid)
            {
                UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Invalid UUID" message:@"The UUID you entered does not appear to be valid. Please double check it." delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil];
                [alert show];
            }
        }

Unfortunately, this doesn’t help if they’ve made a slight typo on one letter or digit, but it does ensure the format is correct and that they have the right number of bytes to comprise a UUID!

 

BeaconDemoAppDelegate

The App Delegate and our associated data record (called BeaconNotificationRegion) are the workhorses of the application. They manage Core Location along with the messages and responses that we present to the user when they enter and leave our beacon regions. We’ll cover them in depth here.

BeaconNotificationRegion

We’ll get our data representation out of the way first. The BeaconNotificationRegion object takes care of connecting our beacon region identifiers (beaconUUID, beaconMajor, and beaconMinor) with the user messages that are attached (helloMessage, and goodbyeMessage) as well as the state of the region if it is actively being ranged (lastState, lastProximity, lastAccuracy, and lastRSSI).


@interface BeaconNotificationRegion : NSObject

@property (strong, nonatomic) NSString* beaconUUID;         // UUID for beacon in this event
@property (strong, nonatomic) NSNumber* beaconMajor;        // Major ID of beacon in this event (optional)
@property (strong, nonatomic) NSNumber* beaconMinor;        // Minor ID of beacon in this event (optional)

@property (strong, nonatomic) NSString* helloMessage;       // Message to display when user enters the beacon region
@property (strong, nonatomic) NSString* goodbyeMessage;     // Message to display when user exits the beacon region

@property (assign, nonatomic) CLRegionState lastState;      // State of beacon as of last update

@property (assign, nonatomic) CLProximity lastProximity;            // Proximity of beacon as of last update
@property (assign, nonatomic) CLLocationAccuracy lastAccuracy;      // Accuracy of beacon as of last update
@property (assign, nonatomic) NSInteger lastRSSI;                   // Signal strength of beacon as of last update
@end

BeaconDemoAppDelegate

The BeaconDemoAppDelegate is where the “magic” happens, so to speak. It performs the following functions:

  • Managing Core Location - This includes registering and unregistering CLBeaconRegion’s that correspond to the parameters specified by the users in the UI. The App Delegate is also responsible for managing ranging for regions that the device moves in and out of.
  • Managing Messaging - User messages are dispatched to the App via local notifications (similar to push notifications, but triggered without use of a push notification). The BeaconDemoAppDelegate receives the local notifications if the App is in the foreground and displays them as UIAlertView‘s. In the background, iOS handles the local notification and displays it like all other notifications in the system.
  • Managing the Notification Regions - When the UI adds, changes, or removes NotificationRegion objects, this bookkeeping takes place throughout the BeaconDemoAppDelegate. This allows the object to ensure the regions are managed properly with Core Location as well as to handle saving and loading them to/from persistent storage.

 Core Location Management

The App Delegate manages an instance of the CLLocationManager class in a property called coreLocation. That part is pretty simple, but there are a few subtleties worth noting here.

Core Location Permissions in iOS8

In iOS8, permissions for using Core Location changed to become more granular. You need to ask the user for specific permission to use location services either in the foreground (by calling [CLLocationManager requestWhenInUseAuthorization]) or at all times (by calling [CLLocationManager requestAlwaysAuthorization]).

Because you will often want to use iBeacon ranging to detect beacons passively, requestAlwaysAuthorization is the typical permission level that you need from the user. This will send beacon messages to your App while the App is in the foreground, as well as launching your App to send beacon messages to it when your App is in the background as well! (Yes, this is incredibly powerful….)

To allow these messages to come through foreground, or background, you will need to modify the Background Modes settings for your project to allow for background location updates.

Background Modes

Our Background Mode Settings

 

There is one more trick, however, and it is quite subtle. iOS needs to know what you want to say to the user when justifying your request to access their location at all times. This is provided via the App’s info.plist in a field named NSLocationAlwaysUsageDescription:

always

Our explanation to the user for “Always” access in BeaconDemo

 

With all those steps in place, you’ll find that the rest is pretty easy. However, if you miss the permissions step, you won’t see much as far as beacon state goes if you’re running in iOS8 or newer.

We’ve wrapped all this up inside [NSApplicationManager didFinishLaunchingWithOptions:] as follows:

 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   // Set up Core Location Manager
   self.coreLocation = [[CLLocationManager alloc] init];
   _coreLocation.delegate = self;

   // iOS8+ - Ask for permissions to use location
   if ([_coreLocation respondsToSelector:@selector(requestAlwaysAuthorization)]) {
     [_coreLocation requestAlwaysAuthorization];
   }
   // Fallback for older versions of iOS
   else
   {
     [self loadNotificationRegions];
   }
   ...

 

 

Registering For Beacon Messages

Likewise, adding and removing BeaconNotificationRegion‘s is pretty straight forward. The delegate calls a helper (buildBeaconRegionForNotificationRegion:) that takes a BeaconRegion (our record object) and generates a CLBeaconRegion which is what Core Location understands. Once we’ve got that object, it’s a matter of calling startMonitoringForRegion: and stopMonitoringForRegion: to manage the objects.

The functions are shown below:

-(void)addNotificationRegion:(BeaconNotificationRegion*)newRegion
{
    // Keep track of the region in our array
    [_activeNotificationRegions addObject:newRegion];

    // Set up a region based on the parameters specified by the user
    CLBeaconRegion* region = [self buildBeaconRegionForNotificationRegion:newRegion];
    if(region)
    {
        // Notify on entry if the user specified a "hello" message
        region.notifyOnEntry = (newRegion.helloMessage != nil);

        // Notify on exit if the user specified a "goodbye" message
        region.notifyOnExit = (newRegion.goodbyeMessage != nil);

        // Register the region with core location
        [_coreLocation startMonitoringForRegion:region];
    }

    // Save notification list to persistent storage
    [self saveNotificationRegions];

}

-(void)removeNotificationRegion:(BeaconNotificationRegion *)region
{
    // Remove region from our array
    [_activeNotificationRegions removeObject:region];

    // Attempt to build up a beacon region based on the BeaconNotificationRegion that was passed in
    CLBeaconRegion* beaconRegion = [self buildBeaconRegionForNotificationRegion:region];

    if (beaconRegion)
    {
        // Deregister with Core Location
        [_coreLocation stopMonitoringForRegion:beaconRegion];
    }

    // Save notification list
    [self saveNotificationRegions];

}

It’s important to note that a call to startMonitoringForRegion: registers the App with iOS at a global level. If you have Location Updates enabled in your Background Modes flag (as we do in the BeaconDemo project) this is sufficient for your App to start getting callbacks on region entry/exit whether you are in the foreground or background. You need to call stopMonitoringForRegion: to unregister from these updates.

 

Once we’ve registered the regions, we will start to receive notifications from Core Location as the device moves around in the world. We process those our didDetermineState: handler.

- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
    // Scan our registered notifications to see if this state change
    // has a user message associated with it
    [self sendNotificationsForRegion:(CLBeaconRegion*)region inState:state];

    // Manage beacon ranging
    if (state == CLRegionStateInside)
    {
        // Start ranging the beacon
        [_coreLocation startRangingBeaconsInRegion:(CLBeaconRegion*)region];
    }
    else if (state == CLRegionStateOutside)
    {
        // Stop ranging the beacon
        [_coreLocation stopRangingBeaconsInRegion:(CLBeaconRegion*)region];
    }

}

There are a couple things going on in there. First, we have a parsing and dispatch function (sendNotificationsForRegion:) which handles matching up the events we receive to hello and goodbye messages that were registered via the UI. Second, we call startRangingBeaconsInRegion: when we enter regions, and call stopRangingBeaconsInRegion: when we exit regions. We’ll cover both in more detail below.

Beacon Ranging

Even though this App doesn’t really need ranging, we’ve included it for illustration purposes. You will want to extend this functionality for your own Apps. However, for our demo, it also helps to illustrate some of the variability and challenges you need to consider when using proximity and signal strength related measurements in BLE.

Once you’ve entered a region and the system has called startRangingBeaconsInRegion:, you will start to see didRangeBeacon: callbacks coming through. We’ve included a simple handler that both stores the values in our BeaconNotificationRegion objects as well as logs the outputs to the Xcode console.

Here’s the code:

- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)beaconRegion
{
    for (CLBeacon* beacon in beacons)
    {
        // Update proximity and accuracy for all the beacons we can match in our
        // notification region array. This is used to update the UI primarily in the demo,
        // but might have other applications in a real world App.
        for (BeaconNotificationRegion* region in _activeNotificationRegions)
        {
            NSString* uuidString = [beaconRegion.proximityUUID UUIDString];

            // Check the parameters for a match against the region
            if ([region.beaconUUID isEqualToString:uuidString]
                && [self NSNumberEqual:region.beaconMajor toNSNumber:beaconRegion.major]
                && [self NSNumberEqual:region.beaconMinor toNSNumber:beaconRegion.minor])
            {
                region.lastProximity = beacon.proximity;
                region.lastAccuracy = beacon.accuracy;
                region.lastRSSI = beacon.rssi;
            }
        }

        // This is just for illustration purposes - lets us see the results of ranging in
        // the Xcode debugger.
        NSArray* proximityToString = @[@"Unknown", @"Immediate", @"Near", @"Far"];
        NSLog(@"\tProximity: %@ Accuracy: %f Rssi:%ld", proximityToString[beacon.proximity], beacon.accuracy, beacon.rssi);
    }
}

This is fairly straight forward. One interesting bit is our call to the NSNumberEqual: helper method we included in the class. We implemented the NSNumberEqual: method so that we can deal with the notion of equality between beacon regions where there can be various permutations of UUID, UUID+Major, or UUID+Major+Minor ID’s specified for our regions.

The logging is particularly interesting to watch if you move the beacon around the device a bit, or place it at various points in your room. You should notice that things like the position of your iOS device, body, and other environmental factors influence the values you observe. Remember this as you design your new iBeacon-enabled Apps! It’s critical to account for this uncertainty.

Messaging

Last, but certainly not least, we cover how we handle messages. It’s really important to note that this is not the ideal scheme for handling messages! Why not? Well, as we mentioned before, ideally you would debounce conditions like being on the fringe of a beacon region to avoid spamming the user.

We don’t have a specific domain or application in mind for this demo App, so it doesn’t do anything fancy. However, if you take this code over to a real iBeacon-enabled App, you should definitely consider these behaviours and what frequency is appropriate for triggering messages and if there are any rules you can build to improve user experience.

-(void)sendNotificationsForRegion:(CLBeaconRegion*)beaconRegion inState:(CLRegionState)state
{
    // We've got a region - we need to pattern match against our registered notifications to see
    // if we have a corresponding message for this event
    for (BeaconNotificationRegion* region in _activeNotificationRegions)
    {
        NSString* uuidString = [beaconRegion.proximityUUID UUIDString];

        // Check the parameters for a match against the region
        if ([region.beaconUUID isEqualToString:uuidString]
            && [self NSNumberEqual:region.beaconMajor toNSNumber:beaconRegion.major]
            && [self NSNumberEqual:region.beaconMinor toNSNumber:beaconRegion.minor])
        {

            // Create a local notification, and set up sounds
            UILocalNotification* notification = [[UILocalNotification alloc] init];
            notification.soundName = UILocalNotificationDefaultSoundName;

            // We have a match! Now, is this a hello or goodbye message?
            if (state == CLRegionStateInside && region.lastState != CLRegionStateInside)
            {
                // Do we have a hello message?
                if (region.helloMessage.length)
                {
                    // Send the hello message to the user
                    notification.alertBody = region.helloMessage;
                    [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
                }
            }
            else if (state == CLRegionStateOutside && region.lastState != CLRegionStateOutside)
            {
                if (region.goodbyeMessage.length)
                {
                    // Send the goodbye message to the user
                    notification.alertBody = region.goodbyeMessage;
                    [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
                }
            }

            // Update the region state for the UI to use
            region.lastState = state;

        }
    }

}

Again, the actual mechanics here aren’t too complex. It’s really a series of filters we apply. First, we need to see if our ID’s match the beacon that we’ve just detected. Second, we check to see if we have a message for that state and then issue a UILocalNotification to let the user know what just happened.

You can see how you could extend this logic in a domain specific manner – for example, if it were a retail scenario, maybe you would only alert the user once per hour, or per day when they enter the store. Or, you might filter it much more specifically and alert them once per Major ID region, and so forth. The good news is that it’s relatively easy to add these domain-specific smarts on top of the system that Core Location provides!

Conclusions:

There’s a lot of content in this post. Let’s review the highlights:

  • Core Location has been extended to manage beacons  and events related to moving in and out of range of the beacons. When inside a beacon region, you can enable ranging to get more specific information about beacons in the environment.
  • Bluetooth Low Energy is a physical radio frequency transmission format that operates in the 2.4ghz band. It works well for most applications, but you will experience artifacts and challenges if you try to range precisely on a bluetooth beacon. You need to account for some degree of uncertainty in your design.
  • When building an iBeacon-Enabled App, there are a lot of great tools at your disposal in Core Location. Region state, proximity, and ranging can be used to provide interesting user experiences! Our BeaconDemo project shows a simple implementation of the ideas, and you can extend it to provide a great experience for your specific applications.

That’s it for now – you should have lots of things to experiment with. Have fun, and definitely take some time to understand the various behaviours you see when entering and exiting regions, ranging beacons, and triggering notifications.

If you’ve got questions or ideas, check out the forums and get in touch!

2 thoughts on “Integrating iBeacon With Your Apps – The BeaconDemo Project”

Leave a Reply