Search code examples
clinuxbluetoothblueznvidia-jetson-nano

How to access ManufacturerData from a Polar heart rate sensor using Bluez and C


Question: Phrased another way. My Intel Bluetooth controller accepts data from my Polar Bluetooth LE device and places the data on the D-bus system bus. How do I use the bluez API and D-Bus to read my Polar sensors heart rate data?

In an attempt to at least see the Polar sensor, I ran c code written by Parthiban Nallathambi at www.linumiz.com: https://www.linumiz.com/bluetooth-list-devices-using-gdbus/. Providing this for credit and background.

The code accurately displayed the Polar sensor attributes, but no data. FYI, the 1st few executions it actually did display ManufacturerData:

        Address : D2:9C:2A:C8:F9:CA
        AddressType : random
        Name : Polar H9 ADAC102E
        Alias : Polar H9 ADAC102E
        Appearance : Other
        Paired : 1
        Trusted : 1
        Blocked : 0
        LegacyPairing : 0
        Connected : 0
        UUIDs : 
                00001800-0000-1000-8000-00805f9b34fb
                00001801-0000-1000-8000-00805f9b34fb
                0000180a-0000-1000-8000-00805f9b34fb
                0000180d-0000-1000-8000-00805f9b34fb
                0000180f-0000-1000-8000-00805f9b34fb
                0000181c-0000-1000-8000-00805f9b34fb
                0000feee-0000-1000-8000-00805f9b34fb
                6217ff4b-fb31-1140-ad5a-a45545d7ecf3
        Adapter : Other
        ServicesResolved : 0 

Then I ran bluetoothctl to display vendor data in ManufacturerData:

steven@DEVELOPMENT-JETSON:~$ bluetoothctl
[NEW] Device D2:9C:2A:C8:F9:CA Polar H9 ADAC102E
[NEW] Primary Service
        /org/bluez/hci0/dev_D2_9C_2A_C8_F9_CA/service0045
        0000feee-0000-1000-8000-00805f9b34fb
        Polar Electro Oy
[NEW] Characteristic
        /org/bluez/hci0/dev_D2_9C_2A_C8_F9_CA/service000e/char000f
        00002a37-0000-1000-8000-00805f9b34fb
        Heart Rate Measurement
[bluetooth]# **connect D2:9C:2A:C8:F9:CA**
Attempting to connect to D2:9C:2A:C8:F9:CA
[CHG] Device D2:9C:2A:C8:F9:CA Connected: yes
Connection successful
[CHG] Device D2:9C:2A:C8:F9:CA ServicesResolved: yes

[Polar H9 ADAC102E]# scan on
Discovery started

[CHG] Device D2:9C:2A:C8:F9:CA RSSI: -67
[CHG] Device D2:9C:2A:C8:F9:CA ManufacturerData Key: 0x006b
[CHG] Device D2:9C:2A:C8:F9:CA ManufacturerData Value: 33 1e 33 33        3.33

I'm just baffled, I can't find any examples of c code that does the following (pseudo code):

  1. Pair to device given device ID or address
  2. Iteratively/continually read ManufacturerData where key = 0x006b
  3. Pull out heart rate data from array

Not looking for someone to write the code, but for someone to point me at the bluez/dbus functions or code if you have it :-), that will accomplish this. Thanks for you time. I'm just stumped.

I have already looked at the Bluetooth for Linux Developers Study Guide, but its in Python and I'm looking for a C guide.


Solution

  • If you followed the example in here then you should have a function named bluez_property_value, in here you need to compare the key param with "ManufacturerData" and then extract that information using GVariant in this case is an a{qv} an array of dictionaries containing a q id and a v variant where this variant is an array of bytes ay.

    void bluez_property_value(const gchar *key, GVariant *value)
    {
        if (g_strcmp0(key, "ManufacturerData") != 0) 
            return;
    
        const guint16 id;
        GVariant *manufacturing_data = NULL;
        GVariantIter *iter;
    
        g_variant_get(value, "a{qv}", &iter);
        while (g_variant_iter_loop(iter, "{qv}", &id, &manufacturing_data))
        {
            const guint8 byte;
            GVariantIter *bytes;
    
            g_variant_get(manufacturing_data, "ay", &bytes);
            while (g_variant_iter_next(bytes, "y", &byte))
                fprintf(stdout, "%02X", byte);
            fflush(stdout)
        }
    }
    
    
    /**
     * @brief read a float from unsigned int, 
     * Check if the first bit is 1, if it is the number is negative
     * so we need to negate the number with the ~ operator and extract
     * a the subset of the bits from the number.
     * 
     * @param number the number to read from
     * @return double, the negative or positive value
     */
    double read_negative_value(int number)
    {
        if ((number & 0x8000) > 0)
            return -(double) ((~number) & 0x7FFF);
        return (double) number;
    }
    
    

    EDIT: Fix code format.

    EDIT2: Added a function to read negative values from manufacturer data.