Search code examples
javascriptcordovaparsingbluetoothheartrate

Interpreting/parsing data from Bluetooth heart rate monitor (Cordova)


I am creating an application using Cordova which requires interpreting data from a Bluetooth HR monitor (capable of recording raw RR intervals, such as the Polar H7). I am using the cordova-plugin-ble-central

I am having a difficult time making sense of the data received from the monitor, despite trawling the internet for answers and reading the Bluetooth Heart Rate Service Characteristic specification numerous times.

Here is my function which runs each time data is received:

onData: function(buffer) {
    console.log(buffer);

    // var data8 = new Uint8Array(buffer);

    var data16 = new Uint16Array(buffer);
    var rrIntervals = data.slice(1);
    for (i=0; i<rrIntervals.length; i++) {
        rrInterval = rrIntervals[i];
        heartRate.addReading(rrInterval); // process RR interval elsewhere
    }
},

When I log the data received in buffer, the following is output to the console: console output

I know how to extract RR intervals (highlighted in yellow), but I don't really understand what the other values represent, which I require as users might be connecting with other monitors which don't transmit RR intervals etc.

A quick plain English explanation of what the data received means and how to parse it would be much appreciated. For example, what number constitutes the flags field, and how to convert this to binary to extract the sub-fields (ie. to check if RR intervals present - I know this is determined by the 5th bit in the flags field.)

The plugin also states that 'Raw data is passed from native code to the success callback as an ArrayBuffer' but I don't know how to check the flags to determine if the data from the specific HR monitor is in 8 or 16 bit format. Below is another console log of when I create both Uint8 and Uint16 arrays from the data received. Again, I have highlighted the heart rate and RR intervals, but I need to know what the other values represent and how to parse them correctly.

console log with Uint8 and Uint16 output

The whole code is below:

var heartRateSpec = {
    service: '180d',
    measurement: '2a37'
};


var app = {
    initialize: function() {
        this.bindEvents();
    },
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    onDeviceReady: function() {
        app.scan();
    },
    scan: function() {
        app.status("Scanning for Heart Rate Monitor");

        var foundHeartRateMonitor = false;

        function onScan(peripheral) {
            // this is demo code, assume there is only one heart rate monitor
            console.log("Found " + JSON.stringify(peripheral));
            foundHeartRateMonitor = true;

            ble.connect(peripheral.id, app.onConnect, app.onDisconnect);
        }

        function scanFailure(reason) {
            alert("BLE Scan Failed");
        }

        ble.scan([heartRateSpec.service], 5, onScan, scanFailure);

        setTimeout(function() {
            if (!foundHeartRateMonitor) {
                app.status("Did not find a heart rate monitor.");
            }
        }, 5000);
    },
    onConnect: function(peripheral) {
        app.status("Connected to " + peripheral.id);
        ble.startNotification(peripheral.id, heartRateSpec.service, heartRateSpec.measurement, app.onData, app.onError);
    },
    onDisconnect: function(reason) {
        alert("Disconnectedz " + reason);
        beatsPerMinute.innerHTML = "...";
        app.status("Disconnected");
    },
    onData: function(buffer) {
        var data = new Uint16Array(buffer);

        if (heartRate.hasStarted() == false) {
            heartRate.beginReading(Date.now());

        } else {
            var rrIntervals = data.slice(1);
            for (i=0; i<rrIntervals.length; i++) {
                rrInterval = rrIntervals[i];
                heartRate.addReading(rrInterval);
            }

        }

    },
    onError: function(reason) {
        alert("There was an error " + reason);
    },
    status: function(message) {
        console.log(message);
        statusDiv.innerHTML = message;
    }
};


app.initialize();

Many thanks in advance for any help or advice.


Solution

  • UPDATE for a more in depth explanation check out this post I wrote on the subject.

    I've figured it out - here's a quick explanation for anyone who encounters a similar problem:

    The data passed into onData(buffer) is just binary data, so whether we convert it into a Uint8Array or a Uint16Array it still represents the same binary data. Of course, the integers in Uint16 will likely be larger as they comprise 16 bits rather than 8.

    The flags field is always represented by the first byte, so we can get this by converting the data (passed in as buffer) to a Uint8Array and access the first element of this array which will be the element with index 0.

    We can then check the various bit fields using bitwise operations. For example, the Bluetooth Heart Rate Service Characteristic specification tells us that the fifth bit represents whether the reading contains RR intervals (1) or doesn't contain any (0).

    Below we can see that the fifth bit is the number 16 in binary:

    128 64  32  16  8   4   2   1
    0   0   0   1   0   0   0   0  
    

    Therefore the operation 16 & flag (where flag is the byte containing the flags field) will return 16 (which can be hoisted to true) if the reading contains RR intervals and 0 (hoisted to false) if it doesn't.