Our setup for this error is:
- the device BLE controller is a nordic N51822
- tested with iPhone A1429 (but fails also on all other iPhones with BLE4.x capabilities)
- iOS Build is 9.1 (13B143) (but also fails with all iOS version from 9.x up, even all the betas)
So what I've done is:
In code i use some constants for convenience:
#define findMeServiceUUID [CBUUID UUIDWithString:@"1802"]
#define genericAccessServiceUUID [CBUUID UUIDWithString:@"1800"]
#define linkLossServiceUUID [CBUUID UUIDWithString:@"1803"]
#define deviceNameCharacteristicUUID [CBUUID UUIDWithString:@"2A00"]
#define alertLevelCharacteristicUUID [CBUUID UUIDWithString:@"2A06"]
#define BT_ALERT_LEVEL_NONE 0
#define BT_LEVEL_MILD 1
#define BT_ALERT_LEVEL_HIGH 2
#define BT_ALERT_LEVEL_RESERVED(LVL) LVL
1. Scan for peripherals with findMe and linkLoss Service (1802 and 1803):
NSDictionary* scanOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO]
forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
NSArray* serviceArray = [NSArray arrayWithObjects:findMeServiceUUID,linkLossServiceUUID, nil];
[centralManager scanForPeripheralsWithServices:serviceArray options:scanOptions];
2. When found an peripheral i add the CBPeripheral* to an mutable array used as datasource for a table view displaying the scan results:
- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(@"INFO: Did discover peripheral <%@> with identifier <%@>", peripheral.name, peripheral.identifier);
[peripherals addObject:peripheral];
// Update UITableView
[self reloadTable];
}
3. Selected the peripheral in table view (set as CBPeripheral* selectedPeripheral) and connected to it with:
NSDictionary* connectOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey];
[centralManager connectPeripheral:selectedPeripheral options:connectOptions];
4. When connected I start the service discovery
- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"INFO: Did connect to peripheral <%@> with identifier <%@>", peripheral.name, peripheral.identifier);
isConnected = YES;
// Update the info UILabel
[self updateLabel];
// Start service discovery
[peripheral discoverServices:nil];
}
5. When a service is discoverd i start to discover the service characteristics:
- (void) peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
NSLog(@"INFO: Did discover %d services on %@", (int)[peripheral services].count, [peripheral name]);
for(CBService *s in [peripheral services]) {
if([[s UUID] isEqual:findMeServiceUUID])
immediateAlertService = s;
if([[s UUID] isEqual:linkLossServiceUUID])
linkLossService = s;
NSLog(@"INFO: Did discover service %@ on %@", [s.UUID representativeString], [peripheral name]);
[peripheral discoverCharacteristics:[NSArray arrayWithObject:alertLevelCharacteristicUUID] forService:s];
}
}
6. When discoverd a characterisics for a service i store thr references to a variable.
- (void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
NSLog(@"INFO: Did discover %d characteristics for service %@ on peripheral %@", (int)service.characteristics.count, service, peripheral);
for(CBCharacteristic *c in [service characteristics])
{
NSLog(@"INFO: Did discover characteristic %@ for service %@ on peripheral %@", [c.UUID representativeString], service, peripheral);
if([service isEqual:immediateAlertService] &&
[[c UUID] isEqual:alertLevelCharacteristicUUID])
immediateAlertAlertLevelCharacteristic = c;
else if([service isEqual:linkLossService] &&
[[c UUID] isEqual:alertLevelCharacteristicUUID]) {
if(isBonded)
return;
linkLossAlertLevelCharacteristic = c;
if(linkLossAlertLevelCharacteristic)
[self writeLinkLossAlert:BT_ALERT_LEVEL_NONE];
}
}
}
7. As you can see if linkLossAlertLevelCharacteristic is discovered i immediately write BT_ALERT_LEVEL_NONE ( = 0 ) to it, to initialize bonding:
- (void) writeLinkLossAlertLevelToTag:(UInt8) linkLossAlertLevel
{
NSData* data = [NSData dataWithBytes:&linkLossAlertLevel length:1];
[selectedPeripheral writeValue:data forCharacteristic:linkLossAlertLevelCharacteristic type:CBCharacteristicWriteWithResponse];
}
- (bool) writeLinkLossAlert:(UInt8)level
{
if(linkLossAlertLevelCharacteristic != nil) {
NSLog(@"INFO: Writes link loss alert %d to peripheral...", level);
[self writeLinkLossAlertLevelToTag:level];
return YES;
}
NSLog(@"ERROR: Couldn´t write to alert level on link loss service.");
return NO;
}
8. The "Pairing" dialog appears and is confirmed to pair with. But instead of pairing the write fails and there after i also get a disconnect with the timeout error.
It works all fine until iOS 9 and i don't use any deprecated API's
I logged the delegate callbacks from CBCentralManager and CBPeripheral:
2015-10-30 11:38:07.881 BLEControl[307:32547] INFO: Bluetooth is enabled
2015-10-30 11:38:14.423 BLEControl[307:32547] INFO: Start to scan for peripherals
2015-10-30 11:38:14.456 BLEControl[307:32547] INFO: Did discover peripheral <Torantrieb> with identifier <<__NSConcreteUUID 0x15d70340> 0545B850-B6BE-1BB9-10E7-80E3559AC9FB>
2015-10-30 11:38:18.129 BLEControl[307:32547] INFO: Stopped to scan for peripherals
2015-10-30 11:38:18.164 BLEControl[307:32547] INFO: Did connect to peripheral <Torantrieb> with identifier <<__NSConcreteUUID 0x15d70340> 0545B850-B6BE-1BB9-10E7-80E3559AC9FB>
2015-10-30 11:38:18.362 BLEControl[307:32547] INFO: Did discover 3 services on Torantrieb
2015-10-30 11:38:18.363 BLEControl[307:32547] INFO: Did discover service 1804 on Torantrieb
2015-10-30 11:38:18.364 BLEControl[307:32547] INFO: Did discover service 1802 on Torantrieb
2015-10-30 11:38:18.364 BLEControl[307:32547] INFO: Did discover service 1803 on Torantrieb
2015-10-30 11:38:18.366 BLEControl[307:32547] INFO: Did discover 0 characteristics for service <CBService: 0x15d85a00, isPrimary = YES, UUID = 1804> on peripheral <CBPeripheral: 0x15d85de0, identifier = 0545B850-B6BE-1BB9-10E7-80E3559AC9FB, name = Torantrieb, state = connected>
2015-10-30 11:38:18.367 BLEControl[307:32547] INFO: Did discover 1 characteristics for service <CBService: 0x15d8d5e0, isPrimary = YES, UUID = 1802> on peripheral <CBPeripheral: 0x15d85de0, identifier = 0545B850-B6BE-1BB9-10E7-80E3559AC9FB, name = Torantrieb, state = connected>
2015-10-30 11:38:18.367 BLEControl[307:32547] INFO: Did discover characteristic 2a06 for service <CBService: 0x15d8d5e0, isPrimary = YES, UUID = 1802> on peripheral <CBPeripheral: 0x15d85de0, identifier = 0545B850-B6BE-1BB9-10E7-80E3559AC9FB, name = Torantrieb, state = connected>
2015-10-30 11:38:18.367 BLEControl[307:32547] INFO: Did discover 1 characteristics for service <CBService: 0x15d8d620, isPrimary = YES, UUID = 1803> on peripheral <CBPeripheral: 0x15d85de0, identifier = 0545B850-B6BE-1BB9-10E7-80E3559AC9FB, name = Torantrieb, state = connected>
2015-10-30 11:38:18.369 BLEControl[307:32547] INFO: Did discover characteristic 2a06 for service <CBService: 0x15d8d620, isPrimary = YES, UUID = 1803> on peripheral <CBPeripheral: 0x15d85de0, identifier = 0545B850-B6BE-1BB9-10E7-80E3559AC9FB, name = Torantrieb, state = connected>
2015-10-30 11:38:18.370 BLEControl[307:32547] INFO: Writes link loss alert 0 to peripheral...
2015-10-30 11:38:22.354 BLEControl[307:32547] ERROR: Failed to write value for characteristic <CBCharacteristic: 0x15d8d890, UUID = 2A06, properties = 0xA, value = (null), notifying = NO>, reason: Error Domain=CBATTErrorDomain Code=15 "Encryption is insufficient." UserInfo={NSLocalizedDescription=Encryption is insufficient.}
2015-10-30 11:38:23.145 BLEControl[307:32547] ERROR: Did disconnect peripheral <Torantrieb> with identifier <<__NSConcreteUUID 0x15d70340> 0545B850-B6BE-1BB9-10E7-80E3559AC9FB>: Error Domain=CBErrorDomain Code=6 "The connection has timed out unexpectedly." UserInfo={NSLocalizedDescription=The connection has timed out unexpectedly.} {
NSLocalizedDescription = "The connection has timed out unexpectedly.";
}
Here is the full code of my Test-View Controller
ViewController.h
#import <UIKit/UIKit.h>
#import <CoreBluetooth/CoreBluetooth.h>
@interface CBUUID (StringExtraction)
- (NSString *)representativeString;
@end
@interface ViewController : UIViewController <UITableViewDelegate,UITableViewDataSource,CBCentralManagerDelegate,CBPeripheralDelegate>
@end
ViewController.m
#import "ViewController.h"
#define findMeServiceUUID [CBUUID UUIDWithString:@"1802"]
#define genericAccessServiceUUID [CBUUID UUIDWithString:@"1800"]
#define linkLossServiceUUID [CBUUID UUIDWithString:@"1803"]
#define deviceNameCharacteristicUUID [CBUUID UUIDWithString:@"2A00"]
#define alertLevelCharacteristicUUID [CBUUID UUIDWithString:@"2A06"]
#define BT_ALERT_LEVEL_NONE 0
#define BT_LEVEL_MILD 1
#define BT_ALERT_LEVEL_HIGH 2
#define BT_ALERT_LEVEL_RESERVED(LVL) LVL
@implementation CBUUID (StringExtraction)
- (NSString *)representativeString;
{
NSData *data = [self data];
NSUInteger bytesToConvert = [data length];
const unsigned char *uuidBytes = [data bytes];
NSMutableString *outputString = [NSMutableString stringWithCapacity:16];
for (NSUInteger currentByteIndex = 0; currentByteIndex < bytesToConvert; currentByteIndex++)
{
switch (currentByteIndex)
{
case 3:
case 5:
case 7:
case 9:[outputString appendFormat:@"%02x-", uuidBytes[currentByteIndex]]; break;
default:[outputString appendFormat:@"%02x", uuidBytes[currentByteIndex]];
}
}
return outputString;
}
@end
@interface ViewController ()
@end
@implementation ViewController {
NSMutableArray* peripherals;
BOOL isScanning;
BOOL isPairing;
BOOL isBonded;
BOOL isConnected;
BOOL hasFailed;
CBCentralManager* centralManager;
CBPeripheral* selectedPeripheral;
CBService *immediateAlertService;
CBCharacteristic *immediateAlertAlertLevelCharacteristic;
CBService *linkLossService;
CBCharacteristic *linkLossAlertLevelCharacteristic;
IBOutlet UILabel* labelPeripheral;
IBOutlet UIButton* buttonScan;
IBOutlet UITableView* tablePeripherals;
}
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if(!self)
return self;
peripherals = [[NSMutableArray alloc] init];
centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self updateLabel];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - BLE
-(void)startScan {
if(isScanning)
return;
[centralManager stopScan];
[peripherals removeAllObjects];
[self reloadTable];
NSDictionary* scanOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO]
forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
NSArray* serviceArray = [NSArray arrayWithObjects:findMeServiceUUID,linkLossServiceUUID, nil];
[centralManager scanForPeripheralsWithServices:serviceArray options:scanOptions];
NSLog(@"INFO: Start to scan for peripherals");
isScanning = YES;
}
-(void)stopScan {
/
if(!isScanning)
return;
[centralManager stopScan];
NSLog(@"INFO: Stopped to scan for peripherals");
isScanning = NO;
}
-(void)pairPeripheral {
if(!selectedPeripheral || isPairing)
return;
isPairing = YES;
selectedPeripheral.delegate = self;
[self updateLabel];
[self connectPeripheral];
}
-(void)connectPeripheral {
if(!selectedPeripheral || isConnected)
return;
NSDictionary* connectOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey];
[centralManager connectPeripheral:selectedPeripheral options:connectOptions];
}
-(void)disconnectPeripheral {
if(!selectedPeripheral || !isConnected)
return;
[centralManager cancelPeripheralConnection:selectedPeripheral];
}
-(void)alertPeripheral {
if(!selectedPeripheral || !isBonded)
return;
[self writeImmediateAlert:BT_ALERT_LEVEL_RESERVED(3)];
}
- (void) writeLinkLossAlertLevelToTag:(UInt8) linkLossAlertLevel
{
NSData* data = [NSData dataWithBytes:&linkLossAlertLevel length:1];
[selectedPeripheral writeValue:data forCharacteristic:linkLossAlertLevelCharacteristic type:CBCharacteristicWriteWithResponse];
}
- (bool) writeLinkLossAlert:(UInt8)level
{
if(linkLossAlertLevelCharacteristic != nil) {
NSLog(@"INFO: Writes link loss alert %d to peripheral...", level);
[self writeLinkLossAlertLevelToTag:level];
return YES;
}
NSLog(@"ERROR: Couldn´t write to alert level on link loss service.");
return NO;
}
- (void) writeImmediateAlertLevelToTag:(UInt8) immediateAlertLevel
{
NSData* data = [NSData dataWithBytes:&immediateAlertLevel length:1];
[selectedPeripheral writeValue:data forCharacteristic:immediateAlertAlertLevelCharacteristic type:CBCharacteristicWriteWithoutResponse];
}
- (bool) writeImmediateAlert:(UInt8)level
{
if(immediateAlertAlertLevelCharacteristic != nil) {
NSLog(@"INFO: Writes immediate alert %d to tag...", level);
[self writeImmediateAlertLevelToTag:level];
return YES;
}
NSLog(@"INFO: Couldn´t write to alert level on immediate alert service.");
return NO;
}
#pragma mark - CentralManager delegate
- (void) centralManagerDidUpdateState:(CBCentralManager *)central
{
if ([central state] == CBCentralManagerStatePoweredOn)
NSLog(@"INFO: Bluetooth is enabled");
else
{
NSLog(@"WARNING: Bluetooth is not enabled");
[labelPeripheral setText:@"Bluetooth is not enabled!"];
}
}
- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(@"INFO: Did discover peripheral <%@> with identifier <%@>", peripheral.name, peripheral.identifier);
[peripherals addObject:peripheral];
[self reloadTable];
}
- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"INFO: Did connect to peripheral <%@> with identifier <%@>", peripheral.name, peripheral.identifier);
isConnected = YES;
[self updateLabel];
[peripheral discoverServices:nil];
}
- (void) centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
isConnected = NO;
if(error) {
NSLog(@"ERROR: Did disconnect peripheral <%@> with identifier <%@>: %@ %@", peripheral.name, peripheral.identifier, error, [error userInfo]);
if(isPairing) {
hasFailed = YES;
isPairing = NO;
[self updateLabel];
}
return;
}
[self updateLabel];
NSLog(@"INFO: Did disconnect peripheral <%@> with identifier <%@>", peripheral.name, peripheral.identifier);
}
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
isPairing = NO;
[self updateLabel];
if(error) {
NSLog(@"ERROR: Did failed to connect to peripheral <%@> with identifier <%@>: %@ %@", peripheral.name, peripheral.identifier, error, [error userInfo]);
return;
}
NSLog(@"WARNING: Did failed to connect to peripheral <%@> with identifier <%@>", peripheral.name, peripheral.identifier);
}
#pragma mark - Peripheral delegate
- (void) peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
NSLog(@"INFO: Did discover %d services on %@", (int)[peripheral services].count, [peripheral name]);
for(CBService *s in [peripheral services]) {
if([[s UUID] isEqual:findMeServiceUUID])
immediateAlertService = s;
if([[s UUID] isEqual:linkLossServiceUUID])
linkLossService = s;
NSLog(@"INFO: Did discover service %@ on %@", [s.UUID representativeString], [peripheral name]);
[peripheral discoverCharacteristics:[NSArray arrayWithObject:alertLevelCharacteristicUUID] forService:s];
}
}
- (void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
NSLog(@"INFO: Did discover %d characteristics for service %@ on peripheral %@", (int)service.characteristics.count, service, peripheral);
for(CBCharacteristic *c in [service characteristics])
{
NSLog(@"INFO: Did discover characteristic %@ for service %@ on peripheral %@", [c.UUID representativeString], service, peripheral);
if([service isEqual:immediateAlertService] &&
[[c UUID] isEqual:alertLevelCharacteristicUUID])
immediateAlertAlertLevelCharacteristic = c;
else if([service isEqual:linkLossService] &&
[[c UUID] isEqual:alertLevelCharacteristicUUID]) {
if(isBonded)
return;
linkLossAlertLevelCharacteristic = c;
if(linkLossAlertLevelCharacteristic)
[self writeLinkLossAlert:BT_ALERT_LEVEL_NONE];
}
}
}
- (void) peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
NSLog(@"INFO: Did update value for characteristic %@, new value: %@, error: %@", characteristic, [characteristic value], error);
}
- (void) peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error)
{
NSLog(@"ERROR: Failed to write value for characteristic %@, reason: %@", characteristic, error);
}
else
{
NSLog(@"INFO: Did write value for characterstic %@, new value: %@", characteristic, [characteristic value]);
isPairing = NO;
isBonded = YES;
[self updateLabel];
}
}
#pragma mark - UI
-(IBAction)onScanButton:(id)sender {
if(!isScanning) {
[self startScan];
[buttonScan setTitle:@"Stop" forState:UIControlStateNormal];
}
else {
[self stopScan];
[buttonScan setTitle:@"Start" forState:UIControlStateNormal];
}
[self updateLabel];
}
-(IBAction)onConnectButton:(id)sender {
[self connectPeripheral];
}
-(IBAction)onDisconnectButton:(id)sender {
[self disconnectPeripheral];
}
-(IBAction)onAlertButton:(id)sender {
[self alertPeripheral];
}
-(void) updateLabel {
if(!selectedPeripheral) {
if(!isScanning)
[labelPeripheral setText:@"Press start to scan"];
else
[labelPeripheral setText:@"Scanning..."];
return;
}
if(hasFailed) {
[labelPeripheral setText:[NSString stringWithFormat:@"%@ pairing failed", selectedPeripheral.name]];
return;
}
if(isBonded) {
[labelPeripheral setText:[NSString stringWithFormat:@"%@ bonded", selectedPeripheral.name]];
return;
}
if(isConnected) {
[labelPeripheral setText:[NSString stringWithFormat:@"%@ connected", selectedPeripheral.name]];
return;
}
if(isPairing) {
[labelPeripheral setText:[NSString stringWithFormat:@"%@ pairing...", selectedPeripheral.name]];
return;
}
}
#pragma Mark - Validating
-(CBPeripheral*)getPeripheral:(NSInteger)arrayIndex {
CBPeripheral* peripheral = NULL;
if(!peripherals)
NSLog(@"FATAL: Peripherals array is NULL");
else {
if(peripherals.count < arrayIndex)
NSLog(@"FATAL: Peripherals array length is too short.");
peripheral = [peripherals objectAtIndex:arrayIndex];
if(!peripheral)
NSLog(@"FATAL: Peripheral is NULL.");
}
return peripheral;
}
# pragma mark - TableView thread synchronization
-(void)reloadTable {
[tablePeripherals performSelectorOnMainThread:@selector(reloadData)
withObject:nil
waitUntilDone:YES];
}
#pragma mark - TableView dataSource and delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 64;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(!peripherals)
return 0;
return [peripherals count];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
selectedPeripheral = [self getPeripheral:indexPath.item];
if(!selectedPeripheral)
return;
if(isScanning)
[self onScanButton:buttonScan];
[self pairPeripheral];
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"SettingsCell"];
if(!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"SettingsCell"];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
}
CBPeripheral* peripheral = [self getPeripheral:indexPath.item];
if(!peripheral) {
[cell.textLabel setText:@"<Unexpected error>"];
return cell;
}
[cell.textLabel setText:peripheral.name];
return cell;
}
@end