Sunday, November 2, 2014

Polar H7 Bluetooth LE Heart Rate Sensor on Ubuntu 14.04

I recently picked up the Polar H7 Heart Rate Sensor (Bluetooth Low Energy). The long term goal is to connect up the sensor to my HTPC running XBMC so that I can see my heart rate on the screen while I work out. I usually work out to music playing on the HTPC or watching P90X videos. So, it'd be nice to be able to see the heart rate information real time as well as record it in a file/db for historic purposes.

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 lescan
LE 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), ServicesCharacteristics, 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

The first 8 bytes of the UUID value indicate the service. The key ones here are:
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.

[CON][00:22:D0:33:1E:0F][LE]> char-desc 0x0024 0x0026
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

It looks like the rate of messages is about 1 per second. There you have it. Real time heart rate data from the Polar H7.

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.

References


15 comments:

  1. Thank you very much for such a useful article.
    Can 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

    ReplyDelete
  2. 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".

    0x034c = 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.

    ReplyDelete
    Replies
    1. 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.

      Used 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

      Delete
    2. 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.
      So the amazing perl one-liner uses --handle=0x0013 instad of 0x0012.
      Thank you guys for this tutorial and the one line script.

      Delete
    3. Thanks for the note Grassi, working like a charm :)

      Delete
  3. Thanks for the great description!

    I 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

    ReplyDelete
    Replies
    1. Sorry for such a late reply. But, yes, I think that during the reporting period 2 measurements for RR were recorded.

      Delete
  4. How do you write char-write-req 0x0012 0100 with PyGatt?

    ReplyDelete
    Replies
    1. Now, I haven't used PyGatt much myself. So, you'll have to verify the suitability of this information.

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

      Delete
    2. 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?

      Delete
    3. Now 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.

      Delete
    4. Okay thanks ;). Have a nice weekend!

      Delete
    5. Have you already done some things with pygatt?

      Delete
  5. Thanks!!!

    I'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")

    ReplyDelete
  6. 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