Bluetooth Logo

Writing an SDK With Core Bluetooth – 14 – The Periphery

This entry is part 18 of 24 in the series Writing an SDK With Core Bluetooth

Now that we have the Central and Peripheral devices finding each other, we need to start “filling in the blanks.”

That begins in the Peripheral, where we will create an instance of the Core Bluetooth Service, and populate it with the two Characteristics we chose.

QUICK META NOTE:

I just wanted to make an observation about my naming convention. In most cases, prefacing a method or property with an underscore (“_“) means that the scope of the entity is private. In this case, it will mean internal (or private). I just wanted to have a handy visual cue.

WALKTHROUGH

The Starting Repo Tag

The Ending Repo Tag

The Comparison Between the Two Tags

The ITCB_SDK_Peripheral_internal.swift File

I modified the ITCB_SDK_Peripheral.peripheralManagerDidUpdateState(_:) method:

BEFORE:

    public func peripheralManagerDidUpdateState(_ inPeripheral: CBPeripheralManager) {
        if .poweredOn == inPeripheral.state {
            inPeripheral.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [_static_ITCB_SDK_8BallServiceUUID],
                                           CBAdvertisementDataLocalNameKey: localName
            ])
        }
    }

AFTER:

    public func peripheralManagerDidUpdateState(_ inPeripheralManager: CBPeripheralManager) {
        if .poweredOn == inPeripheralManager.state {
            if let manager = peripheralManagerInstance {
                assert(manager === inPeripheralManager)
                let mutableServiceInstance = CBMutableService(type: _static_ITCB_SDK_8BallServiceUUID, primary: true)
                _setCharacteristicsForThisService(mutableServiceInstance)
                inPeripheralManager.add(mutableServiceInstance)
                inPeripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [mutableServiceInstance.uuid],
                                               CBAdvertisementDataLocalNameKey: localName
                ])
            }
        }
    }

I added code to instantiate a Core Bluetooth Service, load that Service with Characteristics, and add it to the local database.

It’s quite important to me that others can understand my code. I write about that, here.

I added the _setCharacteristicsForThisService(_:) method to the ITCB_SDK_Peripheral class:

    func _setCharacteristicsForThisService(_ inMutableServiceInstance: CBMutableService) {
        let questionProperties: CBCharacteristicProperties = [.writeWithoutResponse]
        let answerProperties: CBCharacteristicProperties = [.read, .notify]
        let permissions: CBAttributePermissions = [.readable, .writeable]

        let questionCharacteristic = CBMutableCharacteristic(type: _static_ITCB_SDK_8BallService_Question_UUID, properties: questionProperties, value: nil, permissions: permissions)
        let answerCharacteristic = CBMutableCharacteristic(type: _static_ITCB_SDK_8BallService_Answer_UUID, properties: answerProperties, value: nil, permissions: permissions)
        
        inMutableServiceInstance.characteristics = [questionCharacteristic, answerCharacteristic]
    }

This method is called from the peripheralManagerDidUpdateState(_:) method. It instantiates empty versions of our Characteristics, and adds them to the Service.

This could actually be a static method, as it doesn’t do anything that requires identity. I leave it as an instance method, because that makes it a bit easier to read. If I were doing this as a “release” SDK, I would probably make it static.

NOTE: When we create Characteristics as CBMutableCharacteristic instances, we should ensure that the initial value is always nil. Failure to do this will cause the Characteristic to become a “cached” Characteristic, and that means that it won’t be modifiable in the future.

The Peripheral Is Always In Charge

Services and Characteristics are always created, managed and advertised by peripherals. Centrals cannot create or manage them. Any Peripherals, Services and Characteristics that we encounter in a Central are “proxies” for the Peripheral-managed entities.

Note this section:

        let questionProperties: CBCharacteristicProperties = [.writeWithoutResponse]
        let answerProperties: CBCharacteristicProperties = [.read, .notify]

Those are Properties. They describe the capabilities that the Characteristic will publish to the Central.

In the above, we state that the “Question” Characteristic can be written to by the Central, and that the central should not expect a response in return.

We also state that the “answer” property can be read by the Central, and that the Central can subscribe to the Peripheral, in order to receive notifications, when the value of the Characteristic changes.

The permissions:

        let permissions: CBAttributePermissions = [.readable, .writeable]

Simply state that the Central has the permission to read and write the Characteristic. For brevity, I apply these permissions to both Characteristics, but I could, in theory, make the “question” Characteristic write-only, and the “answer” Characteristic read-only.

The Peripheral always has full read/write on all of its Characteristics.

WHERE WE ARE NOW

At this point, we have created a new Service for our “Magic 8-Ball,” and loaded it with blank values of the three Characteristics.

I want to make a note that we don’t store any of this in the Peripheral. The Service (and attached Characteristics) are all kept in the local Bluetooth Database.

There is no need to keep any state in the Peripheral. A Peripheral is a purely “reactive” entity. It gets a question, and immediately sends an answer. It doesn’t log anything, or have a “tween” condition.

It’s always a good idea to reduce state and redundancy of data. Since we don’t need to store anything, we won’t.

Now, there’s no visible difference. We are in the “dark age,” and won’t be able to test anything until we add code to the Central to read the Service.