Your first Simband algorithm

The Vobio API is written in C, and a tutorial is forthcoming.

This tutorial requires a basic understanding of C++11 and some knowledge of lambda functions. We will also use std::unique_ptr and std::vector.

Write a Heart Rate Variability (HRV) algorithm

Concepts

Writing algorithms using the Vobio API is very intuitive. Previously, we talked about basic concepts for understanding and working with Simband data and how to access this data. Don't worry if you don't remember. This tutorial is designed to cover the required concepts and terms once again in brief.

The Vobio API describes data as a stream. What is a stream? A stream is a continuous sequence of time-coded numeric data. A sensor generating data (numbers) in time—or "time-coded numeric data"—produces a stream.

For example: HeartBeat is a stream representing single detected heartbeats, available from this list of currently available streams.

Goals

In this tutorial, we'll do the following:

  • Extract the information from the HeartBeat stream.
  • Process that information.
  • Produce a new stream: HeartRateVariability.

Below is a visual interpretation of the HeartBeat stream:

Visual interpretation of HeartBeat stream

Blue cells represent a HeartBeat, and white cells represent no HeartBeat, in time, as expressed by the increasing array index. We will use the API to tap into this stream and ask for the timestamps of where-there-is-a-heartbeat between the time "Now" and some valid time in the past.

As shown in the above diagram, we are requesting information from "Now = t1" to a valid time in the past "t0". The API will retrieve a list of timestamps where-there-is data and a total count of timestamps.

Tutorial

Roll up your sleeves—it's time to dive into code and learn some cool stuff.

For our tutorial, we will create a hrvalgorithm.h file where we declare the HRVAlgorithm class.

Since we want to get timestamps from the HeartBeat stream, crunch that information and write the new data into a new stream called HeartRateVariability, we will declare two stream IDs (hb and hrv) and associate each ID with its respective stream. We will also need Clock and Timer objects from the Vobio API to synchronize the streams and trigger callbacks. We will also require an UpdateInterval, which will be used to start timer notification with a given interval.

Now lets define a function prototype that takes heart beats as input and calculates the HeartRateVariability. Let us call this function as calculateHeartRateVariability.

Stream, Clock and Timer types are defined in vobio.hpp, a C++11 wrapper with convenience functions for using the C APIs declared in vobio.h. Its lightweight classes and functions take away some of the pain of dealing with C-style functions and with constructing/destructing objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "vobio.hpp"

class HRVAlgorithm
{
public:
    void init();

private:
  bool calculateHeartRateVariability(const std::vector<double>& beats, float& result);

  vobio::stream hb;
  vobio::stream hrv;

  vobio::clock clock;
  vobio::timer timer;

};

Next, let's create a file called hrvalgorithm.cpp.

We will include hrvalgorithm.h (the header file we just created).

So far, hrvalgorithm.cpp looks like this and let's start filling it step-by-step.

1
2
3
4
5
6
7
8
9
10
#include "hrvalgorithm.h"

static const int UpdateInterval = 5;

void HRVAlgorithm::init()
{



}

Now, let's initialize the stream objects. We will use the constructor of vobio::stream class to associate IDs with stream type.

1
2
3
4
5
6
7
8
9
10
#include "hrvalgorithm.h"

static const int UpdateInterval = 5;

HRVAlgorithm::init()
{
    hb = vobio::stream(SIMBAND("heartBeat"));
    hrv = vobio::stream("com.my-adk-sample.myhrvstream");

}

Note: All Simband system streams are available here

Algorithms are time-driven. For a timer, you need a clock, and we define the clock based on the data we want to work on.

We want to work on the HeartBeat (hb) stream, and this stream needs to be time-synchronized in this algorithms context. vobio::clock is the class that wraps vobio_clock_h. We will create an object of that class and store it in the clock declared in hrvalgorithm.h.

To receive periodic callbacks, we will use a timer. To do that, let's create an object of the class vobio::timer that is a wrapper around vobio_timer_h.

The vobio::timer constructor expects a pointer to an object of class vobio::clock as a parameter and a callback with no parameters. We will create an object of vobio::timer and store it in the timer declared in hrvalgorithm.h.

