BLOG

Core Bluetooth for iOS 6

Core Bluetooth was introduced for the first time in iOS 5 to allow iOS devices to consume data provided by devices used in healthcare, sport/fitness, security, automation, entertainment, proximity and so on. In iOS 6.0, this API was extended to allow also iOS devices to serve data.

The Core Bluetooth API is based on the Bluetooth 4.0 Low Energy (LE) specifications. This framework takes care of all the details of the Bluetooth LE standard. However, it is designed to provide developers with the necessary level of control.

Only the latest iOS devices and Macs are compliant with the Bluetooth LE specifications: iPhone 4S, iPhone 5, Mac Mini, the New iPad, MacBook Air, MacBook Pro. Additionally, the iOS 6 iPhone Simulator supports the same standard. This is really useful during the development of your app, because it allows you to test it against the iPhone Simulator with no need to have an extra device.

Involved classes

In the Core Bluetooth (CB) framework, you have 2 major players: Peripheral and Central. The entire framework is designed around these two major components and based on a set of callbacks exchanged between them. Next Fig. 1 highlights the Peripheral, the Central and their relationship.

Figure 1

The Peripheral is the device generating or serving the data. The Central is the device consuming these data. Starting with iOS 6, an iOS device can be both a Peripheral and a Central (never at the same time).

Each of the two components is represented by a class of the Core Bluetooth framework. The Central is represented by the CBCentralManager class, while the Peripheral is represented by the CBPeripheralManager one.

On the Central, a CBPeripheral object represents the corresponding Peripheral that the Central is connected to. Similarly, on the Peripheral, the CBCentral object represents the client the Peripheral connected to.

You can also think of the Peripheral as the device broadcasting the data. It starts advertising to the external world that it has some data. So, it advertises that it can provide services. On the other side, the Central starts to scan the environment looking for some service. If the Central finds the wanted service, then it asks the Peripheral to connect. Once the connection is established, the 2 devices start to exchange data.

Besides the Central and the Peripheral, we have to consider the structured data they exchange. These data are structured in Services. Each Service is then composed by different Characteristics. Characteristics are defined attribute types that contain a single logical value. If you go to http://developer.bluetooth.org, you can find the list of the standardized services and characteristics.

On the Central, services are represented by the CBService class. Each of the services is then composed by characteristics represented by the CBCharacteristic class. Similarly, the services and the characteristics on the Peripheral are represented by the CBMutableService class and the CBMutableCharacteristicclass, respectively. The following Fig. 2 highlights the structure of the classes as explained until now.

Figure 2

The CBUUID and CBATTRequest are 2 helper classes that Apple added to the framework to make easier for the developer to manipulate data. We will see later how to use them.

Usage

Unfortunately, the Apple documentation of the Core Bluetooth framework is currently not complete. Some of the involved classes actually miss the documentation. The only way to understand how this framework works is to watch the 2 WWDC videos on Core Bluetooth and then navigate its header files. So, since I have already done it some time ago, I decided to share this with you.

At iNVASIVECODE, we already developed a few applications using the Core Bluetooth framework. We are really impressed by the quantity of new apps you can start to develop in field of the Internet of Things (IoT). We hope this tutorial is helpful, and encourage you to expand your understanding about Core Bluetooth and other frameworks in one of our intensive iOS training classes or through a specialized consulting session.

Building a Peripheral

Let’s build a full example. You need to have 2 iOS devices. I will show you how to connect 2 iOS devices via Bluetooth and exchange data. Remember to check if your devices are in the above list of the Bluetooth LE compliant devices.

Let’s start with the Peripheral. We need to follow these steps:

  1. Create and start the Peripheral Manager
  2. Setup and publish its services
  3. Advertise the services
  4. Interact with the Central

Create a new Xcode project using the Single-View Application template. Name it BlueServer (use ARC). Once the project is created, add the CoreBluetooth.framework to it. Then, open the ViewController.h file and add the following line:

#import <CoreBluetooth/CoreBluetooth.h>

Make the view controller conform to the CBPeripheralManagerDelegate protocol. Also add this property:

@property (nonatomic, strong) CBPeripheralManager *manager;

In the ViewController.m, add the following line to your viewDidLoad method:

self.manager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];

This line initializes the Peripheral Manager (first item of the above todo list). The first argument sets the delegate (in this case the view controller). The second argument (the queue) is set to nil, because the Peripheral Manager will run on the main thread. If you want to use a different thread to do more sophisticated things, then you need to create a queue and pass it here.

Once the Peripheral Manager is initialized, we need to check immediately its state. This allows to check if the device your application is running on is compliant with the Bluetooth LE standard. You do this implementing the following delegate method (here you can do more sophisticated things and gracefully notify the user in case the device is not compliant with the Bluetooth LE):

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    switch (peripheral.state) {
        case CBPeripheralManagerStatePoweredOn:
            [self setupService];
            break;
        default:
            NSLog(@"Peripheral Manager did change state");
            break;
    }
}

Here, I check the state of the Peripheral. If its state is CBPeripheralManagerStatePoweredOn, then the device is using Bluetooth LE and you are good to go.

Service and Characteristic

The -setupService method is a helper method I created to prepare the service and its characteristics. For this example, I am going to use only one service with one characteristic.

Each service and characteristic must be identified by a unique identifier (UUID). UUIDs can be 16- or 128-bit values. If you are building your client-server (central-peripheral) application, then you need to create your own 128-bit UUIDs. You need to make sure they won’t collide with other existing services provided by other applications maybe closer to your device. If you are building a new device, this is achieved requesting the UUID to the standard committee. If you are building your client-server application (as we are doing now), I suggest you to use the uuidgen command in the Terminal.app. This command generates 128-bit UUIDs. So, open the Terminal.app and generate 2 of them (one for the service and one for the characteristic). Then, you need to add them to the Central and the Peripheral app. For the moment, let’s add the following lines before the implementation of the view controller:

static NSString * const kServiceUUID = @"312700E2-E798-4D5C-8DCF-49908332DF9F";
static NSString * const kCharacteristicUUID = @"FFA28CDE-6525-4489-801C-1C060CAC9767";

Notice that the UUIDs you get from the Terminal app are different than mine. And this is good. So, for this example, just use the UUIDs you obtained with the uuidgen command.

Now, this is the implementation of the -setupService method I used in the previous chunk of code:

- (void)setupService {
    // Creates the characteristic UUID
    CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];

    // Creates the characteristic
    self.customCharacteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

    // Creates the service UUID
    CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];

    // Creates the service and adds the characteristic to it
    self.customService = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];

    // Sets the characteristics for this service
    [self.customService setCharacteristics:@[self.customCharacteristic]];

    // Publishes the service
    [self.peripheralManager addService:self.customService];
} 

First, I create the UUID object for the characteristic using the +UUIDWithString: method. Then, I create the characteristic with that UUID. Notice that I pass nil to the third argument (the value) of the init method. Doing so, I am telling Core Bluetooth that I am going to add the value of the characteristic later. This is usually done, when you want to create the data dynamically. If you already have a static value to transmit, then you can pass it here.

In the same method, the first argument is the UUID previously created. The second argument (the properties) determines how the characteristic value can be used. These are the possible values:

  • CBCharacteristicPropertyBroadcast: permits broadcasts of the characteristic value using a characteristic configuration descriptor. Not allowed for local characteristics.
  • CBCharacteristicPropertyRead: permits reads of the characteristic value.
  • CBCharacteristicPropertyWriteWithoutResponse: permits writes of the characteristic value, without a response.
  • CBCharacteristicPropertyWrite: permits writes of the characteristic value.
  • CBCharacteristicPropertyNotify: permits notifications of the characteristic value, without a response.
  • CBCharacteristicPropertyIndicate: permits indications of the characteristic value.
  • CBCharacteristicPropertyAuthenticatedSignedWrites: permits signed writes of the characteristic value
  • CBCharacteristicPropertyExtendedProperties: if set, additional characteristic properties are defined in the characteristic extended properties descriptor. Not allowed for local characteristics.
  • CBCharacteristicPropertyNotifyEncryptionRequired  : if set, only trusted devices can enable notifications of the characteristic value.
  • CBCharacteristicPropertyIndicateEncryptionRequired: if set, only trusted devices can enable indications of the characteristic value.

The last argument of the same init method is the read, write, and encryption permissions for an attribute. Possible values are:

  • CBAttributePermissionsReadable
  • CBAttributePermissionsWriteable
  • CBAttributePermissionsReadEncryptionRequired
  • CBAttributePermissionsWriteEncryptionRequired

After creating the characteristic, I create the service with again the +UUIDWithString:, passing it the previously defined service UUID string. Finally, I set the characteristic for the service. Remember, each service can contain more characteristics as shown in the next Fig 3.

Figure 3

So, we need to create an array of characteristics and pass it to the service. In this simple case, the array contains only one characteristic.

