Core Bluetooth and the Beacon Manager App

Welcome to the second tutorial in our Bluetooth Low Energy and Beacon series. This time around we get into some code and help you to understand the IoT Design Shop BeaconManager app. The app uses Core Bluetooth to establish a Bluetooth Low Energy (BLE) connection to the beacon so that you can change configuration settings to match your application.

Objectives:

  1. Understand  the relationship between BLE Devices, Services, and Characteristics.
  2. Understand how these principles map into Core Bluetooth calls and what is required to make a connection and adjust characteristics of services on a BLE Device.
  3. Be able to manage an IoT Design Shop beacon with the UUID, Major/Minor ID, and transmission characteristics that you want on your device.

If you aren’t already familiar with the core concepts of beacons and the feature set of IoT Design Shop beacons, it would be a good idea to review our first tutorial now.

 

What You’ll Need:

Now that we’re getting into it a bit, you’re going to need a few ingredients to try these things out for yourself:

  1. An iOS 8 Device - the iOS Simulator does not have Bluetooth Low Energy simulation. You’re going to need a device from this point forward. All of the iOS8-compatible devices have BLE support, so as long as you’ve got something in that category, you should be all set.
  2. Xcode 6.x - our sample projects use Xcode 6. You can download Xcode from the Apple Developer Portal.  While we’re on the topic, you will, of course, also need to be a registered iOS developer to build an deploy builds to your device.
  3. IoT Design Shop Beacon - You’ll need one of our beacon development kits to provide the “beacon” part of the equation. If you don’t already have one, you can learn more about them here.

If you’re missing the prerequisites, you’ll want to hit the Apple Developer Portal to get signed up and ready to deploy. That process is outside the scope of this tutorial. We are assuming that you’ve got a device and you’re ready to build for it!

 

Building the Project:

First things first – you’ll need to download and unzip our BeaconManager project files.

download  Download the BeaconManager Project

Open up BeaconManager.xcodeproj in Xcode. You should be able to build and deploy it to your device right out of the archive.

Next up, we’ll set the stage for what’s going on a bit.

 

Bluetooth LE Concepts – Devices, Services, and Characteristics:

The BeaconManager App follows a fairly typical pattern for connecting to a BLE device, discovering services, and adjusting characteristics. You will see this pattern repeated frequently in other applications, as it’s intrinsic to the Bluetooth 4.0 spec. We’ll review it here so that you have a fairly thorough understanding of how it works.

The Bluetooth Low Energy Hierarchy

BLE Hierarchy

First things first, we need to get a high level understanding of the BLE hierarchy of Device, Service, and Characteristic (pictured above). It’s fairly straight forward:

  • A connectable BLE device typically provides one or more services. Once you’ve connected to a device, you can query for specific services (by UUID) or for all provided services. Some services are standardized by the Bluetooth SIG and have standard UUID’s, but most are defined by vendors as needed.
  • Each BLE service typically provides one or more characteristics. These characteristics can be read-only, write-only, or read-write depending on the nature of the data. Characteristics are how you get data to and from the device.

There are also some additional constructs available in the BLE spec, but we’re going to keep it very simple here, just covering what we need to manage beacons. If you want to learn about things in greater depth, the Core Bluetooth Programming Guide provided by Apple has a bunch of information as well as jump-off points to the Bluetooth SIG and other interesting information about the Bluetooth 4.0 Spec and the Apple implementation of it.

 Configuration Sequence

beacon_workflow

The logic flow for configuring a beacon is pictured to the left. This sequence is very common amongst BLE device connections as it reflects the BLE spec itself as much as any particular workflow.

Devices, Services, and Characteristics all utilize the same UUID scheme for uniquely identifying themselves in the system. This allows us to streamline the workflow a bit by scanning for devices which implement the IoT Design Shop “Beacon Configuration Service” which has a known UUID.

Likewise, the characteristics of the service are known in advance (because we wrote them) so we can streamline access to read and write operations.

However, it’s worth noting that Bluetooth 4.0 can also be used in a relatively “anonymous” fashion as well. Devices can be interrogated for known services, or queried for all services. Likewise, characteristics can be enumerated. In other words, it’s possible to generate a notion of the provided services and characteristics for a relatively unknown device as well. This process is more expensive, and not the preferred route if you know a fair amount about the device you are connecting to. So, our code in the BeaconManager is relatively streamlined and takes advantage of our advance knowledge that the device is one of our IoT Design Shop Beacons.

Core Bluetooth and the BLEManager Module Implementation:

Time to get back to the code. Now that we’ve covered the core concepts, we can take a look at how they are implemented using Core Bluetooth on iOS.

We carefully localized all of the bluetooth logic in the BeaconManager project to the BLEManager module. This makes it both easier to manage, and easier to study, so we’ll focus most of the commentary on this workhorse class provided in the project. Much of the other code in the project is UI-related, and not nearly as unique to the system.

 Bluetooth Central Manager

The CBCentralManager manages all Core Bluetooth functions for a device acting as a BLE Central (as opposed to a peripheral – which is the beacon in this case).

We initialize our CBCentralManager object in the initWithDelegate: method of the BLEManager class:

// Init function
-(id)initWithDelegate:(id)delegate
{
    if (self = [super init])
    {
        // Hold on to the delegate object
        self.delegate = delegate;

        // Set up our Core Bluetooth central manager
        //  We add the  CBCentralManagerOptionShowPowerAlertKey
        //  so that the OS will automatically
        //  inform the user if they have Bluetooth turned off.
        //  This helps to avoid confusion!
        self.CM = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBCentralManagerOptionShowPowerAlertKey]];

    }
    return self;
}

Nothing super unusual there. We do specify the CBCentralManagerOptionShowPowerAlertKey to true. This tells iOS to pop up a nice dialog reminding the user that their Bluetooth is turned off if they try to launch the App in that state. Obviously, enabling Bluetooth is pretty key for this to work!

We revisit the CBCentralManager object in multiple places. It’s the workhorse of the iOS Core Bluetooth implementation.

 

Scanning for IoT Design Shop Beacons

The next step in the process is to initiate a scan to see if there are any of our beacons nearby. This is facilitated by the fact that we have  added a beacon configuration service to our beacon firmware.

Basically, in addition to beacon information, our hardware broadcasts that it has a service with this UUID, which we define in BLEManager.h for use throughout the App:

#define IOT_BEACON_UUID     @"2173E519-9155-4862-AB64-7953AB146156"

Proper generation of UUID’s ensures that it is extremely unlikely that any other device on the planet should have the same UUID. There is typically a UUID generation tool or script on each platform. On OS X it is called uuidgen and can be run in the shell to generate a unique ID each time it is run.

#pragma mark Beacon Discovery

// Scans for beacons nearby
-(void)startScanningForBeacons
{
    // Only scan if Core Bluetooth is ready. This is important in iOS 8. 
    if (self.CM.state == CBCentralManagerStatePoweredOn)
         {
         // We limit our scan to include only IoT Design Shop
         // beacons (with the IOT_BEACON_UUID service).
         NSArray* scanServices = [NSArray arrayWithObject:[CBUUID UUIDWithString:IOT_BEACON_UUID]];

         // Begin scanning for peripherals - as peripherals are
         // discovered, the system will call
         // centralManager:didDiscoverPeripheral: below in the
         // CBCentralManagerDelegate implementation
         [_CM scanForPeripheralsWithServices:scanServices options:nil];
     }
     else
     {
         NSLog(@"Error - Could not start beacon scan because Core Bluetooth was not ready.");
     }

}

// Stops any scans taking place
-(void)stopScannningForBeacons
{
     // Stop scanning for peripherals
     [self.CM stopScan];
}

The code above shows our functions for managing scanning operations in the BLEManager. It’s fairly straight forward. We use the IOT_BEACON_UUID to our advantage because we know that our IoT Design Shop beacons will always advertise that service. This allows us to hone in on BLE devices which are in range and support our configuration service.

 Asynchronous Calls and Delegate Methods

