See the HealthVault Data Type Wiki for current information on using this type.

This document contains general guidelines for using the Exercise and AerobicSession data types. It does not cover all the information that is stored in the type – see the Exercise and AerobicSession documentation for more information.

 

The Exercise data type is a replacement for the AerobicSession type.

In most cases, when a HealthVault data type is updated, applications can use simply switch from using the old data type to updated one. Because of the nature of the changes between the two types, some applications may need to contain code that handles both types of data.  More information about scenarios where that is necessary is contained later in the document.

The Exercise type

The Exercise type records the completion of an exercise. The simplest Exercise record contains the exercise activity and the date and time of the activity:

DateTime exerciseDateTime = DateTime.Now;

ApproximateDateTime exerciseDataTimeApprox =
        new ApproximateDateTime(
            new ApproximateDate(exerciseDateTime.Year, exerciseDateTime.Month, exerciseDateTime.Day),
            new ApproximateTime(exerciseDateTime.Hour, exerciseDateTime.Minute, exerciseDateTime.Second));

CodableValue activity = new CodableValue("Lacrosse", new CodedValue("Lacrosse", "exercise-activities));

Exercise exercise = new Exercise(exerciseDataTimeApprox, activity);

The Exercise type also supports setting the Title, Distance, and Duration of the exercise.

Adding exercise details

In addition to this very basic information, an application may store details about the exercise session in the Details collection.

Each detail describes the type of the detail and the value of the detail. The value is stored as a StructuredMeasurement, storing a value and the units of the value.

StructuredMeasurement averageHeartrate = new StructuredMeasurement();
averageHeartrate.Value = averageHeartrateInt;
averageHeartrate.Units = new CodableValue("BPM");

The type (or kind) of detail is stored as a coded value.

CodedValue averageHeartrateDetailKind = new CodedValue(ExerciseDetail.AverageHeartrate_BPM, "exercise-details");

A list of the kinds of information that can be stored in details is in the exercise-details vocabulary. To make it easier for applications to code data correctly, the ExerciseDetail class defines a set of static strings that correspond to entries in the exercise-details vocabulary. The name of the constant encodes both the detail that is being stored and the units of the detail.

The measurement and the kind are stored in the ExerciseDetail instance:

ExerciseDetail averageHeartrateDetail = new ExerciseDetail(averageHeartrateDetailKind, averageHeartrate);

Finally, that instance is inserted into the Details dictionary on the exercise instance.

exercise.Details.Add(ExerciseDetail.AverageHeartrate_BPM, averageHeartrateDetail);

Loading exercise details

Applications that are interested in a specific detail can simply look for it in the details dictionary:

if (exercise.Details.ContainsKey(ExerciseDetail.AverageHeartrate_BPM))
{
    double value = exercise.Details[ExerciseDetail.AverageHeartrate_BPM].Value.Value;
}

Segments

Some exercises are composed of segments, such as the laps in a race or the legs of a triathlon. An application that wishes to store such data can store ExerciseSegment instances in the Segments collection.

The ExerciseSegment class stores the same set of values as the Exercise class, and it also stores an offset that stores the offset of the start of the segment from the start of the exercise.

ExerciseSamples data type

Some heart rate monitors and cycling computers collect samples (such as heart rate, altitude, position, etc.) at regular intervals during an exercise session. The set of samples for a given type of measurement is stored in the ExerciseSamples data type, and these are associated with an Exercise session through the related items or client items.

ExerciseSamples altitudeSamples = new ExerciseSamples();
altitudeSamples.Name = new CodableValue(ExerciseSamples.Altitude_meters, new CodedValue(ExerciseSamples.Altitude_meters, "exercise-sample-names"));
altitudeSamples.Unit = new CodableValue("Meters", new CodedValue("Meters", "exercise-units"));
altitudeSamples.SamplingInterval = samplingInterval;

 

This sets up the ExerciseSamples instance. The coding for the kind of information stored in a sample is similar to the coding for an exercise detail. In this case, we’re storing altitude expressed in meters.

Next, we need to store the actual samples, which is handled by the ExerciseSampleData property. Each sample may store either one or two double values, depending on the type of information that is being stored (currently, position data is the only sample kind that stores two values). The application should use either the SingleValuedSamples or TwoValuedSamples collection based on the kind of data that is stored.

This code adds a series of altitude values:

double offsetInSeconds = 0.0;
foreach (double altitude in altitudeSampleList)
{
    ExerciseSampleOneValue altitudeSample = new ExerciseSampleOneValue(offsetInSeconds, altitude);
    offsetInSeconds += samplingInterval;

    altitudeSamples.ExerciseSamplesData.SingleValuedSamples.Add(altitudeSample);
}

In this case, because altitude is a single-valued sample, we create an instance of ExerciseSampleOneValue() to store the altitude value and the offsetInSeconds from the start of the sample set, and store it in the SingleValuedSamples collection.

The ExerciseSamples data type also supports data that is collected at irregular intervals, such as positions stored when a user presses a button on a device.

It is recommended that ExerciseSample instances are related to Exercise instances using related items.

Fetching ExerciseSample data

To provide efficient storage of the sample data, it is stored in the OtherData section of the object, which is not fetched by default. This allows an application to examine a ExerciseSamples instance to see if it is interesting before incurring the overhead of fetching the data. 

For example, the following code will look for and process any altitude samples that it finds…

HealthRecordSearcher searcher = PersonInfo.SelectedRecord.CreateSearcher();
HealthRecordFilter filter = new HealthRecordFilter(ExerciseSamples.TypeId);
searcher.Filters.Add(filter);

HealthRecordItemCollection samplesList = searcher.GetMatchingItems()[0];

foreach (ExerciseSamples samples in samplesList)
{
    if (samples.Name.Text == ExerciseSamples.Altitude_meters)
    {
            // query again to load the sample data
        ExerciseSamples fullSample =
                    (ExerciseSamples) PersonInfo.SelectedRecord.GetItem(samples.Key.Id,
                             HealthRecordItemSections.Core | HealthRecordItemSections.OtherData);
        ProcessAltitudeSample(fullSample);
    }
}

After the right kind of sample is found, the object is queried, this time including the sample data itself.

ExerciseSample encoding

HealthVault Connection Center may apply a compress/encode step (using gzip and base64 encode) to the comma-separated data. This can be detected by looking for a ContentEncoding that is not null.

The current version (0903 release) of the ExerciseSamples data type does not directly support this format. Applications wishing to read sample data that is generated from devices connected through Connection Center can use the following method:

    foreach (ExerciseSamples sample in samples)
    {
        ProcessCompressedAndEncodedData(sample);
        foreach (ExerciseSampleOneValue sampleItem in sample.ExerciseSamplesData.SingleValuedSamples)
        {
            int k = 12;
        }
    }

// Handle unpacking/decompressing data if it is in that format.
private void ProcessCompressedAndEncodedData(ExerciseSamples sample)
{
    if (sample.OtherData.ContentEncoding == null)
    {
        return;
    }

    // Only valid encoding here is base64, so we go with that...
    byte[] buffer = Convert.FromBase64String(sample.OtherData.Data);
    if (buffer.Length < 2)
    {
        return;
    }

    // look at the bytes to see if they are the proper gzip header...
    if ((buffer[0] != 31) ||
        (buffer[1] != 139))
    {
        return;    // nothing we can do
    }

    // gzip decompress...

    using (MemoryStream bufferStream = new MemoryStream(buffer))
    {
        using (GZipStream decompress = new GZipStream(bufferStream, CompressionMode.Decompress))
        {
            using (StreamReader reader = new StreamReader(decompress))
            {
                string result = reader.ReadToEnd();
                sample.OtherData.Data = result;
                sample.OtherData.ContentEncoding = null;
                sample.OtherData.ContentType = "text/csv";
            }
        }
    }
}

Exercise and AerobicSession

Exercise is a new version of the AerobicSession data type, and therefore the two types interoperate through versioning when queries are performed. Existing applications that query for AerobicSession instances will see Exercise instances translated into AerobicSession instances, and vice versa.

There are two important caveats to this:

  1. The sample data is only accessible through the type that actually stored the data, so if your application needs access to sample data, it will need to deal with both types natively.
  2. The transforms between the two types may perform well enough for your application if there is lots of data present. This would be another reason to deal with both types natively.

To deal with both types natively, the application will need to be authorized for both types. When a query is performed, a collection containing both AerobicSession and Exercise instances will be returned, and the application may then process instances of each type separately.

Calories and Energy

In the AerobicSession data type, there are two properties – Energy and EnergyFromFat – that have ambiguous definitions. The descriptions say that they store the food energy consumed, which leads some developers to store amounts measured in calories, but the remarks say that the unit is kilojoules, so other developers convert calories to kilojoules and store that number.

The Exercise class defines analogous measures using calories (using the CaloriesBurned_calories and FatCaloriesBurned_calories detail types), but this presented a problem during versioning – if we version the new details to the Energy and EnergyFromFat properties in AerobicSession, some of the data that versions forward will be incorrect.

We therefore decided to add two “shadow details” to Exercise. The EnergyOld_kj and EnergyFromFatOld_kj details version with the properties in AerobicSession, so applications that deal with exercise may want to look at these values, with the caveat that the number in it may be measured in kJ or in calories.

Upgrading Aerobic Sessions to Exercises

To improve performance and reduce application complexity when an application may have to deal with data of either type, it is permissible for applications to upgrade existing AerobicSession instances to Exercise instances.

This can be done in the following manner by an application authorized to access both types:

  1. Perform a query to fetch all AerobicSession instances as Exercise instances. This can be accomplished by adding the Exercise type ID to the HealthRecordFilter.View.TypeVersionFormat collection.
  2. Loop over each Exercise instance.
  3. Fetch the instance as an AerobicSession. If the instance has sample data associated with it, create a separate ExerciseSamples instance for each sample set in the AerobicSession.  Add the instance ID of the new ExerciseSamples object to the Exercise instance related items collection.
  4. Add the instance id of the AerobicSession object to the Exercise instance related item collection.
  5. Save the new Exercise instance.
  6. Delete the AerobicSession instance.