I have an iOS app which is a central to a peripheral running on an nRF51822. A similar Android app works fine and is able to connect to the peripheral, discover services, bond explicitly, and read an encrypted characteristic.
On iOS, however, there is no explicit bonding in the CoreBluetooth API. I am working on the basis that if I want to bond, I should just read an encrypted characteristic in order to force bonding to occur.
When is the right time to do that? If I do it when discovering the characteristic I want to read, in this function...
func peripheral(peripheral: CBPeripheral!, didDiscoverCharacteristicsForService service: CBService!, error: NSError!)
... I never get a callback to peripheral:didUpdateValueForCharacteristic
. Reading an unencrypted characteristic at this point does work though, I believe. Is there any other way to force bonding to occur? Nowhere in the nRF Toolbox iOS app code, as an example, can I find an explicit bond going on.
Here's the iOS app code:
import CoreBluetooth
import CoreLocation
class BluetoothPeripheralController: NSObject, CBPeripheralDelegate {
private var _peripheral: CBPeripheral!
var peripheral: CBPeripheral! {
get {
return self._peripheral
}
set {
if self._peripheral != nil {
self._peripheral = nil
}
self._peripheral = newValue
if self._peripheral != nil {
self.peripheralDidChange(newValue)
}
}
}
private var notificationCenter: NSNotificationCenter = {
return NSNotificationCenter.defaultCenter()
}()
private var testModeCharacteristic: CBCharacteristic?
private var voltageCharacteristic: CBCharacteristic?
private var firmwareLoggingCharacteristic: CBCharacteristic?
static let voltageDidChangeNotification = "voltageDidChangeNotification"
static let testModeDidChangeNotification = "testModeDidChangeNotification"
static let bonded = "bonded"
static let firmwareLoggingDidChangeNotification = "firmwareLoggingDidChangeNotification"
func peripheralDidChange(peripheral: CBPeripheral) {
peripheral.delegate = self
}
func peripheral(peripheral: CBPeripheral!, didDiscoverServices error: NSError!) {
if error != nil {
log.error("Error: \(error)")
return
}
for service in peripheral.services {
log.verbose("Discovering characteristics for service: \(BleService().lookup(service.UUID))")
peripheral.discoverCharacteristics(nil, forService: service as! CBService)
}
}
// Hold on to characteristic instances so we can read/write/notify on them later.
func peripheral(peripheral: CBPeripheral!, didDiscoverCharacteristicsForService service: CBService!, error: NSError!) {
if let chars = service.characteristics as? [CBCharacteristic] {
log.verbose("* \(BleService().lookup(service.UUID))")
for char in chars {
log.verbose("** \(BleCharacteristic().lookup(char.UUID))")
if char.UUID.isEqual(BleCharacteristic.voltageCharacteristicUUID) {
voltageCharacteristic = char
}
if char.UUID.isEqual(BleCharacteristic.testModeCharacteristicUUID) {
testModeCharacteristic = char
log.verbose("Reading value for test mode.")
// Reading any encrypted value here does NOT result in a callback to peripheral:didUpdateValueForCharacteristic.
peripheral.readValueForCharacteristic(char)
}
if char.UUID.isEqual(BleCharacteristic.nordicNrfUartRxUUID) {
firmwareLoggingCharacteristic = char
}
}
}
}
func peripheral(peripheral: CBPeripheral!, didUpdateValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!) {
if error != nil {
log.verbose("Error on value: \(error)")
return
}
let data: NSData = characteristic.value
log.verbose("Got value \(data) for characterisitc \(BleCharacteristic().lookup(characteristic.UUID))")
switch characteristic.UUID {
case BleCharacteristic.voltageCharacteristicUUID:
var voltage: Float = 0.0
data.getBytes(&voltage, length: sizeof(Float))
log.debug("Voltage: \(voltage)")
// The map view is interested in this.
self.notificationCenter.postNotificationName(BluetoothPeripheralController.voltageDidChangeNotification, object: self, userInfo: ["voltage": NSNumber(float: voltage)])
case BleCharacteristic.testModeCharacteristicUUID:
log.debug("Test mode: \(data). We are now bonded.")
self.notificationCenter.postNotificationName(BluetoothPeripheralController.bonded, object: self, userInfo: nil)
default:
log.debug("Unknown characteristic UUID: \(characteristic.UUID)")
}
}
func peripheral(peripheral: CBPeripheral!, didUpdateNotificationStateForCharacteristic characteristic: CBCharacteristic!, error: NSError!) {
if error != nil {
log.verbose("Error updating notification state: \(error)")
return
}
switch characteristic.UUID {
case BleCharacteristic.nordicNrfUartRxUUID:
log.verbose("Notification state change on firmware logging. Now notifying: \(characteristic.isNotifying)")
default:
log.verbose("Unhandled characteristic: \(characteristic.description)")
}
}
// Read/write
func readTestMode() {
if testModeCharacteristic == nil {
log.warning("Trying to read test mode before we have a characterisitc instance.")
return
}
// Calls back to peripheral:didUpdateValueForCharacteristic.
peripheral.readValueForCharacteristic(testModeCharacteristic)
}
}
Here are my security params for the device manager on the Nordic side. The device has no user input capabilities.
// Timeout for Pairing Request or Security Request (in seconds).
#define SEC_PARAM_TIMEOUT 30
// Perform bonding.
#define SEC_PARAM_BOND 1
// Man In The Middle protection NOT required.
#define SEC_PARAM_MITM 0
// No I/O capabilities.
#define SEC_PARAM_IO_CAPABILITIES BLE_GAP_IO_CAPS_NONE
// Out Of Band data not available.
#define SEC_PARAM_OOB 0
// Minimum encryption key size.
#define SEC_PARAM_MIN_KEY_SIZE 7
// Maximum encryption key size.
#define SEC_PARAM_MAX_KEY_SIZE 16