On Save and FIT Write Bug?

I write a FIT Summary string at the end of an activity in my Data Field.

I did have it in the onStop() interrupt, and it worked well. But an activity can have multiple stop/start events. So this was writting the summary string multiple times in the FIT file. I think only the last one was rendered in the Garmin Connect activity summary.

But I realized that onReset() was the better place to put this code. In the simulator, this runs when you SAVE or DISCARD an activity. Perfect. It writes once when you are really done with the activity.

But in a REAL device it doesn't actually get written or show up in Garmin Connect.

My theory is that a device buffers the write to be flushed sometime later. But by then the FIT file is closed.

Anyway, this is at least a simulator bug since it doesn't emulate a real device behavior. And/or a H/W bug (it should complete the write following the onReset() interrupt. Or, I'm not thinking about this correctly.

Easy fix - put that FIT write back into the onStop() function.

If this is a bug, I'll post in the bug forum - just not sure it is yet. Thoughts?

  • For anyone else reading this thread, I think you're referring to DataField.onTimerStop() and onTimerReset().

    I write a FIT Summary string at the end of an activity in my Data Field.

    To be absolutely clear, calling FitContributor.Field.setData() sets the next value to be written, it doesn't actually write anything. The key distinction is that it's the firmware which determines when/whether data will be written

    developer.garmin.com/.../Field.html

    Once a Field is created with the createField() method, you can submit the next Field value with setData(), which will get written to the FIT file at the next opportunity.

    Anyway, this is at least a simulator bug since it doesn't emulate a real device behavior

    I agree it would be nice for the simulator's behavior to match the real device behavior.

    And/or a H/W bug (it should complete the write following the onReset() interrupt.

    Not sure why you would assume this is necessarily a bug. The documentation doesn't say anything about whether the FIT file is writable at this point or not (it would be nice if it did, but it doesn't)
    https://developer.garmin.com/connect-iq/api-docs/Toybox/WatchUi/DataField.html#onTimerReset-instance_function

    onTimerReset() as Void

    The current activity has ended.

    This method is called when the time has stopped and current activity is ended.

    To me all of this is similar to the (counter-intuitive) behavior where it's too late to call setData for lap data in onTimerLap(). You have to constantly call setData for lap data (which is fine because only the last call for each lap will result in written data, if I'm not mistaken.) This has come up a few times in forum discussions.

    As a matter of fact, if you look at the MoxyField sample, setData() is called for lap and session data *every second*, as long as the timer is running. I do the same thing in one of my data field apps which write to FIT. (In another one, I write session data in onTimerStop(), same as you).

    So this was writting the summary string multiple times in the FIT file.

    Do you actually see this when you look in the FIT file?

    I think only the last one was rendered in the Garmin Connect activity summary.

    Seems like it doesn't matter either way, then.

    What's the motivation here? To avoid writing too unneeded data to the FIT file (if that's actually what's happening)? I think this stuff could def be better documented but at least there's working samples.

  • Thanks for the detailed response. The SUMMARY string is intended to be an overall summary of the activity, so the intuitive behavior is to write this when the activity is declared OVER, which is when the user hits SAVE. Writing it any other time is not summarizing the activity but rather a point in time prior to the end. STOP is not an indication of the end of the activity. SAVE is, which causes the onTimerReset() to be called.

    Surely every time you call the setData for the Summary string, it writes to the FIT file. Why would it not? It doesn't know if that is the last time you intend to write the string you want to be shown in Garmin Connect as the summary data string. So, yes, my desire is just to reduce the writes to the FIT file to the one time I actually want to save an activity summary string when I know the user is ending the activity.

    But in this case, I think the STOP button is only used infrequently other than just before a SAVE. So again, I'm ok with all the superfluous FIT writes that don't count.

  • Surely every time you call the setData for the Summary string, it writes to the FIT file. Why would it not?

    As I explained above, that's not necessarily the case.

    If you call setData() for summary or lap fields every second (as in the MoxyField demo and as in some of my own data field apps), it's surely not writing those fields every second. setData() sets the data to be written, it doesn't write data. It says so right in the documentation. Once again:

    developer.garmin.com/.../Field.html

    Once a Field is created with the createField() method, you can submit the next Field value with setData(), which will get written to the FIT file at the next opportunity.

    If setData() is called before the previous data is written out, the previous value will be lost and replaced by the current data.

    For example, if the firmware only decides to write the summary fields when the activity is actually saved, then I could call setData() on a summary field 50,000 times, and only the last value would be written.

    Do you see the analogy here between the lap and summary behavior?

    - Calling setData() for lap fields at onTimerLap() is too late

    - Calling setData() for summary fields at onTimerReset() is too late

    In both cases, you have to call setData() *before* the associated event, even if it's counter-intuitive

    The SUMMARY string is intended to be an overall summary of the activity, so the intuitive behavior is to write this when the activity is declared OVER, which is when the user hits SAVE.

    Yes but my point is that I'm not sure what makes you think onTimerReset() is necessarily called *before* the FIT file is finalized, rather than *after*. It's called onTimerReset(), not beforeFitFinalize() or something like that.

    But in this case, I think the STOP button is only used infrequently other than just before a SAVE. So again, I'm ok with all the superfluous FIT writes that don't count.

    Why not just open the FIT file and see if you actually have superfluous writes? I'm curious, so I'm going to do a test.

  • I did a test with my data field which calls setData() on a session field in onTimerStop(). I started and stopped the activity in the simulator multiple times, then I finally stopped and saved the activity. Then I used Runalyze's FIT viewer tool to look at the saved FIT:

    [https://runalyze.com/tool/fit-viewer]

    Exactly as I would've guessed, there's only one session record, regardless of how many times onTimerStop()/setData() was called

    (I even put a System.println() next to the setData() call, so I could be *absolutely sure* that setData was being called multiple times)

    So just as with laps, you have to save your session data *before* the session actually ends. With laps, that means calling setData() on lap fields every second (at least while the timer is running). With sessions, you could either call setData() on session fields every second (while the timer is running), or call it in onTimerStop(). Either way works for me.

    Maybe it's not intuitive, but that's the way it works.

  • Thanks!! So say your activity is 1 hour long and you hit STOP at minutes 15, 30, 45, and finally at 60 mins just before you then hit SAVE. I know you aren't privy to the actual firmware... but your findings suggest they store the setData for SESSION writes internally. Each setData string OVERWRITES the prior one. And only on a SAVE action does it finally FLUSH the last setData string to the FIT file. That would make sense I guess to prevent useless FIT writes. I would have allowed onTimerReset() to successfully write that final setData to FIT, but now we know. THANKS AGAIN!

  • Like I said, it's analogous to lap data. If you try to call setData in onTimerLap(), it's too late, so there's literally no way to set lap data except to call setData() every second, as far as I know.

    There's nothing stopping you from testing this stuff in the simulator and on your device, and looking at the resulting FIT data yourself. If I were you I wouldn't just take my word for it, I could be mistaken. That's why I went to trouble of testing this.

    Each setData string OVERWRITES the prior one. And only on a SAVE action does it finally FLUSH the last setData string to the FIT file.

    It's right in the documentation that setData doesn't write anything, but only sets the next value to be written. The documentation also says that calling setData multiple times before the value is written will cause previous (unwritten) values to be lost.

    It should be very clear from the documentation that setData() absolutely does not write data every time you call it.

    developer.garmin.com/.../Field.html

    Once a Field is created with the createField() method, you can submit the next Field value with setData(), which will get written to the FIT file at the next opportunity.

    If setData() is called before the previous data is written out, the previous value will be lost and replaced by the current data.

    The behavior pertaining to exactly when stuff gets written isn't clear from the documentation, but like I said, the MoxyField sample is pretty clear and it works:

    1) It calls setData() for activity fields every second

    2) It calls setData() for lap and session fields every second, while the timer is running

    Looking at 2), you might infer that there's no other time when it's safe/appropriate to set data for lap and session fields. But even if you don't infer that, it's easy enough to try setting data in onTimerLap() and onTimerReset(), and discover that it doesn't work, as you did.

    I would have allowed onTimerReset() to successfully write that final setData to FIT,

    You can file a bug report / feature request. I doubt they'll change it, and even if they do, it's probably 100% likely that older devices won't be changed in any case.

  • setData simply caches what you pass until the next time the FW writes that type of data  It itself NEVER causes a write.  Imaging if a device app wanted to set and write to the fit every 75ms.

    setData just "sets the data" for the next FW write.

  • exactly. I would just expect the FW to flush write buffering before closing the FIT file upon a SAVE action (which does trigger the onTimerReset() interrupt). But they don't and that's ok. We have ways around that.

  • forget about the onTimer* stuff in relation to this.  By the time those are called, the data should already be written of it's the proper type.

  • I would just expect the FW to flush write buffering before closing the FIT file upon a SAVE action (which does trigger the onTimerReset() interrupt).

    It's ok for you to expect that, but it's also ok for us to point out that Garmin doesn't give you any reason to expect that at all. Yes, the way it actually works is unintuitive, but if anything, I expect Garmin to be unintuitive.

    We have ways around that.

    It's not really a workaround when everything seems to be working as designed. If you don't agree with that design, that's a different issue.