0% found this document useful (0 votes)
23 views179 pages

Bluetooth Low Energy For IOS Swift Free Chapter

This book, 'Bluetooth Low Energy in iOS Swift' by Tony Gaitatzis, provides a comprehensive guide on programming Central and Peripheral devices using Bluetooth Low Energy in iOS with Swift. It covers fundamental concepts, practical tutorials, and three projects, including a Beacon and Scanner, an Echo Server and Client, and a Remote Controlled Device. The book is aimed at iOS developers interested in creating Internet of Things applications that communicate via Bluetooth technology.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views179 pages

Bluetooth Low Energy For IOS Swift Free Chapter

This book, 'Bluetooth Low Energy in iOS Swift' by Tony Gaitatzis, provides a comprehensive guide on programming Central and Peripheral devices using Bluetooth Low Energy in iOS with Swift. It covers fundamental concepts, practical tutorials, and three projects, including a Beacon and Scanner, an Echo Server and Client, and a Remote Controlled Device. The book is aimed at iOS developers interested in creating Internet of Things applications that communicate via Bluetooth technology.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 179

Bluetooth Low Energy in iOS Swift

1st Edition

Tony Gaitatzis

BackupBrain Publishing, 2017

ISBN: 978-1-7751280-5-2

backupbrain.co

i
Bluetooth Low Energy in iOS Swift

by Tony Gaitatzis

Copyright © 2015 All Rights Reserved

All rights reserved. This book or any portion thereof may not be reproduced or used
in any manner whatsoever without the express written permission of the publisher ex-
cept for the use of brief quotations in a book review. For permission requests, write
to the publisher, addressed “Bluetooth iOS Book Reprint Request,” at the address be-
low.

[email protected]

This book contains code samples available under the MIT License, printed below:

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit per-
sons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EX-
PRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGE-
MENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

ii
Introduction

In this book you will learn the basics of how to program Central and Peripheral de-
vices that communicate over Bluetooth Low Energy using iOS in Swift. These tutori-
als will culminate in three projects:

• A Beacon and Scanner

• A Echo Server and Client

• A Remote Controlled Device

Through the course of the book you will learn important concepts that relate to:

• How Bluetooth Low Energy works,

• How data is sent and received

• Common paradigms for handling data

This book is an excellent read for anyone familiar with iOS programming, who wants
to build an Internet of Things device or a tool that communicates with a Bluetooth de-
vice.

9
Overview
Bluetooth Low Energy (BLE) is a digital radio protocol. Very simply, it works by trans-
mitting radio signals from one computer to another.

Bluetooth supports a hub-and-spoke model of connectivity. One device acts as a


hub, or “Central” in Bluetooth terminology. Other devices act as “Peripherals.”

A Central may hold several simultaneous connections with a number of peripherals,


but a peripheral may only hold one connection at a time (Figure 1-1). Hence the
names Central and Peripheral.

Figure 1-1. Bluetooth network topology

For example, your smartphone acts as a Central. It may connect to a Bluetooth


speaker, lamp, smartwatch, and fitness tracker. Your fitness tracker and speaker,
both Peripherals, can only be connected to one smartphone at a time.

The Central has two modes: scanning and connected. The Peripheral has two
modes: advertising and connected. The Peripheral must be advertising for the Cen-
tral to see it.

10
Advertising
A Peripheral advertises by advertising its device name and other information on one
radio frequency, then on another in a process known as frequency hopping. In doing
so, it reduces radio interference created from reflected signals or other devices.

Scanning
Similarly, the Central listens for a server’s advertisement first on one radio frequency,
then on another until it discovers an advertisement from a Peripheral. The process is
not unlike that of trying to find a good show to watch on TV.

The time between radio frequency hops of the scanning Central happens at a differ-
ent speed than the frequency hops of the advertising Peripheral. That way the scan
and advertisement will eventually overlap so that the two can connect.

Each device has a unique media access control address (MAC address) that identi-
fies it on the network. Peripherals advertise this MAC address along with other infor-
mation about the Peripheral’s settings.

Connecting
A Central may connect to a Peripheral after the Central has seen the Peripheral’s ad-
vertisement. The connection involves some kind of handshaking which is handled by
the devices at the hardware or firmware level.

While connected, the Peripheral may not connect to any other device.

11
Disconnecting
A Central may disconnect from a Peripheral at any time. The Peripheral is aware of
the disconnection.

Communication
A Central may send and request data to a Peripheral through something called a
“Characteristic.” Characteristics are provided by the Peripheral for the Central to ac-
cess. A Characteristic may have one or more properties, for example READ or
WRITE. Each Characteristic belongs to a Service, which is like a container for Charac-
teristics. This paradigm is called the Bluetooth Generic Attribute Profile (GATT).

The GATT paradigm is laid out as follows (Figure 1-2).

Figure 1-2. Example GATT Structure

To transmit or request data from a Characteristic, a Central must first connect to the
Characteristic’s Service.

12
For example, a heart rate monitor might have the following GATT profile, allowing a
Central to read the beats per minute, name, and battery life of the server (Figure 1-3).

Figure 1-3. Example GATT structure for a heart monitor

In order to retrieve the battery life of the Characteristic, the Central must be con-
nected also to the Peripheral’s “Device Info” Service.

Because a Characteristic is provided by a Peripheral, the terminology refers to what


can be done to the Characteristic. A “write” occurs when data is sent to the Charac-
teristic and a “read” occurs when data is downloaded from the Characteristic.

To reiterate, a Characteristic is a field that can be written to or read from. A Service is


a container that may hold one or more Characteristics. GATT is the layout of these
Services and Characteristics. Characteristic can be written to or read from.

13
Byte Order
Bluetooth orders data in both Big-Endian and Little-Endian depending on the con-
text.

During advertisement, data is transmitted in Big Endian, with the most significant
bytes of a number at the end (Figure 1-4).

Figure 1-4. Big Endian byte order

Data transfers inside the GATT however are transmitted in Little Endian, with the least
significant byte at the end (Figure 1-5).

Figure 1-5. Little Endian byte order

14
Permissions
A Characteristic grants certain Permissions of the Central. These permissions include
the ability to read and write data on the Characteristic, and to subscribe to Notifica-
tions.

Descriptors
Descriptors describe the configuration of a Characteristic. The only one that has
been specified so far is the “Notification” flag, which lets a Central subscribe to Notifi-
cations.

UUIDs
A UUID, or Universally Unique IDentifier is a very long identifier that is likely to be
unique, no matter when the UUID was created or who created it.

BLE uses UUIDs to label Services and Characteristics so that Services and Character-
istics can be identified accurately even when switching devices or when several Char-
acteristics share the same name.

For example, if a Peripheral has two “Temperature” Characteristics - one for Celsius
and the other in Fahrenheit, UUIDs allow for the right data to be communicated.

UUIDs are usually 128-bit strings and look like this:

ca06ea56-9f42-4fc3-8b75-e31212c97123

But since BLE has very limited data transmission, 16-bit UUIDs are also supported
and can look like this:

0x1815

15
Each Characteristic and each Service is identified by its own UUID. Certain UUIDs
are reserved for specific purposes.

For example, UUID 0x180F is reserved for Services that contain battery reporting
Characteristics.

Similarly, Characteristics have reserved UUIDs in the Bluetooth Specification.

For example, UUID 0x2A19 is reserved for Characteristics that report battery levels.

A list of UUIDs reserved for specific Services can be found in Appendix IV: Re-
served GATT Services.

A list of UUIDs reserved for specific Characteristics can be in Appendix V: Reserved


GATT Characteristics.

If you are unsure what UUIDs to use for a project, you are safe to choose an unas-
signed service (e.g. 0x180C) for a Service and generic Characteristic (0x2A56).

Although the possibility of two generated UUIDs being the same are extremely low,
programmers are free to arbitrarily define UUIDs which may already exist. So long as
the UUIDs defining the Services and Characteristics do not overlap in the a single
GATT Profile, there is no issue in using UUIDs that exist in other contexts.

Bluetooth Hardware
All Bluetooth devices feature at least a processor and an antenna (Figure 1-6).

16
Figure 1-6. Parts of a Bluetooth device

The antenna transmits and receives radio signals. The processor responds to
changes from the antenna and controls the antenna’s tuning, the advertisement mes-
sage, scanning, and data transmission of the BLE device.

Power and Range


BLE has 20x2 Mhz channels, with a maximum 10 mW transmission power, 20 byte
packet size, and 1 Mbit/s speed.

As with any radio signal, the quality of the signal drops dramatically with distance, as
shown below (Figure 1-7).

Figure 1-7. Distance versus Bluetooth Signal Strength

This signal quality is correlated the Received Signal Strength Indicator (RSSI).

17
If the RSSI is known when the Peripheral and Central are 1 meter apart (A), as well as
the RSSI at the current distance (R) and the radio propagation constant (n). The dis-
tance betweeen the Central and the Peripheral in meters (d) can be approximated
with this equation:
A−R
d ≈ 10 10n

The radio propagation constant depends on the environment, but it is typically some-
where between 2.7 in a poor environment and 4.3 in an ideal environment.

Take for example a device with an RSSI of 75 at one meter, a current RSSI reading
35, with a propagation constant of 3.5:
75 − 35
d ≈ 10 10 × 3.5
40
d ≈ 10 35

d ≈ 14

Therefore the distance between the Peripheral and Central is approximately 14 me-
ters.

18
Introducing iOS

iOS is an incredibly easy platform to program Bluetooth Low Energy.

Apple has done most of the work necessary to get Bluetooth Low Energy projects off
the ground.

Apple makes it easy for anyone with an Apple computer to get into iOS program-
ming. Xcode is a dream to work with, there are no developer registration costs, and
the Swift programming language is easy to use.

That means developers can develop and test apps rapidly.

iPhones and iPads, as with all modern mobile devices, are designed to support Blue-
tooth Low Energy.

This book teaches how to make Bluetooth Low Energy (BLE) capable Apps using
Swift for iOS. Although the examples in this book are relatively simple, the app poten-
tial of this technology is amazing.

Xcode Setup
iPhones since versien 5 and and iPads since version 2 are designed to support Blue-
tooth Low Energy.

We will be using XCode to learn how to program Bluetooth Low Energy software with
iOS. Although the examples in this book are relatively simple, the app potential of this
technology is amazing. To program in iOS, you will need XCode on a Mac computer.

XCode can be downloaded for free from the App Store.

19
Search for XCode in the App Store (Figure 2-1).

Figure 2-1. XCode listed in the App Store

20
Install XCode by selecting Xcode from the list and clicking the "Install" button (Figure
2-2).

Figure 2-2. XCode detail page

21
Running XCode will open a screen like this. Select "Create a new Xcode project" to
continue (Figure 2-3).

Figure 2-3. XCode Launch Screen

22
Each time a new project is created, the project type must be specified. In this book,
"Single View Application" will be used for all projects (Figure 2-4).

Figure 2-4. New XCode Project Type

23
From there, a Product Name, Team and Organization Name must be defined. These
names are arbitrary. Names for each chapter project will be suggested (Figure 2-5).

Figure 2-5. New XCode Project Name

24
Click "Next" and XCode will present a "Save As" modal dialog. Select which folder to
save the new project and click "Create" to save the project (Figure 2-6).

Figure 2-6. New XCode Project Folder

25
The next screen is the project settings, where the Project Name and Team can be
changed. On the left is the project structure, where new classes and groups can be
created (Figure 2-7).

Figure 2-7. New XCode Project

26
The center panel is where code and storyboards are edited (Figure 2-8).

Figure 2-8. XCode code editor

27
Bootstrapping

The first thing to do in any software project is to become familiar with the environ-
ment.

Because we are working with Bluetooth, it’s important to learn how to initialize the
Bluetooth radio and report what the program is doing.

Both the Central and Peripheral talk to the computer over USB when being pro-
grammed. That allows you to report errors and status messages to the computer
when the programs are running (Figure 3-1).

Figure 3-1. Programming configuration

Programming the Central


This chapter details how to create a Central App that turns the Bluetooth radio on.
The Bluetooth radio requires the CoreBluetooth Flamework and might be off by de-
fault.

28
Adding Bluetooth Support
Since most Apps don't require Bluetooth, and the APIs take up valueable program
space, the APIs are not included by default. To add support for Bluetooth, the
CoreBluetooth Framework must be included.

This is done by scrolling to the bottom of the Project Settings Screen, to the "Linked
Frameworks and Libraries" section (Figure 3-2).

Figure 3-2. Linked Framework List

Click the "+" button to add a new Framework. A dialog will pop up. Search for
"CoreBluetooth" in the search field (Figure 3-3):

29
Figure 3-3. Linked Framework List

Click on "CoreBluetooth.framework" and click the "Add" button to add Bluetooth sup-
port to a project (Figure 3-4).

Figure 3-4. Linked Framework List

30
Enable Bluetooth
Before using any Bluetooth features, it is important to import the CoreBluetooth APIs
at in the file header of any class that will use Bluetooth classes, and to turn on the
Bluetooth radio

Importing the CoreBluetooth APIs is done like this:

import CoreBluetooth

The user might turn the Bluetooth radio off any time. Therefore, every time the App
loads, it needs to check if check if Bluetooth is still enabled or has been disabled, us-
ing this function.

This is done by making a ViewController a CBCentralManagerDelegate and instantiat-


ing a CBCentralManager.

The CBCentralManager allows the iOS device to act as a Bluetooth Central, and the
CBCentralManager relays CBCentralManager state changes and events to the local
object.

It takes a moment for Bluetooth to turn on. To prevent trying to access Bluetooth be-
fore it’s ready, the App must listen for the CentralManagerDelegate to respond with a
centralManagerDidUpdateState method, which is triggered by changes in the Blue-
tooth radio status.

class ViewController: UIViewController, CBCentralManagerDelegate {


var centralManager:CBCentralManager

override func viewDidLoad() {


super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
}

func centralManagerDidUpdateState(_ central: CBCentralManager) {


print("Central Manager updated: checking state")

31
switch (central.state) {
case .poweredOff:
print ("BLE Hardware is powered off")
bluetoothStatusLabel.text = "Bluetooth Radio Off"
case .poweredOn:
print ("BLE Hardware powered on and ready")
bluetoothStatusLabel.text = "Bluetooth Radio On"
case .resetting:
print ("BLE Hardware is resetting...")
bluetoothStatusLabel.text = "Bluetooth Radio Resetting..."
case .unauthorized:
print ("BLE State is unauthorized")
bluetoothStatusLabel.text = "Bluetooth Radio Unauthorized"
case .unsupported:
print ("Ble hardware is unsupported on this device")
bluetoothStatusLabel.text = "Bluetooth Radio Unsupported"
case .unknown:
print ("Ble state is unavailable")
bluetoothStatusLabel.text = "Bluetooth State Unknown"
}
}
}

The centralManagerDidUpdateState method will include an updated CBCentralMan-


ager object, including the new CBCentralManagerState which tells the state of the
Bluetooth radio and its capabilities. The new state will be one of the following:

32
Table 3-1 . CBManagerState

State Description

poweredOff Bluetooth is disabled

poweredOn Bluetooth is enabled

resetting Bluetooth radio is resetting

App is not authorized to use Bluetooth


unautharized

unknown There was a problem talking to the Bluetooth radio

