Creating a FIT-File in an app

Hi,

I am doing my first steps in coding an app. My first app is an easy table tennis app. I have a lot of statistical information to write to the FIT file but I want to do the basics first: writing the heart rate. Therefore I have done this:

using Toybox.ActivityRecording as Rec;
using Toybox.Sensor as Snsr;

class TableTennisView extends Ui.View {

var session;
var heartRateFitField;

function initialize() {
View.initialize();
Snsr.setEnabledSensors( [Snsr.SENSOR_HEARTRATE] );
Snsr.enableSensorEvents( method(:onSnsr) );


}

function onSnsr(sensor_info)
{
var HR = sensor_info.heartRate;
if (session != null && session.isRecording() && HR != null) {
heartRateFitField.setData(HR);
}
}

function onUpdate(dc) {
if ( ( session == null ) || ( session.isRecording() == false ) ) {
var sessionOptions = {:sport => Rec.SPORT_TENNIS,:subSport => Rec.SUB_SPORT_MATCH, :name => "Tischtennis"};
session = Rec.createSession(sessionOptions);
Sys.println(session);
heartRateFitField = session.createField("Heart Rate", 0, Rec.Session.DATA_TYPE_UINT8, {:mesgType => Rec.Session.MESG_TYPE_RECORD, :units => "bpm"});
session.start();
}
}
}


Nevertheless, I get this failure during runtime for the createField-line:

Failed invoking <symbol>
UnexpectedTypeException: Expected Number/Float/Boolean/Long/Double, given Class definition


