This is the first in a series of posts introducing Bluetooth Low Energy (BLE). The target audience is iOS and Android mobile developers who want to use BLE to communicate with external hardware, or with another device running their app.
In the last couple of years I’ve spent a bunch of time building cross-platform apps that use Bluetooth LE – in other words, using CoreBluetooth and the Android Bluetooth API in apps that can reliably advertise, scan, and transfer different kinds of data to each other.
In this introduction I want to bring the main features together in some detail and describe how to use them on both platforms. Basically it’s the things I wish I knew when I started out with cross-platform BLE.
I should point out up front that I am not an expert on the Bluetooth protocol, the hardware or the RF behaviour – I haven’t even read much of the spec. I’m just a guy who spent a long time banging his head against the APIs that these platforms provide.
This first post is a high level overview of what it’s like to use BLE, what the words mean and what it has to offer your app.
A Cross-Platform Radio Protocol
Bluetooth Low Energy lets modern iOS and Android devices and various gadgets communicate without needing WiFi or Internet access. It’s an industry standard and all supported devices are compatible with each other. It works reliably over a range of tens of metres.
It’s effectively a client/server protocol. A peripheral is the server – it announces itself by transmitting a regular stream of advertisements. These advertisements are received by a client, which is called a central. The central may choose to establish a temporary connection to the peripheral. Using that connection the central can request to read or write data. The peripheral plays a generally passive role and responds to requests from the central.
When you think about Bluetooth gadgets in the real world – smart light bulbs, iBeacons, and so on – these are all peripherals. They advertise continuously and your phone acts as a central, detecting them when they’re in range, pulling data off them and letting the user control them.
Once granted the right app-level permissions, iOS and Android devices can scan and connect to arbitrary peripherals without requiring any user interaction. This is very different from ordinary Bluetooth where there is a heavyweight pairing procedure.
Conceptually the peripheral is a simple key/value datastore. It hosts services, each of which is a list of one or more characteristics. Each characteristic is a chunk of binary data up to 20 bytes (normally). Every service and characteristic is identified by its own UUID. When a central is reading or writing data, it is doing it with a particular characteristic.
When an iOS or Android device acts a peripheral – advertising, and accepting connections from other devices – it generally supports connections from multiple centrals simultaneously. The app developer may choose to present the same or different data to each central.
Advertising and Scanning
The first step in any BLE communication is to have a peripheral broadcast an advertisement. This is a packet that is repeatedly transmitted typically around 10 or 20 times per second. These packets will normally include two pieces of information: a service UUID and a device name.
The service UUID is how you identify a particular type of device or a particular app. A peripheral may host multiple services but it should pick at least one to mention in its advertisement.
Imagine you’re writing an app that talks to your brand of smart light bulb. You want your app to only detect your light bulbs, not every single BLE device in range. The light bulb will advertise a service UUID that is unique. When you scan in your app you can specify this target UUID and the OS will only bother to tell you when it hears a device that matches.
The device name is just a string and normally human readable. If you have multiple smart light bulbs you might give them different names like “Lounge”, “Kitchen”, and so on. This way you can identify a specific device by its advertisement data alone.
On the central side, your app can choose to be be notified either every time it hears a matching advertisement, or just once per device that comes into range.
Power Saving and BLE Performance
Even though it’s called Bluetooth Low Energy, if you’re advertising or scanning all the time this will use an appreciable amount of energy from your phone battery. As a result operating systems will pretend to do what you asked but don’t guarantee that they will run the radio all the time. iOS is particularly tough about this when your app goes into the background and when the phone is locked.
The exact algorithms are not public but anecdotally it seems that when the OS wants to save power it operates the BLE radio for brief spurts at a time. The theory goes that if some device out there is transmitting an advertisement 10 times per second, if you listen for 100 ms out of every second (10% of the time), you will probably notice that device within one second and this is fast enough.
Ultimately there are trade-offs between power and performance. Android gives the programmer slightly more control over this than iOS. If you are building a device that needs to be detected by locked iPhones it is important that the advertising rate is nice and high.
First let us be clear: BLE does not offer much bandwidth. It is not designed for streaming data. If you do it anyway, speeds are around a few hundred bytes per second under good conditions. (It totally does work though.)
A central that has detected a peripheral by its advertisements may or may not want to connect. In some cases the information contained in the advertisement along with the received signal strength (RSSI) may be all its needs to do its job. Compared with everything else in mobile BLE, transmitting and receiving advertisements is super reliable. If this is enough information to get the job done then I’d highly recommend not even bothering with connections. Unfortunately that’s pretty rare and you will normally need to connect to read data of your choice, or to write any data at all.
From the API’s point of view there are several phases:
- Establishing an initial connection
- Discovering the services that are available
- Discovering the characteristics that are available for a given service
- Finally, using those discovered characteristics to read and write data
Even when you know that a particular device is offering certain services and characteristics you still have to go through the rigmarole of discovering them every time you connect.
In Android steps 2 and 3 are handled by a single request that goes ahead and discovers every service and characteristic available on a given device. This is easy to use but the downside is that it’s very slow. On Android 5 and below it takes around 6 or 7 seconds to interrogate a typical iOS device. This was substantially sped up in Android 6+ but it’s still slow. Caching does help if you make multiple connections to the same device.
In iOS there are two distinct stages. First you discover services, which you can specify by UUID, then you discover characteristics, which you can also specify by UUID. iOS connects really dang fast.
Reading and Writing Data
Both iOS and Android offer a lot of control over the reading and writing of data.
- Initiate a read request or a write request
- Get a callback indicating whether a given read or write request succeeded or failed
- Respond to each read and write request individually, knowing the identity of the central that requested it
- Choose which data to send dynamically in response to each read request
- Choose what to do with the data received in each write request
With this level of control it is straightforward to build up interesting application level protocols using these operations as building blocks.
It is also possible for a connected central to subscribe or unsubscribe to a particular characteristic. When a central has subscribed, the peripheral can transmit updates to this characteristic and as long as the central remains connected they will receive those updates asynchronously.
You might assume (as one Apple sample project does) that this is a great way to create a data channel from peripheral to central. In practice the iOS subscription API has some major issues. In a later post about iOS I will explain why I think it’s silly.
I should mention that a lot of what I am describing in this post is technically GATT, which is just one part of Bluetooth Low Energy. GATT is basically all you get in iOS and Android so all the documentation just calls it “Bluetooth LE”. I won’t try to explain any more because as I mentioned at the start, I only know about what the APIs provide.
In this post I’ve introduced the main concepts of Bluetooth Low Energy – what you can use it for, the general lifecycle of the API, and the main terms such as peripheral, central, service and characteristic.
Even though they provide almost the same functionality, the iOS and Android APIs wound up being fairly different. In the next few posts I’m going to briefly describe the architecture of each one and highlight some of the interesting parts.