Skip to content

Instantly share code, notes, and snippets.

@brayden-morris-303
Last active February 18, 2023 04:53
Show Gist options
  • Save brayden-morris-303/09a738ed9c83a7d14c82 to your computer and use it in GitHub Desktop.
Save brayden-morris-303/09a738ed9c83a7d14c82 to your computer and use it in GitHub Desktop.
Overview of Bluetooth Low Energy (BLE) and CoreBluetooth

CoreBluetooth and BLE

Bluetooth Low Energy (BLE)

Various Names:

  • Bluetooth Low Energy
  • BLE
  • BTLE
  • Bluetooth 4.0
  • Bluetooth Smart

Bluetooth® Smart is the intelligent, power-friendly version of Bluetooth wireless technology. While the power-efficiency of Bluetooth Smart makes it perfect for devices needing to run off a tiny battery for long periods, the magic of Bluetooth Smart is its ability to work with an application on the smartphone or tablet you already own. Bluetooth Smart makes it easy for developers and OEMs to create solutions that will work with the billions of Bluetooth enabled products already in the market today. (bluetooth.com)

Central and Peripheral Roles

AKA Client and Server

Diagram of roles

Generic ATTribute Profile

(GATT Database)

With BLE, all of the interaction between devices happens through the GATT database. The database is comprised of Services and Characteristics. Services just act as groups of related characteristics. Characteristics can be read and written to, and manipulated (depending on the permissions you give).

  • Service
    • Characteristic
  • Service
    • Characteristic
    • Characteristic

All functions are divided up into services and characteristics. Each service and characteristic has its own UUID. Standard services and characteristics have their own predefined 16-bit UUID's. These can be found in the GATT Specifications. Vendor specific services and characteristics (like what we will use) use uniquely generated 128-bit UUID's (I used the CLI uuidgen program to make mine).

Heart Rate Monitor Example (Hypothetical)

Key: UUID, [value], (Human Readable Description)

  • 0x1800, (Generic Access Service)
    • 0x2A00, "MyHeart Monitor", (Device Name)
    • 0x2A01, 0x0200, (Appearance)
  • 0x180A, (Device Information Service)
    • 0x2A29, "Rocketmade", (Manufacturer Name String)
    • 0x2A24, "0.1", (Model Number String)
    • 0x2A27, "0.1", (Hardware Revision String)
    • 0x2A26, "0.1", (Firmware Revision String)
  • 0x180D, (Heart Rate)
    • 0x2A37, [variable], (Heart Rate Measurement)
    • 0x2A38, 0x0002, (Body Sensor Location)

Our Own Useless Little Timer Example (Real)

In this example, we want a characteristic that increments every second. Since every characteristic needs to be in a service, we need to create our own service too.

  • 0x1800, (Generic Access Service)
    • 0x2A00, "Timer", (Device Name)
    • 0x2A01, 0x0200, (Appearance)
  • 0x180A, (Device Information Service)
    • 0x2A29, "Rocketmade", (Manufacturer Name String)
    • 0x2A24, "0.1", (Model Number String)
    • 0x2A27, "0.1", (Hardware Revision String)
    • 0x2A26, "0.1", (Firmware Revision String)
  • 0x1FA05BEC-161E-4877-932E-98D111F3B1FE, (Vendor Specific, Our Timer Service)
    • 0xB70C9A8B-26DB-43DF-9DBA-338205F11067, [variable], (Vendor Specific, Our Timer Characteristic)

Permissions

Each characteristic may have various permissions allowed. These include:

  • Read (Server can write a value to the characteristic)
  • Write (Client can read a value)
  • Notify (Tell the client that the value has changed)
  • Indicate (Like Notify, but with confirmation)

Advertisement and Discovery

Peripheral devices transmit an advertiment packet. This packet often includes useful information such as the device's name and services that the device has available. (Side note: iBeacons just use a custom advertisment packet containing the beacon's UUID, major, and minor in a vendor-specific field.) The central device then scans for Bluetooth devices, and when it picks up the advertisment packet, it can then connect to the peripheral.

CoreBluetooth

Concepts

There are several steps that we need to go to in order to retrieve a value from a bluetooth device.

  1. Scan for devices
  2. Search devices for services
  3. Search services for characteristics
  4. Either
  • Read a characteristic
  • Register for notifications when a characteristic is written to
  1. Perform some kind of action when a characteristic changes

Code

I am just starting with a Single View Application.

Framework

Add CoreBluetooth.framework

Central Manager

The first thing that we need is a central manager. This allows us to look for and connect to peripherals.

ViewController.h

#import <CoreBluetooth/CoreBluetooth.h>

ViewController.m

@property (strong, nonatomic) CBCentralManager *centralManager;

Let's go ahead an initialize it.

ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}

