Sharing passion in Swift

#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:

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.

1

2

3

4

5

6

7

8

9

10

11

letcentral:CBCentralManager

overrideinit(){

letqueue=DispatchQueue(label:"io.swifting.bluetooth")

central=CBCentralManager(delegate:nil,queue:queue)

super.init()

central.delegate=self

}

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.

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.

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!

6 Comments

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.

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.

as far as I know there is still no “out of the box” way of communicating with BLE peripherals when app is in background (as of watchOS 5). As mentioned in RayWenderlich.com tutorial

watchOS and tvOS both rely on Bluetooth as their main system input, so Core Bluetooth has restrictions, to ensure system activities can run. Both can be only the central device, and can use at most two peripherals at a time. Peripherals are disconnected when the app is suspended.And the minimum interval between connections is 30ms, instead of 15ms for iOS and macOS.

The only way could be to start background workout processing in your app with HKWorkoutSession.