Fit contributor lap data not showing up on garmin connect web or mobile

I have a simple data field that is trying to save data to fit file.  I have it saving record, session and lap data.  I can see the record and session data on garmin connect web and mobile but the lap data is not showing up.  I can see the lap data being saved in the fit fille using java -jar fitcsvtool.jar activity.fit --defn none --data lap.

I have a fit.xml file in the resources folder as below:

<fitContributions>
    <!-- RECORD : Record Chart Definitions -->
    <fitField id="0" displayInChart="true" sortOrder="0" precision="2" chartTitle="@Strings.FITNESSTITLE" dataLabel="@Strings.FITNESSLABEL" unitLabel="@Strings.KPH" fillColor="#ff7b00" />

    <!-- SESSION : Session Data Definitions -->
    <fitField id="1" displayInActivitySummary="true" sortOrder="1" precision="2" dataLabel="@Strings.FITNESSLABEL2" unitLabel="@Strings.KPH2" />

    <!-- LAP : Lap Data Definitions -->
    <fitField id="2" displayInActivityLaps="true" sortOrder="2" precision="2" dataLabel="@Strings.FITNESSLABEL3"  unitLabel="@Strings.KPH3" />
</fitContributions>

and strings.xml looks like this:

<strings>
    <string id="AppName">RTFC</string>
    <string id="FITNESSTITLE">Fitness</string>
    <string id="FITNESSLABEL">Fitness</string>
    <string id="FITNESSLABEL2">Avg Fitness</string>
    <string id="FITNESSLABEL3">Avg Fitness</string>
    <string id="KPH">kph</string>
    <string id="KPH2">kph</string>
    <string id="KPH3">kph</string>
</strings>

I have this in function initialize() to create the fields:

        FitFit = createField(
            "FITNESS",
            0,
            FitContributor.DATA_TYPE_FLOAT,
            {:mesgType=>FitContributor.MESG_TYPE_RECORD, :units=>"kph"}
        );
        FitFitSum = createField(
            "FITNESS",
            1,
            FitContributor.DATA_TYPE_FLOAT,
            {:mesgType=>FitContributor.MESG_TYPE_SESSION, :units=>"kph"}
        );
        FitFitLap = createField(
            "FITNESS",
            2,
            FitContributor.DATA_TYPE_FLOAT,
            {:mesgType=>FitContributor.MESG_TYPE_LAP, :units=>"kph"}
        );

        FitFit.setData(0.0);
        FitFitSum.setData(0.0);
        FitFitLap.setData(0.0);

and this in function compute()

     if (info != null && thingtoreturn != null) {
        // Record
            FitFit.setData(thingtoreturn);
        // Session
            Scounter++;
            bigMean += thingtoreturn;
            FitFitSum.setData(bigMean / Scounter);
        // Lap
            Lcounter++;
            lapMean += thingtoreturn;
            FitFitLap.setData(lapMean / Lcounter);
        }

and this bit to reset the lap value:

    public function onTimerLap() as Void {
    //! Handle lap event
        Lcounter = 0;
        lapMean = 0.0;
    }

I can see the fit data in activity_data.csv made from running java -jar fitcsvtool.jar activity.fit --defn none --data lap against a real or simulated fit file:

and activity.csv made in same way:

But I have no lap data in garmin connect mobile (even if I scroll right) or web (I do get session and record data).

There seem to be threads from a few years back which say that garmin connect web and/or mobile is not to be trusted for fit data viewing but I figured this would be fixed by now.  I also spent some time in monkeygraph which again works well for session and record data but seems flaky with lap data. I was wondering if I had too many native lab fields and this was causing the issue... 48 seems like a lot.

