custom fields development

I am attempting to develop and app with added data fields - ActivityRecording::Session.createField
My target is devices such as Fenix HR 3 that support the required v1.30 SDK or later.
I only have the EPIX device which does not support that SDK, so I am using the simulator.

I am able to save a .fit file from the simulator.
I am able to use the FIT SDK/FitToCSV.bat to see that my fields are present in the .fit file
I have tried many other tools to see the data in a better way but they all seem to fail.
Ideas on ways to visualize the data?

1) monkeygraph - cannot get it to work at all. Open the fit file, nothing happens, the monkey just sits there.
Cannot find any documentation on monkeygraph.

2) https://connect.garmin.com/modern/
I can upload some fit files using
https://connect.garmin.com/api/upload/widget/manualUpload.faces
However if i upload the fit files with the added fields, it fails to load them.

3) Basecamp will also load fit files, however it also fails if these extra fields are present, it gives no error but does not load the file.

4) gpsbabel - also fails
  • It is my understanding that monkeygraph and fittocsv are the tools you should be using. Garmin Connect won't know how to present the data until you upload your app, and it sounds like that is probably not something you're ready to do yet. Other tools (like BaseCamp, Strava, ) most likely don't know how to read and display the data yet.

    When you use monkeygraph, are you loading your IQ file? In my testing, you open up monkeygraph, select File > Open IQ File, pick the IQ file for your application, then select File > Open FIT File and pick a FIT file generated from the simulator (they are saved in %TEMP%\Garmin\Activities on the PC).

    Travis
  • Thanks,

    1) I was able to upload .fit files to Garmin Connect using my app. But once i added the custom fields i could not upload. No error was given.

    2) I have tried monkeygraph many times. I generated an .IQ file and loaded it. I was a bit puzzled the .IQ file was small, only 13kb, while when a build for device, i get a prg file over 100kb.
    In any event when i load the .iq, then the .fit, there is a java error that does not really help my understand what is wrong. I just figured monkeygraph was broken. Even without adding fields, i still cannot get it to work. I have windows 10, presume that is not an issue. Tried 'run as administrator' also when starting monkeygraph, no help.

    Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at com.garmin.monkeybrains.fitgraphs.graph.FitGraphXYDataset.<init>(FitGraphXYDataset.java:63)
    at com.garmin.monkeybrains.fitgraphs.ui.MainUiController.buildCharts(MainUiController.java:163)
    at com.garmin.monkeybrains.fitgraphs.ui.MainUiController.buildTabs(MainUiController.java:145)
    at com.garmin.monkeybrains.fitgraphs.ui.MainUiController.actionPerformed(MainUiController.java:134)
    at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
    at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
    at javax.swing.AbstractButton.doClick(Unknown Source)
    at javax.swing.plaf.basic.BasicMenuItemUI.doClick(Unknown Source)
    at javax.swing.plaf.basic.BasicMenuItemUI$Handler.mouseReleased(Unknown Source)
    at java.awt.Component.processMouseEvent(Unknown Source)
    .... and lots more...
  • Sorry one more related question,
    I have to comment in and out the code for the custom fields during this testing, and also would need to do so for different devices that use different SDK.
    Some compilers offer a pre-processor that can check for example SDK version and other such things, so code can be selectively included or not.
    Is there anything like this? So functions like createField can be bypassed if building with an older SDK, like 1.2 i need for EPIX.

    #ifndef _MSC_VER
    // not VC++
    #elif _MSC_VER < 1400
    // older than VC++ 2005
    #elif _MSC_VER < 1500
    // VC++ 2005
    #elif _MSC_VER < 1600
    // VC++ 2008
    #elif _MSC_VER < 1700
    // VC++ 2010
    #else
    // Future versions
    #endif
  • When you "build for device", it sounds like you're not building a release version, so it will be much bigger than a release version with all the extra debug info. That is likely why your sideload is 100k.

    When you build a .iq, it builds release versions of the .prgs (much smaller)

    a .iq file is actually in .zip format so you can look at it with a tool to open.zips. In it you're see a tree structure with .prgs for your various builds. You'll also see .json files if you have app settings, and also .json files since you're using FitContributor (built using the .xml you have that defines your field). In fact, the .jsons are the reason monkeygraph needs a .iq - to understand your field definitions.

    Might it be something with the fitContributions things in your .xml file?
  • Sorry one more related question,
    I have to comment in and out the code for the custom fields during this testing, and also would need to do so for different devices that use different SDK.
    Some compilers offer a pre-processor that can check for example SDK version and other such things, so code can be selectively included or not.
    Is there anything like this? So functions like createField can be bypassed if building with an older SDK, like 1.2 i need for EPIX.


    There's not conditional compile like you posted, but there is a means to use different .mc files for things based on device.

    See "Build File Exclusions" in the programmer's Guide : https://developer.garmin.com/connect-iq/programmers-guide/resource-compiler/

    Instead of including code for a specific device, you exclude code for devices other than the one you're building for.

    You can also just detect the device your building for and using that, skip bits of code you can't use on that device.

    Or you can check if certain calls are available, and if they are not, don't make them

    (see "has" in the programmer's guide)

    ("has" is likely what you want to use, for this)
  • Thanks, very helpful, if i can get it to work.

    Using the simulator with an Epix, which uses 1.2 SDK, i would have thought both the if statements below would fail, however i can see on the console log that the println is called, i thought it should not.

    The programmers guide talks about using this for detecting if a device has support for a sensor, i presume it is also valid for detecting if the SDK has support for a function like below.

    using Toybox.ActivityRecording as Rec;
    using Toybox.FitContributor as Fit;
    using Toybox.System as Sys;

    if (Rec.Session has :createField) {
    if (Fit has :Field) {
    Sys.println...

    Further i tried building the prg for epix, and it fails with this error on the line below
    if (Rec.Session has :createField) {

    ERROR: Unexpected Type Error
    DETAILS: Failed invoking <symbol>
    STORE_ID: 00000000000000000000000000000000
    CALLSTACK:
    ...View.mc (onUpdate:307)
  • Ok, got it, or some of it.

    I learned several things:
    - simulator does not correctly obey the 'has' test, but it works better in the device via prg file
    -I interpreted 'has' as checking an abstract reference to class definitions. If you use an actual instance of a class then it works better. So now it does work when i put a .prg file in the device.
    - The example in the programmers guide is not great. Magnetometer does not exist in Toybox. Toybox - well not sure if that is an instance or just an class name.
    if ( Toybox has :Magnetometer ).
    - when you do the 'app export wizard' the messages come out, but the default in eclipse was autobuild. Right after the export, it did the autobuild, so errors during the export were not seen.

    Still no luck at getting MonkeyGraph to work. I have never seen it work.
  • With the 2.0.0 beta sdk, the sim does honor what's on the real devices, so the "has" should fail where it would on the real device.

    You can't create .iq files with the 2.2.0 beta (it's turned off in betas), but you can test the logic and switch back to 2.1.4 when needed.
  • The example in the programmers guide is not great. Magnetometer does not exist in Toybox. Toybox - well not sure if that is an instance or just an class name.


    You can definitely use the has operator with modules, classes, or instances. i.e., You can write if (Toybox has :AntPlus) to see if the new AntPlus module is available. If you have an Array object a you can use if (a has :indexOf) to tell if the a.indexOf() call is available.

    Also, you can definitely do conditional compilation. You can use the build exclusions mentioned by Jim to define annotations, and then you just use those annotations in the code. For example...

    <!-- edge_1000/exclusions.xml -->
    <resources>
    <build>
    <exclude annotation="no_landscape_mode"/>
    </build>
    </build>


    <!-- fenix3/exclusions.xml -->
    <resources>
    <build>
    <exclude annotation="has_landscape_mode"/>
    </build>
    </build>


    class MyView extends Ui.View
    {
    function onLayout(dc) {
    setLayout(selectLayout(dc));
    }

    (:has_landscape_mode) hidden function selectLayout(dc) {
    var width = dc.getWidth();
    var height = dc.getHeight();

    if (height < width) {
    return Rez.Layouts.MyLayout_Landscape(dc);
    }
    else {
    return Rez.Layouts.MyLayout_Portrait(dc);
    }
    }

    (:no_landscape_mode) hidden function selectLayout(dc) {
    return Rez.Layouts.MyLayout(dc);
    }
    }


    You can use it for things more complex than a simple binary condition. I wouldn't use annotations to decide whether to try to use createField or not. I'd use has for that. The annotations are a compile time thing, whereas has is runtime. This means that if the Epix ever gets a firmware update the code would start working automatically.
  • Thanks for the pointers. I have made some good progress.

    I have found looking at the data in the .fit file to see if it is being stored by the app has been time consuming.

    I have never been able to use MonkeyGraph but would like to. I has failed every time with the following stack dump (see below). I have no idea what the cause of it is.
    I used export to create an IQ file, i can select this in MonkeyGraph. I then select a .FIT file i created with the simulator from the same app. As soon as i select the fit file, this results.

    In the mean time i have had to write my own application to graph the contents of the .FIT file.

    I am a bit puzzled about the rule that the app must be in the store before you can upload .fit data to view there. While i can see that there is info within the fit_contributions.json inside the .IQ file, it seems like the data could be displayed without it, just missing color preferences and nicer label names. It would be very handy to use while developing a new app. I have spent a lot of time trying to confirm what data is added to the .fit file using only the .CSV files the FIT sdk tools generate from .fit files.

    --------------------------

    Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at com.garmin.monkeybrains.fitgraphs.graph.FitGraphXYDataset.<init>(FitGraphXYDataset.java:63)
    at com.garmin.monkeybrains.fitgraphs.ui.MainUiController.buildCharts(MainUiController.java:163)
    at com.garmin.monkeybrains.fitgraphs.ui.MainUiController.buildTabs(MainUiController.java:145)
    at com.garmin.monkeybrains.fitgraphs.ui.MainUiController.actionPerformed(MainUiController.java:134)
    at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
    at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
    at javax.swing.AbstractButton.doClick(Unknown Source)
    at javax.swing.plaf.basic.BasicMenuItemUI.doClick(Unknown Source)
    at javax.swing.plaf.basic.BasicMenuItemUI$Handler.mouseReleased(Unknown Source)
    at java.awt.Component.processMouseEvent(Unknown Source)
    at javax.swing.JComponent.processMouseEvent(Unknown Source)
    at java.awt.Component.processEvent(Unknown Source)
    at java.awt.Container.processEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Window.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$500(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)