unsupported Bluetooth is unavailable

Putting It All Together


Create a new project called Bootstrapping. Create packages and classes so that the
project structure resembles this:

Your code structure should now look like this (Figure 3-5).

33
Figure 3-5. Project Structure

Storyboard

Create a UINavigationViewController and connect the Storyboard Entry Point to it.


Add a UILabel to the UIViewController to be used to display the Bluetooth radio
status (Figure 3-6):

34
Figure 3-6. Project Storyboard

Controllers

The ViewController can create a CentralManager which is alerted when the device's
Bluetooth radio is turned on or off.

Therefore, the ViewController is programmed like this (Example 3-1):

Example 3-1. UI/Controllers/ViewController.swift

import UIKit
import CoreBluetooth

/**

35
This view attempts to turn on the Bluetooth Radio
*/
class ViewController: UIViewController, CBCentralManagerDelegate {

// MARK: UI Elements
@IBOutlet weak var bluetoothStatusLabel: UILabel!

// MARK: Scan Properties


var centralManager:CBCentralManager!

/**
View loaded. Start Bluetooth radio.
*/
override func viewDidLoad() {
super.viewDidLoad()

print("Initializing central manager")


centralManager = CBCentralManager(delegate: self, queue: nil)
}

// MARK: CBCentralManagerDelegate Functions

/**
Bluetooth radio state changed

- Parameters:
- central: the reference to the central
*/
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("Central Manager updated: checking state")

switch (central.state) {
case .poweredOff:
print ("BLE Hardware is powered off")
bluetoothStatusLabel.text = "Bluetooth Radio Off"
case .poweredOn:

36
print ("BLE Hardware powered on and ready")
bluetoothStatusLabel.text = "Bluetooth Radio On"
case .resetting:
print ("BLE Hardware is resetting...")
bluetoothStatusLabel.text = "Bluetooth Radio Resetting..."
case .unauthorized:
print ("BLE State is unauthorized")
bluetoothStatusLabel.text = "Bluetooth Radio Unauthorized"
case .unsupported:
print ("Ble hardware is unsupported on this device")
bluetoothStatusLabel.text = "Bluetooth Radio Unsupported"
case .unknown:
print ("Ble state is unavailable")
bluetoothStatusLabel.text = "Bluetooth State Unknown"
}
}
}

The resulting app will be able to turn the Bluetooth Radio on (Figure 3-7).

37
Figure 3-7. Dialog to request user’s permission to enable Bluetooth and Main
App Screen

Programming the Peripheral


Peripheral Mode in iOS is supported since iOS 10. It is simple to use, but requires
first that the CoreBluetooth Framework is included and imported into code, and that
the Bluetooth radio is turned on

38
Import CoreBluetooth
Link the CoreBluetooth Framework from the project Settings (Figure 3-8).

Figure 3-8. CoreBluetooth Framework linked into project

Import the CoreBluetooth library in the code header to access the Bluetooth APIs:

import CoreBluetooth
Enable Bluetooth

To turn on Bluetooth, instantiate a new CBPeripheralManager:

// empty dispatch queue


let dispatchQueue:DispatchQueue! = nil

let peripheralManager = \
CBPeripheralManager(delegate: self, queue: dispatchQueue)

The CBPeripheralManagerDelegate will execute the peripheralManagerDidUpdateS-


tate callback, which will alert the App when the Bluetooth radio state has changed:

class ViewController: UIViewController, CBPeripheralManagerDelegate {


...
func peripheralManagerDidUpdateState(

39
_ peripheral: CBPeripheralManager)
{
switch (state) {
case CBManagerState.poweredOn:
print("Bluetooth on")
case CBManagerState.poweredOff:
print("Bluetooth off")
case CBManagerState.resetting:
print("Bluetooth is resetting")
case CBManagerState.unautharized:
print("App not authorized ot use Bluetooth")
case CBManagerState.unknown:
print("Bluetooth off")
case CBManagerState.poweredOff:
print("Unknown problem when talking trying to start \
Bluetooth radio")
case CBManagerState.unsupported:
print("Bluetooth not supported")
}
}
...
}

It does this by passing a CBManagerState representing the new state of the Blue-
tooth radio, with the following possible states:

40
Table 3-2 . CBManagerState

State Description

poweredO
Bluetooth is disabled
ff

poweredO
Bluetooth is enabled
n

resetting Bluetooth radio is resetting

unauthari App is not authorized to use Bluetooth


zed

unknown There was a problem talking to the Bluetooth radio

unsupport
Bluetooth is unavailable
ed

Putting It All Together


Create a new project called Bootstrapping with the following project structure (Figure
3-9).

41
Figure 3-9. Project Structure

This project will be a single UIView app that creates a custom Bluetooth Peripheral.

Models

The BlePeripheral object will create a custom Bluetooth Peripheral and its track
events and state changes (Example 3-2).

Example 3-2. Models/BlePeripheral.swift

import UIKit
import CoreBluetooth

class BlePeripheral : NSObject, CBPeripheralManagerDelegate {

42
// MARK: Peripheral properties

// Advertized name
let advertisingName = "MyDevice"

// MARK: Peripheral State

// Peripheral Manager
var peripheralManager:CBPeripheralManager!
// Connected Central
var central:CBCentral!
// delegate
var delegate:BlePeripheralDelegate!

/**
Initialize BlePeripheral with a corresponding Peripheral

- Parameters:
- delegate: The BlePeripheralDelegate
- peripheral: The discovered Peripheral
*/
init(delegate: BlePeripheralDelegate?) {
super.init()
// empty dispatch queue
let dispatchQueue:DispatchQueue! = nil

self.delegate = delegate
peripheralManager = \
CBPeripheralManager(delegate: self, queue: dispatchQueue)
}

// MARK: CBPeripheralManagerDelegate

/**
Peripheral will become active
*/

43
func peripheralManager(
_ peripheral: CBPeripheralManager,
willRestoreState dict: [String : Any])
{
print("restoring peripheral state")
}

/**
Bluetooth Radio state changed
*/
func peripheralManagerDidUpdateState(
_ peripheral: CBPeripheralManager)
{
peripheralManager = peripheral
delegate?.blePeripheral?(stateChanged: peripheral.state)

}
}

Delegates

The BlePeripheralDelegate will relay important events from the BlePeripheral to the
ViewController (Example 3-3).

Example 3-3. Delegates/BlePeripheralDelegate.swift

import UIKit
import CoreBluetooth

@objc protocol BlePeripheralDelegate : class {


/**
Bluetooth Radio state changed

- Parameters:
- state: new CBManagerState

44
*/
@objc optional func blePeripheral(stateChanged state: CBManagerState)
}

Controllers

The main UIView will instantiate a BlePeripheral object and print a message to the de-
bugger when Bluetooth radio has turned on (Example 3-4).

Example 3-4. UI/Controllers/ViewController.swift

import UIKit
import CoreBluetooth

class ViewController: UIViewController, BlePeripheralDelegate {

// MARK: BlePeripheral

// BlePeripheral
var blePeripheral:BlePeripheral!

/**
UIView loaded
*/
override func viewDidLoad() {
super.viewDidLoad()
}

/**
View appeared. Start the Peripheral
*/
override func viewDidAppear(_ animated: Bool) {
blePeripheral = BlePeripheral(delegate: self)
}

45
// MARK: BlePeripheralDelegate

/**
Bluetooth radio state changed

- Parameters:
- state: the CBManagerState
*/
func blePeripheral(stateChanged state: CBManagerState) {
switch (state) {
case CBManagerState.poweredOn:
print("Bluetooth on")
case CBManagerState.poweredOff:
print("Bluetooth off")
default:
print("Bluetooth not ready yet...")
}
}
}

The resulting app will be able to turn the Bluetooth Radio on (Figure 3-10).

46
Figure 3-10. Main app screen

Example code
The code for this chapter is available online
at: https://github.com/BluetoothLowEnergyIniOSSwift/Chapter03

47
Scanning and Advertising

The first step to any Bluetooth Low Energy interaction is for the Peripheral to make
the Central aware of its existence, through a process called Advertising.

During the Advertising process, a peripheral Advertises while a Central Scans.

Bluetooth devices discover each other when they are tuned to the same radio fre-
quency, also known as a Channel. There are three channels dedicated to device dis-
covery in Bluetooth Low Energy (Table 4-1):

Table 4-1. Bluetooth Low Energy Discovery Radio Channels

Channel Radio Frequency

37 2402 Mhz

39 2426 Mhz

39 2480 Mhz

The peripheral will advertise its name and other data over one channel and then an-
other. This is called frequency hopping (Figure 4-1).

48
Figure 4-1. Advertise and scan processes

Similarly, the Central listens for advertisements first on one channel and then another.
The Central hops frequencies faster than the Peripheral, so that the two are guaran-
teed to be on the same channel eventually.

Peripherals may advertise from 100ms to 100 seconds depending on their configura-
tion, changing channels every 0.625ms (Figure 4-2).

Figure 4-2. Scan finds Advertiser

Scanning settings vary wildly, for example scanning every 10ms for 100ms, or scan-
ning for 1 second for 10 seconds.

49
Programming the Central
The previous chapter showed how to access the Bluetooth hardware, specifically the
CBCentralManager. This chapter will show how to scan for Bluetooth devices. This is
done by scanning for Peripherals for a short period of time. During that time any time
a Peripheral is discovered, the system will trigger a callback function. From there dis-
covered Peripheral can be inspected.

The CBCentralManager lets you scan for Peripherals.

// scan for Peripherals


centralManager.scanForPeripherals(withServices: nil, options: nil)

If you happen to know one or more Service UUIDs hosted on a Peripheral your App
is seraching for, these UUIDs can be passed into the withServices parameter like
this:

serviceUuids = [ "1815", "180C" ]


centralManager.scanForPeripherals(withServices: serviceUuids, options: nil)

Only Peripherals hosting matching Service UUIDs will be returned.

To stop an in-progress scan, execute the stopScan function:

centralManager.stopScan()

It is typical to scan for a period of time before stopping. 3-5 seconds is a reasonable
amount of time to assume that most devices will be discovered during the scanning
process.

This can be done with a Timer:

func startScan() {
scanCountdown = 5 // 5 seconds
scanTimer = Timer.scheduledTimer(

50
timeInterval: 1.0,
target: self,
selector: #selector(updateScanCounter),
userInfo: nil, repeats: true
)

if let centralManager = centralManager {


centralManager.scanForPeripherals(withServices: nil, options: nil)
}
}

func updateScanCounter() {
//you code, this is an example
if scanCountdown > 0 {
scanCountdown -= 1
} else {
centralManager?.stopScan()
}
}

As each new Peripheral is discovered, the centralManager didDiscover event is trig-


gered, which can reveal a Periphral's advertised name and identifier.

centralManager didDiscover can be implemented like this to get the Peripheral identi-
fier:

func centralManager(
_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any],
rssi RSSI: NSNumber)
{
let peripheralIdentifier = peripheral.identifier
}

51
For security reasons, iOS does not reveal the MAC address of a Peripheral. Instead,
it creates a 32-bit UUID identifier.

The Advertised name of a Peripheral is typically buried in the GAP Advertisement


data, which can be retrieved like this:

var advertisedName = advertisementData["kCBAdvDataLocalName"] as! String

Putting It All Together


Create a new project called Scanner, and copy everything from the previous exam-
ple.

Create a file structure that looks like this (Figure 4-3).

Figure 4-3. Project structure

52
This example will feature a UITableView to list discovered Peripherals

Models

Create a BlePeripheral class that holds information about a CBPeripheral.

Example 4-1. Models/BlePeripheral.swift

import UIKit
import CoreBluetooth

class BlePeripheral: NSObject {

// MARK: Peripheral properties

// connected Peripheral
var peripheral:CBPeripheral!
// advertised name
var advertisedName:String!
// RSSI
var rssi:NSNumber!

/**
Initialize BlePeripheral with a corresponding Peripheral

- Parameters:
- delegate: The BlePeripheralDelegate
- peripheral: The discovered Peripheral
*/
init(peripheral: CBPeripheral) {
super.init()
self.peripheral = peripheral
}

/**

53
Get a broadcast name from an advertisementData packet.
This may be different than the actual broadcast name
*/
static func getAlternateBroadcastFromAdvertisementData(
advertisementData: [String : Any]) -> String?
{
// grab thekCBAdvDataLocalName from the advertisementData
// to see if there's an alternate broadcast name
if advertisementData["kCBAdvDataLocalName"] != nil {
return (advertisementData["kCBAdvDataLocalName"] as! String)
}
return nil
}

Storyboard

Create a UITableView and connect it to the UINavigationController in place of the de-


fault UIView. Give it the class name "PeripheralTableView."

Add a UITableViewCell to it, with the class name "PeripheralTableViewCell" and the
Reuse Identifier of "PeripheralTableViewCell." In the PeripheralTableViewCell, create
and link three UILabels to be used for describing the connected Peripheral properties
(Figure 4-4):

54
Figure 4-4. Project Storyboard

Views

Link the three UILabels from the PeripheralTableViewCell to the corresponding swift
file and create a render function:

Example 4-2. UI/Views/PeripheralTableViewCell.swift

import UIKit
import CoreBluetooth

class PeripheralTableViewCell: UITableViewCell {

// MARK: UI Elements
@IBOutlet weak var advertisedNameLabel: UILabel!
@IBOutlet weak var identifierLabel: UILabel!

55
@IBOutlet weak var rssiLabel: UILabel!

/**
Render Cell with Peripheral properties
*/
func renderPeripheral(_ blePeripheral: BlePeripheral) {
advertisedNameLabel.text = blePeripheral.advertisedName
identifierLabel.text = \
blePeripheral.peripheral.identifier.uuidString
rssiLabel.text = blePeripheral.rssi.stringValue
}
}

Controllers

The View Controller must be able to initialize a scan when the user clicks the Scan
button. It will scan for Peripherals for 5 seconds, an arbitrarily reasonable scanning
time. The UITableView is updated with each new Peripheral as discovered.

Example 4-3. UI/Controllers/PeripheralTableViewController.swift

import UIKit
import CoreBluetooth

class PeripheralTableViewController: UITableViewController, \


CBCentralManagerDelegate {

// MARK: UI Elements
@IBOutlet weak var scanButton: UIButton!
// Default unknown advertisement name
let unknownAdvertisedName = "(UNMARKED)"
// PeripheralTableViewCell reuse identifier
let peripheralCellReusedentifier = "PeripheralTableViewCell"

// MARK: Scan Properties

56
// total scan time
let scanTimeout_s = 5; // seconds
// current countdown
var scanCountdown = 0
// scan timer
var scanTimer:Timer!
// Central Bluetooth Manager
var centralManager:CBCentralManager!
// discovered peripherals
var blePeripherals = [BlePeripheral]()

/**
View loaded. Start Bluetooth radio.
*/
override func viewDidLoad() {
super.viewDidLoad()
print("Initializing central manager")
centralManager = CBCentralManager(delegate: self, queue: nil)
}

/**
User touched the "Scan/Stop" button
*/
@IBAction func onScanButtonTouched(_ sender: UIButton) {
print("scan button clicked")
// if scanning
if scanCountdown > 0 {
stopBleScan()
} else {
startBleScan()
}
}

/**
Scan for Bluetooth peripherals

57
*/
func startBleScan() {
scanButton.setTitle("Stop", for: UIControlState.normal)
blePeripherals.removeAll()
tableView.reloadData()
print ("discovering devices")
scanCountdown = scanTimeout_s
scanTimer = Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(updateScanCounter),
userInfo: nil,
repeats: true)
if let centralManager = centralManager {
centralManager.scanForPeripherals(
withServices: nil,
options: nil)
}
}

/**
Stop scanning for Bluetooth Peripherals
*/
func stopBleScan() {
if let centralManager = centralManager {
centralManager.stopScan()
}
scanTimer.invalidate()
scanCountdown = 0
scanButton.setTitle("Start", for: UIControlState.normal)
}

/**
Update the scan countdown timer
*/

58
func updateScanCounter() {
//you code, this is an example
if scanCountdown > 0 {
print("\(scanCountdown) seconds until Ble Scan ends")
scanCountdown -= 1
} else {
stopBleScan()
}
}

// MARK: CBCentralManagerDelegate Functions

/**
New Peripheral discovered

- Parameters
- central: the CentralManager for this UIView
- peripheral: a discovered Peripheral
- advertisementData: the Bluetooth GAP data discovered
- rssi: the radio signal strength indicator for this Peripheral
*/
func centralManager(
_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any],
rssi RSSI: NSNumber)
{
print("Discovered \(peripheral.identifier.uuidString) " + \
"(\(peripheral.name))")

// check if this peripheral has already been discovered


var peripheralFound = false
for blePeripheral in blePeripherals {
if blePeripheral.peripheral.identifier == \
peripheral.identifier
{

59
peripheralFound = true
break
}
}

// don't duplicate discovered devices


if !peripheralFound {
print(advertisementData)
// Broadcast name in advertisement data
// may be different than the actual broadcast name
// It's ideal to use the advertisement data version
// as it's supported on programmable bluetooth devices
var advertisedName = unknownAdvertisedName
if let alternateName = \
BlePeripheral.getAlternateBroadcastFromAdvertisementData(
advertisementData: advertisementData)
{
if alternateName != "" {
advertisedName = alternateName
} else {
if let peripheralName = peripheral.name{
advertisedName = peripheralName
}
}
}

let blePeripheral = BlePeripheral(peripheral: peripheral)


blePeripheral.rssi = RSSI
blePeripheral.advertisedName = advertisedName
blePeripherals.append(blePeripheral)
tableView.reloadData()
}
}