We just set self.centralManager's delegate to be the view controller, so we should probably make it a central manager delegate, otherwise we'll get this warning: warning: sending 'ViewController *const __strong' to parameter of incompatible type 'id<CBCentralManagerDelegate>'.

Central Manager Delegate

ViewController.h

@interface ViewController : UIViewController <CBCentralManagerDelegate>

This then gives us a warning: warning: method 'centralManagerDidUpdateState:' in protocol not implemented

To resolve this warning we implement the method.

When the state is powered on, we want to begin scanning to look for bluetooth device. We don't want just any bluetooth device, though. We only want our own. We can tell which ones are ours by the service UUID's.

ViewController.m

#pragma mark CBCentralManagerDelegate Methods

- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    // You should test all scenarios
    if (central.state != CBCentralManagerStatePoweredOn) {
        return;
    }
     
    if (central.state == CBCentralManagerStatePoweredOn) {
        // Scan for devices
        [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kTimerServiceUUID]] options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
        NSLog(@"Scanning started");
    }
}

We're missing the kTimerServiceUUID. Let's define that. It should be the UUID that we had for the service in our timer example.

ViewController.h

static NSString * const kTimerServiceUUID = @"1FA05BEC-161E-4877-932E-98D111F3B1FE";

Now the we are looking for Bluetooth devices, but we aren't doing anything when we find them.

Did Discover Peripheral

First we need to hold onto a peripheral, so we need a property.

ViewController.m

@property (strong, nonatomic) CBPeripheral *discoveredPeripheral;

Now we need to do connect to a peripheral when we detect it.

ViewController.m

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

    NSLog(@"Discovered %@ at %@", peripheral.name, RSSI);

    if (self.discoveredPeripheral != peripheral) {
        // Save a local copy of the peripheral, so CoreBluetooth doesn't get rid of it
        self.discoveredPeripheral = peripheral;

        // And connect
        NSLog(@"Connecting to peripheral %@", peripheral);
        [self.centralManager connectPeripheral:peripheral options:nil];
    }
}

Now that we've connected to the bluetooth device, we need to figure out what services it has to offer.

Did Connect Peripheral

We need to stop scanning, becasue we are connected and don't want to waste the battery. Then we need to find the available services. We don't really care about all the services, so we'll only look for our service.

ViewController.m

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    NSLog(@"Connected");

    [_centralManager stopScan];
    NSLog(@"Scanning stopped");

    peripheral.delegate = self;

    [peripheral discoverServices:@[[CBUUID UUIDWithString:kTimerServiceUUID]]];
}

Oops! We just set the peripheral's delegate to be the view controller. That isn't a peripheral delegate, so let's make it one.

ViewController.h

@interface ViewController : UIViewController <CBCentralManagerDelegate, CBPeripheralDelegate>

Ok. Now that we have that taken care of, we probably should add in some of the Peripheral Delegate methods.

Did Discover Service

ViewController.m

#pragma mark CBPeripheralDelegate Methods

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    if (error) {
        return;
    }

    for (CBService *service in peripheral.services) {
        [peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:kTimerCharacteristicUUID]] forService:service];
    }
}

Oh, we need the characteristic's UUID too! Let's get that in there.

ViewController.h

static NSString * const kTimerCharacteristicUUID = @"B70C9A8B-26DB-43DF-9DBA-338205F11067";

Now to handle what it does when it discovers a characteristic.

Did Discover Characteristic

For every characteristic that is the same as our timer characteristic, we'll watch for changes to it.

ViewController.m

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    if (error) {
        return;
    }

    for (CBCharacteristic *characteristic in service.characteristics) {
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kTimerCharacteristicUUID]]) {
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
        }
    }
}

All that's left is to actually do something with the value that we get.

Did Update Value

ViewController.m

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        return;
    }

    NSInteger value = *(int*)characteristic.value.bytes;
    NSString *valueString = [NSString stringWithFormat:@"%ld", (long)value];
    NSLog(@"Updated value: %@", valueString);
    
    // Actually do something useful with the value
}

Some thoughts

For actually making something with Bluetooth, you definitely don't want to be putting all of this in a view controller. You would want to have your own class handling all of the scanning, connecting, etc.

If you are only wanting to use iBeacon, all of that functionality is in CoreLocation. You don't even have to touch CoreBluetooth.

Additional Resources

Bluetooth Low Energy on Android

CoreBluetooth tutorial (goes over peripheral mode in iOS as well)

Other Technologies

iBeacon

Multipeer Connectivity

@murali998011
Copy link

Hi am working as Bluetooth energy any sample android app could you please send me that sample android things

@iamterryclark
Copy link

Awesome gist! I am now trying to get multiple peripherals connected to my laptop using corebluetooth have you any advice on how to do this? which part of the code would I need to develop on in order to achieve this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment