FitContributor::Field::SetData should accept null as a value

And gaps in both native and CIQ activity FIT records are not displayed correctly by Garmin Connect.

Hopefully I'm not missing something, but I think there's a major gap in the functionality for FitContributor::Field::SetData. Native fields can have "gaps" (null/no data) in their data (in the middle of an activity) but 3rd-party fields can't -- once they write their first value, they have to keep writing for the rest of the activity. So if there's an actual gap in the real data, a 3rd-party app has no choice but to write zeros.

Consider the case of going for a run recording HR (from wrist) to the FIT file. Let's say the runner takes off their watch in the middle run and the wrist HR turns off. The native graph in Garmin Connect will show a nice gap where that happened. It won't show zeros, it will show nulls (no values) -- if you roll over the graph it will skip that portion completely and if you look at the FIT file contents, there are no HR records for that time period.

Now consider the case of a Connect IQ field recording the exact same data.

If there's no HR data available at beginning of the run and the field hasn't called setData yet, no problem. The records for the 3rd-party field won't be present for that time period. But the first time the app calls setData, it's obligated to keep doing so every second, even if it has no data. If you don't call setData, the device just records the last value you set. And if you call setData with null, it crashes.

So in the example of runner who takes off his wrist HR in the middle of a run:
- The native field is able to write no data
- The 3rd party field has two choices:
-- Don't call setData, in which case the graph will have a nice horizontal line where the same old data was written over and over again
-- Call setData with 0, which doesn't match the actual situation. HR data was unavailable -- the runner's heart rate wasn't zero

Oh, and I tested the scenario where a CIQ app doesn't write data at the beginning of the activity (e.g. recording HR while not wearing watch)-- this is displayed properly in the Garmin Connect mobile app but not the website. The website acts as though the first available value existed all the way to the beginning (even if you waited for a few minutes to start recording.) The native field has the opposite problem: Garmin Connect mobile shows a bad graph for the native HR, but the website shows the correct graph.