/**
Bluetooth radio state changed

60
- Parameters:
- central: the reference to the central
*/
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("Central Manager updated: checking state")
switch (central.state) {
case .poweredOn:
print("BLE Hardware powered on and ready")
scanButton.isEnabled = true
default:
print("Bluetooth unavailable")
}
}

// MARK: - Table view data source

/**
return number of sections. Only 1 is needed
*/
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

/**
Return number of Peripheral cells
*/
override func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int
{
return blePeripherals.count
}

/**
Return rendered Peripheral cell

61
*/
override func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
print("setting up table cell")
let cell = tableView.dequeueReusableCell(
withIdentifier: peripheralCellReusedentifier,
for: indexPath) as! PeripheralTableViewCell
// fetch the appropritae peripheral for the data source layout
let peripheral = blePeripherals[indexPath.row]
cell.renderPeripheral(peripheral)

return cell
}
}

Compile and run the app. When it runs, you will see a screen with a scan button.
When the scan button is clicked, it locates your BLE device (Figure 4-5).

62
Figure 4-5. App screen prior to a Bluetooth scan and after discovering Blue-
tooth Peripherals

Programming the Peripheral


The previous Chapter showed how to turn on the Bluetooth radio and detect if Periph-
eral is supported by the iOS hardware

This chapter will show how to advertise a Bluetooth Low Energy Peripheral.

63
Advertising is simple. Create a Dictionary of advertising parameters and pass the Dic-
tionary into the CBPeripheralManager.startAdvertising method:

let advertisementData:[String: Any] = [


CBAdvertisementDataLocalNameKey: advertisingName
]
peripheralManager.startAdvertising(advertisementData)

The Dictionary may contain up to two keys, CBAdvertisementDataLocalNameKey


and CBAdvertisementDataServiceUUIDsKey:

Table 4-2 . advertisementData Dictionary

State Data Type Description

CBAdvertisementDataLocalName The advertised name of a


String
Key Bluetooth Peripheral

CBAdvertisementDataServiceUUI
DsKey [CBUUID] UUIDs of listed Services

As this chapter is focused on Advertising, only the first parameter will be discussed.

When the Peripheral begins to fails to Advertise, the peripheralManagerDidStartAdver-


tising callback of the CBPeripheralManagerDelegate will be triggered, containing an
updated CBPeripheralManager and possible Error:

func peripheralManagerDidStartAdvertising(
_ peripheral: CBPeripheralManager,
error: Error?)
{
if error != nil {
print ("Error advertising peripheral")
print(error.debugDescription)
}
// store a copy of the updated Peripheral

64
peripheralManager = peripheral
}

To stop Advertising, use the CBPeripheralManager.stopAdvertising method:

peripheralManager.stopAdvertising()

Putting It All Together


Create a new app called ExampleBlePeripheral, and copy everything from the previ-
ous example.

This example will add a UISwitch that shows when a Peripheral has begun Advertis-
ing.

Custom scanner callbacks will be created, which respond to events when scanning
has stopped.

Models

Add an advertisingName to the BlePeripheral, functionality to start and stop Advertis-


ing, and a callback handler to handle the peripheralManagerDidStartAdvertising call-
back:

Example 4-4. Models/BlePeripheral.swift

class BlePeripheral : NSObject, CBPeripheralManagerDelegate {

// MARK: Peripheral properties

// Advertized name
let advertisingName = "MyDevice"
...
/**

65
Stop advertising, shut down the Peripheral
*/
func stop() {
peripheralManager.stopAdvertising()
}

/**
Start Bluetooth Advertising.
*/
func startAdvertising() {
let advertisementData:[String: Any] = [
CBAdvertisementDataLocalNameKey: advertisingName
]
peripheralManager.startAdvertising(advertisementData)
}

// MARK: CBPeripheralManagerDelegate

/**
Peripheral started advertising
*/
func peripheralManagerDidStartAdvertising(
_ peripheral: CBPeripheralManager,
error: Error?)
{
if error != nil {
print ("Error advertising peripheral")
print(error.debugDescription)
}
self.peripheralManager = peripheral
delegate?.blePerihperal?(startedAdvertising: error)
}
...
}

66
Delegates

Add a new method to the BlePeripheralDelegate to relay the Advertising started


event:

Example 4-5. Delegates/BlePeripheralDelegate.swift

...
/**
BlePeripheral statrted adertising

- Parameters:
- error: the error message, if any
*/
@objc optional func blePerihperal(startedAdvertising error: Error?)
...

Storyboard

Add a UILabel to show the Peripheral's Advertised name and a UISwitch to show the
Advertising state of the Peripheral (Figure 4-6):

67
Figure 4-6. Project Storyboard

Controllers

Add a UILabel to display the Advertising name and a UISwitch to show the Advertis-
ing state of the Peripheral. Add functionality in the viewDidAppear, viewWillLoad, and
viewDidDisappear to change the text on the UILabel and the state of the UISwitch to
reflect the state of the Peripheral. And add a callback handler for the new BlePeripher-
alDelegate method:

Example 4-6. UI/Controllers/ViewController.swift

...
// MARK: UI Elements
@IBOutlet weak var advertisingLabel: UILabel!
@IBOutlet weak var advertisingSwitch: UISwitch!
...

68
/**
View appeared. Start the Peripheral
*/
override func viewDidAppear(_ animated: Bool) {
blePeripheral = BlePeripheral(delegate: self)
advertisingLabel.text = blePeripheral.advertisingName
}

/**
View will appear. Stop transmitting random data
*/
override func viewWillDisappear(_ animated: Bool) {
blePeripheral.stop()
}

/**
View disappeared. Stop advertising
*/
override func viewDidDisappear(_ animated: Bool) {
advertisingSwitch.setOn(false, animated: true)
}
...
/**
BlePeripheral statrted adertising

- Parameters:
- error: the error message, if any
*/
func blePerihperal(startedAdvertising error: Error?) {
if error != nil {
print("Problem starting advertising: " + error.debugDescription)
} else {
print("adertising started")
advertisingSwitch.setOn(true, animated: true)
}
}

69
...

Compile and run the app. When it runs, a Bluetooth Peripheral will be advertising (Fig-
ure 4-7).

Figure 4-7. App screen showing advertising Peripheral

70
Example code
The code for this chapter is available online
at: https://github.com/BluetoothLowEnergyIniOSSwift/Chapter04

71
Connecting

Each Bluetooth Device has a unique Media Access Control (MAC) address, a 48-bit
identifier value written like this

00:A0:C9:14:C8:3A

Devices advertise data on the network with the intended recipient's MAC address at-
tached so that recipient devices can filter data packets that are intended for them.

For security reasons, iOS does not reveal the MAC address of a Peripheral. Instead,
it creates a 32-bit UUID identifier, like this:

2fe058bd-5edb-4b3f-b7bd-fc8e93e2dbc4

Once a Central has discovered a Peripheral, the central can attempt to connect. This
must be done before data can be passed between the Central and Peripheral. A Cen-
tral may hold several simultaneous connections with a number of peripherals, but a
Peripheral may only hold one connection at a time. Hence the names Central and Pe-
ripheral (Figure 5-1).

Figure 5-1. Bluetooth network topology

72
Bluetooth supports data 37 data channels ranging from 2404 MHz to 2478 MHz.

Once the connection is established, the Central and Peripheral negotiate which of
these channels to begin communicating over.

Because the Peripheral can only hold one connection at a time, it must disconnect
from the Central before a new connection can be made.

The connection and disconnection process works like this (Figure 5-2).

Figure 5-2. Connection and disconnection process

Programming the Central


The previous chapter’s App showed how to discover nearby Peripherals. Once a Pe-
ripheral is discovered, iOS can initiate a connection like this:

centralManager.connect(peripheral)

73
If the connection is successful, the didConnect callback will be triggered.

func centralManager(
_ central: CBCentralManager,
didConnect peripheral: CBPeripheral)
{
}

The didFailToConnect will be triggered otherwise.

func centralManager(
_ central: CBCentralManager,
didFailToConnect peripheral: CBPeripheral,
error: Error?)
{
}

A disconnection can be initiated like this:

centralManager.cancelPeripheralConnection(peripheral)

When successful, the didDisconnectPeripheral event will be triggered:

func centralManager(
_ central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
error: Error?)
{
}

74
Disconnecting is important. The Peripheral can only be connected to one device at a
time. Sometimes, closing an Activity without disconnecting the Peripheral can leave
the Peripheral in a connected state - unable to advertise or connect to a Central
again in the future.

Putting It All Together


Create a new project called Connecting and copy everything from the previous exam-
ple. This example will show how to create an app that looks for a “MyDevice” BLE ad-
vertisement, and connect to it.

BlePeripheral represents a remote Peripheral. PeripheralViewController will connect


to the Peripheral and list the Peripheral properties.

Create a project structure that resembles this (Figure 5-3).

75
Figure 5-3. Added project structure

Models

Modify the BlePeripheral to support CBPeripheralDelegate callbacks

Example 5-1. Models/BlePeripheral.swift

import UIKit
import CoreBluetooth

76
class BlePeripheral: NSObject, CBPeripheralDelegate {
// MARK: Peripheral properties

// delegate
var delegate:BlePeripheralDelegate?
// connected Peripheral
var peripheral:CBPeripheral!
// advertised name
var advertisedName:String!
// RSSI
var rssi:NSNumber!

/**
Initialize BlePeripheral with a corresponding Peripheral

- Parameters:
- delegate: The BlePeripheralDelegate
- peripheral: The discovered Peripheral
*/
init(delegate: BlePeripheralDelegate?, peripheral: CBPeripheral) {
super.init()
self.peripheral = peripheral
self.peripheral.delegate = self
self.delegate = delegate
}

/**
Notify the BlePeripheral that the peripheral has been connected

- Parameters:
- peripheral: The discovered Peripheral
*/
func connected(peripheral: CBPeripheral) {
self.peripheral = peripheral
self.peripheral.delegate = self
// check for services and the RSSI

77
self.peripheral.readRSSI()
}

/**
Get a broadcast name from an advertisementData packet.
This may be different than the actual broadcast name
*/
static func getAlternateBroadcastFromAdvertisementData(
advertisementData: [String : Any]) -> String?
{
// grab thekCBAdvDataLocalName from the advertisementData
// to see if there's an alternate broadcast name
if advertisementData["kCBAdvDataLocalName"] != nil {
return (advertisementData["kCBAdvDataLocalName"] as! String)
}
return nil
}

/**
Determine if this peripheral is connectable
from it's advertisementData packet.
*/
static func isConnectable(advertisementData: [String: Any]) -> Bool {
let isConnectable = \
advertisementData["kCBAdvDataIsConnectable"] as! Bool
return isConnectable
}

// MARK: CBPeripheralDelegate

/**
RSSI read from peripheral.
*/
func peripheral(
_ peripheral: CBPeripheral,
didReadRSSI RSSI: NSNumber,

78
error: Error?)
{
print("RSSI: \(RSSI.stringValue)")
rssi = RSSI
delegate?.blePeripheral?(readRssi: rssi, blePeripheral: self)

}
}

Delegates

Create a BlePeripheralDelegate class that lets the BlePeripheral trigger events as a


result of changes in its state.

Example 5-2. Models/BlePeripheralDelegate.swift

import UIKit
import CoreBluetooth

@objc protocol BlePeripheralDelegate: class {


/**
RSSI was read for a Peripheral

- Parameters:
- rssi: the RSSI
- blePeripheral: the BlePeripheral
*/
@objc optional func blePeripheral(
readRssi rssi: NSNumber,
blePeripheral: BlePeripheral)
}

79
Storyboard

Create a new UIView and give it the class name "PeripheralViewController." Add a se-
gue between the two UIViews. Create and link three UILabels to be used for describ-
ing the connected Peripheral properties (Figure 5-4):

Figure 5-4. Project Storyboard

Controllers

The previous app was able to detect and list nearby Peripherals. This app will allow
connecting to a Peripheral when a user selects that Peripheral from the UITableView.

Add a segue and UITableViewDelegate functionality in the PeripheralTableViewCon-


troller.

80
Example 5-3. UI/Controllers/PeripheralTableViewController.swift

...
override func tableView(
_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath)
{
stopBleScan()
let selectedRow = indexPath.row
print("Row: \(selectedRow)")
print(blePeripherals[selectedRow])
}

// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let peripheralViewController = \
segue.destination as! PeripheralViewController
if let selectedIndexPath = tableView.indexPathForSelectedRow {
let selectedRow = selectedIndexPath.row
if selectedRow < blePeripherals.count {
// prepare next UIView
peripheralViewController.centralManager = centralManager
peripheralViewController.blePeripheral = \
blePeripherals[selectedRow]
}
tableView.deselectRow(at: selectedIndexPath, animated: true)
}
}
}

The PeripheralViewController will connect to a Peripheral and list the advertised


name and identifier in UILabels

