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

Listing and understanding the available services and characteristics is the starting point for any interaction with a bluetooth device

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).

In this example we can see some data being transmitted between the device and the cell phone. What does it mean? Context is everything.

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.

Demo app displaying steps and sleep history information

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!


If you want to know more about technology and innovation, check us out at Lateral View or subscribe to our newsletter!