📜 ⬆️ ⬇️

BlueZ bracelet control

In a research project, I needed a prototype of a medical bracelet. The device had to periodically measure the pulse, alerting the patient, and send the results along with the battery level to the cloud service. Such a device could well be a fitness bracelet with a stationary repeater instead of a smartphone. Therefore, before trying to build a prototype with my own hands, I decided to experiment with something ready. So I got a new Xiaomi mi band 1S Pulse ( review on Geektimes) with an optical heart rate monitor.

The choice of bracelet was associated with its relatively low price, good battery and the fact that the pulse measurement is initiated from the smartphone, and not started by pressing a button.

I began the experiments with a study of the set of services and features available via Bluetooth 4.0 (or Bluetooth Low Energy, hereinafter - BLE). It was not difficult to find something on the network , and this information helped me a lot, but it related to the previous version, without the sensor I needed. Therefore, I started with a BLE scanner.

It turned out that Nordic Semiconductor has a suitable and very convenient tool. This is the Master Control Panel or nRF MCP for Android 4.3+. Having installed the application on the tablet and running “SCAN”, I easily found the mi band and recorded its physical address - C8: 0F: 10: 11: 1B: 6E:
')


By clicking on “OPEN TAB” and then on “CONNECT”, I received a set of services:



The pulse meter identifier turned out to be standard for such devices - 0x180D . Looking ahead, I will say that I was glad early.

As a repeater, I used the Raspberry Pi (model B) and the BT400 BLE-usb adapter from ASUS. It also took BlueZ - a real Swiss knife for working with Bluetooth under Linux and a couple of additional modules for Python.

Preparing Raspberry Pi
For Raspberry Pi, the Raspbian image was used . After apt-get update and apt-get upgrade checked the adapter:
pi@raspberrypi:~ $ lsusb Bus 001 Device 006: ID 0b05:17cb ASUSTek Computer, Inc. Bus 001 Device 005: ID 046d:c077 Logitech, Inc. Bus 001 Device 004: ID 04d9:1602 Holtek Semiconductor, Inc. 

Great, my adapter is in the first line. Installed BlueZ. It is recommended to download the latest code archive, at the time of preparing the article it was BlueZ 5.37, unpack and compile. I was satisfied with version 5.23, which is installed via apt-get. The correctness of the installation can be checked by executing the gatttool –help command.
Gatttool is a BlueZ tool for working with GATT , a common BLE device attribute profile. In older versions, gatttool was not installed by default, it was necessary to “screw” with my hands, but here help was available and it means I have almost everything I need to work with the bracelet. Through pip I installed Pexpect to work with BlueZ from Python. Reloaded Raspberry and turned on the adapter. Adapter status checked with hciconfig:
 pi@raspberrypi:~ $ hciconfig hci0: Type: BR/EDR Bus: USB BD Address: 5C:F3:70:71:7E:F5 ACL MTU: 1021:8 SCO MTU: 64:1 DOWN RX bytes:616 acl:0 sco:0 events:34 errors:0 TX bytes:380 acl:0 sco:0 commands:34 errors:0 

The DOWN flag showed that the adapter is turned off, turned on by the command:
sudo hciconfig hci0 up

Before writing the code for Raspberry, I needed to make sure that all the services I needed (heart rate, battery level and vibrating alert) are available in the terminal mode from BlueZ.
I scanned the BLE environment and easily found a bracelet:

 pi@raspberrypi:~ $ sudo hcitool -i hci0 lescan LE Scan ... C8:0F:10:11:1B:6E (unknown) C8:0F:10:11:1B:6E MI1S 

Connect to the bracelet with the connect command by running gatttool in interactive mode (I key):

 pi@raspberrypi:~ $ sudo gatttool -i hci0 -b C8:0F:10:11:1B:6E -I [C8:0F:10:11:1B:6E][LE]> connect Attempting to connect to C8:0F:10:11:1B:6E Connection successful [C8:0F:10:11:1B:6E][LE]> 


An online connection without pairing usually lasts 20 seconds. This is the so-called low level of secrecy, it is used by default. The list of available services is displayed by the primary command:

 [C8:0F:10:11:1B:6E][LE]> primary attr handle: 0x0001, end grp handle: 0x0009 uuid: 00001800-0000-1000-8000-00805f9b34fb attr handle: 0x000c, end grp handle: 0x000f uuid: 00001801-0000-1000-8000-00805f9b34fb attr handle: 0x0010, end grp handle: 0x0039 uuid: 0000fee0-0000-1000-8000-00805f9b34fb attr handle: 0x003a, end grp handle: 0x0048 uuid: 0000fee1-0000-1000-8000-00805f9b34fb attr handle: 0x0049, end grp handle: 0x004e uuid: 0000180d-0000-1000-8000-00805f9b34fb attr handle: 0x004f, end grp handle: 0x0051 uuid: 00001802-0000-1000-8000-00805f9b34fb 