When a connection is confirmed, the user interface is updated.

81
Example 5-4. UI/Controllers/PeripheralViewController.swift

import UIKit
import CoreBluetooth

class PeripheralViewController: UIViewController, \


CBCentralManagerDelegate, BlePeripheralDelegate {

// MARK: UI Elements
@IBOutlet weak var advertisedNameLabel: UILabel!
@IBOutlet weak var identifierLabel: UILabel!
@IBOutlet weak var rssiLabel: UILabel!

// MARK: Connected Peripheral Properties

// Central Manager
var centralManager:CBCentralManager!
// connected Peripheral
var blePeripheral:BlePeripheral!

/**
UIView loaded
*/
override func viewDidLoad() {
super.viewDidLoad()
print("Will connect to " + \
"\(blePeripheral.peripheral.identifier.uuidString)")
// Assign delegates
blePeripheral.delegate = self
centralManager.delegate = self
centralManager.connect(blePeripheral.peripheral)

/**
RSSI discovered. Update UI

82
*/
func blePeripheral(
readRssi rssi: NSNumber,
blePeripheral: BlePeripheral)
{
rssiLabel.text = rssi.stringValue
}

// MARK: CBCentralManagerDelegate code

/**
Peripheral connected. Update UI
*/
func centralManager(
_ central: CBCentralManager,
didConnect peripheral: CBPeripheral)
{
print("Connected Peripheral: \(peripheral.name)")
advertisedNameLabel.text = blePeripheral.advertisedName
identifierLabel.text =\
blePeripheral.peripheral.identifier.uuidString
blePeripheral.connected(peripheral: peripheral)
}

/**
Connection to Peripheral failed.
*/
func centralManager(
_ central: CBCentralManager,
didFailToConnect peripheral: CBPeripheral,
error: Error?)
{
print("failed to connect")
print(error.debugDescription)
}

83
/**
Peripheral disconnected. Leave UIView
*/
func centralManager(
_ central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
error: Error?)
{
print("Disconnected Peripheral: \(peripheral.name)")
dismiss(animated: true, completion: nil)
}

/**
Bluetooth radio state changed.
*/
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("Central Manager updated: checking state")
switch (central.state) {
case .poweredOn:
print("bluetooth on")
default:
print("bluetooth unavailable")
}
}

// MARK: Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("leaving view - disconnecting from peripheral")
if let peripheral = blePeripheral.peripheral {
centralManager.cancelPeripheralConnection(peripheral)
}
}
}

84
The resulting App will be one that can scan and connect to an advertising Peripheral
(Figure 5-5).

Figure 5-5. App screen after connecting to a Peripheral

85
Peripheral Programming
In iOS, no notifications are sent when a Peripheral is connected to. There Peripheral
code remains the same as the previous chapter.

Example code
The code for this chapter is available online
at: https://github.com/BluetoothLowEnergyIniOSSwift/Chapter05

86
Services and Characteristics

Before data can be transmitted back and forth between a Central and Peripheral, the
Peripheral must host a GATT Profile. That is, the Peripheral must have Services and
Characteristics.

Identifying Services and Characteristics


Each Service and Characteristic is identified by a Universally Unique Identifier (UUID).
The UUID follows the pattern 0000XXXX-0000-1000-8000-00805f9b34fb, so that a
32-bit UUID 00002a56-0000-1000-8000-00805f9b34fb can be represented as
0x2a56.

Some UUIDs are reserved for specific use. For instance any Characteristic with the
16-bit UUID 0x2a35 (or the 32-bit UUID 00002a35-0000-1000-8000-00805f9b34fb) is
implied to be a blood pressure reading.

For a list of reserved Service UUIDs, see Appendix IV: Reserved GATT Services.

For a list of reserved Characteristic UUIDs, see Appendix V: Reserved GATT Char-
acteristics.

Generic Attribute Profile


Services and Characteristics describe a tree of data access points on the peripheral.
The tree of Services and Characteristics is known as the Generic Attribute (GATT) Pro-
file. It may be useful to think of the GATT as being similar to a folder and file tree (Fig-
ure 6-1).

87
Service/
Characterstic
Characterstic
Characterstic
Service/
Characterstic
Characterstic
Characterstic

Figure 6-1. GATT Profile filesystem metaphor

Characteristics act as channels that can be communicated on, and Services act as
containers for Characteristics. A top level Service is called a Primary service, and a
Service that is within another Service is called a Secondary Service.

Permissions
Characteristics can be configured with the following attributes, which define what the
Characteristic is capable of doing (Table 6-1):

88
Table 6-1. Characteristic Permissions

Descriptor Description

Read Central can read this Characteristic, Peripheral can set the value.

Central can write to this Characteristic, Peripheral will be notified when


Write the Characteristic value changes and Central will be notified when the
write operation has occurred.

Central can write to this Characteristic. Peripheral will be notified when


Write without Response the Characteristic value changes but the Central will not be notified that
the write operation has occurred.

Notify Central will be notified when Peripheral changes the value.

Because the GATT Profile is hosted on the Peripheral, the terms used to describe a
Characteristic’s permissions are relative to how the Peripheral accesses that Charac-
teristic. Therefore, when a Central uploads data to the Peripheral, the Peripheral can
“read” from the Characteristic. The Peripheral “writes” new data to the Characteris-
tic, and can “notify” the Central that the data is altered.

Data Length and Speed


It is worth noting that Bluetooth Low Energy has a maximum data packet size of 20
bytes, with a 1 Mbit/s speed.

Programming the Central

89
The Central can be programmed to read the GATT Profile of the Peripheral after con-
nection, like this:

peripheral.discoverServices(nil)

If only a subset of the Services hosted by the Peripheral are needed, those Service
UUIDs can be passed into the discoverServices function like this:

let serviceUuids = [ "1800", "1815" ]


peripheral.discoverServices(serviceUuids)

When the Services are discovered, a callback will be executed by the CBPeripheral-
ManagerDelegate, containing an updated CBPeripheral object. This updated object
contains an array of Services:

func peripheral(
_ peripheral: CBPeripheral,
didDiscoverServices error: Error?)
{
if error != nil {
print("Discover service Error: \(error)")
}
}

In order for the class to access these methods, it must implement CBPeripheralMana-
gerDelegate.

There are Primary Services and Secondary services. Secondary Services are con-
tained within other Services; Primary Services are not. The type of Service can be dis-
covered by inspecting the CBService.isPrimary flag.

boolean isPrimary = service.isPrimary

90
To discover the Characteristics hosted by these services, simply loop through the dis-
covered Services and handle the resulting peripheral didDiscoverCharacteristicsFor
callback:

func peripheral(
_ peripheral: CBPeripheral,
didDiscoverServices error: Error?)
{
if error != nil {
print("Discover service Error: \(error)")
} else {
for service in peripheral.services!{
self.peripheral.discoverCharacteristics(nil, for: service)
}
}
}

func peripheral(
_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error: Error?)
{
let serviceIdentifier = service.uuid.uuidString
if let characteristics = service.characteristics {
for characteristic in characteristics {
// do something with Characteristic
}
}
}

Each Characteristic has certain permission properties that allow the Central to read,
write, or receive notifications from it (Table 6-2).

91
Table 6-2. CBCharacteristicProperties

Permissi
Value Description
on

read Read Central can read data altered by the Peripheral

write Write Central can send data, Peripheral reads

writeWithoutResp Central can send data. No response from


Write
onse Peripheral

notify Notify Central is notified as a result of a change

In iOS, these properties are expressed as a binary integer which can be extracted like
this:

let properties = characteristic.properties.rawValue


let isWritable = (properties & \
CBCharacteristicProperties.write.rawValue) != 0;
let isWritableNoResponse = (properties & \
CBCharacteristicProperties.writeWithoutResponse.rawValue) != 0;
let isReadable = (properties & \
CBCharacteristicProperties.read.rawValue) != 0;
let isNotifiable = (properties & \
CBCharacteristicProperties.notify.rawValue) != 0;

A Note on Caching
Because Bluetooth was designed to be a low-power protocol, measures are taken to
limit redundancy and power consumption through radio and CPU usage. As a result,
a Peripheral’s GATT Profile is cached on iOS. This is not a problem for normal use,

92
but when developing, it can be confusing to change Characteristic permissions and
not see the updates reflected on iOS.

To get around this, the iOS device must be restarted each time a Peripheral with the
same Identifier has has changed its GATT Profile

Putting It All Together


This app will work like the one from the previous chapter, except that once the it con-
nects to the Peripheral, it will also list the GATT Profile for that Peripheral. The GATT
Profile will be displayed in an UITableView (Figure 6-2).

Figure 6-2. GATT Profile downloaded from Peripheral

Create a new project called Services and copy everything from the previous example.
(Figure 6-3).

93
Figure 6-3. Project Structure

Objects

Modify BlePeripheral.swift to discover Services and Characteristics

Example 6-1. Models/BlePeripheral.swift

...
/**
Servicess were discovered on the connected Peripheral
*/
func peripheral(
_ peripheral: CBPeripheral,

94
didDiscoverServices error: Error?)
{
print("services discovered")
// clear GATT profile - start with fresh services listing
gattProfile.removeAll()
if error != nil {
print("Discover service Error: \(error)")
} else {
print("Discovered Service")
for service in peripheral.services!{
self.peripheral.discoverCharacteristics(nil, for: service)
}
print(peripheral.services!)
}
}

/**
Characteristics were discovered
for a Service on the connected Peripheral
*/
func peripheral(
_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error: Error?)
{
print("characteristics discovered")
// grab the service
let serviceIdentifier = service.uuid.uuidString
print("service: \(serviceIdentifier)")

gattProfile.append(service)
if let characteristics = service.characteristics {
print("characteristics found: \(characteristics.count)")
for characteristic in characteristics {
print("-> \(characteristic.uuid.uuidString)")
}

95
delegate?.blePerihperal?(
discoveredCharacteristics: characteristics,
forService: service,
blePeripheral: self)
}
}
...

Delegates

Add a function to the BlePeripheralDelegate to alert when Characteristics have been


discovered:

Example 6-2. Delegates/BlePeripheralDelegate.swift

...
/**
Characteristics were discovered for a Service

- Parameters:
- characteristics: the Characteristic list
- forService: the Service these Characteristics are under
- blePeripheral: the BlePeripheral
*/
@objc optional func blePerihperal(
discoveredCharacteristics characteristics: [CBCharacteristic],
forService: CBService,
blePeripheral: BlePeripheral)
...

96
Storyboard

Add a UITableView and UITableViewCell to the PeripheralViewController. Make the UI-


TableView a "grouped" TableVew and make the UITableViewCell of class "GattTa-
bleViewCell" Give it the Reuse Identifier "GattTableViewCell." In the new GattTa-
bleViewCell, create and link a UILabel to be used to hold the Characteristic UUID (Fig-
ure 6-4):

Figure 6-4. Project Storyboard

Views

The GATT Profile will be represented as a Grouped UITableView, with Services as the
table header and Characteristics as the GattTableViewCell table cell.

Each GattTableViewCell will display the UUID of a Characteristic.

97
Example 6-3. UI/Views/GattTableViewCell.swift

import UIKit
import CoreBluetooth

class GattTableViewCell: UITableViewCell {


@IBOutlet weak var uuidLabel: UILabel!

func renderCharacteristic(characteristic: CBCharacteristic) {


uuidLabel.text = characteristic.uuid.uuidString
print(characteristic.uuid.uuidString)
}
}

Controllers

Add functionality in the PeripheralViewController to render the GATT Profile table and
to handle the blePeripheral discoveredCharacteristics callback from the BlePeripheral-
Delegate class:

Example 6-4. UI/Controllers/PeripheralViewController.swift

class PeripheralViewController: UIViewController, UITableViewDataSource, \


UITableViewDelegate, CBCentralManagerDelegate, BlePeripheralDelegate {

// MARK: UI Elements
@IBOutlet weak var advertisedNameLabel: UILabel!
@IBOutlet weak var identifierLabel: UILabel!
@IBOutlet weak var rssiLabel: UILabel!
@IBOutlet weak var gattProfileTableView: UITableView!
@IBOutlet weak var gattTableView: UITableView!

// Gatt Table Cell Reuse Identifier


let gattCellReuseIdentifier = "GattTableViewCell"
...

98
// MARK: BlePeripheralDelegate

/**
Characteristics were discovered. Update the UI
*/
func blePerihperal(
discoveredCharacteristics characteristics: [CBCharacteristic],
forService: CBService,
blePeripheral: BlePeripheral)
{
gattTableView.reloadData()
}

/**
RSSI discovered. Update UI
*/
func blePeripheral(
readRssi rssi: NSNumber,
blePeripheral: BlePeripheral)
{
rssiLabel.text = rssi.stringValue
}

// MARK: UITableViewDataSource

/**
Return number of rows in Service section
*/
func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int
{
print("returning num rows in section")
if section < blePeripheral.gattProfile.count {
if let characteristics = \
blePeripheral.gattProfile[section].characteristics

99
{
return characteristics.count
}
}
return 0
}

/**
Return a rendered cell for a Characteristic
*/
func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
print("returning table cell")
let cell = tableView.dequeueReusableCell(
withIdentifier: gattCellReuseIdentifier,
for: indexPath) as! GattTableViewCell
let section = indexPath.section
let row = indexPath.row

if section < blePeripheral.gattProfile.count {


if let characteristics = \
blePeripheral.gattProfile[section].characteristics
{
if row < characteristics.count {
cell.renderCharacteristic(
characteristic: characteristics[row])
}
}
}
return cell
}

/**
Return the number of Service sections

100
*/
func numberOfSections(in tableView: UITableView) -> Int {
print("returning number of sections")
print(blePeripheral)
print(blePeripheral.gattProfile)
return blePeripheral.gattProfile.count
}

/**
Return the title for a Service section
*/
func tableView(
_ tableView: UITableView,
titleForHeaderInSection section: Int) -> String?
{
print("returning title at section \(section)")
if section < blePeripheral.gattProfile.count {
return blePeripheral.gattProfile[section].uuid.uuidString
}
return nil
}

/**
User selected a Characteristic table cell.
Update UI and open the next UIView
*/
func tableView(
_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath)
{
let selectedRow = indexPath.row
print("Selected Row: \(selectedRow)")
tableView.deselectRow(at: indexPath, animated: true)
}

101
// MARK: Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("leaving view - disconnecting from peripheral")
if let peripheral = blePeripheral.peripheral {
centralManager.cancelPeripheralConnection(peripheral)
}
}
}

The resulting app will be able to connect to a Peripheral and list the Services and
Characteristics (Figure 6-5).

102
Figure 6-5. App screen showing GATT profile from a connected Peripheral

Programming the Peripheral


The Peripheral can be programmed to host a GATT Profile - the tree structure of Serv-
ices and Characteristics that a connected Central will use to communicate with the
Peripheral.

Services are created and added to the CBPeripheralManager, like this:

103
// Service UUID
let serviceUuid = CBUUID(string: "0000180c-0000-1000-8000-00805f9b34fb")
let service = CBMutableService(type: serviceUuid, primary: true)
peripheralManager.add(service)

When a Service is added to the Peripheral, the peripheralManager didAdd callback


will be triggered by the CBPeripheralManagerDelegate.

