Subscribing for notifications from a CBCharacteristic does not work

First things first: running OSX 10.10.4, iOS 4, Xcode 6.3.2, iPhone 6, Swift


Short story: I have a certain Bluetooth LE device here from which I want to receive notifications when values of a Characteristic change, e.g. by user input. Trying to subscribe to it does not succeed, but rather yields an error `Error Domain=CBATTErrorDomain Code=10 "The attribute could not be found."`


Long story: So, I have a BluetoothManager class in which I start scanning for Peripherals as soon as my `$CBCentralManager.state` is `.PoweredOn`. That's easy, I'm even a good citizen and scan specifically for those with the Service I want


centralManager.scanForPeripheralsWithServices([ServiceUUID], options: nil)


Hoping this will succeed, I implemented the following delegate method:


func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!, advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) {


if *this is a known device* {

connectToPeripheral(peripheral)

return

}

[...] // various stuff to make something a known device, this works

}


So moving along, we get to:


func connectToPeripheral(peripheral: CBPeripheral) {

println("connecting to \(peripheral.identifier)")

[...] // saving the peripheral in an array along the way so it is being retained


centralManager.connectPeripheral(peripheral, options: nil)

}


Yupp, this succeeds, so I get the confirmation and start to discover the Service:


func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!) {

println("Connected \(peripheral.name)")

peripheral.delegate = self

println("connected to \(peripheral)")

peripheral.discoverServices([BluetoothConstants.MY_SERVICE_UUID])

}


Which also works, since that delegate method gets called as well:


func peripheral(peripheral: CBPeripheral!, didDiscoverServices error: NSError!) {

if peripheral.services != nil {

for service in peripheral.services {

println("discovered service \(service)")

let serviceObject = service as! CBService

[...] // Discover the Characteristic to send controls to, this works

peripheral.discoverCharacteristics([BluetoothConstants.MY_CHARACTERISTIC_NOTIFICATION_UUID], forService: serviceObject)

[...] // Some unneccessary stuff about command caches

}

}

}


And what do you know: the characteristic *gets* discovered!


func peripheral(peripheral: CBPeripheral!, didDiscoverCharacteristicsForService service: CBService!, error: NSError!) {

for characteristic in service.characteristics {

let castCharacteristic = characteristic as! CBCharacteristic

characteristics.append(castCharacteristic) // Retaining the characteristic in an Array as well, not sure if I need to do this

println("discovered characteristic \(castCharacteristic)")

if *this is the control characteristic* {

println("control")

} else if castCharacteristic.UUID.UUIDString == BluetoothConstants.MY_CHARACTERISTIC_NOTIFICATION_UUID.UUIDString {

println("notification")

peripheral.setNotifyValue(true, forCharacteristic: castCharacteristic)

} else {

println(castCharacteristic.UUID.UUIDString) // Just in case

}

println("following properties:")

// Just to see what we are dealing with

if (castCharacteristic.properties & CBCharacteristicProperties.Broadcast) != nil {

println("broadcast")

}

if (castCharacteristic.properties & CBCharacteristicProperties.Read) != nil {

println("read")

}

if (castCharacteristic.properties & CBCharacteristicProperties.WriteWithoutResponse) != nil {

println("write without response")

}

if (castCharacteristic.properties & CBCharacteristicProperties.Write) != nil {

println("write")

}

if (castCharacteristic.properties & CBCharacteristicProperties.Notify) != nil {

println("notify")

}

if (castCharacteristic.properties & CBCharacteristicProperties.Indicate) != nil {

println("indicate")

}

if (castCharacteristic.properties & CBCharacteristicProperties.AuthenticatedSignedWrites) != nil {

println("authenticated signed writes ")

}

if (castCharacteristic.properties & CBCharacteristicProperties.ExtendedProperties) != nil {

println("indicate")

}

if (castCharacteristic.properties & CBCharacteristicProperties.NotifyEncryptionRequired) != nil {

println("notify encryption required")

}

if (castCharacteristic.properties & CBCharacteristicProperties.IndicateEncryptionRequired) != nil {

println("indicate encryption required")

}

peripheral.discoverDescriptorsForCharacteristic(castCharacteristic) // Do I need this?

}

}


Now the console output up until here looks like this:


connected to <CBPeripheral: 0x1740fc780, identifier = $FOO, name = $SomeName, state = connected>

discovered service <CBService: 0x170272c80, isPrimary = YES, UUID = $BAR>

[...]

discovered characteristic <CBCharacteristic: 0x17009f220, UUID = $BARBAR properties = 0xA, value = (null), notifying = NO>

control

following properties:

read

write

[...]

discovered characteristic <CBCharacteristic: 0x17409d0b0, UUID = $BAZBAZ, properties = 0x1A, value = (null), notifying = NO>

notification

following properties:

read

write

notify

[...]

discovered DescriptorsForCharacteristic

[]

updateNotification: false


Hey! It says `updateNotification` is `false`. Where does that come from? Why, it's my callback for `setNotify...`:


func peripheral(peripheral: CBPeripheral!, didUpdateNotificationStateForCharacteristic characteristic: CBCharacteristic!, error: NSError!) {

println("updateNotification: \(characteristic.isNotifying)")

}


What gives? I told it to be notifying! Why isn't it notifying? Let's set a breakpoint in the line with the println and check out the error object:


(lldb) po error

Error Domain=CBATTErrorDomain Code=10 "The attribute could not be found." UserInfo=0x17026eac0 {NSLocalizedDescription=The attribute could not be found.}


OK, so this leaves me out of ideas. I wasn't able to finde relevant clues regarding that error code. The description itself I cannot fathom since I tried to set up the notification for a Characteristic that I discovered earlier, hence it *must* exist, right? Also, on Android it seems possible to subscribe for notifications, so I guess I can rule out problems with the device... or can I? Any clues regarding this are truly appreciated!

Replies

hah, I'm in the same boat as you. If you find any solution, please let us know 🙂

You're never setting anything.


Your characteristic is nil, and will always be nil. You need to pull it out of the characteristics contained within the service


Obj-C code


- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
     
     if (error) {
          NSLog("Error discovering characteristics for service %@ %@", service.UUID.UUIDString, error);
          return;
     }

     CBCharacteristic *castCharacteristic = nil;
     for (CBCharacteristic *characteristic in service.characteristics) {
          if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"1234"]]) {
               castCharacteristic = characteristic;
               [peripheral setNotifyValue:YES forCharacteristic:castCharacteristic];
               break;
          }
     }
}

have this been solved? i came across the same question.

i check the charatcteristic, it is not nil, here are the codes:


- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error

if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@RBL_CHAR_RX_UUID]]) {

NSLog(@"%lu",(unsigned long)characteristic.properties);

self.activeReadCBCharacteristic = characteristic;

if (characteristic.properties & CBCharacteristicPropertyNotify ) {

[peripheral setNotifyValue:YES forCharacteristic:characteristic];

}

}

}