ophychius
ophychius

Reputation: 2653

How to make sense of CoreBluetooth data

I've been playing around with CoreBluetooth a lot lately, and although I can connect to some devices, I never seem to be able to properly read the data (characteristic values).

Right now I am connecting with the Wahoo BT Heartrate monitor, and I am getting all the signals but I can't make the data into anything sensible. (Yes, I am aware there is an API, but I am trying to connect without it, to properly get something working with CoreBluetooth).

I have so far not been able to turn the NSData (characteristic.value) into anything sensible. If you have any suggestions on how to make sense of this data that would be very much apreciated.

Upvotes: 1

Views: 1511

Answers (3)

Brabbeldas
Brabbeldas

Reputation: 1949

Below some code to fully parse all the HeartRate measurement characteristic data.

How to process the data depends on several things:

  • is the BPM written into a single byte or two?
  • is there EE data present?
  • calculate the number of RR-interval values, as there can be multiple values within in one message (I have seen up to three).

Here is the actual spec of the Heart_rate_measurement characteristic

// Instance method to get the heart rate BPM information
- (void) getHeartBPMData:(CBCharacteristic *)characteristic error:(NSError *)error
{
    // Get the BPM //
    // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml //

    // Convert the contents of the characteristic value to a data-object //
    NSData *data = [characteristic value];

    // Get the byte sequence of the data-object //
    const uint8_t *reportData = [data bytes];

    // Initialise the offset variable //
    NSUInteger offset = 1;
    // Initialise the bpm variable //
    uint16_t bpm = 0;


    // Next, obtain the first byte at index 0 in the array as defined by reportData[0] and mask out all but the 1st bit //
    // The result returned will either be 0, which means that the 2nd bit is not set, or 1 if it is set //
    // If the 2nd bit is not set, retrieve the BPM value at the second byte location at index 1 in the array //
    if ((reportData[0] & 0x01) == 0) {
        // Retrieve the BPM value for the Heart Rate Monitor
        bpm = reportData[1];

        offset = offset + 1; // Plus 1 byte //
    }
    else {
        // If the second bit is set, retrieve the BPM value at second byte location at index 1 in the array and //
        // convert this to a 16-bit value based on the host’s native byte order //
        bpm = CFSwapInt16LittleToHost(*(uint16_t *)(&reportData[1]));

        offset =  offset + 2; // Plus 2 bytes //
    }
    NSLog(@"bpm: %i", bpm);



    // Determine if EE data is present //
    // If the 3rd bit of the first byte is 1 this means there is EE data //
    // If so, increase offset with 2 bytes //
    if ((reportData[0] & 0x03) == 1) {
        offset =  offset + 2; // Plus 2 bytes //
    }



    // Determine if RR-interval data is present //
    // If the 4th bit of the first byte is 1 this means there is RR data //
    if ((reportData[0] & 0x04) == 0)
    {
        NSLog(@"%@", @"Data are not present");
    }
    else
    {
        // The number of RR-interval values is total bytes left / 2 (size of uint16) //

        NSUInteger length = [data length];
        NSUInteger count = (length - offset)/2;
        NSLog(@"RR count: %lu", (unsigned long)count);

        for (int i = 0; i < count; i++) {

            // The unit for RR interval is 1/1024 seconds //
            uint16_t value = CFSwapInt16LittleToHost(*(uint16_t *)(&reportData[offset]));
            value = ((double)value / 1024.0 ) * 1000.0;

            offset = offset + 2; // Plus 2 bytes //

            NSLog(@"RR value %lu: %u", (unsigned long)i, value);

        }

    }

}

Upvotes: 2

mbuc91
mbuc91

Reputation: 1552

Well, you should be implementing the Heart Rate Profile (see here), which uses the Heart Rate Service. If you look at the Heart Rate Service Specifications, you will see that the format of the Heart Rate Measurement Characteristic changes according to the flags set in the least significant octet of the data packet.

This means that you need to set up your code to handle dynamic packet sizes.

So your general process would be:

  1. Get the first byte of the value property and check it for:
    • Is the heart rate measurement 8 bits or 16 bits?
    • Is sensor contact supported?
    • Is sensor contact detected?
    • Is Energy Expended supported?
    • Is RR-Interval measurement supported?
  2. If the heart rate measurement is 8 bits (bit 0 of byte 0 is 0), then cast the next byte into its intended format (hint: it's uint8_t). If it is 16 bits (i.e. bit 0 of byte 0 is 1), then cast the next two bytes into uint16_t.
  3. If Energy Expended is supported (bit 3 of byte 0 is 1), then cast the next byte into a uint16_t.
  4. Do the same with RR-Intervals.

Using NSData - especially with Core Bluetooth - takes some getting used to, but it's not that bad once you grasp the concept.

Good luck!

Upvotes: 1

Larme
Larme

Reputation: 26036

Well... What you'll have to do, when you read the value for the characteric :
NSData *data = [characteritic value]; theTypeOfTheData value; [data getByte:&value lenght:sizeof(value)];
But, theTypeOfTheData can be whatever has thought the developer of the device. So it could be UInt8, UInt16, or struct (with or without bitfield)... You'll have to get info by contacting the developer of the device or looking if there is some documentation.
For example, with my colleague, we use the the type of data that consumes the less space (because the device hasn't infinite memory) according to what is need. Look into the descriptor of the characteristic. It may points out the type of data.

Upvotes: 0

Related Questions