func peripheralManager(
_ peripheral: CBPeripheralManager,
didAdd service: CBService, error: Error?)
{
}

Characteristics must have defined properties. These properties allow a connected


Central to read data from, write data to, and/or subscribe to notifications from a Char-
acteristic. Some common properties are enumerated in the CBCharacteristicProper-
ties class:

Table 6-3. Common CBCharacteristicProperties

Permissi
Value Description
on

read Read The characteristic’s value can be read.

The characteristic’s value can be written, with a


write Write response from the peripheral to indicate that
the write was successful.

writeWithoutResp The characteristic’s value can be written,


Write
onse without a response from the peripheral.

Notifications of the characteristic’s value are


notify Notify
permitted.

104
Create the Characteristics properties by instanciating and merging CBCharacteris-
ticProperties:

// create read Characteristic properties


var characteristicProperties = CBCharacteristicProperties.read
// append write propertie
characteristicProperties.formUnion(CBCharacteristicProperties.write)
// append notify support
characteristicProperties.formUnion(CBCharacteristicProperties.notify)

A Characteristic must also define it's attribute permissions. An example of an attrib-


ute is a flag that is set when a connected Characteristic wants to subscribe to a Char-
actersitic.

Examples of common Attribute Permissions are enumerated in the CBAttributePer-


missions class.

Table 6-3. Common CBAttributePermissions

Value Description

The Characteristic’s Attributes can be read by a


readable
connected Central.

The Characteristic’s Attributes can be altered by a


writeable
connected Central.

Create Attribute permissions.

// set the Characteristic's Attribute permissions


var characterisitcPermissions = CBAttributePermissions.writeable
// append permissions
characterisitcPermissions.formUnion(CBAttributePermissions.readable)

105
Create a new CBMutableCharacteristic with the defined properties. Optionally an ini-
tial value can be set.

let characteristicUuid = \
CBUUID(string: "00002a56-0000-1000-8000-00805f9b34fb")
var value:Data!

// instantiate a Characteristic
let characteristic = CBMutableCharacteristic(
type: characteristicUuid,
properties: characteristicProperties,
value: value,
permissions: characterisitcPermissions)

Add one or more Characteristics to a Service by creating a [CBMutableCharacteris-


tic] array.

// set the service Characterisic array


service.characteristics = [ characteristic ]

A Note on GATT Profile Best Practices


All Peripherals should contain Device information and a Battery Service, resulting in a
minimal GATT profile for any Peripheral that resembles this (Figure 6-6).

106
Figure 6-6. Minimal GATT Profile for Peripherals

This provides Central software, surveying tools, and future developers to better under-
stand what each Peripheral is, how to interact with it, and what the battery capabili-
ties are.

For pedagogical reasons, many of the examples will not include this portion of the
GATT Profile.

Putting It All Together


Create a new project called GattProfile and copy everything from the previous exam-
ple.

Models

Modify BlePeripheral.swift to build a minimal Gatt Services profile. Build the GATT
Profile structure, and handle the callback when Services are added.

Example 6-5. Models/BlePeripheral.swift

...

107
// MARK: GATT Profile

// Service UUID
let serviceUuid = CBUUID(string: "0000180c-0000-1000-8000-00805f9b34fb")
// Characteristic UUIDs
let characteristicUuid = CBUUID(
string: "00002a56-0000-1000-8000-00805f9b34fb")
// Read Characteristic
var characteristic:CBMutableCharacteristic!
...
/**
Build Gatt Profile.
This must be done after Bluetooth Radio has turned on
*/
func buildGattProfile() {
let service = CBMutableService(type: serviceUuid, primary: true)
var characteristicProperties = CBCharacteristicProperties.read
characteristicProperties.formUnion(
CBCharacteristicProperties.notify)
var characterisitcPermissions = CBAttributePermissions.writeable
characterisitcPermissions.formUnion(CBAttributePermissions.readable)

characteristic = CBMutableCharacteristic(
type: characteristicUuid,
properties: characteristicProperties,
value: nil,
permissions: characterisitcPermissions)
service.characteristics = [ characteristic ]
peripheralManager.add(service)
}
...
/**
Peripheral added a new Service
*/
func peripheralManager(
_ peripheral: CBPeripheralManager,

108
didAdd service: CBService,
error: Error?)
{
print("added service to peripheral")
if error != nil {
print(error.debugDescription)
}
}

/**
Bluetooth Radio state changed
*/
func peripheralManagerDidUpdateState(
_ peripheral: CBPeripheralManager)
{
peripheralManager = peripheral
switch peripheral.state {
case CBManagerState.poweredOn:
buildGattProfile()
startAdvertising()
default: break
}
delegate?.blePeripheral?(stateChanged: peripheral.state)
}
...

The resulting app will be able to host a minimal GATT Profile.

Example code
The code for this chapter is available online
at: https://github.com/BluetoothLowEnergyIniOSSwift/Chapter06

109
Reading Data from a Peripheral

The real value of Bluetooth Low Energy is the ability to transmit data wirelessly.

Bluetooth Peripherals are passive, so they don’t push data to a connected Central.
Instead, Centrals make a request to read data from a Characteristic. This can only
happen if the Characteristic enables the Read Attribute.

This is called “reading a value from a Characteristic.”

Therefore, if a Peripheral changes the value of a Characteristic, then later a Central


downloads data from the Peripheral, the process looks like this (Figure 7-1):

Figure 7-1. The process of a Central reading data from a Peripheral

A Central can read a Characteristic repeatedly, regardless if Characteristic’s value


has changed.

110
Programming the Central
Before reading data from a connected Peripheral, it may be useful to know if a Char-
acteristic provides read permission. Read permission can be read by getting the Char-
acteristic property bit map and isolating the read property from it, like this:

let isReadable = (characteristic.properties.rawValue & \


CBCharacteristicProperties.read.rawValue) != 0

Once the Central has a Bluetooth GATT connection and has access to a Characteris-
tic with which to communicate with a connected Peripheral, the Central can request
to read data from that Characteristic like this:

peripheral.readValue(for: characteristic)

This will initiate a read request from the Central to the Peripheral.

When the Central finishes reading data from the Peripheral’s Characteristic, the pe-
ripheral didUpdateValueFor method is triggered in the CBPeripheralManagerDele-
gate.

In this callback, the Characteristic’s value can read as a Data object using the
characteristic.value property.

func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?)
{
let value = characteristic.value
}

From here the data can be converted into any format, including a String or an Integer.

111
// convert to byte array
let byteArray = [UInt8](value)

// convert to String
let stringValue = String(data: value, encoding: .ascii)

// convert to signed integer


let intValue = value.withUnsafeBytes {
(ptr: UnsafePointer<Int>) -> Int in
return ptr.pointee
}

// convert to float
let floatValue = value.withUnsafeBytes {
(ptr: UnsafePointer<Float>) -> Float in
return ptr.pointee
}

Putting It All Together


Create a new project with the following structure (Figure 7-2).

112
Figure 7-2. Added project files

Models

Modify the BlePeripheral class to include a method that checks if a Characteristic is


readable, one that initiates a read request from a Characteristic, and one that re-
sponds to to the resulting callback.

Example 7-1. Models/BlePeripheral.swift

...
/**
Read from a Characteristic

113
*/
func readValue(from characteristic: CBCharacteristic) {
self.peripheral.readValue(for: characteristic)
}

/**
Check if Characteristic is readable

- Parameters:
- characteristic: The Characteristic to test

- returns: True if characteristic is readable


*/
static func isCharacteristic(
isReadable characteristic: CBCharacteristic) -< Bool
{
if (characteristic.properties.rawValue & \
CBCharacteristicProperties.read.rawValue) != 0
{
return true
}
return false
}

// MARK: CBPeripheralDelegate

/**
Value downloaded from Characteristic on connected Peripheral
*/
func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?)
{
print("characteristic updated")
if let value = characteristic.value {

114
print(value.debugDescription)
print(value.description)

if let stringValue = String(data: value, encoding: .ascii) {


print(stringValue)
// received response from Peripheral
delegate?.blePeripheral?(
characteristicRead: stringValue,
characteristic: characteristic,
blePeripheral: self)
}
}
}
}

Delegates

Add a method to the BlePeripheralDelegate that alerts subscribers of a read opera-


tion on a Characteristic.

Example 7-2. Delegates/BlePeripheralDelegate.swift

...
/**
Characteristic was read

- Parameters:
- stringValue: the value read from the Charactersitic
- characteristic: the Characteristic that was read
- blePeripheral: the BlePeripheral
*/
@objc optional func blePeripheral(
characteristicRead stringValue: String,
characteristic: CBCharacteristic,
blePeripheral: BlePeripheral)

115
...

Storyboard

Create a new UIView, with class name "CharacteristicViewController" and a new se-
gue to it from the PeripheralViewController. Create and link three UILabel in the Gatt-
TableViewCell to show the Characteristic UUID and read/no access properties. Cre-
ate and link three UILabels to show the Peripheral and Characteristic identifiers, plus
a UIBUtton and UITextView to allow the user to trigger a Characteristic read and dis-
play the result on screen (Figure 7-3):

Figure 7-3. Project Storyboard

116
Views

Alter the renderCharacteristic method in the GattTableViewCell class to display the


appropriate UILabelViews when the Characteristic is readable or not

Example 7-3. UI/Views/GattTableViewCell.swift

import UIKit
import CoreBluetooth

class GattTableViewCell: UITableViewCell {

// MARK: UI Elements
@IBOutlet weak var uuidLabel: UILabel!
@IBOutlet weak var readableLabel: UILabel!
@IBOutlet weak var noAccessLabel: UILabel!

/**
Render the cell with Characteristic properties
*/
func renderCharacteristic(characteristic: CBCharacteristic) {
uuidLabel.text = characteristic.uuid.uuidString
let isReadable = \
BlePeripheral.isCharacteristic(isReadable: characteristic)
readableLabel.isHidden = !isReadable
if isReadable {
noAccessLabel.isHidden = true
} else {
noAccessLabel.isHidden = false
}
}
}

117
Controllers

Create a new view controller, CharacteristicViewController that will interact with a se-
lected Charactersitic. It will display the properties of the Characteristic and allow the
user to trigger a read event on the BlePeripheral. The Characteristic's value will be
displayed in a UITextView.

Example 7-4. UI/Controllers/CharacteristicViewController.swift

import UIKit
import CoreBluetooth

class CharacteristicViewController: UIViewController, \


CBCentralManagerDelegate, BlePeripheralDelegate {

// MARK: UI elements
@IBOutlet weak var advertizedNameLabel: UILabel!
@IBOutlet weak var identifierLabel: UILabel!
@IBOutlet weak var characteristicUuidlabel: UILabel!
@IBOutlet weak var readCharacteristicButton: UIButton!
@IBOutlet weak var characteristicValueText: UITextView!

// MARK: Connected devices

// Central Bluetooth Radio


var centralManager:CBCentralManager!
// Bluetooth Peripheral
var blePeripheral:BlePeripheral!
// Connected Characteristic
var connectedService:CBService!
// Connected Characteristic
var connectedCharacteristic:CBCharacteristic!

/**
UIView loaded
*/

118
override func viewDidLoad() {
super.viewDidLoad()
print("Will connect to device " + \
"\(blePeripheral.peripheral.identifier.uuidString)")
print("Will connect to characteristic " + \
"\(connectedCharacteristic.uuid.uuidString)")
centralManager.delegate = self
blePeripheral.delegate = self
loadUI()
}

/**
Load UI elements
*/
func loadUI() {
advertizedNameLabel.text = blePeripheral.advertisedName
identifierLabel.text = \
blePeripheral.peripheral.identifier.uuidString
characteristicUuidlabel.text = \
connectedCharacteristic.uuid.uuidString
readCharacteristicButton.isEnabled = true
// characteristic is not readable
if !BlePeripheral.isCharacteristic(
isReadable: connectedCharacteristic) {
readCharacteristicButton.isHidden = true
characteristicValueText.isHidden = true
}
}

/**
User touched Read button. Request to read the Characteristic
*/
@IBAction func onReadCharacteristicButtonTouched(_ sender: UIButton) {
print("pressed button")
readCharacteristicButton.isEnabled = false
blePeripheral.readValue(from: connectedCharacteristic)

119
}

// MARK: BlePeripheralDelegate

/**
Characteristic was read. Update UI
*/
func blePeripheral(
characteristicRead stringValue: String,
characteristic: CBCharacteristic,
blePeripheral: BlePeripheral)
{
print(stringValue)
readCharacteristicButton.isEnabled = true
characteristicValueText.insertText(stringValue + "\n")
let stringLength = characteristicValueText.text.characters.count
characteristicValueText.scrollRangeToVisible(NSMakeRange(
stringLength-1, 0))
}

// MARK: CBCentralManagerDelegate

/**
Peripheral disconnected

- Parameters:
- central: the reference to the central
- peripheral: the connected Peripheral
*/
func centralManager(
_ central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
error: Error?)
{
// disconnected. Leave
print("disconnected")

120
if let navController = navigationController {
navController.popToRootViewController(animated: true)
dismiss(animated: true, completion: nil)
}

/**
Bluetooth radio state changed

- Parameters:
- central: the reference to the central
*/
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("Central Manager updated: checking state")
switch (central.state) {
case .poweredOn:
print("bluetooth on")
default:
print("bluetooth unavailable")
}
}

// MARK: - Navigation

/**
Animate the segue
*/
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller
// using segue.destinationViewController.
// Pass the selected object to the new view controller.
if let connectedBlePeripheral = blePeripheral {
centralManager.cancelPeripheralConnection(
connectedBlePeripheral.peripheral)
}

121
}
}

Modify the PeripheralViewController to launch the CharacteristicViewCnotroller when


a table cell is clicked. Add the corresponding UITableViewDelegate methods and se-
gue.

Example 7-5. swift/example.com.exampleble/ConnectActivity.swift

...
/**
User selected a Characteristic table cell.
Update UI and open the next UIView
*/
func tableView(
_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath)
{
let selectedRow = indexPath.row
print("Selected Row: \(selectedRow)")
}

// MARK: Navigation

/**
Handle the Segue. Prepare the next UIView with necessary information
*/
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("leaving view - disconnecting from peripheral")

if let indexPath = gattTableView.indexPathForSelectedRow {


let selectedSection = indexPath.section
let selectedRow = indexPath.row
let characteristicViewController = \
segue.destination as! CharacteristicViewController

122
if selectedSection < blePeripheral.gattProfile.count {
let service = blePeripheral.gattProfile[selectedSection]
if let characteristics = \
blePeripheral.gattProfile[selectedSection].\
characteristics
{
if selectedRow < characteristics.count {
// populate next UIView with necessary information
characteristicViewController.centralManager = \
centralManager
characteristicViewController.blePeripheral = \
blePeripheral
characteristicViewController.connectedService = \
service
characteristicViewController.\
connectedCharacteristic = \
characteristics[selectedRow]
}
}
}
gattTableView.deselectRow(at: indexPath, animated: true)
} else {
if let peripheral = blePeripheral.peripheral {
centralManager.cancelPeripheralConnection(peripheral)
}
}
}
}

When run, the App will be able to scan for and connect to a Peripheral. Once con-
nected, it can list the GATT Profile - the Services and Characteristics hosted on the
Peripheral. A Characteristic can be selected and values can be read from that Charac-
teristic (Figure 7-4).

