iBeacon and Background Service

      No Comments on iBeacon and Background Service

Using iBeacon is pretty straight forward, what I wanted to do is use the service in the background, and send a request to the server every time a user enters/exits a region. I’ll explain how I did it in this small tutorial.

iBeacon

iBeacon is a location technology developed by Apple. It’s main purpose is to provide indoor location to applications, but it’s completely up to you to use it in whatever ways you can imagine. iBeacon is a pretty simple piece of technology, it’s a low powered bluetooth device that transmits a UUID, major and minor identifiers, and that’s it. It’s part of Core Location framework, so be sure to import that 🙂 You can use iBeacon devices on Android also.

The main limitation of iBeacons is that you can only monitor 20 regions per application. To be more specific, you can monitor 20 different UUIDs per application. So technically, if your UUIDs are all the same, and only minor and major identifiers are different, you can monitor 2^32 beacons, which should be enough. Apart from this limitation, there are the standard limitations, if the user denies access to location services for your app, turns off location services, is in airplane mode… If the user turned off background app refresh, you’ll receive beacon updates only when your app is in the foreground. So for our tutorial, make sure all of these are enabled 🙂

There are many shapes and sizes of iBeacon devices, you can even use your iPhone as an iBeacon (but not your Android), I’ll be using a device from Radius Networks for this tutorial.

Server

Server is going to be very simple. It’s going to receive a request with parameters, and write it in the datastore. So why don’t we play a bit with python and Google App Engine. If you don’t have it setup, go ahead, and follow that link to setup your environment. Use the App Engine Launcher to create a new app.

First let’s start by creating our model object, let’s call the class BeaconModel, we’ll use it to store some basic info about the event we want to track.

from google.appengine.ext import db

class BeaconModel(db.Model):
    userId = db.StringProperty()
    deviceId = db.StringProperty()
    regionId = db.StringProperty()
    regionName = db.StringProperty(default='')
    action = db.StringProperty()
    timestamp = db.StringProperty()

So the properties we want to track are userID, deviceID, regionID, regionName, action and timestamp. You can modify this to suit your purpose. You can set the default value for the region name like I did there if you want, but you don’t have to. This is the version 2 of my model object, so I’m setting a default value for that property just to keep the DB neat and tidy.

One more thing we need to do is create the main server class. This is pretty simple to do, and the class will only read the values from the http parameters and save them in the database. Saving an object to a database is as easy as calling .put() method on an object.

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db

import beaconModel

class StarterClass(webapp.RequestHandler):
    def post(self):
        self.saveBeacon(self.request)

    def get(self):
        self.saveBeacon(self.request)

    def saveBeacon(self, request):
        new_item = beaconModel.BeaconModel()
        new_item.userId = request.get('userID')
        new_item.deviceId = request.get('deviceID')
        new_item.regionId = request.get('regionID')
        new_item.regionName = request.get('regionName')
        new_item.action = request.get('action')
        new_item.timestamp = request.get('timestamp')
 
        new_item.put()

app = webapp.WSGIApplication([('/.*', StarterClass)], debug=True)

def main():
    run_wsgi_app(app)

if __name__ == '__main__':
    main()

As you can see, we’re going to handle both POST and GET requests. We’ll just create a method that’s going to extract all the properties from a request, and save them to the object.

You can test the server locally to make sure it works, and then just deploy it using App Engine Launcher. Once you deploy it, do a sanity check and make a request just to make sure the data is being written to the database, for example: http://daibeacontestserver.appspot.com//?userID=testUser&deviceID=testDevice&regionID=test&regionName=Name&action=ranging&timestamp=2014-07-10%2023:42:53%20+0000

Now that we have the server working and out of the way, let’s focus on the iOS App.

App

We’ll create a standard Master-Detail app, and we’ll create another ViewController for adding beacons. We won’t use a model object, because CLBeaconRegion object from CoreLocation framework has all the data that we need, so we’ll just use that. We’ll save the beacons in the array, and save the array in the user defaults (keeping it simple).

If you didn’t do it already, add CoreLocation framework to your app:

ibeacon-frameworks

The next important thing to do is to enable background location updates, so do that next:

ibeacon-bg-modes

We’ll keep most of the logic related to location updates in the AppDelegate. We’ll add a getter to lazy-load a CLLocationManager object, and some utility methods. You’ll want to be able to start/stop monitoring a region, so we’ll add those two methods:

- (void)startMonitoringRegion:(CLBeaconRegion *)region {
    [self.locationManager startMonitoringForRegion:region];
    [self.locationManager startRangingBeaconsInRegion:region];
}

- (void)stopMonitoringRegion:(CLBeaconRegion *)region {
    [self.locationManager stopMonitoringForRegion:region];
    [self.locationManager stopRangingBeaconsInRegion:region];
}

We’ll also add a method to send a request to our server, we discussed this in the ‘Server’ chapter (don’t forget to use percent escapes 🙂 ):

- (void)sendRequestWithRegion:(CLBeaconRegion *)region andAction:(NSString *)action {
    NSString *requestString = [[NSString stringWithFormat:@"http://daibeacontestserver.appspot.com/?userID=testUser&deviceID=testDevice&regionID=%@&regionName=%@&action=%@&timestamp=%@", region.proximityUUID.UUIDString, region.identifier, action, [NSDate date]] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *requestUrl = [NSURL URLWithString:requestString];
    NSURLRequest *request = [NSURLRequest requestWithURL:requestUrl];
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                               if (connectionError) {
                                   NSLog(@"Server request finished with error: %@", connectionError.localizedDescription);
                               }
                           }];
}

And all that’s left to do is to implement some CLLocationManagerDelegate callbacks for entering/exiting region, and for ranging beacons. The most important methods here are didEnterRegion: and didExitRegion: You can cast the CLRegion object to CLBeaconRegion (after checking if it is of that class 🙂 ), and use it to fetch some properties you set up for your beacons (name, uuid…). We’re going to use these two methods to send a request to our server, and to display a local notification. Please note, these two methods will trigger only when a device crosses the region boundary, if you add a beacon to your app, and start monitoring for that beacon region, you would have to walk out of it’s range to trigger those methods (or cover the beacon with some pots and pans like I did 🙂 ) And that’s the heart of the app, we’ll just cover the adding of a beacon in the master table, and the rest of the code is your standard master-detail.

So the NewItemViewController has a protocol that we’ll use as a callback to pass a new CLBeaconRegion item to the delegate when we create one. We have to implement a delegate in the MasterTableViewController, save the new beacon to our array of beacons, and start monitoring for that beacon region.

- (void)newItemViewController:(NewItemViewController *)newItemVC didCreateItem:(CLBeaconRegion *)beacon {
    [newItemVC dismissViewControllerAnimated:YES completion:^{
        if (beacon) {
            [_beacons insertObject:beacon atIndex:0];
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
            [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
            [self saveData];
            [_appDelegate startMonitoringRegion:beacon];
        }
    }];
}

One more thing that we must not forget is to delete the beacon, and stop monitoring for the beacon region, so let’s do that using a standard table editing:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete && _beacons.count > indexPath.row) {
        CLBeaconRegion *beacon = [_beacons objectAtIndex:indexPath.row];
        [_appDelegate stopMonitoringRegion:beacon];
        [_beacons removeObjectAtIndex:indexPath.row];
        [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
        [self saveData];
    }
}

And there you have it, you have an app that monitors a beacon region, and sends a request to your server every time it enters/exits a region.

The Code

You can download all the code from my GitHub account DAiBeaconTestApp. You will also find the server code in there. Use it anyway you want, I hope it will help you in your projects.

Have a nice day 🙂
Dejan.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.