I am trying to understand the working of CLBeacons. I have previously worked with CBPeripherals which is actually the corebluetooth framework implemnetation for BLE devices.
What I did was the following steps:
No beacons appear.
Surprisingly, when I use [NSUUID alloc] initWithUUIDString:@"E2C56DB5-DFFB-48D2-B060-D0F5A71096E0"] as the uuid (which I gathered from the AirLocate project), multiple beacons appear.
As per the docs I've read, the proximity UUID is the beacon's identifier. So why is it not working when I use the passed identifier, and why is it working when I am using a defined identifier?
Code ---- 1. The First Screen
#import "TiBViewController.h"
#import "DEASensorTag.h"
#import "TiBScanTableViewCell.h"
#import "TiBLocateViewController.h"
@interface TiBViewController ()
@property (nonatomic,strong) NSTimer *timer_check;
@end
@implementation TiBViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:YES];
[self performSelector:@selector(initialise) withObject:self afterDelay:2.0];
}
-(void)initialise
{
DEACentralManager *centralManager = [DEACentralManager initSharedServiceWithDelegate:self];
if (centralManager.isScanning == NO)
{
[centralManager startScan];
centralManager.isScanning=YES;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}
if(self.timer_check==nil)
{
self.timer_check = [NSTimer timerWithTimeInterval:20
target:self
selector:@selector(timer_function)
userInfo:nil
repeats:YES];
NSRunLoop *runner = [NSRunLoop currentRunLoop];
[runner addTimer:self.timer_check forMode: NSDefaultRunLoopMode];
}
[self.timer_check fire];
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state)
{
case CBCentralManagerStatePoweredOn:
break;
case CBCentralManagerStatePoweredOff:
break;
case CBCentralManagerStateUnsupported:
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
message:@"Device is Supported"
delegate:nil
cancelButtonTitle:@"Dismiss"
otherButtonTitles:nil];
[alert show];
break;
}
case CBCentralManagerStateResetting:
{
break;
}
case CBCentralManagerStateUnauthorized:
break;
case CBCentralManagerStateUnknown:
break;
default:
break;
}
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
DEACentralManager *centralManager = [DEACentralManager sharedService];
YMSCBPeripheral *yp = [centralManager findPeripheral:peripheral];
yp.delegate = self;
[yp readRSSI];
for (TiBScanTableViewCell *cell in [self.peripheralsTableView visibleCells])
{
if (cell.yperipheral == yp)
{
[cell updateDisplay];
break;
}
}
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
for (TiBScanTableViewCell *cell in [self.peripheralsTableView visibleCells])
{
[cell updateDisplay];
}
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
DEACentralManager *centralManager = [DEACentralManager sharedService];
YMSCBPeripheral *yp = [centralManager findPeripheral:peripheral];
if (yp.isRenderedInViewCell == NO)
{
yp.isRenderedInViewCell = YES;
[self.peripheralsTableView reloadData];
}
if (centralManager.isScanning)
{
// if(advertisementData!=nil)
// {
// NSLog(@" ad is %@",advertisementData);
// }
for (TiBScanTableViewCell *cell in [self.peripheralsTableView visibleCells])
{
if (cell.yperipheral.cbPeripheral == peripheral)
{
if (peripheral.state == CBPeripheralStateDisconnected)
{
cell.rssiLabel.text = [NSString stringWithFormat:@"%ld dB", (long)[RSSI integerValue]];
cell.distanceLabel.text = [NSString stringWithFormat:@" distance : %0.3fm",[self calculateAccuracyWithRSSI:[RSSI doubleValue]]];
}
else
{
continue;
}
}
}
}
}
- (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals {
DEACentralManager *centralManager = [DEACentralManager sharedService];
for (CBPeripheral *peripheral in peripherals)
{
YMSCBPeripheral *yp = [centralManager findPeripheral:peripheral];
if (yp)
{
yp.delegate = self;
}
}
[self.peripheralsTableView reloadData];
}
- (void)centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals
{
DEACentralManager *centralManager = [DEACentralManager sharedService];
for (CBPeripheral *peripheral in peripherals)
{
YMSCBPeripheral *yp = [centralManager findPeripheral:peripheral];
if (yp)
{
yp.delegate = self;
}
}
[self.peripheralsTableView reloadData];
}
#pragma mark - CBPeripheralDelegate Methods
- (void)performUpdateRSSI:(NSArray *)args
{
CBPeripheral *peripheral = args[0];
[peripheral readRSSI];
}
- (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(NSError *)error {
if (error)
{
NSLog(@"ERROR: readRSSI failed, retrying. %@", error.description);
if (peripheral.state == CBPeripheralStateConnected)
{
NSArray *args = @[peripheral];
[self performSelector:@selector(performUpdateRSSI:) withObject:args afterDelay:2.0];
}
return;
}
[self placeRSSI:peripheral];
DEACentralManager *centralManager = [DEACentralManager sharedService];
YMSCBPeripheral *yp = [centralManager findPeripheral:peripheral];
NSArray *args = @[peripheral];
[self performSelector:@selector(performUpdateRSSI:) withObject:args afterDelay:yp.rssiPingPeriod];
}
-(void)placeRSSI:(CBPeripheral *)per
{
for (TiBScanTableViewCell *cell in [self.peripheralsTableView visibleCells])
{
if (cell.yperipheral)
{
if (cell.yperipheral.isConnected)
{
if (cell.yperipheral.cbPeripheral == per)
{
cell.rssiLabel.text = [NSString stringWithFormat:@"%@", per.RSSI];
cell.distanceLabel.text = [NSString stringWithFormat:@" distance : %0.3fm",[self calculateAccuracyWithRSSI:[per.RSSI doubleValue]]];
break;
}
}
}
}
}
#pragma mark UITableView
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
DEACentralManager *centralManager = [DEACentralManager sharedService];
return centralManager.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"ListCell";
DEACentralManager *centralManager = [DEACentralManager sharedService];
YMSCBPeripheral *yp = [centralManager peripheralAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
TiBScanTableViewCell *pcell = (TiBScanTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[pcell configureWithPeripheral:yp];
yp.isRenderedInViewCell = YES;
cell = pcell;
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DEACentralManager *centralManager = [DEACentralManager sharedService];
YMSCBPeripheral *yp = [centralManager peripheralAtIndex:indexPath.row];
TiBLocateViewController *locate = [self.storyboard instantiateViewControllerWithIdentifier:@"Locate"];
locate.cbPeripheral=yp.cbPeripheral;
[self.navigationController pushViewController:locate animated:YES];
}
-(void)timer_function
{
DEACentralManager *centralManager = [DEACentralManager sharedService];
for (TiBScanTableViewCell *cell in [self.peripheralsTableView visibleCells])
{
NSIndexPath *indexPath = [self.peripheralsTableView indexPathForCell:cell];
YMSCBPeripheral *yp = [centralManager peripheralAtIndex:indexPath.row];
[centralManager removePeripheral:yp];
[self.peripheralsTableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
-(void)viewWillDisappear:(BOOL)animated
{
[self.timer_check invalidate];
self.timer_check = nil;
[self timer_function];
DEACentralManager *centralManager = [DEACentralManager sharedService];
[centralManager stopScan];
[super viewWillDisappear:YES];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (double)calculateAccuracyWithRSSI:(double)rssi
{
if (rssi == 0) {
return -1.0;
}
double txPower = -70;
double ratio = rssi*1.0/txPower;
if (ratio < 1.0) {
return pow(ratio,10);
}
else {
double accuracy = (0.89976) * pow(ratio,7.7095) + 0.111;
return accuracy;
}
}
@end
The Second Screen
#import "TiBLocateViewController.h" @import CoreLocation;
@interface TiBLocateViewController ()<CLLocationManagerDelegate>
@property (strong,nonatomic) CLLocationManager *locateManager;
@end
@implementation TiBLocateViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.locateManager = [[CLLocationManager alloc]init];
self.locateManager.delegate=self;
self.locateManager.desiredAccuracy = kCLLocationAccuracyBest;
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:YES];
}
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized)
{
NSUUID *per_uuid = self.cbPeripheral.identifier;
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc]
initWithProximityUUID:per_uuid
identifier:[[NSBundle mainBundle] bundleIdentifier]];
beaconRegion.notifyOnExit=YES;
beaconRegion.notifyOnEntry=YES;
beaconRegion.notifyEntryStateOnDisplay=YES;
[manager startMonitoringForRegion:beaconRegion];
[manager startRangingBeaconsInRegion:beaconRegion];
}
}
-(void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
if ([region isKindOfClass:[CLBeaconRegion class]])
{
[manager startRangingBeaconsInRegion:(CLBeaconRegion *)region];
}
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
if ([region isKindOfClass:[CLBeaconRegion class]])
{
[manager startRangingBeaconsInRegion:(CLBeaconRegion *)region];
}
}
-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
{
for (CLBeacon *beacon in beacons)
{
NSLog(@" beacon %@",beacon);
}
}
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSLog(@" error is %@",error.localizedDescription);
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
if ([region isKindOfClass:[CLBeaconRegion class]])
{
[manager stopRangingBeaconsInRegion:(CLBeaconRegion *)region];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:YES];
[self.locateManager stopMonitoringSignificantLocationChanges];
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
Although iBeacons use Bluetooth Low Energy, they are not used with the Core-Bluetooth library in iOS. They are handled through Core Location.
A beacon region is defined in terms of three parameters - the UUID, the major and the minor.
An iBeacon UUID is different to a Bluetooth MAC address - it is more like a "vendor id". All beacons for a given vendor or owner will have the same UUID - e.g. Acme Dept store will allocate themselves a UUID and assign it to all of their beacons, while Big Bank Corp will allocate a different one and assign it to theirs. They can then use the major and minor to indicate different locations. For example the department store might have major=store number and minor=department number (shoes=1, food=2 and so on). For the bank major might be branch location and minor could be location in a branch (1=teller, 2=ATM, 3=back office).
There are also some "well known" UUIDs in use - You found the AirLocate one for example. Estimote also have one that their beacons ship with by default.
When you want to scan for beacon regions you must specify the UUID as a minimum. You can also optionally specify the major and minor. When a beacon is detected that matches the beacon region for which you are scanning you will receive the notification (either didEnter/ExitRegion or didRangeBeacon depending on what modes you have activated).
Some beacons may additionally implement BLE peripheral services and characteristics for setting their parameters (UUID, major, minor, transmit level) but this is not part of the iBeacon spec so different vendors will have different methods for doing this. To work with these settings you would use Core-Bluetooth.
So, in summary, if you are just dealing with the iBeacon side of things then you don't need to do anything with Core-Bluetooth. All you need is Core-Location.