The other important thing here is to understand that virtually all Core Bluetooth calls are asynchronous. In other words, no immediate action results from the call. Instead, when the call completes (or fails), a delegate object will be notified. This is necessary to abstract all of the communications and delays involved in the BLE stack, and is a great service provided by Core Bluetooth. However, it does complicate the process of understanding the logic flow during BLE exchanges.

In this case, the centralManager:didDiscoverPeripheral: is the delegate method we expect to be called when new devices are discovered. Our handler for it in the BLEManager module is fairly simple – we actually end up dispatching it to our own delegate method for reporting this event to the UI and Application Logic in the App. We’ll cover that later on.

// centralManager:didDiscoverPeripheral is called whenever the
// Core Bluetooth central manager discovers a
// new bluetooth device during a scan. We use this to inform
// our delegate that a peripheral was detected.
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    // Notify the delegate
    [_delegate beaconFound:peripheral advertisingData:advertisementData];
}

One other important thing to know about the scan is that you will get back a full set of discovered devices each time you start a new scan. In other words, if you stop, and then start a scan, it will clear the state and report all visible devices again. This is useful for implementing a “refresh” function. Also, you must remember to clear any list of devices you might have kept in the application layer each time you do a new call to start a scan.

 

Establishing a Connection to a Device

In order to continue the process of interrogating our device for beacon configuration services, we first need to establish a connection. Since our scan was limited to devices which reported that they support our beacon configuration service UUID, we’re fairly confident that we have a compatible device. However, you should still double check, just to make sure nothing is wrong.

#pragma mark Beacon Connections

// Attempt to connect to a specified beacon peripheral
-(void)connectToBeacon:(CBPeripheral*)beaconPeripheral
{
    // Initiate the connection - we don't need any of the optional parameters, so that param is just nil
    [_CM connectPeripheral:beaconPeripheral options:nil];
}

// Disconnect or cancel connection with a beacon
-(void)disconnectFromBeacon:(CBPeripheral*)beaconPeripheral
{
    // Shut down the connection
    [_CM cancelPeripheralConnection:beaconPeripheral];
}

Fairly straight forward once again, we call the connectPeripheral method of the CBManager to attempt to establish a connection to the device. However, you need to remember that this call, like most of the others follows the same asynchronous pattern, where we won’t know the outcome of the connection request until we hear back via the delegate.

The delegate handler is shown below. Again, the BLEManager class mostly dispatches these events to our application layer, but you can see that we need to handle both the success and failure cases:

// centralManager:didConnectPeripharal is called whenever a
// connection is successfully established to a device.
// We hand this message off to the delegate so that it can
// inform the rest of the App where needed.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    // Set this peripheral's delegate to be the BLEManager
    peripheral.delegate = self;

    // Notify the delegate
    [_delegate beaconConnected:peripheral];
}

// centralManager:didFailToConnectPeripheral is called when a
// connection attempt fails for a device.
// We hand this message off to the delegate so that it can
// inform the App. It is appropriate to retry if this occurs
// and you believe a connection should be possible
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    [_delegate beaconConnectFailed:peripheral withError:error];
}

Reading Beacon Data from the Device

Core Bluetooth attempts to populate the absolute minimum amount of data required from the device. This is an important optimization which reduces power consumption significantly both on the iOS side and the BLE device. Basically, data transfer = power consumption, so we want to be as efficient as possible.

The corollary is that the process of reading data from characteristics (even if you know what they are) is a multi step process:

  1. Discover Services – First, we have to enumerate the services on the device. This can be streamlined because we already know the UUID of the service we are looking for.
  2. Discover Characteristics of Services – Next, we have to discover the characteristics of the services that we are interested in reading (or writing).
  3. Read Values for Characteristics – Finally, we read in the values for the characteristics that we want to poll.

Once again, these steps are all asynchronous so we have a cascade of calls and callbacks which advance the state of the discovery and read operations. We’ll discuss these below:

// Attempt to discover beacon configuration services
-(void)discoverBeaconConfigurationServices:(CBPeripheral*)beaconPeripheral
{
    // Initiate discovery of the beacon configuration service.
    // This should succeeed whenever we have a valid
    // connection to an IoT Design Shop beacon, because all of
    // the beacons provide this standard config
    // service in their firmware.
    [beaconPeripheral discoverServices:[NSArray arrayWithObject:[CBUUID UUIDWithString:IOT_BEACON_UUID]]];
}