In this example, as a callback we use C++11 lambda.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "hrvalgorithm.h"

static const int UpdateInterval = 5;

HRVAlgorithm::init()
{
    hb = vobio::stream(SIMBAND("heartBeat"));
    hrv = vobio::stream("com.my-adk-sample.myhrvstream");

    clock = vobio::clock(hb);

    timer = vobio::timer(clock, [&] {
        ...
        ...
    });
...
}

We will receive a callback when all the streams in the context have data equivalent to the timeout. For this example, we are working with only one stream.

First, we have to start the timer. We will call the function start of the vobio::timer class, which is a wrapper for vobio_timer_start. It expects a timeout interval in milliseconds.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "hrvalgorithm.h"

static const int UpdateInterval = 5;

HRVAlgorithm::init()
{
    hb = vobio::stream(SIMBAND("heartBeat"));
    hrv = vobio::stream("com.my-adk-sample.myhrvstream");

  clock = vobio::clock(hb);

  timer = vobio::timer(clock, [&] {
      ...
      ...
  });

  timer.start(UpdateInterval);
}

When we get the callback, all the code inside the lambda function will execute.

Since we now have the data available in the hb stream, we should use the API vobio::get_samples() to get the data & timestamps of where-there-is-a-heartbeat and use this information to calculate HeartRateVariability.

To get data from the stream, vobio::get_samples() API expects the lower and upper bounds of the time period. The upper bound of the time period is basically "Now = t1" to some valid time in past "Now - Seconds".

To get the current time in the context's clock, we will call the function now() of vobio::clock class, which is a wrapper for vobio_get_current_time().

1
auto now = clock->now();

vobio::get_samples() API on success will return a vector containing samples of type vobio_sample_s, which holds data and timestamp variables. vobio_sample_s structure is defined in vobio.h.

We will use a vector of type double to save the retrieved timestamps, as it is advisable to free the list.

The following code snippet demonstrates as explained.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
      timer = vobio::timer(clock, [&] {
        ...
        auto now = clock.now();
        std::vector<vobio_sample_s> samples = vobio::get_samples(hb, now - UpdateInterval, now);

        std::vector<double> beats;
        for (auto i = 0; i < samples.size(); ++i)
            beats.push_back(samples[i].timestamp);

        float result(0);
        if (calculateHeartRateVariability(beats, result)) {
            vobio::send(hrv, now, result);
        }
        ...
    });

Now, since we have the timestamps in our beats vector, it's time to crunch the data. To do so, we will create a structure called result declared in HeartRateVariability.h which will hold the calculated data.

We will pass this structure along with the timestamps in beats and the count to HeartRateVariability::Calculate. On success, the passed structure will hold the result.

We will use vobio_send() API to send the result to our o/p stream: hrv.

Below is the complete code for hrvalgorithm.cpp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "hrvalgorithm.h"

static const int UpdateInterval = 5;

void HRVAlgorithm::init()
{
    hb = vobio::stream(SIMBAND("heartBeat"));
    hrv = vobio::stream("com.my-adk-sample.myhrvstream");

    clock = vobio::clock(hb);

    timer = vobio::timer(clock, [&] {
        auto now = clock.now();


        std::vector<vobio_sample_s> samples = vobio::get_samples(hb, now - UpdateInterval, now);

        std::vector<double> beats;
        for (auto i = 0; i < samples.size(); ++i)
            beats.push_back(samples[i].timestamp);

        float result(0);
        if (calculateHeartRateVariability(beats, result)) {
            vobio::send(hrv, now, result);
        }
    });

    timer.start(UpdateInterval);
}

Now, lets add the function that holds the computation of HRV from the obtained beats.

1
2
3
4
5
6
7
bool HRVAlgorithm::calculateHeartRateVariability(const std::vector<double>& beats, float& result)
{
    result = 0;
    return true;
}

Finally, the last step is to add the main function. Here, we will call the vobio_main function by passing a function

1
2
3
4
5
6
7
8
9
10
int main(int argc, char** argv) {
    HRVAlgorithm algo;

    return vobio_main(argc, argv, [] (void* userData) {
        HRVAlgorithm* algo = (HRVAlgorithm*)userData;
        algo->init();
    } , &algo);
}