简体   繁体   中英

Getting Heart-rate variability from Polar H10 (UWP)

I am running the BLE sample from github (Windows) and trying to get the heart rate variability from Polar H10.

However the only services and characteristics it shows me are following:

// first layer keys are serviceUuid's
// second layer keys are characteristicUuid's
// with their respective name/description as values
{
    "1800"    /* Generic Access */                      : {
        "2a00": "Device Name",
        "2a01": "Appearance",
        "2a02": "Peripheral Privacy Flag",
        "2a03": "Reconnection Address",
        "2a04": "Peripheral Preferred Connection Parameters"
    },
    "1801"    /* Generic Attribute */                   : {
        "2a05": "Service Changed"
    },
    "180d"    /* Heart Rate */                          : {
        "2a37": "Heart Rate Measurement",
        "2a38": "Body Sensor Location"
    },
    "180a"    /* Device Information */                  : {
        "2a23": "System ID",
        "2a24": "Model Number String",
        "2a25": "Serial Number String",
        "2a26": "Firmware Revision String",
        "2a27": "Hardware Revision String",
        "2a28": "Software Revision String",
        "2a29": "Manufacturer Name String"
    },
    "180f"    /* Battery Service */                     : {
        "2a19": "Battery Level"
    },
    "6217ff4b-fb31-1140-ad5a-a45545d7ecf3" /* unknown */: {
        "6217ff4c-c8ec-b1fb-1380-3ad986708e2d": "unknown", /* read:true */ // value = 
         uInt16Array [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        "6217ff4d-91bb-91d0-7e2a-7cd3bda8a1f3": "unknown" /* write:true, 
         indicate:true, descriptors:{ descriptorUuid: "2902" }*/
     {
         /* 6172 */
         this service has all the numbers which I have no idea about. 
         Example: 10905, 10906, and etc.  
     }
}

Now, I know that Polar H10 does give you heart rate variability. So why it is not showing me ?

Does anyone have any idea?

EDIT::

private static ushort ParseHeartRateValue(byte[] data)
    {
        //ushort offset = 1;
        // Heart Rate profile defined flag values
        const byte heartRateValueFormat = 0x04;

        byte flags = data[0];
        ushort offset = 1;
        bool HRC2 = (flags & 1) == 1;
        if (HRC2) //this means the BPM is un uint16
        {
            short hr = BitConverter.ToInt16(data, offset);
            offset += 2;
        }
        else //BPM is uint8
        {
            byte hr = data[offset];
            offset += 1;
        }

        //see if EE is available
        //if so, pull 2 bytes

        bool ee = (flags & (1 << 3)) != 0;
        if (ee)
            offset += 2;

        // see if RR is present 
        // if so, the number of RR values is total bytes left / 2(size of uInt 16)

        bool rr = ((flags & 1 << 4) != 0);
        if (rr)
        {
            int count = (data.Length - offset) / 2;
            for (int i = 0; i < count; i++)
            {
                //each existence of these values means an R-Wave was already detected
                //the ushort means the time (1/1024 seconds) since last r-wave

                ushort value = BitConverter.ToUInt16(data, offset);

                double intervalLengthInSeconds = value / 1024.0;
                offset += 2;

            }
        }
        bool isHeartRateValueSizeLong = ((flags & heartRateValueFormat) != 0);

        if (isHeartRateValueSizeLong)
        {
            return BitConverter.ToUInt16(data, 1);
        }
        else 
        {
            return data[1];
        }

      }
   }
 }

If you can read the heartbeat in notifications than there is also the rr-interval. The rr-interval is represented 2 bytes( uint16). You need the rr-interval to calculate heart rate variability in your app.

To get the rr-interval you have to read the flags from the first byte you receive. You read the flags as binary from right to left.

bit 0 = 0: Heart Rate Value Format is set to UINT8. Units:BPM (1 byte).
bit 0 = 1: Heart Rate Value Format is set to UINT16. Units:BPM (2 bytes).

bit 1 and 2: Sensor Contact Status bits. These are not relevant for this.

bit 3 = 0: Energy Expended field is not present.
bit 3 = 1: Energy Expended field is present. Format = uint16. Units: kilo Joules.

bit 4 = 0: RR-Interval values are not present.
bit 4 = 1: One or more RR-Interval values are present. Format = uint16. unit 1/1024 sec.

bit 5, 6 and 7: reserved for future use.

If your first byte for example = 16 = 0x10 = 0b00010000 then byte 2 = is heart rate.

Byte 3 and 4 are the rr-interval.
Byte 5 and 6 (if present) rr-interval.

To calculate heart rate variability you have to sample the rr-interval values over a period of time and take the Standard Deviation of those intervals. To calculate the Standard Deviation:

1. Work out the Mean (the simple average of the numbers)
2. Then for each number: subtract the Mean and square the result
3. Then work out the mean of those squared differences.
4. Take the square root of that and we are done!

How to do that in code, I leave that up to You or you can google it.

Note:

  1. Byte-pairs in SIG specifications are Least Significant Byte First, so for uint16 representation in Windows swap the bytes of a byte-pair first!
  2. rr-interval is not miliseconds but 1/1024 seconds. This is to prevent loosing the decimal numbers in an unsigned division.

According to the official documentation , the UUID of the heart rate measurement is 00002a37-0000-1000-8000-00805f9b34f - which matches the output from your question 2a37 .

The docs provide the following sample for Android:

if (characteristic.getUuid().equals(HR_MEASUREMENT)) {
    byte[] data = characteristic.getValue();
    int hrFormat = data[0] & 0x01;
    boolean sensorContact = true;
    final boolean contactSupported = !((data[0] & 0x06) == 0);
    if( contactSupported ) {
        sensorContact = ((data[0] & 0x06) >> 1) == 3;
    }
    int energyExpended = (data[0] & 0x08) >> 3;
    int rrPresent = (data[0] & 0x10) >> 4;
    final int hrValue = (hrFormat == 1 ? data[1] + (data[2] << 8) : data[1]) & (hrFormat == 1 ? 0x0000FFFF : 0x000000FF);
    if( !contactSupported && hrValue == 0 ){
        // note does this apply to all sensors, also 3rd party
        sensorContact = false;
    }
    final boolean sensorContactFinal = sensorContact;
    int offset = hrFormat + 2;
    int energy = 0;
    if (energyExpended == 1) {
        energy = (data[offset] & 0xFF) + ((data[offset + 1] & 0xFF) << 8);
        offset += 2;
    }
    final ArrayList<Integer> rrs = new ArrayList<>();
    if (rrPresent == 1) {
        int len = data.length;
        while (offset < len) {
            int rrValue = (int) ((data[offset] & 0xFF) + ((data[offset + 1] & 0xFF) << 8));
            offset += 2;
            rrs.add(rrValue);
        }
    }
}

Which shows you how to interpret the HR measurement value (which is byte array).

In UWP you can follow the samples and documentation here to wire up the GattCharacteristic 's ValueChanged event, which will give you the current value of a given characteristic in GattValueChangedEventArgs :

characteristic.ValueChanged += Characteristic_ValueChanged;

Handled like this:

void Characteristic_ValueChanged(GattCharacteristic sender, 
                                    GattValueChangedEventArgs args)
{
    // An Indicate or Notify reported that the value has changed.
    var reader = DataReader.FromBuffer(args.CharacteristicValue)
    // Parse the data however required.
}

DataReader provides a ReadBytes as well as ReadByte method, so you should be able to rewrite the sample Java code to UWP. Unfortunately I don't have a Polar device at my disposal so this part I must leave up to you :-) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM