Sys.getTimer() vs info.elapsedTime in compute data field

I adapted some helpful code from Travis Vitek here, https://forums.garmin.com/showthread.php?364579-Lap-time-and-Last-lap-time&p=919973#post919973, to output system time and time from compute, side-by-side.

In the simulator, the times showed the same, which I didn't find surprising, but my experience programming data fields in the past was that the data in the info passed to compute doesn't match the built-in data fields -- mostly it seems like a custom data field gets the data a bit later. So I'd see, for example, average pace derived from activity info lag the built-in data field pace.

When I tried showing system time vs activity info time from compute on my vivoactive, however, what I saw was that the compute time was about a quarter of a second greater than the system elapsed time. The code I have is like the below. I display the two times side-by-side in the onUpdate(dc) method:

hidden var _M_previous;
hidden var _M_elapsed;
hidden var _M_paused;
hidden var _M_stopped;
hidden var computeElapsed;

function updateElapsed() {
var current = Sys.getTimer();
if (!_M_paused && !_M_stopped && _M_previous != null) {
_M_elapsed += current - _M_previous;
}
_M_previous = current;
}

function compute(info) {
updateElapsed();
computeElapsed = 0;
if (info.elapsedTime) {
computeElapsed = info.elapsedTime;
}
}


I'm really quite puzzled about the relationship between system elapsed time, and elapsed time in activity info. Although having onTimerLap is a massive plus compared to what was available at the end of 2015 when I had to guess when new laps began, it still seems non-trivial to accurately calculate your own lap pace for example, i.e. to match the lap pace the built-in field shows.

Can anyone explain the relationship between system elapsed time you get in onTimerLap, for example, and elapsedTime in compute? I'm also interested whether or not there is a lag between built-in data fields and custom data fields.

