Data Field: AppBuilder

By user request, this app lets you define your own data field, based on a simple math formula like cadence / 2.

If you want to get fancy, there's conditional expressions (like IF in Excel), functions for data analysis (like MIN and AVG), and the data field can also display the text of your choice. The resulting data can be (optionally) written to the activity FIT file.

With AppBuilder, you can implement almost any field that involves math, such as: calculating normalized power and saving the data to the FIT activity file, showing the average pace for even-numbered laps, or recording the amount of time you sprinted.

Full documentation and examples here:
http://ciq-appbuilder.blogspot.com/

AppBuilder 5:
Now with new features! AppBuilder 5 is a completely new app, so please check it out in the store if you are interested in any of the new features.
https://apps.garmin.com/en-US/apps/fd690281-9c22-4fee-a81e-3b7f39aa67c5

- Store up to four formulas per app. Switch between formulas directly on the watch, without using a phone or PC. With four clone apps, you can store up to 16 formulas total
- User variables. Allows for powerful formulas where information can be stored at one point, like the start of a lap, and used later. e.g. Lap elevation change
- Improved timeavg() options. Allows for simpler, more flexible normalized power function
- More functions and variables

4 clones of AppBuilder are available in the store, so you can have 2 formulas in the same activity
  • Former Member
    Former Member over 7 years ago
    Yeah I'm not a coder or math head myself so not implying what is theoretically correct, just trying to figure out why it doesn't work like the Garmin implementation does.

    If I compared them next to each other the Garmin NP field would start updating only after 30s mark and keep running after that also kept the effect of higher values better while using your previous example it dropped a lot more quickly and was very close to the basic power average in the end.

    I'll give it another try next time I'm on my indoor bike to test different variations. I'm a runner too and only started cycling using power data after an injury, so now I do a bit if both. Training peaks allows me to balance both activities using power data to calculate TSS / training load.
  • Former Member
    Former Member over 7 years ago
    Let me try to summarize and clarify. Sorry I can't edit the previous post, the forum is back to its old tricks.

    Proper Normalized Power formula -- hopefully:
    AVG0(TIMEAVG0(Power, 30) ^ 4) ^ 0.25



    This seemed to work finally, thanks. I was almost sure I had tried this too but it was late and was tired the last time so probably not. ;)

    Now the values go hand to hand with native NP. There was a steady few Watts difference but this narrowed down during the course of activity. I believe this is because appbuilder starts counting early from down low including zero values where as native field waits 30s and starts from there.

    I verified this finally by using native Lap NP and change to LAPAVG0 then wait for one lap to both fields go to zero then start another lap. After this the values were identical from the start of the lap ;)).

    For me this is already satisfactory function as the initial 30s should not make notable difference over the whole activity as was proven with the lap test.

    Now we should finally have examples for Normalized power and Normalized lap power. When a user knows their FTP they could also calculate TSS or IF for whole activity or on going lap :)

    Thanks for your patience to help to solve this out, turned out it was a very small thing after all.

  • JTH9, you're welcome. Glad it works now! Thanks for pointing out the error in the formula. Should've listened to my gut which told me that I interpreted the description of the NP algorithm incorrectly.

    I took a stab at changing TIMEAVG so that it doesn't return values when the number of samples is less than the window size, as well as making the old behaviour an option. Unfortunately the code gets a bit bigger, which is not something I want for CIQ1 watches especially. As I alluded to before, the main reason I stopped adding things to AppBuilder is because I may have run out of fat to trim. AppBuilder is unlike most apps the store in the sense that different users will use different amounts of memory, depending on how complex the formulas are. With most apps, devs can just test the "worst-case" memory usage, but for AppBuilder, there's no worst case. You can almost always make your formula more complex.

    Even if I just changed TIMEAVG outright (without making the old behaviour an option), the code would be a little bigger. I do think that it would be nice to have a "real" rolling/moving average that doesn't return irrelevant data for the first T - 1 seconds after the activity is started/unpaused, so I'll see what I can do. I would also like to keep the old behaviour for those who want it.
  • Former Member
    Former Member over 7 years ago
    Think I found a workaround:

    AVG0(IFS(Timer LTE 30, null,Timer GT 30,TIMEAVG0(Power,30)^4))^0.25

    Tested on bike and this gives near identical to native NP behavior. There is ~1W-2W difference which is probably because of lag.

    for laps it's a bit trickier to get native Lap NP behavior, more testing needed.

    I tried also simpler version using IF, but fore some reason the app crashed before I could save the formula.
  • Think I found a workaround:

    AVG0(IFS(Timer LTE 30, null,Timer GT 30,TIMEAVG0(Power,30)^4))^0.25

    Tested on bike and this gives near identical to native NP behavior. There is ~1W-2W difference which is probably because of lag.

    for laps it's a bit trickier to get native Lap NP behavior, more testing needed.

    I tried also simpler version using IF, but fore some reason the app crashed before I could save the formula.


    Yes, that is a more complicated version of my initial workaround that I posted. (Not sure why the app would crash; you mean Garmin Connect on your phone? People have been complaining about issues when the app syncs with bluetooth while you change settings).

    The problem is it won't work when you pause and unpause the activity. At that point you have the same problem: the number of samples becomes zero (older samples are thrown out when you unpause the activity, for obvious reasons), and TIMEAVG(x, 30) will return data for the next 29 seconds, which you don't want.

    There is actually a workaround for that, but it's completely unintuitive.

    It would be better just to change the default behaviour of TIMEAVG and maybe have an option to restore the old behaviour. I'll just have to see if I can make the code a bit smaller.

    I guess the LAP NP would still require a workaround. You could use something like:

    IF (timer GTE LAPMIN(timer) + 30, ...)

    Again that would have issues if you paused the activity during the first 30 seconds of the lap....
  • Former Member
    Former Member over 7 years ago
    I guess the beauty with app like appbuilder is you can build the field any way you like. I just started to build the formula to what seemed to me logical progression as I'm taking either a total or lap average then write the variables starting from that order.

    The original idea was to to combine both normal and normalized power behavior in same field, even test gradual averaging for different time intervals, so I started with IFS. Actually worked reasonably well, but then I don't think it's actually normalized anymore for the first 30s, but more of a hybrid field :) As to why IF didn't work. No idea, might be just a temporary glitch but I got some sort of web error in the connect mobile app (iOS) every time I tried it. Due to time constraint I just leaved it at that.

    Anyway seems the native function could be duplicated with just AVG0(IFS(Timer GT30,TIMEAVG0(Power,30)^4)^4))^0.25

    Apparently stock Lap NP does't reset the averaging on next lap so LAPAVG0(IFS(Timer GT30,TIMEAVG0(Power,30)^4)^4))^0.25 would be sufficient. I verified this with lap tests on the bike.

    I believe the native field was more likely built with 1K laps etc. in mind so last lap data will still carry on to next lap. For intervals this is not very good as the rest lap data would drag the fast lap value down. I guess one could argue if lap normalized power is even needed for max effort intervals, although with more variable running power it could give some (slight) benefit. At least Training Peaks seems to show them even though the watch can't show the data similarly with the native field.

    LAPAVG0(IFS(Lap time LTE30, null,TIMEAVG0(Power,30)^4)^4))^0.25 would reset to show higher value from start of each lap but then again it wouldn't show anything for the first 30s. Arguably for such short intervals you wouldn't have much time watching the value anyway and one should probably take at least a rolling start since running power meters still lag a little.

    As for pauses, I don't usually pause much (sometimes on a rest lap) so didn't test it but briefly. There the values seemed to stay intact, but I didn't validate it further so might be it's just not right if the averaging gets broken down like you said...

    Good if you can support this better with upcoming releases, but I agree it shouldn't break existing functionality...
  • Former Member
    Former Member over 7 years ago
    Forum editing stuck again, last formula got a copy paste error so should be LAPAVG0(IFS(LapTime GT30,TIMEAVG0(Power,30)^4))^0.25

    And apparently same error with double "^4" on all formulas.

    As for IF, I tried it again and gives Access denied and not having permission to access something on a server. A connect mobile bug on iOS?
  • Former Member
    Former Member over 7 years ago
    Here's what I use for now, until a better alternative comes out.

    AVG0(IFS(Timer GTE 30,TIMEAVG0(Power,30)^4))^0.25 - Total run average as per native field

    LAPAVG0(IFS(Timer GTE 30,TIMEAVG0(Power,30)^4))^0.25 - carries the averaging from prev lap (native Lap NP)

    LAPAVG0(IFS(LapTime GTE 30,TIMEAVG0(Power,30)^4))^0.25 - resets at start of each lap, probably should use this if one wants to calculate lap specific IF or TSS (?) (https://www.trainingpeaks.com/blog/estimating-training-stress-score-tss/). Doesn't work for shorter than 30s laps.

    Tried pausing and it doesn't totally mess up the averages, by few watts at most and will narrow down course of the activity compared to native field. Seems pausing the first 30s has the biggest tendency to throw the values off especially if you vary your power a lot.

    If I have to pause then will take the risk. Usually I try to plan my interval workouts so that I don't have to pause unless on a rest lap. Also the values are only used as guidance anyway since the values are not stored and correct values are still calculated for the activity at Training Peaks site...

    Hopefully got it right this time :)
  • Yes that's how I would've done it. Since you have decided to go with the workarounds, I'll post the workaround for counting the number of seconds since the timer was unpaused.

    Its ugly but the advantage is it is it only uses more memory for people who choose to use it, as opposed to all users.

    (Timer - MAX(if (PREV(timer) eq null, timer, null)) plus 1)

    Eg AVG0(IFS(Timer GTE 30
    And

    (Timer - MAX(if (PREV(timer) eq null, timer, null)) plus 1) gte 30
    ,TIMEAVG0(Power,30)^4))^0.25
  • ^ The above formula for counting seconds since the watch was unpaused works because, like most "analysis" functions, PREV(X) returns NULL when the timer is not running. Sorry about the messy format. I posted it from my phone, and now the forum won't let me edit it or repost it in a cleaner format.