comments 4

#44 Watch your Bluetooth!

On WWDC 2017 the breaking news was revealed – WatchOS 4 ships with CoreBluetooth and allows apps to connect up to 2 peripherals! ❤️! This issue will show a simple implementation of a Bluetooth Central that can be used in apps built for iOS 11 and WatchOS 4!


Bluetooth Low Energy (BLE)

Bluetooth Low Energy is a standard of low-range wireless communications between devices. It was introduced in Bluetooth Core Specification 4.0 and is developed by Bluetooth Special Interest Group. The current version of the specification is 5.0 (Bluetooth official website). The standard assumes low energy consumption and client-server architecture. Clients are called centrals. Centrals try to access data on peripherals (servers). Peripherals expose characteristics with values. Similar characteristics are grouped into higher-level construct called services.

The standard is supported on Apple platforms via Core Bluetooth framework since macOS 10.7, iOS 5, tvOS 9 and now will be available also on watchOS 4!

Core Bluetooth

The Core Bluetooth framework reflects central and peripheral roles with CBCentral and CBPeripheral objects. Apps that connect to BLE devices do so with CBCentralManager objects.


The manager scans for peripherals that advertise certain services identified with an UUID (Universally Unique Identifiers). UUIDs are represented by CBUUID objects. An identifier is a 128-bit hexadecimal number, e.g. B7AC06DC-09FF-40ED-B03A-55D09B08EB4A. The identifier can be created with uuidgen tool on macOS Terminal.


Surprise, surprise. An iOS/macOS app can also become a CBPeripheral. It has to use a CBPeripheralManager to advertise available services.


Services and characteristics exposed by the peripheral are seen as CBService and CBCharacteristic objects.


They are identified by UUIDs, as mentioned earlier. In the next section you will get to know where to store used in your app UUIDs.


Bird Service


Our watchOS 4 app will connect to peripherals that expose a Bird Service. The service should expose data related to bird’s properties: name, color and transparency. It can look like this:

  • CBService – Bird
    • name CBCharacteristic – contains bird’s name encoded as UTF-8 String
    • color CBCharacteristic – contains hex color encoded as UTF-8 String
    • alpha CBCharacteristic – contains transparency Integer value ranging from 0-100 encoded as UTF-8 String
Storing UUIDs

Usually UUIDs are repeated over an over in Core Bluetooth – related code. It’s handy to store them in static variables instead of instantiating CBUUID objects every time we want to compare a UUID of a service or a characteristic with another UUID. We propose an empty enum as a storage for UUIDs corresponding to a service.

The enum contains the characteristics: [CBUUID] property which facilities recognition of UUIDs corresponding to characteristics.

Bird Peripheral

A peripheral that our watchOS app will connect to should advertise Bird Service, respond to READ requests of a characteristic’s value and NOTIFY subscribers about value updates. Peripheral role is not allowed on a watchOS and is not a subject of this post. Our demo project on Github contains an implementation of a macOS app that uses CBPeripheralManager object to satisfy Bird Peripheral requirements.


Bird Central

Our watchOS app should be able to:

  • scan for the Bird Service
  • discover service’s characteristics
  • read characteristics’ values
  • subscribe for notifications of value changes

So, let’s write a BirdCentral that fulfils those requirements!

First of all, our object should conform to CBCentralManagerDelegate and CBPeripheralDelegate. It requires conformance to NSObjectProtocol so BirdCentral will inherit from NSObject class.

In the designated initialiser we create a DispatchQueue on which our object will be notified about Bluetooth-related events. We create a CBCentralManager instance with the queue and set the object as central’s delegate.

When the central manager’s state is updated we can start scanning for Bird Service. The scanServices() method checks if Bluetooth is supported and powered on on a device.

If a peripheral is found, we can stop the scan, store the peripheral, become its delegate and connect to the peripheral.

When the central connects to the peripheral, we can discover its services. In our case we pass the BirdService.uuid in an array of services for discovery.

We use a BirdCentralDelegate protocol to notify a delegate about certain Actions that occur – peripheral (un)successful connection, disconnection or reading a value.

If the central fails to connect to or disconnects from the peripheral, we nullify the peripheral property, start scanning for peripherals and notifies the delegate about the action.

When we ask peripheral to discover services we get a callback peripheral(:didDiscoverServices:). We can extract the Bird Service from an array of peripheral’s services and discover its characteristics.

When characteristics are discovered we can read their values, subscribe for notifications about value update and store a characteristic in a property.

Finally, when a value of a characteristic is read or updated the peripheral(:didUpdateValueFor characteristic:error:) gets called. We convert binary data stored in the value property of the characteristic into a string. In the case of the name characteristic we just wrap it into BirdCentral.Value enum. In other cases we have either to convert it into an alpha value that can be used as alpha of a UIView object (i.e. CGFloat from 0.0 – 1.0) or to convert a hexadecimal string into a UIColor. We also tell our delegate about new reading from the peripheral.

Neat! We have just written the BirdCentral that can be used in both – an iOS and a watchOS applications! A single component is portable to both platforms.


Summary

You can check out our Github project to have a full overview on how to use it. It contains three targets – a macOS app that simulates the BirdPeripheral and an iOS and a watchOS app that use the BirdCentral component.

Starting from watchOS 4 you can benefit from Core Bluetooth and connect up to 2 peripherals from your apps. Check out the WWDC 2017 – 712 session What’s New in Core Bluetooth for more!


Links

4 Comments

    • Maciej Piotrowski

      Hi Marco,

      iOS and watchOS simulators don’t support Bluetooth simulation. If you initialised CBPeripheralManager or CBCentralManager objects on a simulator you would get the CBManagerState.unsupported state in peripheralManagerDidUpdateState(_:) or centralManagerDidUpdateState(_:) callbacks.

  1. Peter

    Hi Maciej,

    Do you know if Apple Watch Gen 1 supports connection via Bluetooth? I run your code from Github and for Mac and iOS device works well, but with my watch gen 1, I have a connection issue.

    • Maciej Piotrowski

      Hi Peter,

      at the time of writing this article (watchOS 4 beta 1) I was able to run the code on Apple Watch Series 1. Your device should support Core Bluetooth, unless something has changed since beta 1. Unfortunately, I do not have a test device to run the code on it again.

Leave a Reply


*