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
  • Thanks a lot to FlowState for this powerful and useful app or extension


    I'm thinking it would be useful to have a sort of storage/repository of formulas / examples tested and confirmed to work properly to both learn and be used

    for example I'm thinking to make a running PACE field calculated over last let's say 200m and based on GPS distance / time instead of GPS speed,
    I guess this could lead to a more precise speed/pace calculation





  • So, can I nest my IF statements on a CIQ1 device?
    Trying something simple ...

    IF (<test1>, IF(<test2>, <true2>, <false2>), <false1>)

    ... but getting !AdvFn error?
  • +1 for developer wishlist item "arbitrary text" ... Would like to be able to display "ahead 1.6", "3.5 behind" etc
  • Thanks a lot to FlowState for this powerful and useful app or extension


    I'm thinking it would be useful to have a sort of storage/repository of formulas / examples tested and confirmed to work properly to both learn and be used


    You're welcome. Well, for now there are the examples in the first page of the thread, and the example in the store pages for AppBuilder2 and AppBuilder3. I really don't want to host anything separately because a third-party site:
    - Could go away in the future
    - Will not open in the Garmin Connect app without launching a separate browser

    for example I'm thinking to make a running PACE field calculated over last let's say 200m and based on GPS distance / time instead of GPS speed,
    I guess this could lead to a more precise speed/pace calculation


    I think GPS speed is already natively calculated based on GPS distance divided by time, so I'm not sure where the advantage is here. If you use the Speed variable and display it as Pace, it won't be rounded to the nearest 5 seconds like the normal pace displayed in your running activity, though. You can certainly average your GPS speed over the last X seconds, using TIMEAVG(), if you want a smoothed value. Also, when calculating something like grade, you can grab samples of elevation every 100m of distance (using PREVD()), in order to ignore noise; I don't think this technique is valid for horizontal distance/speed, though.

    So, can I nest my IF statements on a CIQ1 device?
    Trying something simple ...

    IF (<test1>, IF(<test2>, <true2>, <false2>), <false1>)

    ... but getting !AdvFn error?


    Yes, you can nest IF functions on CIQ1. I would have to see your exact formula to tell you why it isn't working, but you might want to look for mismatched brackets or stray commas. (Anything can be nested, but it isn't always a good idea - see below.)

    However, especially for CIQ1 devices, you may wish to use the IFS() function to simplify your formula, both for readability/comprehension, and also to allow it to fit in device memory. CIQ1 devices have two issues with very long formulas:
    - Very long formulas can cause the watch to run out of memory, making the data field crash. (CIQ1 allots half as much memory to CIQ data fields as does CIQ2, which is why it's especially a problem for older watches.)
    - Very long formulas can cause the data field to initialize too slowly, making the data field crash. (Ironically, this is because of a technique I used to save memory.)

    In both of these cases, you would get an IQ! error from the watch (with a little Garmin logo), and not one of AppBuilder's errors.

    Nesting is especially problematic because every function call, bracket, comma and argument contributes to the complexity of the formula. (The problem with "long formulas" isn't just the number of characters, but more importantly, the number of "math tokens".) Having said that, nesting 2, 3 or even 4 levels deep shouldn't be a problem, but it might be hard to read/understand.

    IFS is defined as follows:

    IFS(condition1, true1, condition2, true2, condition3, true3, ...)

    The default value is NULL, if none of the conditions are true (non-zero).

    ------
    Note on IF and IFS:

    Technically, there are no statements in AppBuilder -- everything is a function -- which is very important to understand in the case of IF, because the true and false sub-expressions will always be evaluated. Every expression in AppBuilder will always be evaluated; when using AND and OR, there is no logical short-circuit mechanism as in C or Javascript.

    IOW, the following two formulas will produce very different results:

    IF(Speed > 10, SUM(1), 0)
    SUM(IF(Speed > 10, 1, 0))

    The first formula will display the total unpaused activity time (in seconds), whenever the speed is greater than 10 (mph or km/h), zero at all other times.
    The second formula will display the total time that speed was greater than 10.

    AppBuilder is more like Excel than C# or Javascript.
  • You're welcome. Well, for now there are the examples in the first page of the thread, and the example in the store pages for AppBuilder2 and AppBuilder3. I really don't want to host anything separately because a third-party site:
    - Could go away in the future
    - Will not open in the Garmin Connect app without launching a separate browser



    Yeah, I understand
    so hope examples will grow here in this thread

    I think GPS speed is already natively calculated based on GPS distance divided by time, so I'm not sure where the advantage is here. If you use the Speed variable and display it as Pace, it won't be rounded to the nearest 5 seconds like the normal pace displayed in your running activity, though. You can certainly average your GPS speed over the last X seconds, using TIMEAVG(), if you want a smoothed value. Also, when calculating something like grade, you can grab samples of elevation every 100m of distance (using PREVD()), in order to ignore noise; I don't think this technique is valid for horizontal distance/speed, though.



    first it's not clear how pace is calculated,
    even if it's already gps distance over some time, this time, or distance, is not clear (my best guess is around 30")
    so having the possibility to configure it would be really nice (faster and less accurate or slower and more accurate)

    will try as soon as possible and report results here






  • first it's not clear how pace is calculated,
    even if it's already gps distance over some time, this time, or distance, is not clear (my best guess is around 30")
    so having the possibility to configure it would be really nice (faster and less accurate or slower and more accurate)

    will try as soon as possible and report results here


    Ah, I see. Unfortunately, CIQ data fields are only updated once per second (surely we can all run faster than 30" per second), so you'll never get any better resolution than that. In general you'll never get better resolution from CIQ data field calculations vs a native variable. Furthermore, typically what you see in a CIQ field is delayed by about 1 second (which is obvious if you use the Timer variable in AppBuilder and compare it to the native time).

    Besides, the PREVD/PREVT functions are very rough. PREVD(X, d) gives you the previous value of X at the point of at least d metres ago. Similarly, PREVT(X, d) gives you the previous value of X at the point of at least d seconds ago. By no means are these functions meant to be precise (which would be difficult
    considering the 1 second sampling rate, and also limited app memory which would make it infeasible to store extra samples.)

    Really, PREVD is probably not good for much besides calculating an approximate grade.

    e.g.
    Grade = Elevation - PREVD(Elevation, 100) / (Distance - PREVD(Distance, 100))

    This would give you DELTA(Elevation) / DELTA(Distance) where the previous sample of each was taken at least 100m ago.

    I think the best you can do is take the average of the native Garmin Speed variable over a certain period of time. Of course there are different algorithms for doing so, but I've only provided a simple moving average with TIMEAVG().

    If you wanted to do a weighted moving average, you could perhaps use the PREVT function to take different samples (e.g. at T, T-1, T-2, ... T-9) and assign weights them in your own formula, but YMMV. PREVT wasn't really designed for that sort of thing, though and the problem is that the datafield is not updated at exactly every 1 second, so some samples could be rejected (e.g. if there was only 0.99 seconds between 2 samples). To do this kind of thing properly, I would really need to extend the PREV() function (which grabs the last sample) to a PREVN() function (which would grab the last Nth sample). One way to get around the current limitation could be to use PREVT() with a time value of 0.5 seconds, so you never miss a sample. Another way to do it could be to nest PREV() functions, (e.g. PREV(PREV(X)) should give you the 2nd last sample of X), but that just makes the formula more complicated and increases the chance that it won't run, especially on older devices.
  • OK, so here's the formula I've been playing with. This is a simplified version, but it still produces an !AdvFn error.

    =if(
    (timer + (5000 * 1.02 - Distance_raw) / TIMEAVG(Speed_raw, 30)) GT 1130,
    timer + (5000 * 1.02 - Distance_raw) / TIMEAVG(Speed_raw, 30),
    (timer + (5000 * 1.02 - Distance_raw) / TIMEAVG(Speed_raw, 30))-1130)

    (New lines added to aid readability.)

    The intention is to test a version of one of your example formulas to predict the finish time of a 5k, displaying this prediction if slower than a PB is predicted or, if not, to display "-0.06" or similar - indicating how far ahead of PB pace I am running.

    Obviously, the core formula works in isolation, but once combined in this way into an IF produces the error.

    Any help / insight gratefully received :)

    (I did try code tags, but they don't display well on my device!)
  • somebizzare, I don't have a physical CIQ1 device to test on, but I did try your formula out on the simulator, in CIQ1 (FR230) and CIQ2 (FR935). The formula does not produce an error for CIQ2, but it does crash the virtual FR230 with an out of memory error when the formula is being parsed (translated from text into a format AppBuilder can work with), because as written it is too complex. (You should see that as a "Garmin IQ error" with logo, not the AppBuilder !AdvFn error).

    This is where allowing local variables would be really helpful, but unfortunately that would increase the code size and I'm not sure I can take the chance of breaking existing formulas for CIQ1 by adding more code. The problem is I have no way of knowing whether people are using complex formulas to go right up against the existing memory limit. If they are, then I can't add anything unless I figure out a way to trim existing fat (which I've done many times before -- each time is harder than the last).

    Your formula above can be simplified a little bit.

    e.g. This has two repetitions of the meaty part, instead of three, and it doesn't cause a crash
    (timer + (5000 * 1.02 - Distance_raw) / TIMEAVG(Speed_raw, 30)) - if((timer + (5000 * 1.02 - Distance_raw) / TIMEAVG(Speed_raw, 30)) GT 1130, 0, 1130)

    You can also multiply out 5000 * 1.02 = 5100 to simplify the formula further. Getting rid of unnecessary brackets will also help.

    However, I don't know what the rest of your formula looks like, so there's always a chance that it'll still be too big. Even if you manage to simplify the whole thing, there's always a chance that it'll run out of memory after you start the activity (because you have two instances of TIMEAVG, and that'll take twice as much memory -- storing 60 samples instead of 30), even though they're averaging the same thing).

    I have no idea why you would get the !AdvFn error, though, that doesn't seem right.

    Edit: Okay, I tried to actually run the formula in the simulator (with simulated FIT data) and it crashed. I guess two instances of TIMEAVG(x, 30) are too much for CIQ1. I changed the 30 to a 15 and it worked.
  • (You should see that as a "Garmin IQ error" with logo, not the AppBuilder !AdvFn error).

    Definitely an AppBuilder !AdvFn error ;)

    This is where allowing local variables would be really helpful ...
    I'm just enough of a programmer to have had that thought already ...

    (timer + (5000 * 1.02 - Distance_raw) / TIMEAVG(Speed_raw, 30)) - if((timer + (5000 * 1.02 - Distance_raw) / TIMEAVG(Speed_raw, 30)) GT 1130, 0, 1130)
    ... But not enough that I noticed that optimisation - thanks ;)

    Edit: Okay, I tried to actually run the formula in the simulator (with simulated FIT data) and it crashed. I guess two instances of TIMEAVG(x, 30) are too much for CIQ1. I changed the 30 to a 15 and it worked.
    I'd already done quite a lot of debugging, creating various versions and simplifications of the formula and loading them up via AppBuilder 1/2/3/4 ... But as I was using GCM and don't have a simulator it was pretty labour intensive to say the least. In some of those tests I had used lower values for TIMEAVG, but since they hadn't helped had reverted to 60 seconds. Once I get to use this in a real situation I can find out if I need the maximum value possible or if something lower, like 10 seconds will be more useful anyway. If so, perhaps the saving in memory will allow me room to nest this inside another IF as I originally conceived it. (I take your point re simplifying to save the last few bytes of memory - it goes against the grain to write opaque code though.)

    Thanks once again for your help :)
  • My FR230 has had firmware update - from version 7.50 to 7.60. The change log states that this
    'adds support for Connect IQ 1.4.4'. Does this have any, hopefully positive, implications for AppBuilder functionality on my watch?