In this tutorial, we'll demonstrate how to use an iDevice as both a smart iBeacon emitter and an iBeacon observer for iBeacon communication using the programming language Swift, a new programming language for iOS and OS X. For this example, we will pretend that we are shopkeepers trying to send daily deals to customers running the store's app.
The “brain” is an ad server which publishes a deal whenever it detects a new device on the iBeacon's designated channel. When an observer device gets close enough to an iBeacon emitter, it uses the beacon's information to subscribe to the iBeacon communication channel, receive the brain's ad, then leave the channel.
What is iBeacon and How Does It Work?
iBeacon is not a physical device, but rather a bluetooth protocol. It allows a device to transmit a small amount of information to another device once they are in close proximity (20m max) to one another.
Think of iBeacon as a lighthouse. An observer first needs to know in what direction to look. Once an observer can see the light, he/she can determine color of the light and the frequency with which it rotates. He can also roughly determine how near/far they are from the source. However, with both iBeacons and lighthouses, the source and observer cannot communicate any further without an external technology. That's where PubNub comes in. In the example of the lighthouse, a great candidate for communication is a radio. In the example of iBeacons, a great candidate is PubNub.
By default, the only information an iBeacon can send to an observer is a set of two numbers (the major and minor numbers). An observer can also determine its rough proximity (far = greater than 10m away, near = within a few meters, immediate = within a couple centimeters) to a beacon and act accordingly.
The driving idea behind a smart iBeacon and iBeacon communication is that one usually wants a beacon to send an observer more information than this limited set. This is accomplished by using the two numbers provided by an iBeacon with a PubNub channel to allow a device to act as the brains of the beacon. An observer uses the iBeacon's information to subscribe to a PubNub channel to which the “brain” device is already listening. That brain device can detect this subscription and initiate complex communication or trigger almost any sort of event.
Below is video demonstration of the iBeacon communication application we'll be building today in action:
**NOTE: Since we wrote this tutorial, we’ve released a new, completely-redesigned version of our iOS SDK (4.0). We rebuilt the entire codebase and took advantage of new features from Apple, simplifying and streamlining the SDK.
The code from this tutorial still works great, but we recommend checking out our new SDK. We've included a migration guide to help you move from 3.x to 4.0, and a getting started guide to help you create a simple Hello World application in minutes.**
The Brains of the iBeacon: A Simple Ad Server
You’ll first need to sign up for a PubNub account. Once you sign up, you can get your unique PubNub keys in the PubNub Developer Portal.
In this example, the iOS device emitting the iBeacon will also host the ad server “brain” for that iBeacon. However, this code could easily be implemented on an independent machine. This independence is useful when one device needs to handle events for many iBeacons (and in turn many possible channels), or when the emitter device's only capability is emitting an iBeacon communication signal.
Our Brain class requires PNConfiguration and PNChannel objects to setup the communication channel for our iBeacon. The channel name should include the major and minor identification numbers you plan to transmit with your iBeacon. We also will use the serverStatus UILabel to provide updates to the user. Remember to indicate your class is a PNDelegate or else the setDelegate call in the next section will throw an error.
The setup method is called by the UIViewController to trigger the brain to connect to the PubNub service and subscribe to the iBeacon's channel. The caller tells us which label to use for status updates in the upcoming delegate functions. The label must be assigned before the PubNub setup calls because it will be updated by delegate functions as setup occurs. The setup function also receives a major and minor number. These numbers will be broadcast by the emitter with which the brain is associated.
These methods all update the server status label depending on the results of the actions made in the previous section. They also output subscription erros to the console. They are all delegate methods meaning that they will be automatically called when the type of event they describe occurs.
Like the methods above, this method is a delegate method. However, it is called whenever a presence event occurs on the iBeacon's channel. Here, it sends the ad information intended for an observer whenever one joins the channel and ignores all other event types (i.e. a user leaves the channel, a user times out). In this example, the information is the deal of the day's text. However, this could be replaced with a mobile push notification trigger, a link to an ad/deal, or any other action that should happen when a user interacts with a channel.
These methods are once again delegated methods. However, they don't update the status label. They merely log message sends/failures on the console.
If you wanted to get rid of the logging/label updating, your one beacon brain could function with less than 20 lines of code:
And there you have it. A simple working iBeacon brain.
Broadcasting an iBeacon
Moving onto the beacon itself, we simply define the major and minor numbers, set a unique string that our observers will use to find the beacon, make sure that bluetooth is on, and transmit the beacon signal.
In this example, our emitter's UIView uses 6 labels and a button. The labels are used to display the iBeacon's UUID, Major and Minor ID numbers, it's Identity, our beacon's status, and PubNub's status. The button is used to begin the iBeacon's transmission. We also create a Brain object which receives control when an observer is close enough to a beacon. To create the iBeacon, we will utilize the CoreLocation and CoreBluetooth libraries.
in our UIViewController, we define the following variables:
Once again, remember to include the delegate indication when declaring the class. An observer device is only able to find an iBeacon if it knows the beacon's UUID. You can generate a UUID by using the uuidgen command in terminal. Be sure to use the UUID here and in the observer part of this tutorial. You should also remember to create the labels on your view and connect them to the outlet objects you define in this section.
In the viedDidLoad() method, we define the beacon region using the previously defined UUID and the major/minor numbers which will be broadcast by the brain. The updateInterface call will update our UILabels to reflect the region we defined.
Once again, updateInterface simply uses the properties we defined for the region to update the user interface.
This IBAction function hooks up to our transmit button. The first call designates our NSDictionary as the data backing the beacon. The second defines our blutooth manager with the UIViewController class we just wrote as its delegate. Defining the bluetooth manager allows us to determine what happens when a bluetooth related event occurs. The delegate class must have one mandatory method (which is coincidentally the only one we need). One again, remember to connect this function to the button on your view.
This delegate method is called when the device's bluetooth changes state (including when the manager is defined). In addition to updating the beacon status label and outputting some logging messages to consol, it basically advertises the beacon when the device's bluetooth is on and stops advertising when it is off.
Now we've completed the code for the emitter device. Before you test it, remember to make sure your device has an internet connection and that it's bluetooth hardwear is on. Another note is that iBeacons only work on devices equipped with bluetooth 4.0 or higher. In the app, merely wait for the server status to display “ready to transmit” then press the transmit button. Voila, you have a working smart iBeacon emitter.
Observing the Beacon From Another Device
Now that we've made an iBeacon communication emitter, we'll move on to the code running on the observer devices. It follows that the code in this and the next section runs independently from the the code in the previous two sections.
That said, the observer follows a model similar to the emitter. The viewController handles the iBeacon's detection while another class (which we will call the customer) receives control once a beacon's information is harvested. To create the observer, we utilize the CoreLocation and CoreBluetooth libraries.
The UILabels are all used for the purpose of this demo to display information about the iBeacon our observer device detects. Similarly, one of the labels allows the PubNub delegate class (Customer) to update the status the connection to PubNub. It also has a label used to display the deal retreived from the iBeacon emitter. The found label is used to update the status of the observer component of this code (i.e. we've found a beacon, we're looking for one, etc.). We still have our iBeacon libraries and a customer object. We also still have a UUID object – make sure it's the same as the one used in the emitter.
In the viewDidLoad method, we set the viewController as the delegate for the location manager. We also define a beacon region using the identifier and uuid we used with the emitter. We also call the setup method of our customer object (to be detailed in the next section).
Once our connection to PubNub has been setup by the Customer.setup method, we hit the start detection button. The action method it calls begins looking for an iBeacon and updates the monitoring status (found) label accordingly. The if statmenet contains a location authorization request required in iOS 8 and later. However, calling it in an older iOS crashes the program. Thus, we need to check our iOS version and make sure we call it if and only if our device is running iOS 8 or later.
Under normal iBeacon operation, one should only range beacons once one knows they are in a beacon's region. However, the only way to know this is for certain is to enter an iBeacon's region. This is inconvenient because an iBeacon can be detected from up to 20m away. The testing line is placed in the didStartMonitoringForRegion method so you don't have to leave the building every time you want to test your code. The didEnterRegion method begins ranging beacons whenever a user enters an iBeacon region. The monitoringDidFailForRegion prints errors to console. Finally, the didExitRegion method resets the state variables the customer class uses and stops beacon ranging. All of these methods update our status text accordingly.
Our reset method is used when we want to reset the state variables used by the customer and the labels. (i.e. when we leave the beacon's region).
The didRangeBeacons delegate method is called when we have ranged a set of beacons who's regions we are currently occupying. It gives us an array of beacons, but for now we are only looking at one. The first line of the method exists because of our testing call to “startRangingBeacons” so we don't segfault when referencing an empty object. This might occur when we start the app out of range of any beacons. The rest of this method updates the labels to reflect current information about the beacon we are receiving data from. Officially, it can take up to 20 seconds to accurately display beacon information, but in practice it takes about 3. Right now, we hand control over to the customer object when our distance is immediate. It is easier to test the customer object when we reset our state at the “near” distance as opposed to when we leave the beacon's region.
Receiving the Ads (iBeacon Communication)
Our customer class handles the observer's communication with the emitter beacon's brain. Once it receives control from the view controller, it uses the information obtained from the iBeacon to subscribe to the channel which the brain is monitoring. Once it receives a message from the brain, the customer displays the contents of the message, in this case an ad/deal, then unsubscribes.
Our customer class maintains control over two labels. One to display the ad it receives from the iBeacon and the other to send status updates. It also requires some state variables to prevent itself from continuously subscribing to the channel and processing messages while within the threshold range of the iBeacon.
The setup method connects the customer to PubNub and sets itself as the delegate class. It also sets the ad and status update labels.
The getAdOfTheDay method attempts to subscribe to the iBeacon's channel utilizing the major and minor numbers observed by the viewController. However, it makes sure that the observer is connected to PubNub and that it hasn't already attempted to subscribe. If there is no connection when subscribeAttempt is true, it displays an error.
Once the customer is subscribed to the channel, it should almost immediately receive a message from the brain. The didReceiveMessage delegate method is called when said message is received. It outputs the receipt to console and updates the status text. The method checks to make sure it hasn't already received a deal before updating the view. It then unsubscribes from the iBeacon's channel.
These functions merely update the status label and print logging info to the console. The didConnectToOrigin delegate method switches the connected boolean to true, but the other methods could theoretically be removed without affecting the class' functionality.
Between these four classes, you should have a working iBeacon capable of complex communication with its observers. From here, you can modify the communication model to anything from multi-device chat to location based authentication (i.e. unlock a door when in proximity to an iBeacon). Enjoy!