Please help, thanks!

  • record looks fine (should be no data for first 4 mins as output settles down).

    There should be no data or a value of 0.0? In the code you posted, setData() is called immediately in initialize() for the record field, with a value of 0.0.

  • No, I have the same name between the session and the lap fields and it works

  • Sorry, it should be a value of 0.0 and it is.

  • Like this right?

    - I had to redo the sim sorry so the data is different, I have changed code a little to send data sooner to make testing easier and now it setData s every compute (but sends 0.0 if data is null, rather than waiting for non-null) to try to rule that out (even though all examples I have found do a null check and send nothing if null).

    //replace null value with 0
    var forfit = thingtoreturn;
    if (forfit == null) {
        forfit = 0.0;
    }
    // Record
    FitFit.setData(forfit);
    // Lap
    Lcounter++;
    lapMean += forfit;
    FitFitLap.setData(lapMean / Lcounter);
    // Session
    Scounter++;
    bigMean += forfit;
    FitFitSum.setData(bigMean / Scounter);
    

    Anyway, here is monkeygraph from a sim:

    I changed lap to be field ID 1 to match all the examples I had found so its now called lap 1 in monkeygraph rather than lap 2 (I updated my fit.xml to match this).

    And here is the fit2csv tables right hand end:

    I make that a 2dp match for first 3 laps in terms of values.

    I still don't understand where the values for the first column in monkeygraph lap 1 tab is coming from.  Looks a bit like my strings:

    <strings>
        <string id="AppName">RTFC</string>
        <string id="RECTITLE">Fitness</string>
        <string id="RECLABEL">Fitness</string>
        <string id="LAPLABEL">Avg Fitness</string>
        <string id="SESLABEL">Avg Fitness</string>
        <string id="KPH">kph</string>
    </strings>

  • Ok thank you, in this vein I have made an even simpler new app based on an empty SimpleDataField template into which I have pasted the Bananas Earned example from https://developer.garmin.com/connect-iq/api-docs/Toybox/FitContributor.html.

    and added in a resources.xml into APPNAME/resources 

    <fitContributions>   
        <fitField id="0" displayInChart="true"  sortOrder = "0" precision="2"
        chartTitle="@Strings.namaste_graph_label_rec" dataLabel="@Strings.namaste_label_rec"
        unitLabel="@Strings.namaste_units_rec" fillColor="#FF0000" />
    
        <fitField id="1" displayInChart="false" displayInActivityLaps="true"  
        sortOrder = "1" precision="2"
        dataLabel="@Strings.namaste_label_lap"
        unitLabel="@Strings.namaste_units_lap" />
    
        <fitField id="2" displayInActivitySummary="true" sortOrder = "2" precision="2"
        dataLabel="@Strings.namaste_label_ses1"
        unitLabel="@Strings.namaste_units_ses1" />
    </fitContributions>
    

    and a strings.xml into APPNAME/resources/strings

    <strings>
        <string id="AppName">testlap</string>
        <string id="namaste_label_rec">NamastesDataLableRec</string>
        <string id="namaste_graph_label_rec">NamastesChartTitleRec</string>
        <string id="namaste_units_rec">UnitsRec</string>
        <string id="namaste_label_lap">NamastesDataLableLap</string>
        <string id="namaste_units_lap">UnitsLap</string>
        <string id="namaste_label_ses1">NamastesDataLableSes1</string>
        <string id="namaste_units_ses1">UnitsSes1</string>
    </strings>

    both of which were minimally changed from https://developer.garmin.com/connect-iq/core-topics/activity-recording/


    I then duplicated the fields to have a record, a lap and a session field.

    I made it add 1 to totalbananas (starting from 0) each compute and then calculated the lap mean and session mean the same way as the MoxyField sample in the sdk.

    Here is the code, its tiny:

    using Toybox.FitContributor;
    using Toybox.WatchUi;
    class testlapView extends WatchUi.SimpleDataField
    {
        var bananasEarnedField = null;
        var bananasEarnedFieldLAP = null;
        var bananasEarnedFieldSES1 = null;
        var totalBananas = 0;
        var LapSum = 0.0, Ses1Sum = 0.0, LapTickCount = 0, Ses1TickCount = 0;
        var LapAv = 0.0, Ses1Av = 0.0;
    
        const CALORIES_PER_BANANA = 105.0;
        const BANANAS_FIELD_ID = 0;
        const BANANAS_FIELD_LAP_ID = 1;
        const BANANAS_FIELD_SES1_ID = 2;
    
        function initialize() {
            SimpleDataField.initialize();
    
            // Create the custom FIT data field we want to record.
            bananasEarnedField = createField(
                "bananas_earned_CFstring",
                BANANAS_FIELD_ID,
                FitContributor.DATA_TYPE_FLOAT,
                {:mesgType=>FitContributor.MESG_TYPE_RECORD, :units=>"UnitsCF_REC"}
            );
    
            bananasEarnedFieldLAP = createField(
                "bananas_earned_LAP_CFstring",
                BANANAS_FIELD_LAP_ID,
                FitContributor.DATA_TYPE_FLOAT,
                {:mesgType=>FitContributor.MESG_TYPE_LAP, :units=>"UnitsCF_LAP"}
            );
    
            bananasEarnedFieldSES1 = createField(
                "bananas_earned_SES1_CFstring",
                BANANAS_FIELD_SES1_ID,
                FitContributor.DATA_TYPE_FLOAT,
                {:mesgType=>FitContributor.MESG_TYPE_SESSION, :units=>"UnitsCF_SES1"}
            );
    
            bananasEarnedField.setData(0.0);
            bananasEarnedFieldLAP.setData(0.0);
            bananasEarnedFieldSES1.setData(0.0);
        }
    
        function compute(info) {
            if (info.timerState == 3) {totalBananas++;}
            if (info != null && info.calories != null) {
                // Calculate and set data to be written to the Field
                //totalBananas = (info.calories / CALORIES_PER_BANANA).toFloat();
                LapTickCount++;  
                Ses1TickCount++;
                LapSum += totalBananas;
                Ses1Sum += totalBananas;
                LapAv = LapSum / LapTickCount;
                Ses1Av = Ses1Sum / Ses1TickCount;
    
                bananasEarnedField.setData(totalBananas);
                bananasEarnedFieldLAP.setData(LapAv);
                bananasEarnedFieldSES1.setData(Ses1Av);
                  
            }
            // Display the data on the screen of the device
            return totalBananas;
        }
        function onTimerLap()
        {
            LapTickCount = 0;
            LapSum = 0.0;
        }
    }

    I ran this same on simulated 955 and a real 955 (s/w 18.20) loading beta in from lap store.  Lapped every 10 seconds and stopped at 60 seconds.

    I learnt a couple of things:

    1-This has the same problem as my other code, I get Record and Session data through to garmin connect web and mobile but not lap data.

    2-For whatever reason, my monkeygraph lap tab is grabbing random strings and seems to only return as many rows without #VALUE? errors for lap data as you have fit fields.  I named my strings so I could see where they came from; row 1 is column 1 is the Record datalabel, row 2 col1 is the lap datalabel and row 3 col1 is the session datalabel, same pattern for the unit names in column 2. I don't understand why, is this normal?

    Sim monkeygraph example:

    3-Using fitcsvtool, the simulated lap data looks good:

    However using my watch the lap data is actually session data, I can see this from the column name but also from the data (going from 0-60 with lap every 10, lap averages should be ~5,~15,~25 etc. as they are in the sim example):

    tl;dr I used as much example code as possible and still have the same issue. 

    I'm still struggling with monkeygraph.

    Additionally my lap data from running on real watch in fit files via fitcsvtool shows its actually session data but sampled at lap press time (some kind of 0-n lumped average where n is current time) while same code ran on simulated same watch shows correct numbers and column name.

    Thanks for reading this far!

  • I think I have exactly the same issue and it appeared out of nowhere (did not touch the app, stopped working). See below:

    Fitfields stopped working two weeks ago

    I am the author of the "Shotgun Sports" application. The app has a couple of custom fields for the hits, misses, shots and accuracy. I just received a bug report from one of my users (Forerunner 965, Software 17.26) that the custom fields do not show up in the Lap data. I verified this with my own device (Fenix 7x with 16.20).

    What I can see is:

    • The Stats part of the mobile application and connect-web service show the numbers correctly for all the fields (total hits, total lost, total shots, accuracy). 
    • When I go to Laps in the mobile application, I see the colums Hits, Lost, Shots correctly, but there is no data in them
    • In the Connect web application I do not see any of my custom fields in the Laps-tab

    The user reported that the activity they recorded about two weeks ago worked correctly and I can see that my past Shotgun Sports activities have all the data in place. 

    This leads me to guess that something happened within the last weeks that broke the functionality. The app has not been updated, so maybe there is something that has changed in the firmware or API's?

    PS. I tried to submit a bug report but got a message "A category is required for this ideation." which brought me here and I found your post :)

  • I can confirm the exact same problem in my Datafield. Maybe it's worth you post the above as a bug report here: forums.garmin.com/.../bug-reports

  • Don't have any experience with the lap-type fields, but shouldn't the setData() function for them be called inside the onTimerLap() function, not inside the compute()? At least, it looks too redundant to set data every second when it's enough to do so just one time per lap. For the session-type fields I'm setting values inside the onTimerStop(), so, I suppose, it should work in similar way for the lap-type fields

  • Don't have any experience with the lap-type fields, but shouldn't the setData() function for them be called inside the onTimerLap() function, not inside the compute()? At least, it looks too redundant to set data every second when it's enough to do so just one time per lap. For the session-type fields I'm setting values inside the onTimerStop(), so, I suppose, it should work in similar way for the lap-type fields

    This may not be intuitive but you can't call setData() for lap data in onTimerLap(), because it's too late at that point. Data for the previous lap has already been written when onTimerLap() is called.

    setData() sets the value for the *next write*, it doesn't write data itself (*), which is why it's called setData() and not writeData(). So it's valid and in fact pretty much necessary to call setData() once per second for lap data. As a matter of fact, this pattern can be seen in the MoxyField SDK sample.

    (*) However, data won't be written until you call setData() for the first time.

    For the session-type fields I'm setting values inside the onTimerStop(), so, I suppose, it should work in similar way for the lap-type fields

    That works for session fields because the session hasn't ended at that point, and because it's pretty much guaranteed that onTimerStop() will be called before the session ends (**). (** Maybe unless auto-pause is triggered and the user ends the session at that point? I've never used auto-pause so I have no idea. To be safe you could call setData() on both onTimerStop() and onTimerPause())

    The MoxyField sample even calls setData() for session data once per second, although it probably isn't necessary.