I have tried really a lot of things to get that running, but it will not work. Can someone point me in the right direction?
  • I'm guessing the problem is this:

    Sys.println(session);

    the variable session is a object and not a type it is expecting as the error indicates. Also, once enabled via the sensor system the HR is automatically recorded in the FIT file but it probably is a worthwhile exercise to get everything ironed out and working.
  • As lcj2 said, heartrate will be in the .fit file if you just enable SENSOR_HEARTRATE and if a HRM is available. That and other things are in the .fit without doing a createField. You don't even have to access the hr in your app for it to be in the .fit

    If you look at the RecordSample in the SDK, that does a very simple recording, but if you run it on a watch, the .fit will get uploaded to GC and you can see some of whats there already. createField is really just for your "extras"

    Oh.. Another thing.. You'll want to do a seesion.stop() and session.save() when you're done. Again, check out the RecordSample
  • Hi lcj2,

    no, the problem is not in the println line (this line just prints me the name of the pointer) but in that line:
    heartRateFitField = session.createField("Heart Rate", 0, Rec.Session.DATA_TYPE_UINT8, {:mesgType => Rec.Session.MESG_TYPE_RECORD, :units => "bpm"});

    Hi Jim and @lcj2: this is a very good hint that the heartrate is defaulty in the FIT file. The simulator can't simulate this and even if I try that on my physical watch I would have to load the fit file to monkeygraph since GC needs an official app to show me the results. The code I created is mainly transferred from the SDK examples RecordSample and Sensor. That said, I have even two questions:

    • During my fitContribution investigations some weeks ago I learned that I am able to control the GC's graph color via the resources XML file by stating fillColor="#FFFF00". Where can I setup the color in the case I use the session object instead of the fitContributor?
    • Why do I get that failure on the session.createField() line, I mean can you see any unexpexted type failures in this line (The session object is created, "Heart Rate" is a string only, Rec.Session.DATA_TYPE_UINT8 is just a number)? Did I miss something in the dictionary?
      Failed invoking <symbol>
      UnexpectedTypeException: Expected Number/Float/Boolean/Long/Double, given Class definition

      OK, Solved this partly. As soon as I write "2" instead of "Rec.Session.DATA_TYPE_UINT8" and "18" instead of "Rec.Session.MESG_TYPE_RECORD" the failure is gone. How can I specify the correct types, I mean I am seeking for a class which defines them...
  • DATA_TYPE_UINT8 and MESG_TYPE_RECORD are constants in the FitContributor module. Assuming that you've aliased Toybox.ActivityRecording to Rec, you're code is trying to look for those symbols in the wrong places. You need to write...

    using Toybox.FitContributor as Fit;

    // then in your code...
    heartRateFitField = session.createField("Heart Rate", 0, Fit.DATA_TYPE_UINT8, {:mesgType => Fit.MESG_TYPE_RECORD, :units => "bpm"});


    Travis
  • Travis,

    thanks for making this transparent. In the API's documentation this is not mentioned and I did wonder where they are defined. This is not the next thought (to include the FitContribution).

    Nevertheless, I am wondering how I can control colors in graphs. If I include the fitContributor I have to specify the fillColor in the resources file which decides which color the graph will get. This is not mentioned in the session's API doc
  • It isn't mentioned in the Session documentation because it is not an operation that is available on that type. As a matter of fact, it isn't in the class reference documentation at all, because this is not something that you can control via any of the class interfaces.

    The only way to control the color of the graphs is via the fitContributions resource. How to do this is covered in the programmer's guide here.

    Travis
  • Hi Travis, ok, from my understanding there are two ways to contribute to the fit file from an app:
    1. using the session's createField method
    2. using the fitContributor

    I am wondering why Garmin has decided to do so.

    Just another question: I have setup a session and enabled the Heartrate sensor. Is it possible to access the activity's info field? I tried it via the Activity.getActivityInfo() field which returns the well known info object. Nevertheless, accessing the heart rate seems to be fixed to a certain value. I want to grab heartrate, average heart rate and calories burned but I am unsure if the values are reliable. Is there a better way to access those fields?
  • Actually, 1 and 2 are two pieces of the same thing, and not really two different things the way I see it.

    As far as hr from Activity.info, where and how are you seeing it that it's not changing? Are you feeding in fit data in the sim or on a watch? If you are doing this with the sim, are you using simulated data or playing back a .fit? Simulated data has pretty much everything available, but when playing back a .fit, all that's available is what was recorded originally. If there was no HRM, you won't get hr, for example. Are you playing back a .fit where the hr is fairly constant?

    Make sure you don't just save off Activity.getActivityInfo(), but you get it each time you'll be pulling data from it. You don't get a pointer to live data, you get a static snapshot of live data.

    I seem to recall you've used a recording app of mine, and if you did, pretty much everything you see there is the data I just pull out of Activity.Info (exceptions are things like steps, daily cals and temperature, and the history graphs and HW Compass info), and what's recorded is just the standard data (no createField(), etc)
  • Hi Travis, ok, from my understanding there are two ways to contribute to the fit file from an app:
    1. using the session's createField method
    2. using the fitContributor

    There are two ways you can contribute to a fit file...

    • using Session.createField
    • using DataField.createField.


    The functionality in the FitContributor module is the bit that is common to all code creating custom fit contributions. So that explains why it exists.

    As for why there are two different classes with createSession methods, I'm not sure. It seems that they could have stuffed createField into the FitContributor module. That would seem the simplest to understand, and it keeps separation between the various parts of the API. That said, it is entirely possible that the separation is because different behavior is required based on context. Setup for adding custom data to a session that is owned by one of the built-in applications could be totally different from what is required when the session is created by an application. Without more information from Garmin there is really no way to tell for sure.

    Travis
  • Ok, let's hope that we can with session.createField() contribute also other types than float as it is with the fitcontributor which is somehow buggy (at least the entire displaying chain on GC web).

    Yesterday I created a first version of my first app and sideloaded it on my watch. I was anxioused to examine the results in monkeygraph but monkeygraph told me that it can't read the iq file I created in parallel. Then I double checked the iq file's content and realized that there is no .json file. Is this the problem that monkeygraph doesn't load the iq file?