With the release of Mavericks, Apple have brought the capabilities of Core Bluetooth in line with those of iOS. In particular Mavericks can now function as a peripheral thanks to the inclusion of the CBPeripheralManager class.
However, Apple have chosen not to include support for iBeacons in Mavericks. iBeacon support on iOS 7 has been implemented as part of the Core Location framework but Core Location on Mavericks doesn’t contain any iBeacon support.
iBeacons are built on top of Core Bluetooth so I wondered whether it would be possible to use a MacBook running Mavericks to create an iBeacon.
iOS as an iBeacon
On iOS we can use a Bluetooth 4 enabled device to create an iBeacon using the following code:
//// BLCAppDelegate.m// Beacon//// Created by Matthew Robinson on 15/09/13.// Copyright (c) 2013 Blended Cocoa. All rights reserved.//#import "BLCAppDelegate.h"#import <CoreLocation/CoreLocation.h>#import <CoreBluetooth/CoreBluetooth.h>@interfaceBLCAppDelegate()<CBPeripheralManagerDelegate>@property(strong,nonatomic)CBPeripheralManager*peripheralManager;@end@implementationBLCAppDelegate-(BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary*)launchOptions{_peripheralManager=[[CBPeripheralManageralloc]initWithDelegate:selfqueue:nil];returnYES;}-(void)peripheralManagerDidUpdateState:(CBPeripheralManager*)peripheral{if(peripheral.state==CBPeripheralManagerStatePoweredOn){NSUUID*proximityUUID=[[NSUUIDalloc]initWithUUIDString:@"A7C4C5FA-A8DD-4BA1-B9A8-A240584F02D3"];CLBeaconRegion*region=[[CLBeaconRegionalloc]initWithProximityUUID:proximityUUIDmajor:1minor:1000identifier:@"com.blendedcocoa.beacon"];NSDictionary*proximityData=[regionperipheralDataWithMeasuredPower:nil];[peripheralstartAdvertising:proximityData];}}-(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager*)peripheralerror:(NSError*)error{NSLog(@"Started advertising");}
Creating an iBeacon on iOS is quite simple. First we create a CLBeaconRegion with the required proximityUUID, major & minor and then use the peripheralDataWithMeasuredPower: method to get an NSDictionary containing the advertising data to be passed to Core Bluetooth.
Advertisement Data
It occurred to me that it may be possible to take this NSDictionary and pass it to a CBPeripheralManager instance on Mavericks.
Using NSKeyedArchiver and NSKeyedUnarchiver it was easy to transfer the NSDictionary to an OS X app running on Mavericks and passing the dictionary to a CBPeripheralManager resulted in an iBeacon that could be detected by an iOS device.
Obviously it isn’t very convenient to create the adverisement data on iOS and then transfer the archived data to an OS X app so the next step was to investigate to see exactly what was being stored in the dictionary.
The dictionary key is the NSString literal kCBAdvDataAppleBeaconKey. The value is an NSData object contaning the proximityUUID, major, minor and the calibrated measured power (in 2’s complement). It wasn’t very hard to take this information and create a class to generate the advertisement dictionary directly on OS X.
//// BLCBeaconAdvertisementData.h// BeaconOSX//// Created by Matthew Robinson on 1/11/2013.// Copyright (c) 2013 Blended Cocoa. All rights reserved.//#import <Foundation/Foundation.h>@interfaceBLCBeaconAdvertisementData : NSObject@property(strong,nonatomic)NSUUID*proximityUUID;@property(assign,nonatomic)uint16_tmajor;@property(assign,nonatomic)uint16_tminor;@property(assign,nonatomic)int8_tmeasuredPower;-(id)initWithProximityUUID:(NSUUID*)proximityUUIDmajor:(uint16_t)majorminor:(uint16_t)minormeasuredPower:(int8_t)power;-(NSDictionary*)beaconAdvertisement;@end
//// BLCBeaconAdvertisementData.m// BeaconOSX//// Created by Matthew Robinson on 1/11/2013.// Copyright (c) 2013 Blended Cocoa. All rights reserved.//#import "BLCBeaconAdvertisementData.h"@implementationBLCBeaconAdvertisementData-(id)initWithProximityUUID:(NSUUID*)proximityUUIDmajor:(uint16_t)majorminor:(uint16_t)minormeasuredPower:(int8_t)power{self=[superinit];if(self){self.proximityUUID=proximityUUID;self.major=major;self.minor=minor;self.measuredPower=power;}returnself;}-(NSDictionary*)beaconAdvertisement{NSString*beaconKey=@"kCBAdvDataAppleBeaconKey";unsignedcharadvertisementBytes[21]={0};[self.proximityUUIDgetUUIDBytes:(unsignedchar*)&advertisementBytes];advertisementBytes[16]=(unsignedchar)(self.major>>8);advertisementBytes[17]=(unsignedchar)(self.major&255);advertisementBytes[18]=(unsignedchar)(self.minor>>8);advertisementBytes[19]=(unsignedchar)(self.minor&255);advertisementBytes[20]=self.measuredPower;NSMutableData*advertisement=[NSMutableDatadataWithBytes:advertisementByteslength:21];return[NSDictionarydictionaryWithObject:advertisementforKey:beaconKey];}@end
Mavericks as an iBeacon
Finally, we are now able to create an iBeacon on OS X using the following code which is similar to the iOS version but uses BLCBeaconAdvertisementData instead of a CLBeaconRegion to create the adverisement.
//// BLCAppDelegate.m// BeaconOSX//// Created by Matthew Robinson on 1/11/2013.// Copyright (c) 2013 Blended Cocoa. All rights reserved.//#import "BLCAppDelegate.h"#import <IOBluetooth/IOBluetooth.h>#import "BLCBeaconAdvertisementData.h"@interfaceBLCAppDelegate()<CBPeripheralManagerDelegate>@property(nonatomic,strong)CBPeripheralManager*manager;@end@implementationBLCAppDelegate-(void)applicationDidFinishLaunching:(NSNotification*)aNotification{_manager=[[CBPeripheralManageralloc]initWithDelegate:selfqueue:nil];}-(void)peripheralManagerDidUpdateState:(CBPeripheralManager*)peripheral{if(peripheral.state==CBPeripheralManagerStatePoweredOn){NSUUID*proximityUUID=[[NSUUIDalloc]initWithUUIDString:@"A6C4C5FA-A8DD-4BA1-B9A8-A240584F02D3"];BLCBeaconAdvertisementData*beaconData=[[BLCBeaconAdvertisementDataalloc]initWithProximityUUID:proximityUUIDmajor:5minor:5000measuredPower:-58];[peripheralstartAdvertising:beaconData.beaconAdvertisement];}}@end
That is all that is needed to create an iBeacon using a Bluetooth 4 enabled Mac running Mavericks. Fortunately, the Core Bluetooth framework on Mavericks understands what to do with the iBeacon advertisement data even though iBeacon support hasn’t been added to Core Location.