Multipeer Networking and You

The goal of this prototype is to get two devices communicating via multipeer networking. If Apple's done their job right (Framework development + documentation) it should be easy, right?

Once you wrap your head around how the whole system is implemented then yes, it actually is fairly easy to get multiple devices communicating.

First off, let's lay our our user story/use case:

Two devices are connected via multipeer, each one has a button (which is activated when a connection is secured) which will increment the counter on the other device.  

Apple's Multipeer Framework page is a bit light but it does lay out the general pieces that we need.

  • Session objects handle the communications.
  • Advertiser objects tells others they're available.
  • Browser objects browse for advertised devices.
  • Peer ID's allow for unique identification.

One of the interesting things about the multipeer networks is that unlike a traditional client/server network each node could be advertising and discovering at the same time.

This lead me to one concept that I had to wrap my head around before I could really grok this concept: It's the browsing device that invites the advertiser to a session.

Initially, I was thinking of the advertiser as more of a server and the browser as a client. Wrong, wrong, wrong. Thinking like that made it even more difficult for me to really understand what was going on under the hood.

Let's describe the workflow:
- You declare a service identifier (just an NSString constant) so your app knows what to advertise/browse. - The app starts both advertising and browsing for that service at the same time. - If the app finds a peer, create a session and invite that peer to it. - If the app receives an invitation to a session, accept it. - Stop advertising or discovering if either of these things happens, restart is the peer is lost. - Start sharing that sweet, sweet data.

As a warning, it's what my app's implementing is an exceptionally simplified version of what this framework can do. It's assuming that there are only two peers that always want to connect to each other.

  • The app should be alerting users when you receive a peering invitation, allow them to decline.
  • You probably keep a list of found peers stored so that you can act when the peer is lost.

In true Apple Framework fashion, there's the 'Simple to implement but boring looking' Apple standard and the 'Handle everything yourself with base classes' approach. A lot of the examples out there is using the Apple way, which is to use MCAdvertiserAssistant to handle the advertising and MCBrowserViewController to browse for available peers.

The issue with each of these is that I was hoping to implement more of a SpaceTeam-esque method of matchmaking so I realized that I would have to roll my own.

Code - ASWASWLobbyInitViewController.m

Everything is contained inside one view controller.

Imports + Interface + Implementation stuff

@import MultipeerConnectivity;

static NSString * const HotColdServiceType = @"hotcold-service";

@interface ASWLobbyInitViewController () <MCNearbyServiceAdvertiserDelegate, MCSessionDelegate, MCNearbyServiceBrowserDelegate>

@property IBOutlet UILabel *myDeviceName;
@property IBOutlet UILabel *theirDeviceName;
@property IBOutlet UILabel *theirButtonCounter;
@property IBOutlet UIButton *incrementCounterButton;

@property MCSession *session;
@property MCNearbyServiceAdvertiser *advertiser;
@property MCNearbyServiceBrowser *browser;
@property MCPeerID *localPeerID;
@property NSMutableArray *connectedPeers;


