Lima Band SDK — Developing an iOS Library for Bluetooth Fitness Wristbands
As part of an ongoing project involving fitness tracking wristbands we found ourselves in need of a framework flexible enough to allow us to support different models and brands of these bluetooth devices. We would like to share with you a little bit of the process we followed to build it.
Developing for bluetooth devices
Supporting bluetooth wristbands is not easy — official documentation is often missing and unofficial sources are usually incomplete. Reverse engineering the wristband protocol was often necessary to understand the correct behaviour that our custom SDK needed to follow. The SDK was developed using two different data sources: SDKs for other platforms, and manually comparing different outputs generated by reference apps.
While explaining in full detail how to perform reverse engineering on a bluetooth device is way beyond the scope of this article, we will provide an overlook of the general process that we followed.
First of all, any study on a bluetooth device should probably start with one question: which services and characteristics does this device provide? We can answer it with Lightblue, a bluetooth utility available for macOS and iOS
After an initial study of all the services and characteristics used by the device we proceded to capturing bluetooth traffic with an Android device, then analyzing it using Wireshark. Check this link for more information about capturing Bluetooth traffic with Android.
Reverse engineering the SetUserInfo command
The basic analysis process consists on opening up the reference app, perform an operation (i.e. set user information) then stopping the packet capture, checking what data was transmitted during that action and then comparing the different outputs to infer their meaning. Wireshark allows to promote dissected properties (such as btatt
values) to columns on the table, making it much easier to visualize the data and the sequence in which it is transmitted. We need to pay special attention to which bluetooth characteristic is being used to deliver the data since most of the times different operations use specific characteristics instead of grouping them all under one.
We open up our reference app, perform a single operation (such as setting user information) and then we stop the capture. The device protocol data that interests us is captured and then filtered using the btatt
protocol filter. In this example we set the user information using the reference app’s UI with known values such as age (25), height (175 cm) and weight (70 kg).
After performing the operation we noticed a strange, long string of values: 78:d1:01:00:00:19:af:46:00:05:00:78:78:78:00:00:00:00:00:eb
. Since we control the input data from the app and we know what age, weight and height we’ve entered, we try to find those values in that sequence. That’s how we notice that they are stored in the 5th., 6th. and 7th. position in the data (represented in hex as 19:af:46
). That’s far too unusual to be a coincidence, so we perform yet another test with a different set of values. The known values change accordingly. We document our findings and start building a model that reflects it.
let age : UInt8 = data.scanValue(start: 5, length: 1)
let height : UInt8 = data.scanValue(start: 6, length: 1)
let weight : UInt8 = data.scanValue(start: 7, length: 1)
That’s how, with a lot of time, patience and multiple attempts we start understanding the message structure used by the protocol.
Sending data manually
Sometimes once we observed a behavior we can attempt to reproduce it manually. For instance, we suspected that our reference app always sends a 0x02
value to the 0xFF0F
characteristic, but we wanted to test it ourselves and see what happened. To do so, we replayed this using Lightblue
Reading the characteristic returned a value of 0xFFFF
. Right after we sent a 0x02
we were prompted by a pairing request from the device. After the successful pairing, reading the characteristic returns a value of 0x02
. That’s how we inferred that the purpose of this characteristic is a part of the initialization process and later included it as such in our implementation of the SDK.
Current state of the SDK
At the time of this writing, our effort has led us to a working implementation for the Xiaomi Mi Band device. The SDK currently allows to perform multiple operations on the band such as vibrating, fetching the current live steps value, get device information, set user information, get steps and sleep history data, get the current battery level and more.
The SDK provides an easy-to-use API for developers based on closures, providing an abstraction over the CoreBluetooth framework that uses the delegate pattern. Fetching a list of nearby devices and displaying them on a table is as easy as this example:
LimaBandClient.shared.scan(filterBySignalLevel: true) {
(success, devices) in
if let devices = devices {
self.devices = devices
self.tableView.reloadData()
}
}
The included demo app includes examples for all of the features available on the SDK.
The library can be found here. Have in mind this is a work in progress, therefore there is room for improvement. We encourage you to contact us with remarks and comments to make it better!