Search code examples
objective-cios7bluetooth-lowenergy

iOS 7 ANCS: Discovering the primary ANCS Service UUID


Under iOS7, is the primary ANCS Service meant to be constantly advertised, or does it need to be enabled in obfuscated settings / implemented using a custom CBPeripheralManager (using the Apple-specified Service and Characteristic UUIDs) for a potential Notification Consumer to successfully discover it and subscribe?

The Apple documentation (both the CoreBluetooth Programming Guide, and the ANCS Specification) are surprisingly bereft of any information on this. They seem to hint at requiring a custom implementation, but this is just conjecture on our part.

Given the primary ANCS Service UUID: 7905F431-B5CE-4E99-A40F-4B1E122D00D0, performing a scan yields no hits. Scanning the entire BLE spectrum, as expected, yields hits for other BLE devices, but not a single ANCS device.

EDIT 1:

Defining a custom CBPeripheralManager and manually adding the Apple-specified ANCS Service with its associated Characteristics fails, with the NSError: Error Domain=CBErrorDomain Code=8 "The specified UUID is not allowed for this operation."

Consequently, it appears that the Service UUID is reserved by Apple (as it should be), and we cannot enable it in this manner.

Any insight is greatly appreciated; we've reached out to Apple about this, and will update when we hear from them.

The code below reproduces the NSError mentioned above:

// define the ANCS Characteristics
CBUUID *notificationSourceUUID = [CBUUID UUIDWithString:@"9FBF120D-6301-42D9-8C58-25E699A21DBD"];
CBMutableCharacteristic *notificationSource = [[CBMutableCharacteristic alloc] initWithType:notificationSourceUUID properties:CBCharacteristicPropertyNotifyEncryptionRequired value:nil permissions:CBAttributePermissionsReadEncryptionRequired];
CBUUID *controlPointUUID = [CBUUID UUIDWithString:@"69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9"];
CBMutableCharacteristic *controlPoint = [[CBMutableCharacteristic alloc] initWithType:controlPointUUID properties:CBCharacteristicPropertyWrite value:nil permissions:CBAttributePermissionsWriteEncryptionRequired];
CBUUID *dataSourceUUID = [CBUUID UUIDWithString:@"22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB"];
CBMutableCharacteristic *dataSource = [[CBMutableCharacteristic alloc] initWithType:dataSourceUUID properties:CBCharacteristicPropertyNotifyEncryptionRequired value:nil permissions:CBAttributePermissionsReadEncryptionRequired];

// define the ANCS Service
CBUUID *ANCSUUID = [CBUUID UUIDWithString:@"7905F431-B5CE-4E99-A40F-4B1E122D00D0"];
CBMutableService *ANCS = [[CBMutableService alloc] initWithType:ANCSUUID primary:YES];
ANCS.characteristics = @[notificationSource, controlPoint, dataSource];

// define the Advertisement data
NSMutableDictionary *advertisementData = [NSMutableDictionary dictionary];
[advertisementData setValue:@"CUSTOM_ANCS" forKey:CBAdvertisementDataLocalNameKey];
[advertisementData setValue:@"7905F431-B5CE-4E99-A40F-4B1E122D00D0" forKey:CBAdvertisementDataServiceUUIDsKey];

// publish the ANCS service
[self.peripheralManager addService:ANCS];

Solution

  • As a belated answer to this question, now that Mavericks is out, here is what we've come up with.

    Our initial efforts to implement the ANCS specification between two iOS devices, one as Peripheral one as Central, were unsuccessful. Apple responded to us after some time (hat tip to their evangelists) and told us this was impossible.

    With the addition of the CBPeripheralManager class and CBPeripheralManagerDelegate protocol to the CoreBluetooth.framework embedded in the IOBluetooth.framework on OSX Mavericks (deep breath), we can now use the BLE radio on an OSX device to implement and advertise ANCS.

    Thus, this snippet belongs to a CBPeripheralManager on OSX:

    - (void) advertiseANCS
    {
        NSLog(@"%s", __FUNCTION__);
    
        // define the ANCS Characteristics
        CBUUID *notificationSourceUUID = [CBUUID UUIDWithString:@"9FBF120D-6301-42D9-8C58-25E699A21DBD"];
        CBMutableCharacteristic *notificationSource = [[CBMutableCharacteristic alloc] initWithType:notificationSourceUUID properties:CBCharacteristicPropertyNotifyEncryptionRequired value:nil permissions:CBAttributePermissionsReadEncryptionRequired];
        CBUUID *controlPointUUID = [CBUUID UUIDWithString:@"69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9"];
        CBMutableCharacteristic *controlPoint = [[CBMutableCharacteristic alloc] initWithType:controlPointUUID properties:CBCharacteristicPropertyWrite value:nil permissions:CBAttributePermissionsWriteEncryptionRequired];
        CBUUID *dataSourceUUID = [CBUUID UUIDWithString:@"22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB"];
        CBMutableCharacteristic *dataSource = [[CBMutableCharacteristic alloc] initWithType:dataSourceUUID properties:CBCharacteristicPropertyNotifyEncryptionRequired value:nil permissions:CBAttributePermissionsReadEncryptionRequired];
    
        // define the ANCS Service
        CBUUID *ANCSUUID = [CBUUID UUIDWithString:@"7905F431-B5CE-4E99-A40F-4B1E122D00D0"];
        CBMutableService *ANCS = [[CBMutableService alloc] initWithType:ANCSUUID primary:YES];
        ANCS.characteristics = @[notificationSource, controlPoint, dataSource];
    
        // define the Advertisement data
        NSMutableDictionary *advertisementData = [NSMutableDictionary dictionary];
        [advertisementData setValue:@"ANCS" forKey:CBAdvertisementDataLocalNameKey];
        [advertisementData setValue:@[ANCSUUID] forKey:CBAdvertisementDataServiceUUIDsKey];
    
        // publish the ANCS service
        [self.peripheralManager addService:ANCS];
        [self.peripheralManager startAdvertising:advertisementData];
    }
    

    Whereas this snippet belongs on a CBCentralManager on an iOS device:

    - (void) discoverANCS
    {
        NSLog(@"%s", __FUNCTION__);
    
        NSMutableArray *services = [NSMutableArray array];
        [services addObject:@"7905F431-B5CE-4E99-A40F-4B1E122D00D0"];
    
        NSMutableDictionary *options = [NSMutableDictionary dictionary];
        [options setValue:[NSNumber numberWithBool:NO] forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
    
        [self.centralManager scanForPeripheralsWithServices:services options:options];
    }
    

    The iOS device can now see and connect to the OSX radio, which implements the ANCS specification as detailed in the Apple documentation.

    <CBCentralManager: 0x14e23280> <CBPeripheral: 0x14d27b40 identifier = 7231B80F-874E-DB5F-2AF9-7F376911E2B7, Name = "ANCS", state = disconnected> {
        kCBAdvDataChannel = 39;
        kCBAdvDataIsConnectable = 1;
        kCBAdvDataLocalName = ANCS;
    } -60
    

    Happy hunting