@implementation ASWLobbyInitViewController {
    NSNumber *buttonCounter;

HotColdServiceType is simply a string we'll be using later as the service identifier.

We declare ourselves to be able to handle MCNearbyServiceAdvertiserDelegate, MCSessionDelegate and MCNearbyServiceBrowserDelegatedelegate messages.

We have our outlets for our our labels and a few private properties to keep instances of our session, advertiser, browser, peerID and an array of connected peers.

One private variable is an NSNumber of the button counter. It's the value we'll be sending across.


- (void)viewDidLoad {
    [super viewDidLoad];

    self.localPeerID = [[MCPeerID alloc] initWithDisplayName:[[UIDevice currentDevice] name]];

    self.myDeviceName.text = @"";
    self.theirDeviceName.text = @"";
    self.theirButtonCounter.text = @"0 times";
    self.incrementCounterButton.enabled = NO;
    buttonCounter = [[NSNumber alloc] initWithInt:0];
    self.connectedPeers = [[NSMutableArray alloc] init];

    // Browser for others
    self.browser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.localPeerID
    self.browser.delegate = self;

    // Advertise to others
    self.advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.localPeerID
    self.advertiser.delegate = self;


Right now, I'm using the device name as the device's Peer ID. In the future I think I'll be generating something a bit more fun.

After that, we set some default values and initialize the browser and the advertiser.

We're ready to get this show on the road.


-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    self.myDeviceName.text = self.localPeerID.displayName;

    [self.browser startBrowsingForPeers];
    [self.advertiser startAdvertisingPeer];

We set our display name on the label and start both browsing and advertising. From here things get event based.


-(void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID
      withContext:(NSData *)context
invitationHandler:(void (^)(BOOL, MCSession *))invitationHandler {  
    // Creates a session anytime someone connects using the service.
    NSLog(@"Received Invitation from %@", peerID.displayName);

    if (!self.session) {
        self.session = [[MCSession alloc] initWithPeer:self.localPeerID
        self.session.delegate = self;
        invitationHandler(YES, self.session);

        [self.advertiser stopAdvertisingPeer];
        [self.browser stopBrowsingForPeers];


When a browsing peer sends you an invitation, this method will fire.

Locking it down with an if (!self.session) means that we won't respond if we've already connected to a session. Since we're purposefully locking this down to 2 peers, we're good.

When we initialize session property we pass it our local peer id this caused me a great deal of confusion. I assumed that you create your session with the peerID value being passed in by your peer.

The docs are actually pretty clear about this:

Create an MCPeerID object that represents the local peer, and use it to initialize the session object.  

After setting the delegate for the session to self, we then have to call the handler block with a true boolean and the session object as parameters and then stop both advertising and browsing.


-(void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info {
    NSLog(@"FOUND PEER %@", peerID.displayName);

    if (!self.session){
        self.session = [[MCSession alloc] initWithPeer:self.localPeerID
        self.session.delegate = self;

        [browser invitePeer:peerID toSession:self.session withContext:nil timeout:5];

        [self.advertiser stopAdvertisingPeer];
        [self.browser stopBrowsingForPeers];

-(void)browser:(MCNearbyServiceBrowser *)browser lostPeer:(MCPeerID *)peerID {
    NSLog(@"LOST PEER %@", peerID);

    // Kill the session
    self.session = nil;

    // Start looking again.
    [self.advertiser startAdvertisingPeer];
    [self.browser startBrowsingForPeers];

On the browsing side, we're simply looking for any advertised services, inviting them to a session (Notice that on this end we're also initializing it with our local session ID) setting our delegate, and stopping both advertising and browsing once one is found.

Now, if we lose the peer after finding it, we're simply restarting the advertising and browsing services.

#pragma mark - MCSessionDelegate

-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {
    NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"Message received: %@", message);
    dispatch_async(dispatch_get_main_queue(),^ {
        self.theirButtonCounter.text = message;

-(void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream
      withName:(NSString *)streamName
      fromPeer:(MCPeerID *)peerID {


-(void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName
      fromPeer:(MCPeerID *)peerID
  withProgress:(NSProgress *)progress {

-(void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName
      fromPeer:(MCPeerID *)peerID
         atURL:(NSURL *)localURL
     withError:(NSError *)error {

-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
    NSArray *stateStringRepresentation = @[@"MCSessionStateNotConnected", @"MCSessionStateConnecting", @"MCSessionStateConnected" ];

    NSLog(@"SESSION STATE CHANGE: %@", stateStringRepresentation[state] );

    if (state == MCSessionStateConnected) {
        NSLog(@"Connected to %@", peerID.displayName);

        [self.connectedPeers addObject:peerID];

        dispatch_async(dispatch_get_main_queue(),^ {
            self.theirDeviceName.text = peerID.displayName;
            self.incrementCounterButton.enabled = YES;

        [self.view setNeedsDisplay];

The two important delegate events are didReceiveData and didChangeState.

didReceiveData is simply called when some data is returned from your peer, It's sent as NSData, so you have to change it back to a NSString.

You also see that I'm setting the text on the label in the main thread using the main queue. If you want the UI to be updated immediately you need to make sure that you call those on the main thread.

Once a session has been created, you need to watch for MCSessionStateConnected to be returned in the didChangeState method. This means that you're good to go to begin sending information across.

I'm enabling the button and setting the peer label on the main queue once we're connected.

'Press This' Button Action

- (IBAction)incrementCounterAndSend:(UIButton *)sender {
    buttonCounter = @([buttonCounter intValue] + 1);
    NSString *message = [NSString stringWithFormat:@"%d times", [buttonCounter integerValue]];
    NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
    NSError *error = nil;
    if (![self.session sendData:data
                          error:&error]) {
        NSLog(@"[Error] %@", error);

Pretty simple stuff. Just incrementing the button integer by one, creating a 'X times' string and sending that across the wire.

That's it! You end up with a 2 peer system as soon as you arrive on the view controller. I haven't tested it with more than 2 though.

Now I think I'll rewrite this in Swift.


I've been thinking a lot about matchmaking.

An Example of Greatness

One of the better implementations of this is in the iOS game Space Team. Each step of the matchmaking process reinforces that you and your trusty group of friends/frienemies/co-workers need to work together… as a SpaceTeam.

Now, if I was dissecting the gameplay mechanics I would go into detail about how genius I think having a rotating knob as the start button. Simply: It's a great way to prime on the fact that you'll be using skeuomorphic controls.

Inevitably, there's always going to be at least one person who's first. That person gets shown a "Searching for nearby signals" screen (This also shows you the weird alien that has been randomly generated for this play-through).

"Okay", you think. "This looks like I'm waiting on some other people here. I know (because I'm sitting in a group of people playing SpaceTeam) that I need to wait for at least one other of these people to join before we can play".

"Oh nice, here they are."
"Great, let's get started. I think I'll press this giant button."

"Oh! I only show the transmit-y thing when I hold down the button I guess that I need to hold it until they also hold it and the game knows we're both ready."

The interesting thing about this whole experience is that it's extremely discoverable. You understand that it's a lobby, you understand that one of the crew members must represent yourself, you understand that everyone needs to press and hold the giant ass-button in order to start the game off.

It also helps that the groups of people who play this game are all in a room (usually tipsy, in my experience) and can yell at each other to "HOLD THE READY BUTTON ALREADY I NEED TO CATH A BUS IN 20 MINUTES" (also in my own experience).


It's a simple representation of the important tasks that need completion:

  • We need to get all of the devices talking to each other.
  • We need to limit the number of devices in a session.
  • We need to get everyone to commit to playing.
  • We need to know who's currently playing

You get a graphical representation of the devices being open for communication, you get shown who's already in the lobby, you can discover who you are by pressing the button and seeing which avatar responds, and you can ask around the table to see who everyone else is.

So Now What?

I've been toying around with the idea of multi-peer networking for the Hot & Cold app.

The app is going to be a glorified, expensive and overly complicated game of Hide and Go Seek. I'd like the person using the hiding device to be shown how far the Seek device is.

I could easily get this functionality by simply making both devices transmit as iBeacons (sharing the same UUID but with different major or minor values). Each would broadcast, each would receive and everything could/should work.

The issue comes down to the fact that with this simple implementation you could have an infinite number of Seek devices and an infinite number of Hide devices. If multiple Hide devices are broadcasting their beacons then finding individuals using the Seek devices gets nearly impossible.

Not an idea situation. So, easiest solution: Get a matchmaking service in place before we start our game of Hide and Go Seek.

My initial idea was to simply ape the SpaceTeam method of creating a lobby: locking it down after another person joins, randomly generate a major and minor number for both devices to use as identifiers, and then let them press and hold a button to signify when ready. Having people in the same physical location is a definite bonus here.

This is probably going to be my next dev step after verifying/writing the iBeacon setup in my prototype apps. It's quick and dirty, get's me writing against the multi-peer APIs and gets the app closer to my minimum viable prototype.

If I want to expand this out a bit more I can envision a system where the matchmaking service pairs you up either randomly or with a selected person in a larger group of devices in multi-peer range. This might just add a lot of complexity for not a lot of gain for this project, we'll have to see.

Hot&Cold - Prototype Code Review

Aaand I have iBeacons. (v0.1)

All told, I had the iBeacon code up and running in a little less that 2 hours. Unfortunately, those 2 hours were spread over three weeks as my 9 month old son decided to go through a sleep regression. Babies!

Back to the code.

The code itself was simple after reading the API docs. I've broken out the applications into three beautifully designed screens.

The app is using Storyboards.


Honestly, nothing too much of interest here. The app has a couple of segues set up. Each of the two buttons are hooked up to the same IBAction call and depending on which one you select you end up going to the required screen.

- (IBAction)gotoSegue:(UIButton *)sender {
    NSString *segueIdent = @"";
    if ([sender.titleLabel.text isEqualToString:@"Hide"]) {
        segueIdent = @"toSend";
    } else {
        segueIdent = @"toReceive";
    [self performSegueWithIdentifier:segueIdent sender:self];


The implementation of this is almost identical to Apple's AirLocate APLDefaults file


extern NSString *BeaconIdentifier;  
NSString *BeaconIdentifier = @"com.example.ampersand-softworks.HotCold";  

The BeaconIdentifier is simply being stored as a global string constant and is declared in the header file.

-(id) init { 
    self = [super init];
    if (self){
        _supportedProximityUUIDs = @[[[NSUUID alloc] initWithUUIDString:@"D9EED498-BFDB-43C0-8B55-D06BB74C430B"]];
        _defaultPower = @-59;
    return self;

The init method populates an array of supported UUIDs, I simply used the uuidgen command in the terminal to get myself a new one and added it to the list. This is future-proofing the app as well since adding support for a whole new set of beacons is trivial.

+(ASWDefaults*) sharedDefaults {    
    static id sharedDefaults = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedDefaults = [[self alloc] init];
    return sharedDefaults;

In the past I've used the @synchronized in order to thread this. This is my first chance to use GCD and blocks in this manner. I heart blocks.

- (NSUUID *) defaultProximityUUID {
    return _supportedProximityUUIDs[0];

Simply returns the one (and only) uuid that we're using. Again, future proofing.


Simply exposes the stuff that was set up.

extern NSString *BeaconIdentifier;

@interface ASWDefaults : NSObject

+(ASWDefaults *) sharedDefaults;

@property (nonatomic, copy, readonly) NSArray *supportedProximityUUIDs;
@property (nonatomic, copy, readonly) NSUUID *defaultProximityUUID;
@property (nonatomic, copy, readonly) NSNumber *defaultPower;



Let's get our device broadcasting as an iBeacon. I was surprised at just how little code was needed in order to get the beacon broadcasting with the required region information.

@import CoreLocation;
@import CoreBluetooth;

Awwwwww yeah, precompiled header modules. I'm pretty sure the guy giving the talk on modules at WWDC mentioned that all #import calls are mapped to @import behind the scene.

CBPeripheralManager *perhipheralManager = nil;  
CLBeaconRegion *region = nil;

NSDictionary *beaconPerhipheralData;  
NSNumber *power = nil;  

We need an instance of the CBPerhipheralManager and the CLBeaconRegion classes to begin broadcasting.

  • CBPeripheralManager is what actually handles the bluetooth broadcasts.
  • CLBeaconRegion gets fed the uuid, major version, minor version and identifier so the peripheral manager knows what values to throw out into the aether.

The beaconPerhipheralData dictionary and the power variables are simply there to hold setup values.

@interface ASWHideViewController () <CBPeripheralManagerDelegate>

@property NSUUID *uuid;
@property NSNumber *major;
@property NSNumber *minor;

  • We set ourselves up to receive any CBPeripheralManager events that get fired
  • Set up some private variables.
- (void)viewDidLoad {
    [super viewDidLoad];

    self.uuid = [ASWDefaults sharedDefaults].defaultProximityUUID;
    self.major = [NSNumber numberWithShort:0];
    self.minor = [NSNumber numberWithShort:0];
    power = [ASWDefaults sharedDefaults].defaultPower;

    region = [[CLBeaconRegion alloc] initWithProximityUUID:self.uuid
                                                     major:[self.major shortValue]
                                                     minor:[self.minor shortValue]
    beaconPerhipheralData = [region peripheralDataWithMeasuredPower:power];

Inside the viewDidLoad we pull the uuid, major, minor, identifier and power values from the ASWDefaults file and alloc a new instance of the CLBeaconRegion. From there, we store that value in a dictionary for future peripheral manager use.

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    if (!perhipheralManager) {
        perhipheralManager = [[CBPeripheralManager alloc] initWithDelegate:self
                                                                     queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
    } else {
        perhipheralManager.delegate = self;

Lazy load the peripheral manager with a default background thread.

-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
        [perhipheralManager startAdvertising:beaconPerhipheralData];
    } else if (peripheral.state == CBPeripheralManagerStatePoweredOff){
        NSLog(@"Transmission Ceased");
        [perhipheralManager stopAdvertising];

Here's where the magic happens. If bluetooth is turned on we begin broadcasting the iBeacon region information using startAdvertising. Just like that, we have an iBeacon.


The first bit of surprising information that I discovered is that all of the iBeacon receiving code is handed by a Core Location locationManager instance. The minute that you initialize it, the user is asked if they will allow you app to know your location.

It makes sense, you could get someone's location within a centimetre with a combination of an iBeacon and GPS. It was just a bit unexpected that the first time I ran the app.

Right now, the prototype simply shows the distance to the beacon (the value is actually the accuracy value from a CLBeacon).

@interface ASWSeekViewController () <CLLocationManagerDelegate>

@property (weak, nonatomic) IBOutlet UILabel *howClose;

@property CLLocationManager *locationManager;
@property NSMutableDictionary *rangedRegions;


The CLLocationManager does all of the magic and this view controller is it's delegate. Some other properties are simply set up.

- (void)viewDidLoad {
    [super viewDidLoad];

    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;

    self.rangedRegions = [[NSMutableDictionary alloc] init];
    for (NSUUID *uuid in [ASWDefaults sharedDefaults].supportedProximityUUIDs) {
        CLBeaconRegion *region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:[uuid UUIDString]];
        self.rangedRegions[region] = [NSArray array];

The loop here is pretty interesting.

What we're doing is grabbing all of the beacon UUIDs from ASWdefaults file and filling the rangedBeacons array with CLBeaconRegion instances containing those values. This is used in the locationManager:didRangeBeacons and locationManager:startRangingBeaconsInRegion methods.

-(void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    for (CLBeaconRegion *region in self.rangedRegions) {
        [self.locationManager startRangingBeaconsInRegion:region];

This is where the magic happens. Just loop through the CLBeaconRegions in the rangedBeacons dictionary and start listening for events.

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

    if ([beacons count] > 0) {
        // Let's assume we're getting one beacon for now.
        CLBeacon *beacon = beacons[0];
        self.howClose.text = [NSString stringWithFormat:@"%f", beacon.accuracy];
    } else {
      self.howClose.text = @"No Beacons Found";


If a beacon is found we simply show the accuracy value on screen. I do get weird instances when I get -1.000 as the value. It seems to happen if there's a lot of interference.

I'll have to do more testing with the accuracy values to see what can happen.

Final thoughts

  • The app will probably crash right now if you have bluetooth disabled. I'll have to lock down the app at various points to alert the user that it's required. Not too bad.
  • I'll have to figure out the messaging for how the app asks for access to your location. It might be a bit surprising if the user gets the location request since location = GPS (at leas in my mind) and I'm not using that at all.
  • The location manager in ASWSeekViewController assumes that it'll find only one beacon. The rest of the code is future proof but this one is a hack. Need to finish it off.
  • The accuracy value I'm getting back on the beacons returned from locationManager:didRangeBeacons:inRegion method isn't updated too regularly. This is going to be perfectly cromulent for the app's use case since I won't be displaying the distance values directly. But I will be able to use these values to figure out when HOT or COLD will be yelled at the person.
  • Having [locationManager startRangingBeacons] in the viewDidAppear is for the prototype only. Need to better figure out a way handling the activation of the search.
  • Each CLBeacon has a near/far/immediate proximity value available for use. Even though Apple bragged about the iBeacon's "centimetre accuracy" they seem to be recommending you use these values instead.

Words by brett ohland

Three Things

Look at alllll the lists

As I was finishing up my last few weeks at my previous job I ran across a blog post by Jeff Atwood over on Coding Horror.

The crux of his argument was to take all of your fancy systems that you use as a crutch and start the day off figuring out the three things you need to get done today, store that in your brain, and do them. Simple stuff.

When it comes my jobby-job, I feel I'm pretty disciplined at getting things done. Working in agile teams has definitely helped me craft a system where I sit down and figure out what needs to get done on a daily basis and with a team sit down and figure out our weekly goals. So in general, To-Do apps have never been a part of my workflow. It's programming, you have features, bugs and products to ship. Get it done.

In my personal life on the other hand, is a mess. I've tried most of the popular to do apps available for iOS. GTD-style systems for ubiquitous capture start out great for me but I can never get into the habit of setting aside time to prune and digest the list on a regular basis. Generalized buckets of tasks never get crossed off and location specific reminders just depress me every time I cross that imaginary region marker.

So Jeff's idea of a mental, three item to do list seems like it should work for me. Unfortunately, depending on which "mode" my brain is in (work or personal) this mental model will completely fall apart. I personally know that I sometimes have the memory retention of a sugar addled child with ADHD at a shiny object convention. I tend to rely on notebooks or index cards to keep myself organized.

Another issue of mine is the fact that I can be great at getting stuff done for a few days and then will fall back into bad habits, build up a large backlog, feel trapped and just mentally check out of ever getting the car's oil changed. As a real adult with car payments, keys, a family and stuff I should be able to do better.

So I have a system that works at my job, a system that falls apart a lot in life, a lot of opinions, a technical background and a friend who builds web backends.

Guess this is perfect timing to design and build my version of a to-do app.

The elevator pitch:

You open the app in the morning. It asks you for your to do items (less than 5?) and you go about your day. Once a day a notification pops up to remind you of your items (customizable) and at the end of the day if all items are done you get a gold star marker for the day on a calendar. Don't break the chain! Everything is synched to a backend.

I haven't had much time to work on Hot&Cold recently so I've been working with Tim on a plan for this app. We'll see where it goes.

Estimote Beacons

With some free time between projects at my previous job, I pre-ordered a set of Estimote Beacons to get some first-hand experience playing with our magical location aware future.

I ordered the beacons in October (I believe I was in the 2nd round of pre-orders) and they arrived on December 31st. Not too bad considering they were pre-release. The packaging was surprisingly nice, it even included a personalized note (with a corrected spelling mistake) asking for feedback.

Downloading their app I attempted to get the ranging features to work, no matter how many times I force closed, restarted the app, launched the app, cycled bluetooth I could not get the beacons to actually show up in the app.

What ended up fixing the app was fully cycling the power on the phone. For some reason that seemed to restart whatever iBeacon background process that was needed to make it work. Hopefully that bit of info will help someone out there.

Inside of the Estimote App, you can tap on a beacon in the radar and read off some of the properties. My first bit of a shock was that all three of the beacons arrived reporting less that 30% battery power remaining. After 5 months one is reporting 9%, one says 6% and one's completely drained.

The Estimote site says:

Inside each beacon is a non-rechargeable lithium battery that should last up to 2 years depending on beacon configuration. Yes, the Bluetooth is really low energy. You don't have to worry about changing it, because in two years time you'll probably get your hands on our new much more advanced Beacons.

So… thanks? Definitely something to follow up with them on.

Otherwise the beacons are quite nice, they're weather sealed and definitely feel like a quality object and the gecko back will indeed stick anywhere.

Since the beacons follow the iBeacon standard you don't really need to install or use their SDK in order to start working with them, if you want to quickly get up and running you simply need the default UUID that each beacon is programmed with. I'll save you some Googling by just saying it's B9407F30-F5F8-466E-AFF9-25556B57FE6D.

So are they worth it? Honestly if I would have known just how easy it was to get an iOS device broadcasting as a beacon I probably wouldn't have bothered picking up a set just to play around with. Unfortunately the battery issues are going to stop me from actually installing, using or recommending them to any fellow developers or clients.