// peripheral:didDiscoverServices is called to report the
// result of a discoverServices call on a CBPeripheral. It
// differs a little bit from the
// central manager callbacks in that there is not a separate
// callback for failure. So, we handle the success and the
// failure conditions in the single
// callback, and dispatch the results to the delegate
// accordingly
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    if (error || (peripheral.services.count == 0))
    {
        // Failure
        [_delegate beaconServiceDiscoveryFailed:peripheral withError:error];
    }
    else
    {
        // Success
        [_delegate beaconServicesDiscovered:peripheral];

    }
}

Populating Services

Our first pairing of calls/callbacks relates to discovering services. Obviously, we already know (or believe) that our device provides IoT Design Shop Beacon Configuration services because it reported that it did in our scan. However, to get Core Bluetooth to properly populate the services array for a peripheral (CBPeripheral), we need to discover services that we are interested in. You can view the list of discovered services for a CBPeripheral object only after discovery is complete.

Note: As the comments indicate above, CBPeripheral differs from CBCentralManager in that there are not explicit callbacks for success/failure conditions. You must check for errors and have a conditional inside the peripheral:didDiscoverServices: method as indicated above.

Populating Characteristics

Next step – hopefully we’ve successfully retrieved our services (we really should have if we’ve connected to an IoT Design Shop Beacon). So, we move forward to discovering and loading characteristics:

-(void)loadBeaconConfigurationData:(CBPeripheral *)beaconPeripheral
{
    // We're going to ask the peripheral to load all of the
    // characteristics for the beacon service.
    // Because we know this service isn't too heavy, and we
    // want all charactersitics it's ok to do this.
    // Typically, you would request a subset of the
    // characteristics that you are interested in.
    CBUUID* beaconUUID = [CBUUID UUIDWithString:IOT_BEACON_UUID];
    for (CBService* service in beaconPeripheral.services)
    {
        // Important note here - you can't use equivalence on
        // CBUUID's pointers. You need to compare their
        // data bytes.
        if ([service.UUID.data isEqualToData:beaconUUID.data])
        {
            [beaconPeripheral discoverCharacteristics:nil forService:service];
            break;
        }
    }
}

In this case, we’ve taken one liberty worth discussing so that you understand the subtle difference between this call and what we’ve been doing previously.

Specifically, our call to [beaconPeripheral discoverCharacteristics: forService:] passes nil in for the criteria. Effectively, this asks Core Bluetooth to retrieve all the characteristics for the specified service. We did this partially as an example, and partially as a shortcut, because we know that our configuration service only has a few characteristics, all of which are needed for the App.

In the “real world” you might not want to do this in general. Unknown services could have lots of characteristics and might return a bunch of data you don’t really need thereby consuming extra power and bandwidth.

Also, because we know that we want to read in all of the values found in our configuration characteristics, our callback takes the liberty of kicking a request to load the values as they are discovered:

// peripheral:didDiscoverCharacteristicsForService is called
// when Core Bluetooth has finished retrieving the GATT
// characteristics for a specified service. In this particular
// case, we report back only if we fail to enumerate the
// characteristics. If we are successful,
// we forge ahead and ask the peripheral to load the values of
// all of the characteristics on our behalf.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    if (error || (service.characteristics.count == 0))
    {
        // Failure
        [_delegate beaconCharacteristicReadFailed:peripheral withError:error];
    }
    else
    {
        // Ask the peripheral to pull down the data for the characteristics
        for (CBCharacteristic* characteristic in service.characteristics)
        {
            [peripheral readValueForCharacteristic:characteristic];
        }

    }
}

Again, the asynchronous nature of these calls makes things a bit more convoluted to understand, but the system works well for handling the nature of the communications and minimizing the amount of data requested and power consumed.

Reading Characteristic Values

The final step is to wait for the actual data values to be read from the device and reported back to the App. These take the form of one final method which is implemented by the delegate:

