3s Average Power

Hello everyone,

I am pretty noob in programming but I managed to create a (even rather complex) cycling Datafield.

The only thing I am missing is that I would like to display a 3s average power instead of currentPower, but due to my poor programming knowledge I don't know how can I do it.

Is there anyone willing to help and share a piece of code?

I found a couple of similar questions in the forum but they are from several years ago and I am not able to integrate that code in my datafield (VSC throws several errors).

Thank you!

  • compute() is called right about every second (there can be a slight variation at times).  use the last 3 values for currentPower and save the average to display in onUpdate;

  • Here's an example of a class that implements a rolling average, and an example of using it to calculate 3s rolling power in a data field.

    https://pastebin.com/LSmgB0Kf

    There's a couple of assumptions / implementation decisions here:

    - if there's less than 3 seconds of power data, the calculated 3s average should be null. In other words, the rolling average isn't valid if there isn't a full set of data for the entire window.

    - if there's an interruption in data (meaning currentPower is null) [*] then all of the previously captured power data should be thrown away. In other words, the 3s power average isn't valid unless there are 3 *consecutive* seconds of power data.

    [*] examples of how this could happen: the user stops the timer or the sensor loses communication with the watch ("sensor drop-out")

    EDIT: see following comment for cleaned-up example with the assumptions as options:

    forums.garmin.com/.../1921826

  • Hello,

    thank you, this looks really good as an explanation. For the assumption is no problem I already have an idea for a workaround.

    However, I was wondering if there is a mistake in the code because I always get null, even when I throw multiple consecutive power data at the code.

  • No problem!

    For the assumption is no problem I already have an idea for a workaround.

    Yeah to be clear I made those assumptions because that's how I think it should work (and I'm guessing that's how Garmin's native fields work). The 2nd assumption is very easy to get rid of: just remove _power3s.reset()) in compute().

    This first assumption is not so easy to get rid of, because you have to change how getAverage() works. (The code becomes a bit more complex).

    I can post a new example which has options to enable/disable those assumptions.

    However, I was wondering if there is a mistake in the code because I always get null, even when I throw multiple consecutive power data at the code.

    I did test this briefly, and it seemed to work, but it's possible there's a bug.

    Here's my test code and output:

    var x = new RollingAverage(3);
    x.addValue(1);
    System.println(x.getAverage()); // null
    x.addValue(2);
    System.println(x.getAverage()); // null
    x.addValue(3);
    System.println(x.getAverage()); // 2.000000
    x.addValue(4);
    System.println(x.getAverage()); // 3.000000
    x.addValue(5);
    System.println(x.getAverage()); // 4.000000

  • NOTE: this is the best version of the example code

    Here's a new example which has the 2 assumptions as options, instead of hard-coded behaviour. The option values themselves are currently hardcoded - you have to change them manually in the code.

    EDIT: edited to move all the code for the assumptions inside the RollingAverage class, and cleaned up a few minor things

    https://pastebin.com/apZfePEY

    EDIT: maybe "RollingAverage" isn't the best name for that class, as I can see it might lead to confusion. If it were more generic and had more functionality, "DataSet" or maybe "RollingWindowData" might be more appropriate. Anyway, it's just an example

  • However, I was wondering if there is a mistake in the code because I always get null, even when I throw multiple consecutive power data at the code.

    Can you show me your full code?

    In my example, _power3s is supposed to be a member variable of the View class, not a local variable. It can only be created and initialized once (when the view class is created), because it needs to store data that persists across multiple calls to compute() (it needs to store the last 3 seconds of power data).

    idk if this makes it more clear:

  • Hello, now it's perfect, the problem was indeed the member vs local variable.

    I just added this as a workaround, not sure if it's the correct way but it fits my needs.

  • I just added this as a workaround, not sure if it's the correct way but it fits my needs.

    See updated comment above for code which allows the 2 assumptions to be cleanly enabled or disabled.

    https://forums.garmin.com/developer/connect-iq/f/discussion/408803/3s-average-power/1921826#1921826

    Speaking of the code you posted: in my opinion, it's not right to display 0 when the 3 second average is null.

    For example, even without the 2 assumptions, getAverage() should still return null when there is *no* data. The concept of an average is meaningless for an empty set of numbers. And when the 3s average is null, it makes sense to me to display something which means "not applicable" - for Garmin watches, this is usually "--".

    This is why programming languages have a concept of null in the first place (which is usually distinct from 0). They need a way to signify nothing / invalid data / not applicable / etc.

  • You definitely have a point, I was indeed considering also to display N/A, at the moment I did this code mainly because I have color coded elements connected to the mValuePower3s var which do not react well to null or text inputs, but I can add a further condition for the display part. Thank you very much for the help!

  • It's so much true, that my first version of ANT+ HRM was not approved by Garmin / ANT exactly because of this, they required it to display "--" when there's no connection.