iOS Bluetooth Low Energy and Custom Hardware — Part 2: Communication 1


In part one, I went over the high-level aspect to BLE communication. In this post, I will be providing iOS code for each step of the BLE communication process. For reference, all the code are written in swift 3.0.

Note: Delegate operates asynchronously so if you have code that depends on the results from a Delegate you should run that code at the end of the delegate callback.


Discovery and Connection

Set Up CBCentralManager

// set up CBCentralManager
class MyBluetoothManager: NSObject {
	var bt_manager: CBCentralManager!
	var peripheral_manager: MyPeripheralManager
	private let queue = DispatchQueue(label: "com.queue.central.sampleble", qos: DispatchQoS.background)
	
	override init() {
		let options = [CBCentralManagerOptionShowPowerAlertKey: true]
		bt_manager = CBCentralManager(delegate: self, queue: queue, options: options)
	}
	
	//...
}

extension MyBluetoothManager: CBCentralManagerDelegate {
	//...
}

 

Scan For Peripherals 

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

 

Initializes the Central to scan for Bluetooth devices nearby. For every peripheral that the Central discovers, a callback via the CentralDelegate, didDiscoverPeripheral, occurs.

 

Did Discover Peripheral

extension MyBluetoothManager: CBCentralManagerDelegate {
	func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
		let peripheral_uuid = peripheral.identifier.uuidString
		
		if peripheral_uuid == my_peripheral_uuid {
			// this peripheral is your custom hardware; do some action (i.e. connect)
			my_peripheral_manager = MyPeripheralManager(peripheral)
		}	
	}
}

 

The delegate function gets invoke automatically when the central discovers a peripheral. The peripheral object corresponds to the current physical peripheral that was discovered. You can find out information such as the UUID and name through the peripheral object.

 

Stop Scan

bt_manager.stopScan()

 

Stops the Central from scanning for more Bluetooth devices. This will stop the didDiscoverPeripheral callback.

 

Connect Peripheral

bt_manager.connect(my_peripheral_manager.peripheral, options: nil)

 

Initiates and establishes a connection with the specified peripheral. The didConnectPeripheral callback will occur following the connection request.

 

Did Connect Peripheral

extension MyBluetoothManager {
	func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
		print("Successfully connected to \(peripheral.identifier.uuidString)")
	}
}

 

This delegate function gets invoke automatically when the Central connects with the peripheral successfully. The connected peripheral object is a parameter in the callback, which you can use for exploration and communication.

 

Exploration

Set Up Peripheral Manager

class MyPeripheralManager: NSObject {
	var peripheral: CBPeripheral
	var services: [CBService]
	var communication_characteristic: CBCharacteristic?
	let communication_characteristic_uuid: "6E400001-B5A3-F393­E0A9­E50E24DCCA9E"	// sample UUID put your own hardware uuid
	
	init(peripheral: CBPeripheral) {
		super.init()
		self.peripheral = peripheral
	}
}

extension MyPeripheralManager: CBPeripheralDelegate {
	//...
}

 

Discover Services

peripheral.delegate = self
peripheral.discoverServices(nil)

 

The Central begins to go through all the services available on the connected peripheral. The didDiscoverServices callback part of PeripheralDelegate gets invoke once services discovery is completed.

 

Did Discover Services

extension MyPeripheralManager: CBPeripheralDelegate {
	func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
		services = peripheral.services
		
		// read characteristics of services here
	}
}

 

This delegate function is automatically invoked after the discoverServices function finishes. Any error(s) are reported in this callback. If there are no errors, then a list of the services the peripheral offers is now available.

 

Discover Characteristics For Service

for service in services {
	print("Service: " + String(describing: service))
	peripheral.discoverCharacteristics(nil, for: service)
}

 

The Central begins to discover what characteristics are part of a specified service. When all the characteristics for the service are found, the callback, didDiscoverCharacterisitcsForService, occurs.

 

Did Discover Characteristics For Service

extension MyPeripheralManager: CBPeripheralDelegate {
	func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor: CBService, error: Error?) {
		if let characteristics = didDiscoverCharacteristicsFor.characteristics {
			for characteristic in characteristics {
				// do something with characteristic (i.e. check uuid, find out type, subscribe to it, read, write, etc)
				
				if characteristic.uuid.uuidString == communication_characteristic_uuid {
					communication_characteristic = characteristic
				}
			}
		}
	}
}

 

This delegate function is automatically invoked by the method, discoverCharacteristicsForServices, when all the characteristics of a service are discovered. Any error(s) are reported in this callback. If there are no errors, then the list of characteristics are available through the CBService object passed into the function.

 

Interaction

Above the divider line is Case 1, where the Central reads data manually from the Peripheral. Below the divider line is Case 2, where the Central subscribes to a characteristic and gets a notification every time its value updates.

 

Case 1

Read Value For Characteristic

peripheral.readValue(for: communication_characteristic)

 

Reads the data at the specified characteristic. When the characteristic has data, the callback, didUpdateValueForCharacteristic, is invoked once.

 

Did Update Value For Characteristic

extension MyPeripheralManager: CBPeripheralDelegate {
	func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
		if let received_data = characteristic.value {
			// do something with data
		}
	}
}

 

This delegate function is automatically invoked by readValueForCharacteristic. Any error(s) are reported here. If there are no errors, then you can access the data of the characteristic through its value member variable.

 

Case 2

Set Notify Value For Characteristic

peripheral.setNotifyValue(true, for: communication_characteristic)

 

The Central subscribes to a specific characteristic. Afterwards, any updates to the specific characteristic, the Central will get a notification and invoke didUpdateValueForCharacteristic.

 

Did Update Value For Characteristic.

extension MyPeripheralManager: CBPeripheralDelegate {
	func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
		if let received_data = characteristic.value {
			// do something with data
		}
	}
}

 

This delegate function is automatically invoked by readValueForCharacteristic. Any error(s) are reported here. If there are no errors, then you can access the data of the characteristic through its value member variable.

 

Writing

Write Value For Characteristic

let msg_string = "Hello World"
let msg = msg_string.data(using: String.Encoding.utf8)

// write withoutResponse will result in no callback to delegate
// use withResponse for callback
peripheral.writeValue(msg, for: communication_characteristic, type: .withResponse)

 

Writes some data to the characteristic’s receiving end. Once the writing is complete, the didWriteValueForCharacteristic callback is invoked, but only if the write type is with a response.

 

Did Write Value For Characteristic

extension MyPeripheralManager: CBPeripheralDelegate {
	func peripheral(_ peripheral: CBPeripheral, didWriteValueFor: CBCharacteristic, error: Error?) {
		let sent_msg = String(data: didWriteValueFor.value!, encoding: .utf8)
		print("Message sent was: \(sent_msg)")
	}
}

 

This delegate is automatically invoked by writeValueForCharacteristic. Any error(s) are reported here. If there are no errors, then you can access the data sent through the passed in CBCharacteristic object parameter.


 

I hope you found this post helpful. If you found this post helpful, share it with others so they can benefit too. To stay in touch, follow me on Twitter, leave a comment, or send me an email at steven@brightdevelopers.com.


About Steven To

Steven To is a software developer that specializes in mobile development with a background in computer engineering. Beyond his passion for software development, he also has an interest in Virtual Reality, Augmented Reality, Artificial Intelligence, Personal Development, and Personal Finance. If he is not writing software, then he is out learning something new.

One thought on “iOS Bluetooth Low Energy and Custom Hardware — Part 2: Communication

Comments are closed.