// peripheral:didUpdateValueForCharacteristic is called when
// the request to retrieve the value of a characteristic is
// complete. This is the end of the road for our "read"
// sequence of parameters from our device. This method will
// fire multiple times - once for each characteristic
// we read.
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    if (error)
    {
        // Failure
        [_delegate beaconCharacteristicReadFailed:peripheral withError:error];
    }
    else
    {
        // Success
        [_delegate beaconCharacteristicRead:characteristic];
    }
}

Once again, we are forwarding these events back to the Application layer for the most part. This is used by the App to determine when all the values have been read in, and triggers a UI transition which allows the user to start editing the values in the App.

 

Writing Beacon Data to the Device

We’re through the heavy lifting now. Writing characteristic values back to the device looks very similar to the pattern for reading data. Here’s our call for writing characteristics back, and it’s corresponding callback in the delegate:

-(void)writeBeaconCharacteristic:(CBCharacteristic*)beaconCharacteristic data:(NSData*)data
{
    // Very straight forward - we ask to write the data with a
    // response. This means that our
    // peripheral:didWriteValueForCharacteristic:error:
    // will be called with the result of the data write
    // operation.
    [beaconCharacteristic.service.peripheral writeValue:data forCharacteristic:beaconCharacteristic type:CBCharacteristicWriteWithResponse];
}

// peripheral:didWriteValueForCharacteristic is called when the
// request to write a value into a characteristic is complete.
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    if (error)
    {
        // Failure
        [_delegate beaconCharacteristicWriteFailed:characteristic withError:error];
    }
    else
    {
        // Success
        [_delegate beaconCharacteristicWriteComplete:characteristic];
    }
}

Fairly self explanatory. As with the other delegate events, we forward our message onto the Application layer for a reaction.

That wraps up our BLEManager walk-through. We hope it was helpful. You may want to use BLEManager as a foundation for some of your future project, which is why it is written in a relatively abstract manner with most of the calls simply being forwarded via delegate to the application layer.

 

Other Notable Code

So, there are a few interesting tidbits on the Application side as well. We’ll cover them briefly here, drawing attention to the highlights.

BeaconManagerAppDelegate

The BeaconManagerAppDelegate module implements the methods of the BLEManagerDelegate which means that it’s the central hub for all of those messages we were gleefully handing off “to the Application layer” in the previous sections.

There’s good news, and bad news. In reality, BeaconManagerAppDelegate also doesn’t do a heck of a lot with those messages. Instead, it routes them through the NSNotificationCenter in order to make them globally available to the views and other operations that might need to know when they occur.

Here’s an example of a couple of the handlers:

-(void)beaconFound:(CBPeripheral*)peripheral  advertisingData:(NSDictionary*)adData
{
    // This method is called whenever beacons are detected
    // during a scan. Because various parts of our
    // UI use this information, we are dispatching it via the
    // Notification Center to allow various
    // other objects to subscribe to these events.
    [[NSNotificationCenter defaultCenter] postNotificationName:@"beaconFound" object:peripheral userInfo:adData];
}

-(void)beaconConnected:(CBPeripheral *)peripheral
{
    // This method is called when a successful connection is
    // established to a beacon. Because various parts of the UI
    // may use this information, we dispatch it via the
    // Notification Center
    [[NSNotificationCenter defaultCenter] postNotificationName:@"beaconConnected" object:peripheral];
}

Various parts of the UI subscribe and unsubscribe from these notifications as they gain/leave focus throughout the App. Keep an eye out, as you’ll see which objects register for which events. This isn’t the only way to do this, but it does decouple the BLE events and management and allows various objects to react to the events at appropriate times without having to presuppose all of those possible situations.

 View Controllers and UI

The BeaconManagerMasterViewController and BeaconManagerDetailViewController deal with the process of discovering beacons, and editing their parameters, respectively.

BeaconManagerMasterViewController starts a BLE scan every time it gains focus, and enumerates the results in a Table View.

BeaconManagerDetailViewController drills into the characteristics of a single BLE device, managing the process of connecting to the device, displaying it’s current settings, writing to the device, and ultimately closing the connection when complete.