The last line of code is used to publish the service adding it to the Peripheral Manager. Once done that, the Peripheral Manager notifies its delegate with the method -peripheralManager:didAddService:error:. Here, if there is no error, you can start advertising the service:

- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error {
    if (error == nil) {
        // Starts advertising the service
        [self.peripheralManager startAdvertising:@{ CBAdvertisementDataLocalNameKey : @"ICServer", CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:kServiceUUID]] }];
    }
}

When the Peripheral Manager starts advertising the service, its delegate receives the message -peripheralManagerDidStartAdvertising:error: and when the Central subscribes to the service, the delegate receives -peripheralManager:central:didSubscribeToCharacteristic:. This is the place where you can generate dynamic data for the Central.

Now, to send data to the Central, you need to prepare some chunk of data and then send updateValue:forCharacteristic:onSubscribedCentrals: to the Peripheral.

Building a Central

Now that we have the Peripheral, let’s build our Central (the client). Remember, the Central is the device consuming the data provided by the Peripheral. As I highlighted in Fig. 1, the Central is represented by the CBCentralManager object.

So, let’s create a new Xcode project and name it BlueClient. Use ARC. Add the CoreBluetooth.framework to your target and import it in the view controller header:

#import <CoreBluetooth/CoreBluetooth.h>

On the Central, your class must be conform to 2 protocols: the CBCentralManagerDelegate and the CBPeripheralDelegate.

@interface ViewController : UIViewController <CBCentralManagerDelegate, CBPeripheralDelegate>

Also add these two properties:

@property (nonatomic, strong) CBCentralManager *manager;
@property (nonatomic, strong) NSMutableData *data;

Now, as I already did for the Peripheral, I create a Central object:

self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];

Again, the first argument represents the CBCentralManager delegate (in this case the view controller). The second argument is instead the dispatch queue on which the events will be dispatched. Again, if you pass nil, you are telling the Central Manager that you want to use the main queue.

Once the Central Manager is initialized, its state can be checked. This will allow to verify that the device your application is running on is Bluetooth LE compliant. You do this implementing the following delegate method:

- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    switch (central.state) {
        case CBCentralManagerStatePoweredOn:
            // Scans for any peripheral
            [self.manager scanForPeripheralsWithServices:@[ [CBUUID UUIDWithString:kServiceUUID] ] options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
            break;
        default:
            NSLog(@"Central Manager did change state");
            break;
    }
}

The -scanForPeripheralsWithServices:options: method is used to tell the Central Manager to start looking for a specific service. If you pass nil as first argument, the Central Manager starts to look for any service.

The kServiceUUID is the same UUID string I used for the Peripheral, so add again these 2 lines of code before your class implementation:

static NSString * const kServiceUUID = @"312700E2-E798-4D5C-8DCF-49908332DF9F";
static NSString * const kCharacteristicUUID = @"FFA28CDE-6525-4489-801C-1C060CAC9767";

Remember to use the UUIDs you obtained from the uuidgen command.

As soon as a Peripheral is discovered during the scanning, the Central delegate receives the following callback:

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI   

This call notifies the Central Manager delegate (the view controller) that a peripheral with advertisement data and RSSI was discovered. RSSI stands for Received Signal Strength Indicator. This is a cool parameter, because knowing the strength of the transmitting signal and the RSSI, you can estimate the current distance between the Central and the Peripheral. So, you can use the distance as a filter for a given service: only if the Central is close enough to the Peripheral, then your app does something.

Any advertisement/scan response data stored in advertisementData can be accessed via the CBAdvertisementData key. Here, you can now stop the scanning and connect to the Peripheral:

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
    // Stops scanning for peripheral
    [self.manager stopScan];

    if (self.peripheral != peripheral) {
        self.peripheral = peripheral;
        NSLog(@"Connecting to peripheral %@", peripheral);
        // Connects to the discovered peripheral
        [self.manager connectPeripheral:peripheral options:nil];
    }
}

The options parameter is an optional dictionary (NSDictionary) and can use the following keys if needed (their value is always a boolean):

  • CBConnectPeripheralOptionNotifyOnConnectionKey. This is a NSNumber (Boolean) indicating that the system should display an alert for a given peripheral, if the application is suspended when a successful connection is made. This is useful for applications that have not specified the Central background mode and cannot display their own alert. If more than one application has requested notification for a given peripheral, the one that was most recently in the foreground will receive the alert.
  • CBConnectPeripheralOptionNotifyOnDisconnectionKey. This is a NSNumber (Boolean) indicating that the system should display a disconnection alert for a given peripheral, if the application is suspended at the time of the disconnection. This is useful for applications that have not specified the Central background mode and cannot display their own alert. If more than one application has requested notification for a given peripheral, the one that was most recently in the foreground will receive the alert.
  • CBConnectPeripheralOptionNotifyOnNotificationKey. This is a NSNumber (Boolean) indicating that the system should display an alert for all notifications received from a given peripheral, if the application is suspended at the time. This is useful for applications that have not specified the Central background mode and cannot display their own alert. If more than one application has requested notification for a given peripheral, the one that was most recently in the foreground will receive the alert.