123
Figure 7-4. App screens showing GATT Profile for connected Peripheral and val-
ues read from a Characteristic on a connected Peripheral

Programming the Peripheral


This chapter will show how to create a Characteristic with read access.

A read-only Characteristic is created and added to a Primary Service like this:

// create Characteristic UUID


let readCharacteristicUuid = CBUUID(

124
string: "00002a56-0000-1000-8000-00805f9b34fb")
// Make Characteristic readable
let characteristicProperties = CBCharacteristicProperties.read
// Make Attributes readable
let characterisitcPermissions = CBAttributePermissions.readable
// create the readable Characteristic
let readCharacteristic = CBMutableCharacteristic(
type: readCharacteristicUuid, properties: characteristicProperties,
value: nil,
permissions: characterisitcPermissions)
// set the Characteristic as the only one in the Service
service.characteristics = [ readCharacteristic ]

When the Central requests to read data from the Peripheral’s Characteristic, the pe-
ripheralManager didReceiveRead method is triggered the CBPeripheralManagerDele-
gate object.

func peripheralManager(
_ peripheral: CBPeripheralManager,
didReceiveRead request: CBATTRequest)
{
}

The Central cannot read the Characteristic value until the request's value is set:

func peripheralManager(
_ peripheral: CBPeripheralManager,
didReceiveRead request: CBATTRequest)
{
let characteristic = request.characteristic
if let value = characteristic.value {
// Respond to the Central with the Characteristic value
let range = Range(uncheckedBounds: (
lower: request.offset,

125
upper: value.count - request.offset))
request.value = value.subdata(in: range)
}
}

The Central needs to know if the request was successful. This is done by responding
to the with a CBATTError status.

CBATTError status messages include the following:

Table 7-1. Common CBATTError values

Value Description

success Operation was successful

readNotPermitted Read request not permitted.

requestNotSupport
Request was not supported.
ed

invalidOffset Requested data offset is invalid.

For example, a successful read request warrents a CBATTError.success response:

...
peripheral.respond(to: request, withResult: CBATTError.success)
...

A full implementation looks like this:

func peripheralManager(
_ peripheral: CBPeripheralManager,
didReceiveRead request: CBATTRequest)
{

126
let characteristic = request.characteristic
if let value = characteristic.value {
// if the requested offset is
// larger than the Characteristic value, send an error
if request.offset > value.count {
peripheralManager.respond(
to: request,
withResult: CBATTError.invalidOffset)
return
}
// Respond to the Central with the Characteristic value
let range = Range(uncheckedBounds: (
lower: request.offset,
upper: value.count - request.offset))
request.value = value.subdata(in: range)

peripheral.respond(to: request, withResult: CBATTError.success)


}
}

Putting It All Together


Copy the previous chapter's project into a new project and modify the files below.

Models

Modify the BlePeripheral class to include build a readable Characteristic, sets a ran-
dom String to the Characteristic every 5 seconds, and responds when the Central ini-
tiates a read request:

127
Example 7-6. Models/BlePeripheral.swift

...
// MARK: GATT Profile

// Service UUID
let serviceUuid = CBUUID(string: "0000180c-0000-1000-8000-00805f9b34fb")
// Characteristic UUIDs
let readCharacteristicUuid = CBUUID(
string: "00002a56-0000-1000-8000-00805f9b34fb")
// Read Characteristic
var readCharacteristic:CBMutableCharacteristic!
...

/**
Build Gatt Profile.
This must be done after Bluetooth Radio has turned on
*/
func buildGattProfile() {
let service = CBMutableService(type: serviceUuid, primary: true)
let characteristicProperties = CBCharacteristicProperties.read
let characterisitcPermissions = CBAttributePermissions.readable
readCharacteristic = CBMutableCharacteristic(
type: readCharacteristicUuid,
properties: characteristicProperties,
value: nil, permissions: characterisitcPermissions)
service.characteristics = [ readCharacteristic ]
peripheralManager.add(service)
}

/**
Set a Characteristic to some text value
*/
func setCharacteristicValue(
_ characteristic: CBMutableCharacteristic,
value: Data

128
) {
characteristic.value = value
if central != nil {
peripheralManager.updateValue(
value,
for: readCharacteristic,
onSubscribedCentrals: [central])
}
}
...
/**
Connected Central requested to read from a Characteristic
*/
func peripheralManager(
_ peripheral: CBPeripheralManager,
didReceiveRead request: CBATTRequest)
{
let characteristic = request.characteristic
if let value = characteristic.value {
//let stringValue = String(data: value, encoding: .utf8)!
if request.offset > value.count {
peripheralManager.respond(
to: request, withResult: CBATTError.invalidOffset)
return
}
let range = Range(uncheckedBounds: (
lower: request.offset,
upper: value.count - request.offset))
request.value = value.subdata(in: range)
peripheral.respond(to: request, withResult: CBATTError.success)
}
delegate?.blePeripheral?(characteristicRead: request.characteristic)
}
...

129
Delegates

Add a method to the BlePeripheralDelegate that sends a notification when a Charac-


teristic has been read:

Example 7-7. Delegates/BlePeripheralDelegate.swift

...
/**
Characteristic was read

- Parameters:
- characteristic: the Characteristic that was read
*/
@objc optional func blePeripheral(
characteristicRead fromCharacteristic: CBCharacteristic)
...

Controllers

Add a method to the BlePeripheralDelegate that sends a notification when a Charac-


teristic has been read:

Example 7-7. UI/Controllers/ViewController.swift

...
@IBOutlet weak var characteristicValueTextField: UITextField!
...

// MARK: Update BlePeripheral Properties

/**
Generate a random String

- Parameters

130
- length: the length of the resulting string

- returns: random alphanumeric string


*/
func randomString(length: Int) -> String {
let letters : NSString = "abcdefghijklmnopqrstuvwxyz” + \
“ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let len = UInt32(letters.length)
var randomString = ""
for _ in 0 ..< length {
let rand = arc4random_uniform(len)
var nextChar = letters.character(at: Int(rand))
randomString += \
NSString(characters: &nextChar, length: 1) as String
}
return randomString
}

/**
Set Read Characteristic to some random text value
*/
func setRandomCharacteristicValue() {
let stringValue = randomString(
length: Int(arc4random_uniform(
UInt32(blePeripheral.readCharacteristicLength - 1))
)
)
let value:Data = stringValue.data(using: .utf8)!
blePeripheral.setCharacteristicValue(
blePeripheral.readCharacteristic,
value: value
)
characteristicValueTextField.text = stringValue
}
...
/**

131
BlePeripheral statrted adertising

- Parameters:
- error: the error message, if any
*/
func blePerihperal(startedAdvertising error: Error?) {
if error != nil {
print("Problem starting advertising: " + error.debugDescription)
} else {
print("adertising started")
advertisingSwitch.setOn(true, animated: true)
setRandomCharacteristicValue()
randomTextTimer = Timer.scheduledTimer(
timeInterval: 5,
target: self,
selector: #selector(setRandomCharacteristicValue),
userInfo: nil,
repeats: true
)
}
}
...

When run, the App will be able to host a simple GATT profile with a single Characteris-
tic that sets the Characteristic to a random String every 5 seconds (Figure 7-5).

132
Figure 7-5. App screen showing random Characteristic value on Advertising Pe-
ripheral

Example code
The code for this chapter is available online
at: https://github.com/BluetoothLowEnergyIniOSSwift/Chapter07

133
Writing Data to a Peripheral

Data is sent from the Central to a Peripheral when the Central writes a value in a Char-
acteristic hosted on the Peripheral, presuming that Characteristic has write permis-
sions.

The process looks like this (Figure 8-1):

Figure 8-1. The process of a Central writing data to a Peripheral

Programming the Central


Before attempting to write data a Characteristic, it is useful to know if the Characteris-
tic has “write” permissions. Write permissions can be read by accessing a Character-
istic’s properties and isolating the Write properties:

// Characteristic supports write permissions


let properties = characteristic.properties.rawValue;
let isWriteable = (properties & \
CBCharacteristicProperties.write.rawValue) != 0 ||

134
(properties & \
CBCharacteristicProperties.writeWithoutResponse.rawValue) != 0

// Characteristic supports write (with response) permissions


let isWriteableWithResponse = (properties & \
CBCharacteristicProperties.write.rawValue) != 0

// Characteristic supports write without response permissions


let isWriteableWithoutResponse = (properties & \
CBCharacteristicProperties.writeWithoutResponse.rawValue) != 0
The Characteristic is written to like this:
peripheral.writeValue(value, for: characteristic)

Regardless of the initial data type, the value written to the Characetristic must be
sent as a Data object. Here is how to convert some common data types into a Data
object:

// convert String to Data


let stringValue = "Hello"
let arrayValue = Array(stringValue.utf8)
let stringDataValue = Data(Array(byteValue[0..<length]))

// convert Int to Data


var intValue = 8;
let intDataValue = Data(buffer: UnsafeBufferPointer(
start: &intValue,
count: 1))

// convert Float to Data


var floatValue = 10.2;
let floatDataValue = Data(buffer: UnsafeBufferPointer(
start: &floatValue,
count: 1))

135
If the Characteristic supports write (with response), the peripheral didWriteValueFor
event gets triggered in the CBPeripheralDelegate following a write operation:

func peripheral(
_ peripheral: CBPeripheral,
didWriteValueFor characteristic: CBCharacteristic,
error: Error?)
{
}

Putting It All Together


Copy the project from the previous chapter into a new project, titled "WriteCharacter-
istic."

Models

Modify the BlePeripheral class to include methods to test the write permissions of a
Characteristic, to write a value to a Characteristic, and to handle the resulting
CBPeripheralDelegate callback:

Example 8-1. Models/BlePeripheral.swift

...
/**
Write a text value to the BlePeripheral

- Parameters:
- value: the value to write to the connected Characteristic
*/
func writeValue(value: String, to characteristic: CBCharacteristic) {
let byteValue = Array(value.utf8)
// cap the outbound value length to be

136
// less than the characteristic length
var length = byteValue.count
if length > characteristicLength {
length = characteristicLength
}
let transmissableValue = Data(Array(byteValue[0..<length]))
print(transmissableValue)
var writeType = CBCharacteristicWriteType.withResponse
if BlePeripheral.isCharacteristic(
isWriteableWithoutResponse: characteristic) {
writeType = CBCharacteristicWriteType.withoutResponse
}
peripheral.writeValue(
transmissableValue,
for: characteristic,
type: writeType)
print("write request sent")
}

/**
Check if Characteristic is writeable

- Parameters:
- characteristic: The Characteristic to test

- returns: True if characteristic is writeable


*/
static func isCharacteristic(
isWriteable characteristic: CBCharacteristic) -> Bool
{
if (characteristic.properties.rawValue & \
CBCharacteristicProperties.write.rawValue) != 0 ||
(characteristic.properties.rawValue & \
CBCharacteristicProperties.writeWithoutResponse.rawValue) != 0 {
return true
}

137
return false
}

/**
Check if Characteristic is writeable with response

- Parameters:
- characteristic: The Characteristic to test

- returns: True if characteristic is writeable with response


*/
static func isCharacteristic(
isWriteableWithResponse characteristic: CBCharacteristic) -> Bool {
if (characteristic.properties.rawValue & \
CBCharacteristicProperties.write.rawValue) != 0 {
return true
}
return false
}

/**
Check if Characteristic is writeable without response

- Parameters:
- characteristic: The Characteristic to test

- returns: True if characteristic is writeable without response


*/
static func isCharacteristic(
isWriteableWithoutResponse characteristic: CBCharacteristic) -> Bool
{
if (characteristic.properties.rawValue & \
CBCharacteristicProperties.writeWithoutResponse.rawValue) != 0 {
return true
}
return false

138
}

// MARK: CBPeripheralDelegate

/**
Value was written to the Characteristic
*/
func peripheral(
_ peripheral: CBPeripheral,
didWriteValueFor characteristic: CBCharacteristic,
error: Error?)
{
print("data written")
delegate?.blePeripheral?(
valueWritten: characteristic,
blePeripheral: self)
}

...

Delegates

Add a function to the BlePeripheralDelegate to alert subscribers that a write opera-


tion happened.

Example 8-2. Delegates/BlePeripheralDelegate.swift

...
/**
Value written to Characteristic

- Parameters:
- characteristic: the Characteristic that was written to
- blePeripheral: the BlePeripheral
*/

139
@objc optional func blePeripheral(
valueWritten characteristic: CBCharacteristic,
blePeripheral: BlePeripheral)
...

Storyboard

Create and link a UILabel in the GattTableViewCell to show the write property and a
UITextField and UIButton to allow a user to type and submit text to the Characteristic
(Figure 8-2):

Figure 8-2. Project Storyboard

140
Views

Modify the GattTableViewCell to display the appropriate UILabelView when a Charac-


teristic is writeable.

Example 8-3. UI/Views/GattTableViewCell.swift

...
@IBOutlet weak var writeableLabel: UILabel!
...
func renderCharacteristic(characteristic: CBCharacteristic) {
uuidLabel.text = characteristic.uuid.uuidString
let isReadable = BlePeripheral.isCharacteristic(
isReadable: characteristic)
let isWriteable = BlePeripheral.isCharacteristic(
isWriteable: characteristic)
readableLabel.isHidden = !isReadable
writeableLabel.isHidden = !isWriteable
if isReadable || isWriteable {
noAccessLabel.isHidden = true
} else {
noAccessLabel.isHidden = false
}
}
...

Controllers

Add funcionality to the CharacteristicViewController to display new UI elements to


handle write operations, as well as an implementation of the new BlePeripheralDele-
gate method listed above.

Example 8-4. UI/Controllers/CharacteristicViewController.swift

...

141
@IBOutlet weak var writeCharacteristicButton: UIButton!
@IBOutlet weak var writeCharacteristicText: UITextField!
...
/**
Load UI elements
*/
func loadUI() {
advertisedNameLabel.text = blePeripheral.advertisedName
identifierLabel.text = \
blePeripheral.peripheral.identifier.uuidString
characteristicUuidlabel.text = \
connectedCharacteristic.uuid.uuidString
readCharacteristicButton.isEnabled = true
// characteristic is not readable
if !BlePeripheral.isCharacteristic(
isReadable: connectedCharacteristic) {
readCharacteristicButton.isHidden = true
characteristicValueText.isHidden = true
}
// characteristic is not writeable
if !BlePeripheral.isCharacteristic(
isWriteable: connectedCharacteristic) {
writeCharacteristicText.isHidden = true
writeCharacteristicButton.isHidden = true
}
}
...
/**
User touched Read button. Request to write to the Characteristic
*/
@IBAction func onWriteCharacteristicButtonTouched(_ sender: UIButton) {
print("write button pressed")
writeCharacteristicButton.isEnabled = false
if let stringValue = writeCharacteristicText.text {
print(stringValue)
blePeripheral.writeValue(

142
value: stringValue,
to: connectedCharacteristic)
writeCharacteristicText.text = ""
}
}
...
/**
Characteristic was written to. Update UI
*/
func blePeripheral(
valueWritten characteristic: CBCharacteristic,
blePeripheral: BlePeripheral)
{
print("value written to characteristic!")
writeCharacteristicButton.isEnabled = true
}
...