(Another thing I noticed was that the activity info elapsed time doesn't take account of any pauses. Although this is something I can live.)
  • Ok, for the last item, elapsedTime is the time since the recording starts (I went for a 20 minute run), but there is also timerTime, which is the time you're not paused (out of that 20 minutes, I stopped for a minute and would show 19 minutes). I'm not sure why you need getTimer(), as with timerTime, you have the info specific to the activity being recorded. With these two time values, you might have the info you need.


    As you're dealing with milliseconds and the native field and your compute() code are running at a slightly different times, you could see a slight difference, and maybe there's something with if the ms are rounded up or not. Also, on the real watch, do you have auto-pause turned on?
  • Thanks

    Ok, for the last item, elapsedTime is the time since the recording starts (I went for a 20 minute run), but there is also timerTime, which is the time you're not paused (out of that 20 minutes, I stopped for a minute and would show 19 minutes).


    Yes I was using elapsedTime. As I never pause in an activity, it doesn't make any difference, but timerTime as certainly better if I ever stick anything onto the app store, which I would like to do one day.

    I'm not sure why you need getTimer(), as with timerTime, you have the info specific to the activity being recorded. With these two time values, you might have the info you need.


    You can't get anything about lap from activity info. It's a shame this wasn't added with the SDK updates, but having onTimerLap is a massive improvement nevertheless.

    As you're dealing with milliseconds and the native field and your compute() code are running at a slightly different times, you could see a slight difference, and maybe there's something with if the ms are rounded up or not.


    This is a quarter of a second, roughly 250ms, difference. I remember seeing on the forums some time ago posts saying that the compute call has got slightly out of data information compared to the native fields, and this has been my observation also. What I didn't expect was to see the activity info time ahead of the system time.

    Also, on the real watch, do you have auto-pause turned on?


    no
  • Former Member
    Former Member over 8 years ago
    compute() and onUpdate() both run at 1Hz, but are not synchronous with each other or with the moment you push the start button. Because these events run independently at 1Hz you will always see up to 1 second difference when comparing to other states in the system.

    I can't tell for sure from your snippet, but it appears you may not be capturing the time between when the timer is started and when the first compute event runs after that with your getTimer() tracking. If you are not already, you will see some advancement in the system timer if you initialize it in the onTimerStart() event.
  • Thanks, understood your first para

    Re your second para

    I can't tell for sure from your snippet, but it appears you may not be capturing the time between when the timer is started and when the first compute event runs after that with your getTimer() tracking. If you are not already, you will see some advancement in the system timer if you initialize it in the onTimerStart() event.


    When you say initialize the system timer, what do you mean? I'm not sure I can call initialize on the system timer can I? Do you mean initialize my own timer variable _M_previous? I copied this from Travis' code, so it's just
    function onTimerStart() {
    _M_stopped = false;
    _M_previous = Sys.getTimer();
    }


    I might be getting the wrong idea, but is it possible that onTimerStart and compute are called in a non-deterministic order, so that onTimerStart could actually occur after compute? I'm still struggling with the problem of how to get the lap time in the same "time coordinate system" as that in the info parameter of compute, so that I can calculate reasonably accurate lap times (and thus interpolate lap distance, etc).

    If onTimerLap is called asynchronously, for example, how can I even get the lap time accurately?
  • entry transports

    I might be getting the wrong idea, but is it possible that onTimerStart and compute are called in a non-deterministic order, so that onTimerStart could actually occur after compute? I'm still struggling with the problem of how to get the lap time in the same "time coordinate system" as that in the info parameter of compute, so that I can calculate reasonably accurate lap times (and thus interpolate lap distance, etc).

    If onTimerLap is called asynchronously, for example, how can I even get the lap time accurately?


    I wouldn't calculate the timer yourself at all and would rely on the info.timerTime and info.elapsedTime (those are from memory but close enough) for the time of the activity. I've never had a single complaint that the timers in my data fields were an issue for users. Here is an example simple data field which does lap calculations (https://github.com/lcj2/ciq_monkeyfuel). The calories lap value can be replaced with lap time using the info.timerTime to get what I think you want. This also includes some FIT element items which can be ignored but should be enough to get you going with lap averages and lap values. So for the time value look at the code specific to the calorie count and it will pretty much be the same for the timer. Maintain a timer variable and on lap event update the last lap timer value and then simply subtract the current timer from the last lap timer value to get the current lap time. Hope this helps and I wouldn't worry about maintaining your own timers.
  • I wouldn't calculate the timer yourself at all and would rely on the info.timerTime and info.elapsedTime (those are from memory but close enough) for the time of the activity. I've never had a single complaint that the timers in my data fields were an issue for users. Here is an example simple data field which does lap calculations (https://github.com/lcj2/ciq_monkeyfuel). The calories lap value can be replaced with lap time using the info.timerTime to get what I think you want. This also includes some FIT element items which can be ignored but should be enough to get you going with lap averages and lap values. So for the time value look at the code specific to the calorie count and it will pretty much be the same for the timer. Maintain a timer variable and on lap event update the last lap timer value and then simply subtract the current timer from the last lap timer value to get the current lap time. Hope this helps and I wouldn't worry about maintaining your own timers.


    Thanks. In fact what I did was abandoned trying to interpret the time in onTimerLap, and simply set a flag in onTimerLap that was picked up in compute, at which point I decided that a new lap had started.

    What I was trying to do was develop a data field that shows avg pace (which I can calculate simply from the avg speed in the compute info), and lap pace side by side. As a test on my watch, I displayed 3 fields: built-in avg pace, my own avg / lap pace, and built-in lap pace fields. Whilst the build-in avg pace and my own avg pace were v v close (main issue was a lag due to compute being called a bit later than the build-in data fields), it took several minutes for my lap pace and the built-in lap pace to converge. I find it a bit annoying that these don't match sooner, as it's the most obvious test to do.

    If it's not possible, I just need to live with it. It would just be good to match the built-in fields -- if I were using someone else's data field, it would also be the first thing I'd do, just to check the field I was using was programmed correctly.
  • Thanks. In fact what I did was abandoned trying to interpret the time in onTimerLap, and simply set a flag in onTimerLap that was picked up in compute, at which point I decided that a new lap had started.

    What I was trying to do was develop a data field that shows avg pace (which I can calculate simply from the avg speed in the compute info), and lap pace side by side. As a test on my watch, I displayed 3 fields: built-in avg pace, my own avg / lap pace, and built-in lap pace fields. Whilst the build-in avg pace and my own avg pace were v v close (main issue was a lag due to compute being called a bit later than the build-in data fields), it took several minutes for my lap pace and the built-in lap pace to converge. I find it a bit annoying that these don't match sooner, as it's the most obvious test to do.

    If it's not possible, I just need to live with it. It would just be good to match the built-in fields -- if I were using someone else's data field, it would also be the first thing I'd do, just to check the field I was using was programmed correctly.


    If pace is what you want, use the sample and look at lap burn rate and the average algorithm used, I find it works well. Substitute the speed and you should not need to track the timer at all for it.
  • If pace is what you want, use the sample and look at lap burn rate and the average algorithm used, I find it works well. Substitute the speed and you should not need to track the timer at all for it.


    Thanks. I'm already doing something very similar -- effectively just zeroing out the lap time and lap distance in the onTimerLap method. The avg pace is then just lapTime / (lapDistance / lapLength), where lapLength is a constant, = 1609.344 for statute miles. The maths is v simple -- it's working out what's possible in terms of the SDK that's harder.

    I appreciate your comments, and also the pointer to your github site. It would also be good to get some clarification from someone like Brian.ConnectIQ. I am interested as to whether it is in fact possible to calculate lap time accurately, i.e. as accurately as the watch displays, in a data field. I've spent some hours on this, some of which have been quite puzzling, and it would be good to get a definitive answer.
  • Using the example code the following generates a timer value within +/- one second of the Garmin provided lap time. This is in line with Brian.ConnectIQ's explanation on the independent nature of the onCompute() and onUpdate() which can produce up to a one second difference.

    using Toybox.Application as App;
    using Toybox.WatchUi as Ui;

    class TimerExampleApp extends App.AppBase {

    hidden var _mView;

    function initialize() {
    AppBase.initialize();
    }

    //! onStart() is called on application start up
    function onStart(state) {
    }

    //! onStop() is called when your application is exiting
    function onStop(state) {
    }

    //! onSettingsChanged() is called when user settings are changed
    function onSettingsChanged() {
    _mView.updateUserSettings();
    }

    //! Return the initial view of your application here
    function getInitialView() {
    _mView = new TimerExampleView();
    return [ _mView ];
    }

    }

    class TimerExampleView extends Ui.SimpleDataField {

    // ENUM of timer states
    enum
    {
    STOPPED,
    PAUSED,
    RUNNING
    }

    // instance variables
    hidden var _mTimerState = STOPPED;
    hidden var _mTimer = 0;
    hidden var _mTimerLastLap = 0;

    //! Set the label of the data field here.
    function initialize() {
    SimpleDataField.initialize();
    label = "Lap Time";
    self.updateUserSettings();
    }

    //! Update user settings
    function updateUserSettings() {
    }

    //! This is called each time a lap is created, so increment the lap number.
    function onTimerLap()
    {
    _mTimerLastLap = _mTimer;
    }

    //! The timer was started, so set the state to running.
    function onTimerStart()
    {
    _mTimerState = RUNNING;
    }

    //! The timer was stopped, so set the state to stopped.
    function onTimerStop()
    {
    _mTimerState = STOPPED;
    }

    //! The timer was started, so set the state to running.
    function onTimerPause()
    {
    _mTimerState = PAUSED;
    }

    //! The timer was stopped, so set the state to stopped.
    function onTimerResume()
    {
    _mTimerState = RUNNING;
    }

    //! The timer was reeset, so reset all our tracking variables
    function onTimerReset()
    {
    _mTimerState = STOPPED;
    _mTimer = 0;
    _mTimerLastLap = 0;
    }

    //! The given info object contains all the current workout
    //! information. Calculate a value and return it in this method.
    function compute(info) {
    if (info.timerTime != null) {
    _mTimer = info.timerTime;
    }
    var lapTime = "00:00";
    if (_mTimer != 0) {
    lapTime = (_mTimer - _mTimerLastLap) / 1000;
    var hour = (lapTime / 3600).toLong();
    var minute = (lapTime / 60).toLong() - (hour * 60);
    var second = lapTime - (minute * 60) - (hour * 3600);
    if (lapTime >= 3600) {
    lapTime = Lang.format("$1$:$2$:$3$", [hour.format("%d"), minute.format("%02d"), second.format("%02d")]);
    } else {
    lapTime = Lang.format("$1$:$2$", [minute.format("%d"), second.format("%02d")]);
    }
    }

    // return value to display
    return lapTime;
    }

    }
  • Thanks. That's very similar to what I'm doing already. So it does seem like it isn't possible to get the lap time / lap distance as accurately as the watch displays, in a data field.