Depending on the result of the connection, the delegate can receive either centralManager:didFailToConnectPeripheral:error: or centralManager:didConnectPeripheral:. In case of success, you can ask the Peripheral which services is advertising. So, in the didConnectPeripheral callback you can do the following:

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    // Clears the data that we may already have
    [self.data setLength:0];
    // Sets the peripheral delegate
    [self.peripheral setDelegate:self];
    // Asks the peripheral to discover the service
    [self.peripheral discoverServices:@[ [CBUUID UUIDWithString:kServiceUUID] ]];
}

At this point, the Peripheral starts to notify its delegate with a bunch of callbacks. Since in the previous method I asked the Peripheral to discover the service, the Peripheral delegate receives -peripheral:didDiscoverServices:. If there is no error, the Peripheral can be asked to discover the characteristics for a given service. You can do it in this way:

- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverServices:(NSError *)error {
    if (error) {
        NSLog(@"Error discovering service: %@", [error localizedDescription]);
        [self cleanup];
        return;
    }

    for (CBService *service in aPeripheral.services) {
        NSLog(@"Service found with UUID: %@", service.UUID);

        // Discovers the characteristics for a given service
        if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
            [self.peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:kCharacteristicUUID]] forService:service];
        }
    }
}   

Now, if a characteristic is discovered, the Peripheral delegate receives -peripheral:didDiscoverCharacteristicsForService:error:. At this point, the Peripheral can be asked to notify its delegate as soon as the characteristic value is updated using -setNotifyValue:forCharacteristic::

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    if (error) {
        NSLog(@"Error discovering characteristic: %@", [error localizedDescription]);
        [self cleanup];
        return;
    }
    if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
        for (CBCharacteristic *characteristic in service.characteristics) {
            if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {
                [peripheral setNotifyValue:YES forCharacteristic:characteristic];
            }
        }
    }
}

Here, if the value of a characteristic is updated, then the Peripheral delegate receives -peripheral:didUpdateNotificationStateForCharacteristic: error:. Here, you can read the new value using -readValueForCharacteristic:

- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        NSLog(@"Error changing notification state: %@", error.localizedDescription);
    }

    // Exits if it's not the transfer characteristic
    if (![characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {
        return;
    }

    // Notification has started
    if (characteristic.isNotifying) {
        NSLog(@"Notification began on %@", characteristic);
        [peripheral readValueForCharacteristic:characteristic];
    } else { // Notification has stopped
        // so disconnect from the peripheral
        NSLog(@"Notification stopped on %@.  Disconnecting", characteristic);
        [self.manager cancelPeripheralConnection:self.peripheral];
    }
}

When the Peripheral sends the new value, the Peripheral delegate receives -peripheral:didUpdateValueForCharacteristic:error:. The second argument of this method contains the characteristic. You can then read its value using the -value property. This is a NSData containing the value of the characteristic.

At this point, you can either disconnect or wait for other data.

Conclusions

I showed you a basic example on how to use the Core Bluetooth framework. I hope that this small tutorial, the WWDC videos and the documentation (the available one) can help you create new iOS applications using the Bluetooth LE. Check also the examples coming with the documentation. There, you will find all the delegate methods I used in this tutorial.

Keep coding,

Geppy

19 Notes/ Hide

  1. massivepixel reblogged this from invasivecode and added:
    Keeping this in mind once I finish some of my games. I will eventually plan on writing a child tethering app to alert me...
  2. kumayast reblogged this from invasivecode
  3. keefmoon reblogged this from invasivecode and added:
    Looking forward to trying out some Bluetooth goodness.
  4. i0sdev reblogged this from invasivecode
  5. sashatan reblogged this from interglacial
  6. ikeisuke reblogged this from interglacial
  7. interglacial reblogged this from do-nothing
  8. geppyp reblogged this from invasivecode
  9. do-nothing reblogged this from invasivecode
  10. invasivecode posted this

Recent comments

Blog comments powered by Disqus
Designed and developed in California © 2008-2014 INVASIVECODE INC