Both are interesting, but not particularly unusual from a typical iOS user experience pattern. Please read through the classes and understand the flow.

 

 Managing IoT Design Shop Beacons:

Our final section is a bit of a primer for what you can do with all this. Once again, if it’s not fresh in your mind, it would be a good idea to review our first tutorial now to restore your memory on the settings and what we can control on the beacons.

The characteristics provided by the IoT Design Shop Beacon Configuration Service are as follows:

 

Beacon UUID Characteristic (Read/Write)
2173E519-9155-4862-AB64-7953AB146157

This is a 16-byte value which can be used to set the UUID of the beacon in the advertising header.

 

Beacon Major/Minor ID Characteristic (Read/Write)
2173E519-9155-4862-AB64-7953AB146158

Two 16-bit values (4 bytes total, or two short integers) are written to control Major ID and Minor ID of the beacon respectively.

 

Beacon Transmit Settings Characteristic (Read/Write)
2173E519-9155-4862-AB64-7953AB146159

Two 16-bit values (4 bytes total, or two short integers) are written to control transmit power and transmit interval respectively.

Valid values for transmit power are:

’2′ = 0dB transmit power. This is the most powerful signal strength available.
’1′ = -6dB transmit power. Medium power setting
’0′ = -23dB transmit power. Low power setting

Reducing transmission power to the minimum required for an application will improve battery life in your beacon at the expense of decreasing the effective range at which mobile devices can detect the beacon.

Setting the transmission interval:

BLE radios alternate between periods of transmission and dormancy. This interval is called the transmission interval in our system. If you decrease the interval, the radio will operate more frequently as there is less “down time” between bursts. This will increase power consumption, but may make it quicker for devices to detect when they enter the range of a beacon.

This value is a 16-bit (short int) value which specifies the number of 625ns ticks to wait between advertising bursts. For example:

’160′ = (160 * .625) = 100msec interval. This means the beacon will broadcast 10 times per second.

’1600′ = (1600 * .625) = 1000msec interval. This means the beacon will broadcast 1 time per second.

Setting this value too low is likely to case the BLE subsystem to fail. We recommend 100msec as the minimum value you would choose. In real world applications, advertising once per second or once per 5 seconds is usually more than enough, and will drastically increase your battery life on your beacons.

 

Configuration Process

The Bluetooth Manager App gives a pretty good overview of how we imagine a typical beacon configuration process to work. Usually, you would have a scheme for how you wanted to lay out your beacon(s) and would go into the environment with the App in hand. To keep things simple, turn on one at a time, configure, and then observe.

You can create new Apps and systems which mange the beacons by manipulating the characteristics directly. Just be careful to ensure you are writing valid data to the fields, as the system on the beacons is very trustworthy! It will take the provided data and write it to flash RAM regardless of whether it’s logical or valid.

The address of the configuration services is immutable, so even if you make a mistake, you should always be able to connect to the beacon and adjust any errors you may have made when setting the parameters.

 

Conclusions:

That wraps things up as far as configuration goes. Our next tutorial shows a sample Application which does some interesting stuff with Core Location and beacon proximity events. Thanks for tuning in and using IoT Design Shop products.

 

 

 

 

2 thoughts on “Core Bluetooth and the Beacon Manager App”

  1. This is a great reference project for learning how to use NSNotifications to separate Bluetooth functionality from views. I do have one problem, though. When I compile and run the application on my iPod Touch, 5th generation, with Bluetooth turned on, I get the following API MISUSE violation:

    2014-10-16 13:51:47.203 BeaconManager[1057:60b] CoreBluetooth[API MISUSE] can only accept commands while in the powered on state

    This error comes up when the following line of code is called in the startScanningForBeacons function.
    [_CM scanForPeripheralsWithServices:scanServices options:nil];

    Even so, the scan starts and I can use the application to discover bluetooth LE devices. My concern is that if this project is used as a launching point for a new app, that app will not pass muster with Apple if there is an API MISUSE violation.

    Has anyone worked around this issue?

    Thank you,
    -Ted

Leave a Reply