Compile and run. The updated app will be able to scan for and connect to a Periph-
eral. Once connected, it lists services and characteristics, connect to Characteristics
and send and data (Figure 8-3).

143
Figure 8-3. App screens showing GATT Profile of connected Peripheral with
Write access to a Characteristic, and an interface to send text to a Characteris-
tic

Programming the Peripheral


This chapter will show how to create a Characteristic with read access.

A writeable Characteristic is has the CBCharacteristicProperties.write property:

// Characteristic is writable

144
var characteristicProperties = CBCharacteristicProperties.write

It is possible to append other properties to additionally make the Characteristic read-


able:

// Read support
characteristicProperties.formUnion(CBCharacteristicProperties.read)

// add write support


characteristicProperties.formUnion(CBCharacteristicProperties.notify)

Everything else about creating the Characteristic remains the same as for a readable
Characteristic:

// Characteristic Attributes are readable


var characterisitcPermissions = CBAttributePermissions.readable

// Set Characteristic UUID


let readWriteCharacteristicUuid = CBUUID(
string: "00002a56-0000-1000-8000-00805f9b34fb")

// Create a writeable Characteristic


readWriteCharacteristic = CBMutableCharacteristic(
type: readWriteCharacteristicUuid,
properties: characteristicProperties,
value: nil,
permissions: characterisitcPermissions)

// Set the Characteristic as belonging to the Service


service.characteristics = [ readWriteCharacteristic ]

When a connected Central attempts to write to a Characteristic, the peripheralMan-


ager didReceiveWrite callback is triggered by the CBPeripheralManagerDelegate ob-

145
ject. If the Characteristic has the CBCharacteristicProperties.write property, it is im-
portant to respond to the Central with a CBATTError status.

CBATTError status messages include the following:

Table 8-1. Common CBATTError values

Value Description

success Operation was successful

writeNotPermitted Write request was not permitted.

readNotPermitted Read request not permitted.

requestNotSupported Request was not supported.

invalidOffset Requested data offset is invalid.

More than one request may come in at a time, so it is useful to iterate through and
deal with each request separately.

func peripheralManager(
_ peripheral: CBPeripheralManager,
didReceiveWrite requests: [CBATTRequest])
{
for request in requests {
peripheral.respond(to: request, withResult: CBATTError.success)
// do something with the incoming data
}
}

146
Putting It All Together
Create a new project called WriteCharacteristic and copy the files from the previous
chapter's project.

The Peripheral will be modified to support writes.

Models

Modify the BlePeripheral class to create a writeable Characteristic and to handle in-
coming write requests:

Example 8-5. Models/BlePeripheral.swift

...
// Service UUID
let serviceUuid = CBUUID(string: "0000180c-0000-1000-8000-00805f9b34fb")
// Characteristic UUIDs
let readWriteCharacteristicUuid = CBUUID(
string: "00002a56-0000-1000-8000-00805f9b34fb")
// Read Characteristic
var readWriteCharacteristic:CBMutableCharacteristic!
...
/**
Build Gatt Profile.
This must be done after Bluetooth Radio has turned on
*/
func buildGattProfile() {
let service = CBMutableService(type: serviceUuid, primary: true)
var characteristicProperties = CBCharacteristicProperties.read
characteristicProperties.formUnion(
CBCharacteristicProperties.notify)
var characterisitcPermissions = CBAttributePermissions.writeable
characterisitcPermissions.formUnion(CBAttributePermissions.readable)
readWriteCharacteristic = CBMutableCharacteristic(

147
type: readWriteCharacteristicUuid,
properties: characteristicProperties,
value: nil,
permissions: characterisitcPermissions)
service.characteristics = [ readWriteCharacteristic ]
peripheralManager.add(service)
}
...
/**
Connected Central requested to write to a Characteristic
*/
func peripheralManager(
_ peripheral: CBPeripheralManager,
didReceiveWrite requests: [CBATTRequest])
{
for request in requests {
peripheral.respond(to: request, withResult: CBATTError.success)
if let value = request.value {
delegate?.blePeripheral?(
valueWritten: value,
toCharacteristic: request.characteristic)
}
}
}
...

Delegates

Add a method to the BlePeripheralDelegate to process successful writes to a Charac-


teristic:

Example 8-6. Delegates/BlePeripheralDelegate.swift

...
/**

148
Value written to Characteristic

- Parameters:
- value: the Data value written to the Charactersitic
- characteristic: the Characteristic that was written to
*/
@objc optional func blePeripheral(
valueWritten value: Data,
toCharacteristic: CBCharacteristic)
...

Storyboard

Add a UILabel and UITextView to show the Characteristic Log in the UIView in the
Main.storyboard to create the App's user interface (Figure 8-4):

149
Figure 8-4. Project Storyboard

Controllers

Add a UITextView that logs the incoming Characteristic values, and a callback han-
dler for the the BlePeripheral writes:

Example 8-7. UI/Controllers/ViewController.swift

...
@IBOutlet weak var characteristicLogText: UITextView!
...

/**
Value written to Characteristic

- Parameters:

150
- stringValue: the value read from the Charactersitic
- characteristic: the Characteristic that was written to
*/
func blePeripheral(
valueWritten value: Data,
toCharacteristic: CBCharacteristic)
{
//let stringValue = String(data: value, encoding: .utf8)
let hexValue = value.hexEncodedString()
characteristicLogText.text = characteristicLogText.text + \
"\n" + hexValue
if !characteristicLogText.text.isEmpty {
characteristicLogText.scrollRangeToVisible(NSMakeRange(0, 1))
}

}
...

Compile and run. The updated app will host a simple GATT Profile featuring a write-
only Characteristic.

Example code
The code for this chapter is available online
at: https://github.com/BluetoothLowEnergyIniOSSwift/Chapter08

151
Using Notifications

Being able to read from the Central has limited value if the Central does not know
when new data is available.

Notifications solve this problem. A Characteristic can issue a notification when it’s
value has changed. A Central that subscribes to these notifications will know when
the Characteristic’s value has changed, but not what that new value is. The Central
can then read the latest data from the Characteristic.

The whole process looks something like this (Figure 9-1):

Figure 9-1. The process of a Peripheral notifying a connected Central of


changes to a Characteristic

152
Programming the Central
Before the Central can subscribe to the Characteristic’s notifications, it is useful to
know if the Characteristic supports notifications. To determine if notifications are en-
abled, get the properties of the Characteristic and isolate the notify property.

let isNotifiable = (characteristic.properties.rawValue & \


CBCharacteristicProperties.notify.rawValue) != 0

The Central can subscribe to or unsubscribe from a Characteristic's notifications by


passing a boolean to the the CBPeripheral.setNotifyValue() method:

// subscribe to a Characteristic
peripheral.setNotifyValue(true, for: characteristic)

// unsubscribe from a Characterisic


peripheral.setNotifyValue(false, for: characteristic)

When the Characterstic has been subscribed to or unsubscribed from, the peripheral
didUpdateNotificationStateFor callback is triggered in the CBPeripheralDelegate.

func peripheral(
_ peripheral: CBPeripheral,
didUpdateNotificationStateFor characteristic: CBCharacteristic,
error: Error?)
{
}

Once subscribed, any changes to a Characteristic automatically trigger the peripheral


didUpdateValueFor method in the CBPeripheralDelegate.

func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,

153
error: Error?)
{
}

Putting It All Together


These features will be added to BlePeripheral to subscribe to notifications, and we
will alter TalkActivity to display a checkbox to subscribe and unsubscribe from the no-
tifications and respond to notification alerts.

Models

Add methods to BlePeripheral to test if a Characteristic is notifiable, to subscribe and


unsubscribe from a Characterstic, and to handle the resulting callback from
CBPeripheralManagerDelegate:

Example 9-1. Models/BlePeripheral.swift

...
/**
Subscribe to the connected characteristic.

When change is successful, delegate.subscriptionStateChanged() called


*/
func subscribeTo(characteristic: CBCharacteristic) {
self.peripheral.setNotifyValue(true, for: characteristic)
}

/**
Unsubscribe from the connected characteristic.

When change is successful, delegate.subscriptionStateChanged() called


*/
func unsubscribeFrom(characteristic: CBCharacteristic) {

154
self.peripheral.setNotifyValue(false, for: characteristic)
}
...
/**
Check if Characteristic is notifiable

- Parameters:
- characteristic: The Characteristic to test

- returns: True if characteristic is notifiable


*/
static func isCharacteristic(
isNotifiable characteristic: CBCharacteristic) -> Bool {
if (characteristic.properties.rawValue & \
CBCharacteristicProperties.notify.rawValue) != 0
{
return true
}
return false
}

// MARK: CBPeripheralDelegate

/**
Characteristic has been subscribed to or unsubscribed from
*/
func peripheral(
_ peripheral: CBPeripheral,
didUpdateNotificationStateFor characteristic: CBCharacteristic,
error: Error?)
{
print("Notification state updated for: " +
"\(characteristic.uuid.uuidString)")
print("New state: \(characteristic.isNotifying)")
delegate?.blePeripheral?(
subscriptionStateChanged: characteristic.isNotifying,

155
characteristic: characteristic,
blePeripheral: self)
if let errorValue = error {
print("error subscribing to notification: ")
print(errorValue.localizedDescription)
}
}
...

Delegates

Add a method to the BlePeripheralDelegate to notify subscribers that a Characteristic


has been subscribed to or unsubscribed from:

Example 9-2. Delegates/BlePeripheralDelegate.swift

...
/**
A subscription state has changed on a Characteristic

- Parameters:
- subscribed: true if subscribed, false if unsubscribed
- characteristic: the Characteristic that was subscribed/unsubscribed
- blePeripheral: the BlePeripheral
*/
@objc optional func blePeripheral(
subscriptionStateChanged subscribed: Bool,
characteristic: CBCharacteristic,
blePeripheral: BlePeripheral)
...

156
Storyboard

Create and link a UILabel in the GattTableViewCell to show the notification property
and a UILabel and UISwitch to show the Characteristic subscription state (Figure 9-
2):

Figure 9-2. Project Storyboard

Views

Modify the GattTableViewCell to hold a UILabel that shows the notification permis-
sions for a Characteristic, as well as functionality to show or hide the UILabel to re-
flect the Characteristic permissions:

157
Example 9-3. UI/Views/GattTableViewCell.swift

...
@IBOutlet weak var notifiableLabel: UILabel!
...
func renderCharacteristic(characteristic: CBCharacteristic) {
uuidLabel.text = characteristic.uuid.uuidString
print(characteristic.uuid.uuidString)
var isReadable = false
var isWriteable = false
var isNotifiable = false
if (characteristic.properties.rawValue & \
CBCharacteristicProperties.read.rawValue) != 0 {
print("readable")
isReadable = true
}
if (characteristic.properties.rawValue & \
CBCharacteristicProperties.write.rawValue) != 0 ||
(characteristic.properties.rawValue & \
CBCharacteristicProperties.writeWithoutResponse.rawValue) != 0 {
print("writable")
isWriteable = true
}
if (characteristic.properties.rawValue & \
CBCharacteristicProperties.notify.rawValue) != 0 {
print("notifiable")
isNotifiable = true
}
readableLabel.isHidden = !isReadable
writeableLabel.isHidden = !isWriteable
notifiableLabel.isHidden = !isNotifiable
if isReadable || isWriteable || isNotifiable {
noAccessLabel.isHidden = true
} else {
noAccessLabel.isHidden = false
}

158
}
...

Controllers

Modify the CharacteristicViewController to show a Subscribe to Notifications UIS-


witch and UILabel, plus interactions for the switch. Implement the BlePeripheralDele-
gate method subscriptionStateChanged to handle changes in the notification state of
the Characteristic:

Example 9-4. UI/Controllers/CharacteristicViewController.swift

...
@IBOutlet weak var subscribeToNotificationLabel: UILabel!
@IBOutlet weak var subscribeToNotificationsSwitch: UISwitch!
...
func loadUI() {
advertisedNameLabel.text = blePeripheral.advertisedName
identifierLabel.text = \
blePeripheral.peripheral.identifier.uuidString

characteristicUuidlabel.text = \
connectedCharacteristic.uuid.uuidString
readCharacteristicButton.isEnabled = true

// characteristic is not readable


if !BlePeripheral.isCharacteristic(
isReadable: connectedCharacteristic) {
readCharacteristicButton.isHidden = true
characteristicValueText.isHidden = true
}

// characteristic is not writeable


if !BlePeripheral.isCharacteristic(
isWriteable: connectedCharacteristic) {

159
writeCharacteristicText.isHidden = true
writeCharacteristicButton.isHidden = true
}

// characteristic is not writeable


if !BlePeripheral.isCharacteristic(
isNotifiable: connectedCharacteristic) {
subscribeToNotificationsSwitch.isHidden = true
subscribeToNotificationLabel.isHidden = true
}
}
...
/**
User toggled the notification switch.
Request to subscribe or unsubscribe from the Characteristic
*/
@IBAction func onSubscriptionToNotificationSwitchChanged(
_ sender: UISwitch)
{
print("Notification Switch toggled")
subscribeToNotificationsSwitch.isEnabled = false
if sender.isOn {
blePeripheral.subscribeTo(
characteristic: connectedCharacteristic)
} else {
blePeripheral.unsubscribeFrom(
characteristic: connectedCharacteristic)
}

}
...
/**
Characteristic subscription status changed. Update UI
*/
func blePeripheral(
subscriptionStateChanged subscribed: Bool,

160
characteristic: CBCharacteristic,
blePeripheral: BlePeripheral)
{
if characteristic.isNotifying {
subscribeToNotificationsSwitch.isOn = true
} else {
subscribeToNotificationsSwitch.isOn = false
}
subscribeToNotificationsSwitch.isEnabled = true
}
...
}

Compile and run. The app can scan for and connect to a Peripheral. Once con-
nected, it can display the GATT profile of the Peripheral. It can connect to Characteris-
tics, Subscribe to notifications, and receive data without polling.

When you hit the “Subscribe to Notifications" button, the text field should begin
populating with random text generated on the Peripheral (Figure 9-3).

161
Figure 9-3. Apps screen showing GATT Profile of connected Peripheral with Noti-
fications available in a Characteristic, and text read from a Characteristic

Programming the Peripheral


Often, battery life is at a premium on Bluetooth Peripherals. For this reason, it is use-
ful to notify a connected Central when a Characteristic’s value has changed, but not
send the new data to the Central. Waking up the Bluetooth radio to send one byte
consumes less battery than sending 20 or more bytes.

162
Creating the Characteristic
A Characteristic that supports notifications must have a
CBCharacteristicProperties.notify property:

var characteristicProperties = CBCharacteristicProperties.notify

It is possible to append other properties to additionally make the Characteristic read-


able or writable:

// notification support
var characteristicProperties = CBCharacteristicProperties.notify

// add read support


characteristicProperties.formUnion(CBCharacteristicProperties.read)

// add write support


characteristicProperties.formUnion(CBCharacteristicProperties.write)

Additionally, the Characteristic's Attributes must be readable and writeable:

// Characteristic Attributes are readable


var characterisitcPermissions = CBAttributePermissions.readable
characterisitcPermissions.formUnion(CBAttributePermissions.writeable)

Just as before, the Characteristic is created by instantiating a CBMutableCharacteris-


