Raymond
Raymond

Reputation: 477

How to listen for beacons and just get the closest one

I am wanting to listen for beacons and acquire the one that is closest. I can currently get the UUID however I am having a lot of trouble getting the other data. All of the google search, github search and stack overflow searches show acquiring this information when you have the UUID already (another words, when you have the UUID from the get go). I even tried apples sample code and it can't even find my beacon. (RFduino, there are apps out there though that see it "Particle Detector" is one of them, so I know it can be done.)

The following code shows how I am getting the uuid from a beacon as the user just walks around. While I can run with just the UUID it will not give me which one they are closest too.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;

    if ([_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
        NSLog(@"requestAlwaysAuthorization!!!");
        [_locationManager requestAlwaysAuthorization];
    }

    AIBBeaconRegionAny *beaconRegionAny = [[AIBBeaconRegionAny alloc] initWithIdentifier:@"Any"];
    [self.locationManager startRangingBeaconsInRegion:beaconRegionAny];

    return YES;
}

- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
    NSLog(@"locationManagerDidChangeAuthorizationStatus: %d", status);
}

- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
{
    //NSLog(@"locationManager:%@ didRangeBeacons:%@ inRegion:%@",manager, beacons, region);
          {
              CLBeacon *beacon = [beacons firstObject];
              NSLog(@"Proximity UUID: [%@]", beacon.proximityUUID.UUIDString);

              [[NSNotificationCenter defaultCenter] postNotificationName:@"BeaconFound" object:beacon.proximityUUID.UUIDString];
          }
}

- (void)locationManager:(CLLocationManager *)manager rangingBeaconsDidFailForRegion:(CLBeaconRegion *)region withError:(NSError *)error
{
    NSLog(@"locationManager:%@ rangingBeaconsDidFailForRegion:%@ withError:%@", manager, region, error);

}

I basically want to know what the closest UUID is so I can pass it off to a DB call and am stuck.

Upvotes: 0

Views: 1896

Answers (3)

davidgyoung
davidgyoung

Reputation: 64916

In order to get the closest beacon do something like this:

- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
{
  CLBeacon *closest = nil;
  for (CLBeacon *beacon in beacons) {
    if (closest == nil || closest.accuracy > beacon.accuracy) {
      closest = beacon;
    }
  }
  NSLog(@"The closest beacon is %@", beacon);
}

Upvotes: 1

davidgyoung
davidgyoung

Reputation: 64916

Three issues here:

  1. You cannot read iBeacon identifiers using CoreLocation APIs on iOS (or using any other APIs) unless you know the beacon's ProximityUUID up front. This is a security restriction that Apple has put in place, which does not exist on other platforms like Android, OSX or Linux. Even if you try to read the raw bluetooth packets using CoreBluetooth, you can't do it, because iOS filters out raw advertisement data from advertisements that are iBeacon transmissions. You can read more about this here: http://developer.radiusnetworks.com/2013/10/21/corebluetooth-doesnt-let-you-see-ibeacons.html

  2. In order to read beacon identifiers, you need to use CoreLocation ranging APIs, which you do in your example. But you need to set up a CLBeaconRegion filter first which says what beacon identifiers (e.g. which ProximityUUID) will match your filter. Your code references a class called AIBBeaconRegionAny, but it does not show how this is set up, or what ProximityUUID it is looking for. If this CLBeaconRegion filter does not match your transmitting beacon, you will not get beacons in the callback.

  3. Once you detect beacons, in order to see which is closest, you need to iterate through the beacons array in the locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region callback, and compare the accuracy property of each beacon. The beacon with the lowest accuracy value is the closest.

Be careful when doing #3 because distance estimates for beacons vary quite a bit with each sample. You may see the estimated closest beacon switching back and forth over time.

Upvotes: 2

Caleb
Caleb

Reputation: 124997

In your -locationManager:didRangeBeacons:inRegion: method you're only looking at the first beacon in the array:

CLBeacon *beacon = [beacons firstObject];

and you're not even checking the proximity or accuracy properties. Instead, you should do something like sorting the array based on the beacons' proximity or accuracy (depending on your needs) and use the result to determine the closest beacons.

Upvotes: 1

Related Questions