Determine that the service can be four digits after uuid. It turned out two common services (generic), two services specified by the manufacturer (fee0 and fee1), HRM service (180d) and alert (1802).
The list of characteristics of the bracelet is displayed by the char-desc command in ascending order of pointers (handles). Found a characteristic with the identifier ff0c in the list:
handle: 0x002c, uuid: 0000ff0c-0000-1000-8000-00805f9b34fb,
The pointer 0x002c for the battery charge level has already been defined for the previous version of the bracelet. I tried to read the data with the char-read-hnd command (read the data on the pointer):



The battery "surrendered" first. In response, not only the charge level, this is the first byte in hex (the smartphone on the eve showed 70%), but also full information about charging: the number of cycles, the date of the last charge, the battery status. Under the terms of the problem, I needed only a level.

The second was "subdued" vibrating alert. According to the data from the MCP, I assumed that this is Immediate Alert, and the Alert Level is the command to be sent to the identifier 0x2A06:



In the list of characteristics, this identifier corresponds to the line:
handle: 0x0051, uuid: 00002a06-0000-1000-8000-00805f9b34fb
I sent a command to pointer 0x0051 with the value 01:

 [C8:0F:10:11:1B:6E][LE]> char-write-cmd 0x0051 01 


The bracelet responded with two weak buzzes, the value 02 is two times for 01, i.e. four signals, and 03 - two, but stronger. With the pulse rate, everything turned out to be much more complicated. The MCP showed the following:



The characteristics associated with this service are:

 handle: 0x004b, uuid: 00002a37-0000-1000-8000-00805f9b34fb handle: 0x004c, uuid: 00002902-0000-1000-8000-00805f9b34fb handle: 0x004d, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x004e, uuid: 00002a39-0000-1000-8000-00805f9b34fb 

The pulse rate is transmitted to the smartphone in the notification or push-notification mode, it can not be considered as the battery charge level. It is necessary to enable the notification by writing to CCC (Client Characteristic Configuration) with pointer 0x004 (the CCC always identifies 2902) value 0100 and waiting for notification.

Nothing happened, the value was successfully recorded, but there were no notifications, the bracelet simply turned off after a few seconds. Running gatttool in console mode with the –listen key didn’t produce any results either, gatttool just hung on, waiting. Riddle, in one word.

To clarify the situation, I had to use a BLE sniffer (on a laptop with Windows 8). At the heart of it was a flashed BLE-usb-dongle on the CC2540 chip from Texas Instruments and the Smart Packet Sniffer program from the same manufacturer. Everything you need, including the programmer, can be easily found as a set for the developer, and I freely downloaded the program and firmware from the TI website.

Important! The sniffer should be launched when the bracelet is in the presentation mode (advertising mode), i.e. before connecting to a smartphone. Otherwise it will be invisible. It is also a good idea to remove all unnecessary BLE devices away from the sniffer, and even better to escape, this is very helpful then to understand the log.

This is how packages look in a sniffer in presentation mode:



I determined that this is my bracelet across the AdvA (Advertising Address) field. After establishing a connection with a smartphone, in the GATT-connection mode, the picture has changed:



Here you can see how the value 0100 is recorded in the CCC with the pointer 0x004C, allowing notifications of the pulse rate.

One of the researchers of the previous version of the bracelet in his blog wrote that not all services can be accessed by an anonymous device. As far as I could figure out, in some cases, a device that interacts with a bracelet should transfer to the bracelet the correct information about the user, which is partially hashed when pairing with a smartphone.

This data with a length of 20 bytes, as in the previous version, is recorded in the characteristic with the pointer 0x0019 and does not change with each new connection. The first four bytes are the uid of the smartphone, then, in the clear, the bytes of gender, age, height, weight, rewriting resolution bytes (must be 00) and 10 bytes of a sequence similar to a hash. I could not read user info from the bracelet.

When analyzing the packages, we managed to find out the following:
  1. Every time you connect, all notification permissions are sent to the bracelet (CCC ID 2902)
  2. Next comes the transfer of user information.
  3. Then, according to the 0x0028 pointer, the date and time are recorded.
  4. After that, the data on the battery level and the number of steps per day are read.
  5. Before you receive a pulse rate notification on the pointer 0x004E, the corresponding characteristic "pulse control point" is written the sequence 0x15 0x00 0x00 (I assume that this is a reset)
  6. Then 0x15 0x02 0x01 is also written there, which in my case corresponds to the left hand
  7. After that, after 15-20 seconds, a notification arrives with a pulse rate of two bytes, for example, 06 40. The second byte is the heart rate in hex.

