4

I took several months developing an application based on iBeacons and I'm really frustrated.

The general idea is that when a beacon is detected, the user is notified with information specific to that iBeacon.

The application was designed as follows, all iBeacons have the same UUID, the Major determines the building (Museum, shop...) and the Minor the specific product (pictures, shoes...). So this application may serve multiple clients.

When the application starts, I begin to do monitoring and ranging for region with our UUID. When the app is in foreground all works perfect. But in background or suspended state the problems starts. Ranging is not allowed on background or suspended state.

I know that the app will be launched into the background for about 5 seconds when you enter or exit the region of a beacon. You can here do ranging in the background for this five second period, after which iOS will suspend your app again.

I managed to extend ranging for up to 3 minutes in the background with the technique learned here. I also get an extra callback with notifyEntryStateOnDisplay = YES;

But this is not enough, if a client enters a region with the app in background or suspended state, he will be notified. And during the extra 3 minutes he will be notified if the ranging detects another iBeacon, but when the 3 minutes background task expired, if no region exit is triggered he will not receive any notification again.

Is there no real solution in a scenario like this? I think it is a very common scenario and I'm surprised no way to deal with it.

EDITED: I tried to find a solution to the problem by monitoring two regions as recommended David Young in his response. In order to obtain more events of entry/exit regions.

I added the code I implemented to try to monitor two regions.

but something I have done incorrectly and didRangeBeacons:InRegion: callback is firing every 10 ms when the expected is every second.

On AppDelegate.m, I'm doing the following inside didFinishLaunchingWithOptions:

[self.locationManager startMonitoringForRegion:self.beaconRegion];
        [self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];
        [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
        [self.locationManager startMonitoringForRegion:self.beaconRegion2];
        [self.locationManager stopRangingBeaconsInRegion:self.beaconRegion2];
        [self.locationManager startRangingBeaconsInRegion:self.beaconRegion2];

Then, on didRangeBeacons:InRegion:

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

     if(beacons.count > 0){
          [self.locationManager stopRangingBeaconsInRegion:region];
          for (CLBeacon *beacon in beacons){
             NSLog(@"beacon detected major: %@ minor: %@", beacon.major,beacon.minor);
          }
           [self.locationManager startRangingBeaconsInRegion:region];   
     }

}

When I run the application on the simulator, and there is a beacon of each network in range, the message is displayed on the console approximately every 10 ms.

I suspect that the stop and restart the ranging is breaking the expected callback flow, but when only one region is in the range, callbacks occur every second as expected.

wottle
  • 13,095
  • 4
  • 27
  • 68
Kepa Santos
  • 557
  • 7
  • 21

2 Answers2

7

The problem you describe is common. The easiest approach is to extend background ranging time forever using a background location mode. (This is similar to what the OP has already done, but for an unlimited period of time.)

Two other options:

  1. Get hardware beacons that allow you to tune down the transmitter power (my company's RadBeacon products allow this) so the transmissions do not overlap. This way you get an exit event then a new enter event as you move from beacon to beacon.

  2. Redesign your identifier schene so the major field is dedicated to identifying up to 20 different regions (based on UUID and major 1-20). You then monitor for all of these regions. Your individual beacons can still use the minor however you want and specifically as the key to trigger messaging. When placing your beacons, you make sure that none with overlapping transmissions share the same major. This will ensure a new background entry event as you move from one to the other.

davidgyoung
  • 63,876
  • 14
  • 121
  • 204
  • Can you give me more information about first option? The second one is complicated, because the Major identifies the client, that would change the entire backend and android application. – Kepa Santos May 01 '15 at 11:57
  • a crazy idea, if i would define a fictitious network with the same UUID but with a specific Major and Minor. And I begin to monitoring and ranging to the 2 regions, one with only the UUID and the second with the UUID and the specific Major and Minor. If I distribute fictitious network beacons placing them in strategic places where there may be overlap. I would get extra region enter and exits? – Kepa Santos May 01 '15 at 12:21
  • For the first option, you simply make beacons transmit less far, say 10 meters. As long as your beacons are at least 20 meters apart, you will get exit/entry events as you move between them. Your second proposal in the comment above is a workable variant of the second option in my answer. You just need to deploy additional beacons to make that variant happen. – davidgyoung May 01 '15 at 12:59
  • I'm trying to monitorize two regions, but I noticed something strange. When I was monitoring only one region, "didRangeBeacons" callback was firing every second. But now, with 2 regions, when both of them are in range, is firing approximately every 10 miliseconds. This is the expected behavior? – Kepa Santos May 04 '15 at 12:36
  • `didRangeBeacons:InRegion:` should get called at 1Hz for each ranged region. Even with 10 ranged regions, you would get calls only every 100 ms. Are you sure you aren't seeing this timing inside a loop within that callback method? If so, it is possible that ranging 100 visible beacons simultaneously would give you a a smaller number of callbacks, but with per-beacon processing at a rate of one per 10 ms. – davidgyoung May 04 '15 at 16:44
  • I added the code that is making callbacks occur every 10 ms instead of every second. I can't understand why this is happening, could you take a look? Thanks for your help!! – Kepa Santos May 04 '15 at 18:06
2

Beacon ranging in the background works just fine on iOS 8, provided you jump through all the hoops.

Firstly, you need the correct authorization. Add the key NSLocationAlwaysUsageDescription to your Info.plist, and call

[self.locationManager requestAlwaysAuthorization];

Your app must have a Background Mode of Location Updates. Specify in Info.plist or via the Capabilities (sits parallel alongside Build Settings).

Then check for authorisation in applicationDidBecomeActive with

switch([CLLocationManager authorizationStatus])
{
    ...

Once authorised, you need to do:

self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
// If you don't do this, location updates will pause after 15 minutes in the same place
// Usually this is what you want, so I've left this line commented out
// self.locationManager.pausesLocationUpdatesAutomatically = NO;

// Create a NSUUID with the same UUID as the broadcasting beacon
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:kProximityUUID];

_beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
                                                  identifier:@"uk.co.airsource.testregion"];

// Necessary to get continued background ranging.
// See https://community.estimote.com/hc/en-us/articles/203914068-Is-it-possible-to-use-beacon-ranging-in-the-background-
_locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
[_locationManager startUpdatingLocation];
[_locationManager startMonitoringForRegion:self.beaconRegion];
Airsource Ltd
  • 32,379
  • 13
  • 71
  • 75