It would be nice to have some kind of parity with native fields, especially if the data is to be analyzed automatically later.
  • Hi! You seem to be competent to answer my questions :)
    My Fenix 3 HR is recording power meter connection dropouts as "0", not as "Null". Is this normal for this device? If not, what can cause this behavior and how can I fix this? Apart from the power meter, only a Garmin chest HR monitor is connected at the same time. (Sorry for hijacking your post.)

  • Was this fixed? (Is Garmin even aware of the problems?)

  • It's a bug in the app, not a garmin problem in my opinion.  When you do a createField, you specify the DATA_TYPE_* and in setData, the data needs to be that type of data.  

    if you use DATA_TYPE_UINT8, what should it do with a null?  You want to check for null, and use 0 in that case, or just skip the setData and it will use the last value when it's displayed.

  • There's no solution to the problem he raised. If that is a bug in the app, then how would you fix it?

    Either the setData should accept null, or createField should have also nullable types, or createField should have one more boolean parameter to tell that it's nullable, or createField should have one more parameter to be able to set which value we'll use to indicate no-data or invalid data. Then we could pass 0 there and use 0 in setData. All of these solutions need change in the SDK.

    However there's one more solution maybe: when we're talking about fields with known value types, for example heart rate, where we already have the INVALID_HR constant, all the systems using the fit data (at least in Garmin) should treat 0 as INVALID_HR (just as they treated it when my app was 1st uploded and got rejected because thisisant checked it and the app didn't display "--" when the HR was 0). GCM and GCW both should treat 0 the same way in fields that is known to be HR (and they know in many cases because we give the info in :nativeNum)

  • There's no solution to the problem he raised. If that is a bug in the app, then how would you fix it?

    It's easy.  A null check.

    either

    if(value==null) {value=0;}

    myField.setData(value);

    or

    if(value!=null) {myField.setData(value);}

    it depends on if you want the last valid value to keep displaying, or if you want to see a dip to zero.

    Space in the fit file is limited, as is the number of fields allowed for your DF along with fields by other data fields used at the same time  (yes, another DF could use them all, and your app couldn't allow any.

    DATA_TYPE_* allowed is probably defined in the fit specification, so it's not just "add DATA_TYPE_NULL"

    Trying to write a null indicates poor code to me.  Depending on what you are using for getHeartrateHistory, INVALID_HR will either be 255 or null, and you could use

    if(value==null || value==255) {...}

    to handle both.

    You know that nativeNum is actually meaningless when it comes to base data with Garmin Connect, right?  You can't override what the FW is doing for HR.

  • You should read more carefully the original post. This is what he already does (as does my DF), the problem is that it's displayed incorrectly.

  • maybe you should open a bugreport about this.

  • This thread is from 4 years back.  you only need to call setData() when something changes, and it needs to be fone before the fit dat is actually written.

    Again the simple solution is doing a null check, as you should also do with most things in Activity.Info.  From the API doc:

    The Activity.Info class contains information about the current activity.

    This information can be retrieved with the getActivityInfo() method. Fields in this class may return null so should be checked for null values prior to use.

    What do you expect to see if the value is null?  How do you put a null in an 8 bit signed value? That's what's set aside when the fitContrib is written!

  • It's a bug in the app, not a garmin problem in my opinion

    A bug in which app? The app that "point__man" uses? It's not clear to me whether he's referring to a CIQ app or native functionality. Seems more likely that it's native functionality, but since the comment is over two years old we'll never know.

    A bug in one of my apps? Obviously I wouldn't have to posted this if I didn't realize that you can't call setField(null). It's right in the OP.

    When you do a createField, you specify the DATA_TYPE_* and in setData, the data needs to be that type of data.  

    Yes, that is the *design* of createField and setData. Since it apparently wasn't obvious, I should've stated that I regard this as a design issue and not a "bug".

    if you use DATA_TYPE_UINT8, what should it do with a null?

    I think I explained what I would like it to do -- it should cause the data field to write "no data" at the next available opportunity, for parity with the native functionality / device firmware, which can do that for native fields when there's a sensor drop-out or disconnection.

    it depends on if you want the last valid value to keep displaying, or if you want to see a dip to zero.

    As stated, I would like to do neither, because:

    1) neither of those things is correct (think of what would happen if 3rd-party software tries to analyze that data if there's a long period of disconnection -- any calculated average will be wrong, for example).

    2) native metrics can have gaps in the FIT graph (although at the time of the post, they weren't handled properly in Garmin's platforms either, in some cases).

    You know that nativeNum is actually meaningless when it comes to base data with Garmin Connect, right?  You can't override what the FW is doing for HR.

    His point was that if Garmin knows you're trying to write HR, it also knows that 0 isn't a real HR value (I guess), so it can treat 0 as no data. He wasn't suggesting that Garmin will suddenly start respecting native_num, since we all know that will never happen.

    Anyway that's not an ideal solution, since this issue can apply to anything a CIQ app wants to write, not just stuff that has a native_num mapping.

    It's easy.  A null check.

    The problem isn't that we don't know how to write a null check, the problem is that we would like to write "no data" to the graph at some points, just like the native firmware can do.

    So if there's an actual gap in the real data, a 3rd-party app has no choice but to write zeros.

    (Or to write the same value that was previously written -- neither of these solutions is ideal.)

    Anyway, personally I don't even care about this design issue anymore, since I've given up all hope for this kind of issue to ever be resolved. Same goes with other design issues like settings strings consuming memory on the device.

    Honestly, I think Garmin should auto-lock super old threads like this. Not much good ever comes of resurrecting them, IMO. If there's further interest in a topic, a new topic could be created (maybe with a link to the old topic.)

  • maybe you should open a bugreport about this.

    I appreciate the interest and support, but TBH I think it's a waste of time. Thanks, tho.