What event is triggered when a lap is discarded?

I'm writing a simple data field to track the current lap max HR.

I know that onTimerLap() is called when the lap button is pressed so I can reset the tracking, but how do I know when a lap is discarded again? If the user discards the lap (e.g. if they pressed the lap button by mistake) I need to undo the tracking reset in my code.

Thanks!

  • Unfortunately I don't think there's a CIQ event that corresponds to discarding a lap. (The lap discard feature is fairly new and I haven't seen any new CIQ data field timer events in years).

    Might be a good feature request tho.

  • Ah that's a shame - makes sense for something quite new though I guess. Many thanks!

  • Display the event and see when it happens and compare it to what's recorded. Maybe it's delayed and the ciq event is only sent when the lap is "final" (the red button to remove lap is not there any more)

  • Thanks, that's a good idea, I'll play with that and let you know how it goes.

  • Maybe it's delayed and the ciq event is only sent when the lap is "final" (the red button to remove lap is not there any more)

    I'm 100% sure it doesn't work this way - source: tested with a datafield that records lap data based on lap events.

    If you think it about it, that implementation would break all CIQ data fields that deal with lap data (as CIQ data fields have to calculate all lap info, as opposed to reading it from the API.)

    Something as simple as displaying the amount of time in the current lap would break: e.g. if the user waited 8 seconds for the lap banner to disappear, the CIQ data field's conception of the current lap time would be 8 seconds behind the actual lap time displayed by the native field. The only conceivable way to maintain parity between the CIQ field's lap stats and the native lap stats would be to expect the user to dismiss the lap banner as quickly as possible on every lap.

    You can test with my data field appbuilder 5, using the following formulas:

    laptime

    (the amount of time in the current lap, based on the activity timer when onTimerLap() was triggered)

    lapstart(timer)

    (the activity timer at the start of the current lap, also based on onTimerLap())

    The reality is as straightforward as we think it is:

    - onTimerLap() is still triggered when the user takes a manual lap or an auto lap event happens

    - there is no additional event (that we know of) which is triggered when the user discards a lap

  • Considering all these, I wouldn't be surprised if there were some bugs related to the remove lap feature even in the built-in fields...

    As a developer I'm not sure I would want to implement a hook for the "onLapRemove". It's not simple at all. Basucally you would need to move all the relevant variables you currently have in the datafield view class and move them to some lap class, and then do something like this:

    var lastLapData as LapData?;
    var currentLapData as LapData;

    function compute(info) {
    if (lastLapData != null) {
    lastLapData.compute(info);
    }
    currentLapData.compute(info);

    ???? update fit fields' data ???
    }

    function onUpdate(dc) {
    // use currentLapData (time, distance, steps, etc...) to display
    }

    function onTimerLap() {
    lastLapData = currentLapData;
    currentLapData = new LapData();
    ??? call some Garmin function to tell the system our code is capable of onLapReverted() so that the Garmin code prepares for it
    }

    function onLapReverted() {
    currentLapData = lastLapData;
    lastLapData = null;
    ??? call some Garmin function to tell the system what to do with the already written lap data...
    }

    However all this will not help, unless Garmin also implements similar thing in Field and they do something similar to the fit data. I wouldn't be surprised if they can't really do anything without a serious rewrite of the way they write data. It's analog to do a BufferedOutputStream with a possibility to mark it and then go back and rewrite history.

  • Considering all these, I wouldn't be surprised if there were some bugs related to the remove lap feature even in the built-in fields...

    idk it seems to work ok afaik, although I haven't done any in-depth testing. I have to admit that when forum users suggested an "undo [end] workout step" feature, I was skeptical for similar reasons. But it turns out Edge has had that feature forever. It's kinda funny that Edge seems to have undo workout step and watches have undo lap, but users really want both features on the same device (watches).

    However all this will not help, unless Garmin also implements similar thing in Field and they do something similar to the fit data.

    Again I haven't done any in-depth testing, but it seems to work properly. (I haven't dug into the FIT file internals to see how "undo" lap is actually implemented though).

    From the POV of a CIQ field that writes lap data, it seems like there would be no code changes necessary regarding setData() itself, as long as it's already constantly calling setData() on lap fields with the currently known lap value. (As discussed at length, it's too late to call setData() when onTimerLap() fires).

    The key is the data field needs to correctly calculate the currently known lap value, which will require code changes.

    As a developer I'm not sure I would want to implement a hook for the "onLapRemove". It's not simple at all. Basucally you would need to move all the relevant variables you currently have in the datafield view class and move them to some lap class, a

    "??? call some Garmin function to tell the system our code is capable of onLapReverted() so that the Garmin code prepares for it"

    I don't see why this is necessary. What exactly does Garmin code have to prepare for?

    "??? call some Garmin function to tell the system what to do with the already written lap data..."

    Again I also don't see why this is necessary. setData() only sets the next value to be written, it's always been up to the firmware to decide when and how to write it. CIQ lap data that's written for a reverted lap should be handled by the firmware in the exact same way that any native data that may have already written to the file is handled.

    This is another example of why I think it's a big mistake to think of setData() as literally writing to the FIT file, because it doesn't.

    I see two possibilities for how lap undo works vis-a-vis writing FIT files (specifically native data):

    - native lap data is actually written immediately, at the time a manual or auto lap is triggered, same as before the lap undo function existed. If the lap is undone, then a marker is written to the FIT file to tell the FIT reader (e.g. Connect) to ignore the previous lap.

    - alternatively, native lap data writes are delayed for manual laps (with a buffer of up to 8 seconds, which is the window for the lap banner, and thus the lap undo, to timeout). If a lap is undone, he firmware simply doesn't write the lap data.

    Either way, CIQ calls to setData() for MESG_TYPE_LAP fields can be handled the same way, and in fact, the app does not need to know the implementation details.

    As for the final piece of the puzzle - keeping track of lap data itself - I don't think it's so hard for a CIQ data field to handle lap data properly in the case of lap undo (obviously assuming the CIQ API changes to support this event).

    It will take roughly twice as much memory for lap data and a bit more code, but the idea is this (similar to what you said, with some refinement):

    - Currently a data field which supports lap data has a set of variables which tracks lap data. Let's call this set S_current_lap

    - When onTimerLap() is fired, all the values in S_current_lap are reset

    - In order to support undoing laps, when onTimerLap() is fired, we also copy/assign S_current_lap (before reset) to another set S_previous_lap (similar to what you said)

    - For as long as it's possible to undo laps (perhaps there needs to be another event to signify that it's not possible to undo the previous lap anymore), the lap values in both S_current_lap and S_previous_lap need to be maintained

    (this is what i think you are missing above. since it's possible to undo the previous lap event, there are actually two possible sets of lap data that need to be maintained for the next 8 seconds)

    - If the user undoes the previous lap, then S_current_lap needs to be reassigned to take on the value of S_previous_lap (and the data field's lap counter should be decremented)

    - Once per second, setData() should be called for any FIT fields with MESG_TYPE_LAP with the CIQ data field's *current conception* of what the lap value is. Again, it doesn't matter if this is called "incorrectly" for a lap that's eventually undone, because the firmware should handle that case in the same way as it does for native fields: either it will use a buffer to avoid writing "wrong" lap data, or it will write lap data immediately and write an "ignore last lap" marker later on if necessary

    TL;DR

    Here's what I think would need to change in the CIQ API:

    - onTimerLapUndo() needs to be added

    - onTimerLapFinalized() should be added (for convenience), to signify that it's no longer possible to undo the previous lap event

    Here's what CIQ data field devs would need to do:

    - when onTimerLap() is triggered, maintain *two sets* of lap data: the new lap data as if the new lap will not be undone, and the old lap data as if the new lap will be undone

    - continue to call setData() on MESG_TYPE_LAP FIT fields continuously (once per second), with the data field's current conception of what the current lap values are. (i.e. when a new lap has not been undone, call setData() with the new lap data. if the new lap is undone, then call setData() with the old lap data). This part isn't that complex, since you would simply call setData() with the same value(s) that you would display on the data field itself (assuming the data field displays the same lap data that it is writing).

    - when onTimerLapUndo() is triggered, switch to the old lap data

    Obviously this is not super trivial for devs to implement (it's not impossible, but not trivial either), so I'm wondering if this stuff will ever be exposed to CIQ. After all, it's already been possible for Edge devices to skip backwards and forwards within workouts, but I'm not sure if CIQ data fields are able to maintain proper workout step data (analogous to lap data) when this happens.

  • I would also argue that it would be better design to have lap data (and any calculated activity info) in a separate class anyway, as opposing to being in a handful of member variables in the datafield's view class, although ofc I absolutely realize that the latter approach is more practical when there are memory constraints (for both code and data). (I have absolutely used the latter approach more than once.)

  • I see two possibilities for how lap undo works vis-a-vis writing FIT files (specifically native data):

    Ofc we could probably find out for sure if we played with the lap undo function and looked at the fit file in a tool like fitfileviewer.com after the fact, assuming it doesn't obscure whatever's happening here