In theory, in order to get the pulse rate, it was necessary to repeat all these transactions, possibly eliminating the 3rd and 4th points. As it turned out, you can do without a reset. It is impossible to do this manually, the bracelet would be disconnected before I could enter all the commands. Therefore, I prepared a Python script:

 import sys, pexpect from time import sleep gatt=pexpect.spawn('gatttool -I') gatt.logfile = open("pylog.txt", "w") gatt.sendline('connect C8:0F:10:11:1B:6E') gatt.expect('Connection successful') # Check battery level gatt.sendline('char-read-hnd 0x002c') gatt.expect('Characteristic value.*') batt = gatt.after batt = int(batt.split()[2],16) print 'Battery level:', batt, '%' # Send alert gatt.sendline('char-write-cmd 0x0051 01') sleep(5) # Allow notification gatt.sendline('char-write-req 0x004c 0100') gatt.expect('Characteristic value.*') # Send user data gatt.sendline('char-write-req 0x0019 F8663A5F0126B45500040049676F7200000000DC') gatt.expect('Characteristic value.*') # Set control point gatt.sendline('char-write-req 0x004e 150201') # Waiting for notification try: gatt.expect('Notification handle.*') hrm = gatt.after hrm = int(hrm.split()[6], 16) print 'HRM:', hrm except: print 'Bad control point or timeout' sys.exit(0) 

By the way, the number of completed steps can be read anonymously, it is available for reading at the pointer 0x001D. The answer is four bytes, you need to read from left to right.

The script displays the battery level, sends a notification, waits and prints the pulse rate. The riddle is solved, it remains to learn how to send data to the cloud service, as which I decided to use Thingspeak . This is a free service with a simple API and ready-made visualization.

Setting up Thingspeak took no more than five minutes. You must register and enter your personal space. Next, create a new channel, specify the name, number and field labels in the channel settings. Save settings and go to the tab Keys API. There, copy the API Key for Write (Write API Key):



After that, switch to the Private View tab (if “Make Public” was not specified when setting up the channel).

The following Python construction is responsible for sending data to Thingspeak:

 baseURL = 'https://api.thingspeak.com/update?api_key=%s'%YOUR_API_KEY f = urllib2.urlopen(baseURL + "&field1=%s&field2=%s" % (batt, hrm)) print f.read() f.close() 

Full code
 import sys, pexpect from time import sleep import urllib2 sample_interval = 180 #sec sample_qty = 8 api_key = 'YOUR_API_KEY' baseURL = 'https://api.thingspeak.com/update?api_key=%s'%api_key def getData(): try: gatt=pexpect.spawn('gatttool -I') # gatt.logfile = open("pylog.txt", "w") # for debug only gatt.sendline('connect C8:0F:10:11:1B:6E') gatt.expect('Connection successful', timeout=60) # Get battery level gatt.sendline('char-read-hnd 0x002c') gatt.expect('Characteristic value.*') batt = gatt.after batt = int(batt.split()[2],16) # Send alert gatt.sendline('char-write-cmd 0x0051 01') sleep(5) # Allow notification gatt.sendline('char-write-req 0x004c 0100') gatt.expect('Characteristic value.*') # Send user data gatt.sendline('char-write-req 0x0019 F8663A5F0126B45500040049676F7200000000DC') gatt.expect('Characteristic value.*') # Set control point gatt.sendline('char-write-req 0x004e 150201') # Waiting for notification gatt.expect('Notification handle.*', timeout=60) hrm = gatt.after hrm = int(hrm.split()[6], 16) except: hrm = 0 batt = 0 return (str(batt), str(hrm)) def main(): sample_count = 0 while True: try: batt, hrm = getData() f = urllib2.urlopen(baseURL + "&field1=%s&field2=%s" % (batt, hrm)) print f.read() f.close() sample_count = sample_count + 1 if (sample_count >= sample_qty): break sleep(sample_interval) except: print 'Connection error' break if __name__ == '__main__': main() sys.exit(0) 


In the normal mode of operation, the sending number is displayed in the console, starting with one. The range of the repeater is 3-4 meters in direct line of sight, which is normal for the medical chamber. However, the bracelet at this distance is easily shielded by the palm.

Tested the resulting system in the process of training on a stationary bike, 20 minutes. The interval between measurements is 3 minutes, the number of measurements is 8. The bracelet tends to overestimate the pulse rate when it is not in contact with the skin, for greater accuracy it placed the sensor on the back of the wrist. Thingspeak result:


As can be seen from the graph, 8 measurements had no effect on the battery charge. I think the experiment can be considered quite successful and the experience gained can be used to design your own device or, for example, search for an OEM.

Useful links:


Source: https://habr.com/ru/post/276343/


All Articles