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-F393E0A9E50E24DCCA9E" // 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
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.
Pingback: iOS Bluetooth Low Energy and Custom Hardware - Part 3: Optimizing Data Throughput - bright developers