unable to read heart rate service over Bluetooth

1.6k Views Asked by At

I am looking to create a simple python script that reads heart rate data from a Polar sensor over Bluetooth. I have read through a lot of other posts and cannot find something simple that I am able to successfully execute.

I have the device MAC address for the Polar wearable. I know the service UUID for the value I want to read (0x180D for HR). I don't care much about which library or service I use, but I cannot seem to get this to work.

I am able to have my script successfully recognize the Polar sensor, however, I cannot figure out how to read the value from this. I have downloaded a Bluetooth Scanner app for my phone which is able to successfully connect and read the value, so I know it should be easy to do and cannot figure out how to write this up.

Any help would be tremendously appreciated.

2

There are 2 best solutions below

1
On

Although the code example from @ukBaz is useful and will accidentally show the correct broadcast HR in some cases, the part that unpacks the 5 bit flags is completely wrong (in wrong bit order and shifted by 3 bits). I discovered the problem when I was also trying to decode the other data such as R-R intervals and its flag was always wrong.

To correct the flags, change the bitstruct.unpack() lines as I've done below. The corrected code then is:

import asyncio
import bitstruct
import struct
from bleak import BleakClient

HR_MEAS = "00002A37-0000-1000-8000-00805F9B34FB"

async def run(address):

    async with BleakClient(address) as client:
        connected = await client.is_connected()
        print("Connected: {0}".format(connected))

        def hr_val_handler(sender, data):
            """Simple notification handler for Heart Rate Measurement."""
            (unused, rr_int, nrg_expnd, snsr_cntct_spprtd, snsr_detect, hr_fmt) \
                = bitstruct.unpack("b3b1b1b1b1b1", data)
            if hr_fmt:
                hr_val, = struct.unpack_from("<H", data, 1)  # uint16
            else:
                hr_val, = struct.unpack_from("<B", data, 1)  # uint8
            print("HR: {0:3} bpm. Complete raw data: {1} ".format(hr_val, data.hex(sep=':')))

        await client.start_notify(HR_MEAS, hr_val_handler)

        while await client.is_connected():
            await asyncio.sleep(1)


if __name__ == "__main__":
    address = ("xx:xx:xx:xx:xx:xx")  # Change to address of device
    loop = asyncio.get_event_loop()
    loop.run_until_complete(run(address))
3
On

If you look at the Bluetooth assigned 16-bit UUIDs numbers then you are correct that 0x180D is the Heart Rate service

enter image description here

It will be the Heart Rate Measurement Characteristic 0x2A37 that will have the values you are seeking:

enter image description here

I don't have a device to test this with and I don't know what platform you are writing the code for.

As a result I've used the bleak library as that is the most cross platform library.

The example also subscribes to notifications from the device rather than reads the value. This is a more typical way to get values that are regularly updating.

import asyncio
import bitstruct
import struct

from bleak import BleakClient


HR_MEAS = "00002A37-0000-1000-8000-00805F9B34FB"


async def run(address, debug=False):

    async with BleakClient(address) as client:
        connected = await client.is_connected()
        print("Connected: {0}".format(connected))

        def hr_val_handler(sender, data):
            """Simple notification handler for Heart Rate Measurement."""
            print("HR Measurement raw = {0}: {1}".format(sender, data))
            (rr_int,
             nrg_expnd,
             snsr_cntct_spprtd,
             snsr_detect,
             hr_fmt,
             ) = bitstruct.unpack("p3b1b1b1b1b1<", data)
            if hr_fmt:
                hr_val, = struct.unpack_from("<H", data, 1)
            else:
                hr_val, = struct.unpack_from("<B", data, 1)
            print(f"HR Value: {hr_val}")

        await client.start_notify(HR_MEAS, hr_val_handler)

        while await client.is_connected():
            await asyncio.sleep(1)


if __name__ == "__main__":
    address = ("xx:xx:xx:xx:xx:xx")  # Change to address of device
    loop = asyncio.get_event_loop()
    loop.run_until_complete(run(address))

You can read more about the Heart Rate Measure value is constructed in:

https://www.bluetooth.com/specifications/specs/gatt-specification-supplement-6/