tic object:

// Set Characteristic UUID


let readWriteNotifyCharacteristicUuid = CBUUID(
string: "00002a56-0000-1000-8000-00805f9b34fb")

// Create a writeable Characteristic


let readWriteNotifyCharacteristic = CBMutableCharacteristic(

163
type: readWriteNotifyCharacteristicUuid,
properties: characteristicProperties,
value: nil,
permissions: characterisitcPermissions)

Responding to Callbacks
When a Central subscribes to a Characteristic, the peripheralManager didSub-
scribeTo method is triggered by the CBPeripheralManagerDelegate object. One of
the parameters is a CBCentral, the Central that is subscribed to the Characteristic. It
is important to save this reference to the Central as its a requirement in sending a no-
tification.

func peripheralManager(
_ peripheral: CBPeripheralManager,
central: CBCentral,
didSubscribeTo characteristic: CBCharacteristic)
{
// save a copy of the Central
self.central = central
}

When a Central unsubscribes from a Characterstic, peripheralManager didUnsub-


scribeFrom method is triggered by the CBPeripheralManagerDelegate object:

func peripheralManager(
_ peripheral: CBPeripheralManager,
central: CBCentral,
didUnsubscribeFrom characteristic: CBCharacteristic)
{
// remove reference to the Central
self.central = null
}

164
Just prior to the Characteristic sending out a notification, the peripheralManagerIs-
Ready method is triggered:

func peripheralManagerIsReady(
toUpdateSubscribers peripheral: CBPeripheralManager)
{
}

Sending Notifications
Notifications are automatically sent when the CBPeripheralManager calls the update-
Value method:

peripheralManager.updateValue(
value,
for: readWriteNotifyCharacteristic,
onSubscribedCentrals: [central])

Putting It All Together


These features will be added to BlePeripheral to allow notification subscriptions from
Characteristics.

Models

Add a reference to the connected Central, build a GATT Profile with a notification sup-
port, and callback handlers to process Characteristic subscription and unsubscrip-
tion by a Central:

Example 9-5. Models/BlePeripheral.swift

165
...
// MARK: GATT Profile

// Service UUID
let serviceUuid = CBUUID(string: "0000180c-0000-1000-8000-00805f9b34fb")
// Characteristic UUIDs
let readWriteNotifyCharacteristicUuid = CBUUID(
string: "00002a56-0000-1000-8000-00805f9b34fb")
// Read Characteristic
var readWriteNotifyCharacteristic:CBMutableCharacteristic!

// Connected Central
var central:CBCentral!
...
/**
Build Gatt Profile.
This must be done after Bluetooth Radio has turned on
*/
func buildGattProfile() {
let service = CBMutableService(type: serviceUuid, primary: true)
var characteristicProperties = CBCharacteristicProperties.read
characteristicProperties.formUnion(
CBCharacteristicProperties.notify)
var characterisitcPermissions = CBAttributePermissions.writeable
characterisitcPermissions.formUnion(CBAttributePermissions.readable)

readWriteNotifyCharacteristic = CBMutableCharacteristic(
type: readWriteNotifyCharacteristicUuid,
properties: characteristicProperties,
value: nil,
permissions: characterisitcPermissions)

service.characteristics = [ readWriteNotifyCharacteristic ]
peripheralManager.add(service)
randomTextTimer = Timer.scheduledTimer(
timeInterval: 5,

166
target: self,
selector: #selector(setRandomCharacteristicValue),
userInfo: nil,
repeats: true)
}

/**
Generate a random String

- Parameters
- length: the length of the resulting string

- returns: random alphanumeric string


*/
func randomString(length: Int) -> String {
let letters : NSString = \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let len = UInt32(letters.length)
var randomString = ""
for _ in 0 ..< length {
let rand = arc4random_uniform(len)
var nextChar = letters.character(at: Int(rand))
randomString += \
NSString(characters: &nextChar, length: 1) as String
}
return randomString
}

/**
Set Read Characteristic to some random text value
*/
func setRandomCharacteristicValue() {
let stringValue = randomString(
length: Int(arc4random_uniform(
UInt32(readCharacteristicLength - 1)))
)

167
let value:Data = stringValue.data(using: .utf8)!
readWriteNotifyCharacteristic.value = value
if central != nil {
peripheralManager.updateValue(
value,
for: readWriteNotifyCharacteristic,
onSubscribedCentrals: [central])
}
print("writing " + stringValue + " to characteristic")
}
...
/**
Connected Central subscribed to a Characteristic
*/
func peripheralManager(
_ peripheral: CBPeripheralManager,
central: CBCentral,
didSubscribeTo characteristic: CBCharacteristic)
{
self.central = central
delegate?.blePeripheral?(
subscriptionStateChangedForCharacteristic: characteristic,
subscribed: true)
}

/**
Connected Central unsubscribed from a Characteristic
*/
func peripheralManager(
_ peripheral: CBPeripheralManager,
central: CBCentral,
didUnsubscribeFrom characteristic: CBCharacteristic)
{
self.central = central
delegate?.blePeripheral?(
subscriptionStateChangedForCharacteristic: characteristic,

168
subscribed: false)
}

/**
Peripheral is about to notify subscribers of changes to a Characteris-
tic
*/
func peripheralManagerIsReady(
toUpdateSubscribers peripheral: CBPeripheralManager)
{
print("Peripheral about to update subscribers")
}
...

Delegates

Add a method to the BlePeripheralDelegate to relay changes in a Characteristic's sub-


scription state:

Example 9-6. Delegates/BlePeripheralDelegate.swift

...
/**
A subscription state has changed on a Characteristic

- Parameters:
- characteristic: the Characteristic that was subscribed/unsubscribed
- subscribed: true if subscribed, false if unsubscribed
*/
@objc optional func blePeripheral(
subscriptionStateChangedForCharacteristic \
characteristic: CBCharacteristic,
subscribed: Bool)
...

169
Storyboard

Add a UILabel and UISwitch to show the Notification state (Figure 9-4).

Figure 9-4. Project Storyboard

Add a UISwitch to show the subscribed state of the Characteristic, and a callback
handler to process the changes to the Characteristic subscription state.

Example 9-7. UI/Controllers/VewController.swift

...
@IBOutlet weak var subscribedSwitch: UISwitch!
...
/**
View disappeared. Stop advertising
*/
override func viewDidDisappear(_ animated: Bool) {

170
subscribedSwitch.setOn(false, animated: true)
advertisingSwitch.setOn(false, animated: true)
}
...
/**
A subscription state has changed on a Characteristic

- Parameters:
- characteristic: the Characteristic that was subscribed/unsubscribed
- subscribed: true if subscribed, false if unsubscribed
*/
func blePeripheral(
subscriptionStateChangedForCharacteristic: CBCharacteristic,
subscribed: Bool)
{
subscribedSwitch.setOn(subscribed, animated: true)
}
...

Compile and run. The app can handle subscriptions to a Characteristic and send noti-
fications when the Characteristic's value has changed (Figure 9-5).

171
Figure 9-5. App screen Advertising Peripheral with updates a Characteristic

Example code
The code for this chapter is available online
at: https://github.com/BluetoothLowEnergyIniOSSwift/Chapter09

172
Streaming Data

The maximum packet size you can send over Bluetooth Low Energy is 20 bytes.
More data can be sent by dividing a message into packets of 20 bytes or smaller,
and sending them one at a time

These packets can be sent at a certain speed.

Bluetooth Low Energy transmits at 1 Mb/s. Between the data transmission time and
the time it may take for a Peripheral to process incoming data, there is a time delay
between when one packet is sent and when the next one is ready to be sent.

To send several packets of data, a queue/notification system must be employed,


which alerts the Central when the Peripheral is ready to receive the next packet.

There are many ways to do this. One way is to set up a Characteristic with read,
write, and notify permissions, and to flag the Characteristic as “ready” after a write
has been processed by the Peripheral. This sends a notification to the Central, which
sends the next packet. That way, only one Characteristic is required for a single data
transmission.

This process can be visualized like this (Figure 10-1).

173
Figure 10-1. The process of using notifications to handle flow control on a
multi-packed data transfer

Programming the Central


The Central in this example waits for the Peripheral to change the Characteristic
value to the “ready” flow control message and to send a notification indicating the
change. This process signals to the Central that the Characteristic is ready to receive
the next packet of data.

Set up the parameters of the flow control and packet queue like this:

174
// Flow control response
let flowControlMessage = "ready"

// outbound value to be sent to the Characteristic


var outboundByteArray:[UInt8]!

// packet offset in multi-packet value


var packetOffset = 0

The flow control works by requesting a Characteristic read event when a notification
callback is triggered:

func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?)
{
print("characteristic updated")
if let value = characteristic.value {
if let stringValue = String(data: value, encoding: .ascii) {
if stringValue == flowControlMessage {
packetOffset += characteristicLength
if packetOffset < outboundByteArray.count {
writePartialValue(
value: outboundByteArray,
offset: packetOffset, to: characteristic)
} else {
// done writing message
}
}
}
}
}

175
The first packet of data is initialized and sent:

func writeValue(value: String, to characteristic: CBCharacteristic) {


// get the characteristic length
let writeableValue = value + "\0"
packetOffset = 0
// get the data for the current offset
outboundByteArray = Array(writeableValue.utf8)
writePartialValue(
value: outboundByteArray,
offset: packetOffset,
to: characteristic)
}

Subsequent packets are sent one at a time until there are no more left:

func writePartialValue(
value: [UInt8],
offset: Int,
to characteristic: CBCharacteristic)
{
// don't go past the total value size
var end = offset + characteristicLength
if end > outboundByteArray.count {
end = outboundByteArray.count
}
let transmissableValue = Data(Array(outboundByteArray[offset..<end]))
peripheral.writeValue(transmissableValue, for: characteristic)
}

176
Putting It All Together

Models

Add methods to the BlePeripheral to handle partial writes, properties to track flow
control, and modify the peripheral didUpdateValueFor method to handle the inboud
flow control value:

Example 10-1. Models/BlePeripheral.swift

...
// MARK: Flow control

// Flow control response


let flowControlMessage = "ready"
// outbound value to be sent to the Characteristic
var outboundByteArray:[UInt8]!
// packet offset in multi-packet value
var packetOffset = 0
...
/**
Write a text value to the BlePeripheral

- Parameters:
- value: the value to write to the connected Characteristic
*/
func writeValue(value: String, to characteristic: CBCharacteristic) {
// get the characteristic length
let writeableValue = value + "\0"
packetOffset = 0
// get the data for the current offset
outboundByteArray = Array(writeableValue.utf8)
writePartialValue(
value: outboundByteArray,
offset: packetOffset,

177
to: characteristic)
}

/**
Write a partial value to the BlePeripheral

- Parameters:
- value: the full value to write to the connected Characteristic
- offset: the packet offset
*/
func writePartialValue(
value: [UInt8],
offset: Int,
to characteristic: CBCharacteristic)
{
// don't go past the total value size
var end = offset + characteristicLength
if end > outboundByteArray.count {
end = outboundByteArray.count
}
let transmissableValue = \
Data(Array(outboundByteArray[offset..<end]))
print("writing partial value: \(offset)-\(end)")
print(transmissableValue)
var writeType = CBCharacteristicWriteType.withResponse
if BlePeripheral.isCharacteristic(
isWriteableWithoutResponse: characteristic) {
writeType = CBCharacteristicWriteType.withoutResponse
}
peripheral.writeValue(
transmissableValue,
for: characteristic,
type: writeType)
print("write request sent")
}
...

178
/**
Value downloaded from Characteristic on connected Peripheral
*/
func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?)
{
print("characteristic updated")
if let value = characteristic.value {
print(value.debugDescription)
print(value.description)

// Note: if we need to work with byte arrays


// instead of Strings, we can do this
// let byteArray = [UInt8](value)
// or this:
// let byteArray:[UInt8] = Array(outboundValue.withCString)
if let stringValue = String(data: value, encoding: .ascii) {
print(stringValue)
// received response from Peripheral
delegate?.blePeripheral?(
characteristicRead: stringValue,
characteristic: characteristic,
blePeripheral: self)
if stringValue == flowControlMessage {
packetOffset += characteristicLength
if packetOffset < outboundByteArray.count {
writePartialValue(
value: outboundByteArray,
offset: packetOffset, to: characteristic)
} else {
print("value write complete")
// done writing message
delegate?.blePeripheral?(
valueWritten: characteristic,

179
blePeripheral: self)
}
}
}
}
}
...

The resulting app can send larger amounts of data to a connected Peripheral by
queueing and transmitting packets one at a time (Figure 10-2).

180
Figure 10-2. App screens showing GATT Profile for the Advertising Peripheral
and multipart value queued to be sent to a Characteristic.

Programming the Peripheral


The Peripheral in this example processes a value written to a Characteristic, then
sets the Characteristic's value to the "ready" flow control message, and sends a noti-
fication to the Central. In this way, the Central is triggered to read the Characteristic.
When the Central reads the flow control message, it knows when to send the next
packet of data.

181
When the Peripheral's Characteristic is written to, the peripheralManager didReceive-
Write method is triggered in the CBPeripheralManagerDelegate object. Once the in-
coming data is processed, the flow control value can be written to the Characteristic
locally and the notification sent to the Central notifying it of the the change.

The implementation looks like this:

Set up the parameters of the flow control:

let flowControlString = "ready"

The flowControlString is written to the Characteristic and a notification is sent after


the Central initiates a write request:

func peripheralManager(
_ peripheral: CBPeripheralManager,
didReceiveWrite requests: [CBATTRequest])
{
for request in requests {
// Do something with the incoming request.value
// notify the Central of a successful write
peripheral.respond(to: request, withResult: CBATTError.success)
// convert flow control into a Data object
let flowControlValue:Data = flowControlString.data(using: .utf8)!
// send flow control value
peripheralManager.updateValue(
flowControlValue,
for: readWriteNotifyCharacteristic,
onSubscribedCentrals: [request.central])
}
}

182
Putting It All Together
Copy the previous chapter's project into a new project.

Models

Add functionality to BlePeripheral to describe the flow control value, and to send the
flow control message when a write request is triggered:

Example 10-2. Models/BlePeripheral.swift

...
// HARK: Flow Control
let flowControlString = "ready"
...
/**
Connected Central requested to write to a Characteristic
*/
func peripheralManager(
_ peripheral: CBPeripheralManager,
didReceiveWrite requests: [CBATTRequest])
{
for request in requests {
peripheral.respond(to: request, withResult: CBATTError.success)
// convert flow control into a Data object
let flowControlValue:Data = flowControlString.data(
using: .utf8)!
// send flow control value
peripheralManager.updateValue(
flowControlValue,
for: readWriteNotifyCharacteristic,
onSubscribedCentrals: [request.central])
if let value = request.value {
delegate?.blePeripheral?(
valueWritten: value,

183
toCharacteristic: request.characteristic)
}
}
}
...

The resulting app can receive a queued stream of data from a Central by issuing a
receive/response flow control on the Characteristic (Figure 10-3).

184
Figure 10-3. App screen showing multipart value queued to be sent to a Charac-
teristic.

Example code
The code for this chapter is available online
at: https://github.com/BluetoothLowEnergyIniOSSwift/Chapter10

185

You might also like