Bluetooth Logo

Writing an SDK With Core Bluetooth – 15 – The Center

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

THIS IS NOT STRAIGHTFORWARD –NOT EVEN A LITTLE

One thing that we’ll learn about Core Bluetooth (which can also apply to other types of asynchronous communication), is that nothing is really direct or straightforward.

That means that we can’t just ask a CBCentralManager instance for its CBPeripherals. We need to ask the manager to discover the Peripherals, and, possibly, build up its own list; which we can then query.

The same goes for Services. We can’t just ask a CBPeripheral object for its CBService instances. We have to ask it to discover its Services.

After that, each Service needs to discover its Characteristics. That said, just to add insult to injury, we don’t ask the Service to discover its Characteristics; we ask the Peripheral to do so, on behalf of the Service.

Once the Characteristics have been discovered, then we can finally aggregate the Peripheral device.

We did just enough on the Peripheral side to give the Central something to find.

The Central, however, has its work cut out for it…

DELEGATES ARE REQUIRED

Even though we aren’t publishing a true Delegate pattern in our SDK, under the hood, it needs to use Delegates.

We’ve already encountered CBCentralManagerDelegate and CBPeripheralManagerDelegate. They were necessary for us to set up our initial advertisement and scanning.

The manager delegates will be needed to discover the devices, but then we’ll need to go into CBPeripheralDelegate in order to access the Services published by a Peripheral device.

INTERMISSION: WHAT’S REALLY GOING ON, HERE?

Before we dive back into the code, let’s take a moment to review the structure of the system:

The System Diagram

In the image above, we have a visual “map” of what our system will look like.

Each of the “balls” represents a device, running an instance of our app.

The one on the left (with the Bluetooth icon) is running the app as a Central, and the three on the right (with the “8” icon), are running the app in Peripheral mode.

Each Peripheral creates an instance of the “Magic 8-Ball” CBMutableService, which, in turn, aggregates the two CBMutableCharacteristic instances that comprise the communication between the Peripheral and the Central.

On the Central side, the Central “discovers” the three Peripherals, and builds a local “map” of them. Each Peripheral proxy that the Central knows about has a proxy of the Service (and Characteristics) that are published by the Peripheral that it represents.

In our system, when the Central makes a change to the CBService in its proxy of a Peripheral, the Bluetooth connection will send that new state over to the Peripheral device, which will then set the state of its CBMutableService to match that of the Central, which is mapping the Peripheral CBMutableCharacteristic instances with instances of CBCharacteristic.

At that point, the Peripheral’s Core Bluetooth subsystem will send a CBPeripheralManagerDelegate call to the Peripheral app, saying that it has received a change, and that the Peripheral needs to read the Characteristic’s value.

BUT THAT ALL HAPPENS AFTER WE BUILD THE CENTRAL “MAP”

Building the initial map is quite involved, and I’ll try and walk through what will happen. In the next entry, I’ll show the code. It will be quite involved.

Let’s look at what happens when we “map” the Peripherals:

The Voyage of Discovery

In the above image, we see what happens.

  1. Upon instantiation, the Central Mode SDK starts scanning for Peripherals, filtering for our “Magic 8-Ball” Service.
  2. The SDK is a CBCentralManagerDelegate, so it gets a callback when Core Bluetooth discovers a Peripheral.
  3. The SDK then asks that Peripheral to discover its Services (there should only be one).
  4. The SDK is also a CBPeripheralDelegate, so it receives a callback, with the discovered Service.
  5. The SDK then asks that Service to discover all three of our Characteristics.
  6. The SDK receives the response from the Peripheral that it has discovered the Characteristics for the Service.
  7. At that point, the Peripheral is ready for its close-up, and the SDK informs the observer that it has a Peripheral.
  8. The Peripheral is still connected (meaning that it is no longer advertising). Until we release the connection, it cannot be discovered by other Central SDKs.

Whew! That’s quite a bit, eh? Note that we keep the majority of that action inside the SDK. I believe that the job of a good SDK is to abstract this kind of complexity from the user of the SDK.

I should also note that this is all completely asynchronous. We send out requests for discovery, and then forget that we asked. Each time we get a callback, we don’t care whether or not it’s in response to any previous request.

I call this “Chaotic Neutral vs. Ordered Evil.” Long story, but the moral is that we are reactive, not proactive. I think that it’s very important to have this posture when dealing with device interfaces. We don’t have control over the other end.

Also, these responses may come in over non-main threads (in reality, in our example, they will, but let’s pretend they don’t). That means that the app needs to make sure to switch over to the main queue when working with any UI.

Discovery tends to take a while in BLE. There’s a rather involved “dance” that goes on, where a Peripheral advertises in bursts, and the Central needs to be listening when that Peripheral advertises.

You could discover a Peripheral almost immediately, or it could take several seconds.

In the next entry, I’ll review how I got the first real Bluetooth discovery working in our SDK.