Now, I haven't really played with low level communication with Bluetooth devices. It seems that Bluetooth Low Energy (BLE) is a lot different from Bluetooth Classic, but for me it didn't matter much since it's the first time I'm messing around with Bluetooth in general.
The first challenge in connecting to the Polar H7 turned out to be a lack of a BLE compatible adapter. My laptop has a Broadcom BCM2070 chip which only provides Bluetooth 3.0. It turns out that BLE is only available with 4.0+. So, I needed to purchase a USB dongle. Once I had that it was time to figure out how to connect to it. I thought it'd be a lot easier than it actually was.
Long story short... it took me a few days of digging around on Google Search to find the information I needed. I was surprised at how little information there really was for connecting to and communicating with BLE HRM devices and specifically the Polar H7. Here's how I got the initial connection established to be able to get some data from the device on Ubuntu 14.04 using hcitool and gatttool.
Scanning for the Polar H7
ali@sling:~$ sudo hcitool -i hci1 lescanLE Scan ...
00:22:D0:33:1E:0F (unknown)
00:22:D0:33:1E:0F
This will run a BLE scan and provide the MAC address of the Polar H7. The scan will run continously, so you'll have to CTRL+C to stop. The MAC address is required to establish a connection to it.
Connecting to the Polar H7
The next thing is to connect to the device, and for that I used gatttool in interactive mode. My USB Bluetooth 4.0 dongle shows up as hci1 on my system. It might be different for you. Run hcitool dev to find out.ali@sling:~$ sudo gatttool -i hci1 -b 00:22:D0:33:1E:0F -I
[ ][00:22:D0:33:1E:0F][LE]> connect
[CON][00:22:D0:33:1E:0F][LE]>
If you want to jump forward to the reading of heart rate data skip directly to it. I'll now cover a bit of information on what data I was able to gather from the gatttool connection.
Services and Characteristics
If you want to really familiarize yourself with the BLE capabilities touched upon here you need to know Generic Attribute Profile (GATT), Services, Characteristics, and Client Characteristic Configuration. I have to admit that I've barely scratched the surface on this. There's probably a lot more to know.
To get a list of all services presented by the device.
[CON][00:22:D0:33:1E:0F][LE]> primary
attr handle: 0x0001, end grp handle: 0x000b uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x000c, end grp handle: 0x000e uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x000f, end grp handle: 0x0014 uuid: 0000180d-0000-1000-8000-00805f9b34fb
attr handle: 0x0015, end grp handle: 0x0023 uuid: 0000180a-0000-1000-8000-00805f9b34fb
attr handle: 0x0024, end grp handle: 0x0026 uuid: 0000180f-0000-1000-8000-00805f9b34fb
attr handle: 0x0027, end grp handle: 0xffff uuid: 6217ff49-ac7b-547e-eecf-016a06970ba9
attr handle: 0x0001, end grp handle: 0x000b uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x000c, end grp handle: 0x000e uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x000f, end grp handle: 0x0014 uuid: 0000180d-0000-1000-8000-00805f9b34fb
attr handle: 0x0015, end grp handle: 0x0023 uuid: 0000180a-0000-1000-8000-00805f9b34fb
attr handle: 0x0024, end grp handle: 0x0026 uuid: 0000180f-0000-1000-8000-00805f9b34fb
attr handle: 0x0027, end grp handle: 0xffff uuid: 6217ff49-ac7b-547e-eecf-016a06970ba9
The first 8 bytes of the UUID value indicate the service. The key ones here are:
- 0x180d - Heart Rate (org.bluetooth.service.heart_rate)
- 0x180f - Battery Service (org.bluetooth.service.battery_service)
My end goal for this blog entry is to demonstrate Heart Rate data readings. So, I'll save that for last. Let's start at the Battery Service.
Going forward note that all values returned are in HEX format.
Battery Service
Let's focus on the below line
attr handle: 0x0024, end grp handle: 0x0026 uuid: 0000180f-0000-1000-8000-00805f9b34fb
Services provide characteristics which provide data. Here's how we read characteristics from the Battery Service using the char-desc and char-read-hnd commands. We need the attr handle and end grp handle values here so note those.
handle: 0x0024, uuid: 2800
handle: 0x0025, uuid: 2803
handle: 0x0026, uuid: 2a19
The UUID of interest is 0x2a19. Note the handle i.e. 0x0026. This will provide the current battery level.
[CON][00:22:D0:33:1E:0F][LE]> char-read-hnd 0x0026
Characteristic value/descriptor: 51
The value is displayed in HEX. Therefore the value of 51 is actually 81%.
Heart Rate Service
Now, we get to the heart of the matter (pun intended)... reading the heart rate information. Let's look at the Heart Rate service.
attr handle: 0x000f, end grp handle: 0x0014 uuid: 0000180d-0000-1000-8000-00805f9b34fb
[CON][00:22:D0:33:1E:0F][LE]> char-desc 0x000f 0x0014
handle: 0x000f, uuid: 2800
handle: 0x0010, uuid: 2803
handle: 0x0011, uuid: 2a37
handle: 0x0012, uuid: 2902
handle: 0x0013, uuid: 2803
This part was tricky to figure out. Here we're going to be dealing with NOTIFY to get the real time heart rate data. See the Heart Rate Service for more details. Notify has to be turned on by changing the Characteristic Configuration at handle 0x0012 i.e. UUID 0x2902. The data returned is from handle 0x0011 i.e. UUID 0x2a37 which is the characteristic that provides Heart Rate Measurement.
[CON][00:22:D0:33:1E:0F][LE]> char-write-req 0x0012 0100
[CON][00:22:D0:33:1E:0F][LE]> Characteristic value was written successfully
Notification handle = 0x0011 value: 06 61
Notification handle = 0x0011 value: 16 62 70 02 71 02
Notification handle = 0x0011 value: 16 60 75 02 84 02
Notification handle = 0x0011 value: 16 5f 8f 02
Notification handle = 0x0011 value: 16 5d a2 02 9d 02
Notification handle = 0x0011 value: 16 5d 93 02
Notification handle = 0x0011 value: 16 5c 92 02 99 02
The above will keep going on. I was able to get over 5000 readings during a test session. To turn off notify...
[CON][00:22:D0:33:1E:0F][LE]> char-write-req 0x0012 0000
[CON][00:22:D0:33:1E:0F][LE]> Characteristic value was written successfully
I came across the Polar H7 heart rate packet format this article. Code source: MyTracks for Android
* Polar Bluetooth Wearlink packet example;
* Hdr Len Chk Seq Status HeartRate RRInterval_16-bits
* FE 08 F7 06 F1 48 03 64
* where;
* Hdr always = 254 (0xFE),
* Chk = 255 - Len
* Seq range 0 to 15
* Status = Upper nibble may be battery voltage
* bit 0 is Beat Detection flag.
So far, I can't relate the packet data format to the readings I'm seeing from the device. After fiddling around with the data received I think I figured it out. The heart rate is the second octet.
06 61 => 97
16 62 70 02 71 02 => 98
16 60 75 02 84 02 => 96
16 5f 8f 02 => 95
16 5d a2 02 9d 02 => 93
16 5d 93 02 => 93
16 5c 92 02 99 02 => 92
What's next?
XBMC add-ons are written in Python, and I'll need to be able to read the heart rate data in to the add-on script to be able to display it on screen. From what I can tell Python capabilities with Bluetooth LE are limited and appear to be in its infancy. The best option I've seen is https://github.com/IanHarvey/bluepy. It works, but I don't see it as ideal. A binary has to be compiled from source for it to work. I'll write more about it later.
Thank you very much for such a useful article.
ReplyDeleteCan you tell how to obtain RR interval ?
As I understand from here Notification handle = 0x0011 value: 16 49 4c 03
49 it is 73 beats per minute
4c it is 76 (RR interval) but it must be near 1000 ms
Units of RR interval is 1/1024 sec
http://developer.polar.com/wiki/H6_and_H7_Heart_rate_sensors
http://stackoverflow.com/questions/17422218/bluetooth-low-energy-how-to-parse-r-r-interval-value
How it can be done ?
Thanks
John
The Heart Rate Measurement characteristic specification says that the RR Interval value is UINT16. So, for your example value of "16 49 4c 03" the RR Interval data is in "4c 03". But, as per the specification the order of those is to be swapped (look at the LSO...MSO part) which is then "03 4c".
ReplyDelete0x034c = 844
844/1024 = 0.82421875 seconds
If you look at the example output for my readings you'll notice that I have multiple RR Interval values. Decoding is the same. Each one is UINT16 with oldest record coming first.
Maybe this will help others who are perl hacking. I'm still not sure about the RR output. The command below outputs HRM, RR as per the Ali's comment, the last hex in the RR, the first bit in the RR, and the calculated unpacked RR.
DeleteUsed a polar h7.
gatttool -b 00:22:D0:92:08:3A --char-write-req --handle=0x0012 --value=0100 --listen | perl -ne 'if(/.*value: (\w+) (\w+) (\w+) (\w+)/) { ($x,$y,$z,$a) = ($1,$2,$3, $4);$rr = hex("$a$z"); printf ("%d, %f, %x, %x, %d\n", hex($y), $rr/1024, $a, $z, $rr);}'
Characteristic Write Request failed: Attribute can't be written
81, 0.742188, 2, 0, 760
81, 0.729492, 2, 0, 747
81, 0.741211, 2, 0, 759
80, 0.754883, 3, 5, 773
82, 0.705078, 2, 0, 722
83, 0.655273, 2, 9, 671
86, 0.633789, 2, 59, 649
88, 0.608398, 2, 6, 623
91, 0.601562, 2, 44, 616
With the Polar H7 2016 Modell, at least the one I received from Amazon today, I need to adjust the handles to "+1" from the doc.
DeleteSo the amazing perl one-liner uses --handle=0x0013 instad of 0x0012.
Thank you guys for this tutorial and the one line script.
Thanks for the note Grassi, working like a charm :)
DeleteThanks for the great description!
ReplyDeleteI am wondering how to interpret the values after the first RR-interval.
To stay with the example from above: 16 62 70 02 71 02
62 -> 98 HR
70 02 -> has to be formed into 0270 = 624
71 01 -> What to do with this? This also seems like a ms RR-Interval (625), but why do we have the value in the same line?
Is it just because the H7 measured two values in the one second interval?
Thanks in advance!
Max
Sorry for such a late reply. But, yes, I think that during the reporting period 2 measurements for RR were recorded.
DeleteHow do you write char-write-req 0x0012 0100 with PyGatt?
ReplyDeleteNow, I haven't used PyGatt much myself. So, you'll have to verify the suitability of this information.
DeleteThe PyGatt python module provides a method char_write_handle which I believe will do what you required. This Connecting to a Bluetooth smart/LE weight scale with bluez/bluetoothctl/gatttool post on stackoverflow seems to provide an example.
Let me know if this helped.
Yes I found that too but I don't really know how to use the char_write_handle with 0x0012 0100. The subscribe method is also not working. Do you have an idea?
DeleteNow you've gotten me curious. I'm going to give it a try over the next few days and update you. I'd like to get it done sooner, but I'm looking at a 3 day weekend with family stuff planned.
DeleteOkay thanks ;). Have a nice weekend!
DeleteHave you already done some things with pygatt?
DeleteThanks!!!
ReplyDeleteI've made some fixes because handlers can be more than 16 and also some devices don't show the 2902 handler when we ask for characteristics.
while 1:
try:
gt.expect(r"handle: ([x0-9a-f]+), uuid: ([0-9a-f]{8})", timeout=10)
except pexpect.TIMEOUT:
break
handle = gt.match.group(1)
uuid = gt.match.group(2)
log.info("handle: " + handle + " con UUID= " + uuid)
# if uuid == "00002902":
# # We send the request to get HRM notifications
# log.info("Activando notificacion")
# gt.sendline("char-write-req " + handle + " 0100")
# elif uuid == "00002a37":
if uuid == "00002a37":
hr_handle = handle
log.info("Handle antes: " + handle)
handle = hex(int(handle,16) +1)
log.info("Handle despues: " + handle)
log.info("Activando notificacion en handle: " + handle)
gt.sendline("char-write-req " + handle + " 0100")
To enable notification i sent "char-write-req 0100" form python in interactive mode for heart rate service. But notifications not enabled. So i need to read value every time. How to solve this?Kindly help me.
ReplyDelete