Has anyone used the Activity.WorkoutStep details available in 3.2?

I'm trying to develop a datafield that uses the new WorkoutStep, WorkoutIntervalStep and WorkoutStepInfo classes.  I can't find any information other than what is in the API documentation, but by playing around a bit I have found the following:

My environment is an F6 running 11.10 firmware, Eclipse with SDK 3.2.2 and I am using a running interval workout that was downloaded on my watch from TrainingPeaks via the Garmin Training API.  It has a couple warm up steps and then intervals repeating 4x with active and rest portions, followed by a cool down step.

1) There is no way to load a workout in the simulator.  This is a hassle since it means I have to do all of my debugging on my watch, but that's fine -- lots of System.println() and looking at the error log has gotten me pretty far.

2) Toybox.Activity.getCurrentWorkoutStep() works correctly and returns null when there is no workload loaded and returns an Activity.WorkoutStepInfo instance when one is loaded.

3) The information in the WorkoutStepInfo instance looks correct.  I mostly tested with the intensity attribute and it follows correctly as I progress through the workout.

4) Unless I am doing something wrong, the step attribute in the WorkoutStepInfo instance never has any information.  When I try to read it, has:activeStep and has:restStep always return false.  This may be due to the way that the workout file is constructed(?), but without having either of these attributes there is no way to get a WorkoutStep instance which has the detail information about the step including the target and duration information.

5) This may be user error on my part, but the WORKOUT_INTENSITY_xxxx enumerations aren't working.  It compiles when I use them but crashes runtime.  For my playing around I had to hardcode the values.

Thanks in advance for any guidance if anyone else has played with this new feature and been successful in getting it to work!

Top Replies

  • Hi,I am curious about the unit for targetValueLow/targetValueHigh when target type is speed.

    step: Obj: 143
    durationType: 0(time)

    durationValue: 10.000000
    targetType: 0(speed)
    targetValueLow…

  • I  found it is mm/s !!!

    That's what's in the FIT file, but how does the Connect IQ API report the same numbers? I would expect CIQ to use m/s for consistency, especially considering the numbers…

All Replies

  • 5) This may be user error on my part, but the WORKOUT_INTENSITY_xxxx enumerations aren't working.  It compiles when I use them but crashes runtime.  For my playing around I had to hardcode the values.

    It is my understanding that you should be able to play back the workout .FIT file via the Simulation > FIT Data > Playback File menu. You should be able to use the Simulation > FIT Data > Simulate Data menu to generate some data, but it might not satisfy the requirements of the fit activity. You can advance through the workout steps with the lap button.

    5) This may be user error on my part, but the WORKOUT_INTENSITY_xxxx enumerations aren't working.  It compiles when I use them but crashes runtime.  For my playing around I had to hardcode the values.

    That is exactly what the documentation has to say. 

    Returns:Toybox.Activity.WorkoutStepInfo — if a workout is active and there is a next step, null otherwise

    4) Unless I am doing something wrong, the step attribute in the WorkoutStepInfo instance never has any information.  When I try to read it, has:activeStep and has:restStep always return false.  This may be due to the way that the workout file is constructed(?), but without having either of these attributes there is no way to get a WorkoutStep instance which has the detail information about the step including the target and duration information.

    The WorkoutStepInfo returned by Activity.getCurrentWorkoutStep() should have a step member. The info.step should be a WorkoutIntervalStep, which has the activeStep and restStep fields. Each of those, if non-null, should be a WorkoutStep.

    I haven't personally used these APIs yet. I'll tinker with them today to see if I can reproduce.

    5) This may be user error on my part, but the WORKOUT_INTENSITY_xxxx enumerations aren't working.

    Those values should exist if the device has support for the feature. You can do a has check to verify the symbols are available:

    if (Activity has :WORKOUT_INTENSITY_ACTIVE) {
      // sanity check that workout step support is available
    }
    
    



    We did have an issue where some of the API symbols weren't properly guarded so they were visible but not fully implemented. It looks like the 11.10 firmware for the fenix6 is beta firmware, and it may have been affected by this issue. If this is the case, that might also explain the other problems you are seeing.

  • Thanks Travis.  Here is my compute function for your reference.

        function compute(info) {
            // See Activity.Info in the documentation for available information.
            if(info has :currentHeartRate){
                if(info.currentHeartRate != null){
                    mValue = info.currentHeartRate;
                } else {
                    mValue = 0.0f;
            	}
            }
                
            // get current workout step
            var currentStepInfo = Toybox.Activity.getCurrentWorkoutStep();
            if (currentStepInfo != null){
    			
    			System.println("not = null");
    			mInWorkout = true;
    			
    			// get step details
    			var StepDetails = currentStepInfo.step;
    			       	
            	mStepIntensity = currentStepInfo.intensity;
    //			mStepName = currentStepInfo.name;
    
    			// DEBUG CODE
    			var myformat = "si:$1$";
    			var myparams = [mStepIntensity.format("%d")];
    			var debugstring = Lang.format(myformat,myparams);
    			System.println(debugstring);
    
    
    			// are we in an active or rest step?
    				// add code here after we figure out how it all works
    
    			if (currentStepInfo.step has:activeStep) {
    				System.println("active step exists");
    	
    				var currentActiveStep = StepDetails.activeStep;
    
    				mStepDurationType = currentActiveStep.durationType;
    				mStepDurationValue = currentActiveStep.durationValue;
    				mStepTarget = currentActiveStep.targetType;
    				mStepTargetHigh = currentActiveStep.targetValueHigh;
    				mStepTargetLow = currentActiveStep.targetValueLow;
    				var myformat = "si:$1$ sn:$2$ sdt:$3$ sdv:$4$ st:$5$ sth:$6$ stl:$7$";
    				var myparams = [mStepIntensity.format("%d"), mStepName, mStepDurationType.format("%d"), mStepDurationValue.format("%d"), mStepTarget.format("%d"), mStepTargetHigh.format("%d"), mStepTargetLow.format("%d")];
    
    			} else {
    				System.println("no active step");
    			}
    			
    			if (currentStepInfo.step has:restStep) {
    				System.println("rest step exists");				
    			} else {
    				System.println("no rest step");
    			}
    			if (currentStepInfo.step has:repititionNumber) {
    				System.println("rep number exists");				
    			} else {
    				System.println("no rep number");
    			}
    			
    			
    		} else {
    			
    			System.println("currentStepInfo is null");
    			mInWorkout = false;
    			mStepIntensity = 255;
    			mStepName = "None";
    			mStepDurationType = 0;
    			mStepDurationValue = 0;
    			mStepTarget = 0;
    			mStepTargetHigh = 0;
    			mStepTargetLow = 0;
    				
            }
        }
    

  • It is my understanding that you should be able to play back the workout .FIT file via the Simulation > FIT Data > Playback File menu. You should be able to use the Simulation > FIT Data > Simulate Data menu to generate some data, but it might not satisfy the requirements of the fit activity. You can advance through the workout steps with the lap button.

    Also, this didn't work for me when I tried, but I made a lot of fixes to my code the last time I tried this so I'll try it again and see.

  • Also, another note about my code - I ended up messing with the has:activeStep because it crashed otherwise but thinking further it makes no sense to use this.  Either my issue is with the way I am accessing the step attribute or the firmware on my watch.  It compiles correctly so I think it is more likely the latter and probably related to the API implementation in the F6 firmware as you mention.

  • Not sure you need all the "has" checks, but what you probably want to to is null checks on the values, as the DF can be running when you don't have a workout running..

  • Thanks, Jim.

    They actually were null checks and I changed them to has checks when the null checks crashed according to my debugging.  I have determined that I am either accessing it wrong or it isn’t fully implemented in my watch.

    In theory, it shouldn’t even get that far if there is no workout running since the earlier getCurrentWorkoutStep should return null first.

  • I haven't forgot about this... I just need some time to tinker.

  • 4) Unless I am doing something wrong, the step attribute in the WorkoutStepInfo instance never has any information.  When I try to read it, has:activeStep and has:restStep always return false.  This may be due to the way that the workout file is constructed(?), but without having either of these attributes there is no way to get a WorkoutStep instance which has the detail information about the step including the target and duration information.

    I have just been able to play with this same feature. I have the same experience as you, but I have found out something abou the step.

    Yes, the step attribute in WorkoutStepInfo is not working, BUT I have been able to access attributes in WorkoutSteps anyway.

    I am currently using these lines (of course with tests inbetween):

                var currentStepInfo = Toybox.Activity.getCurrentWorkoutStep();

                var myHigh = currentStepInfo.step.targetValueHigh;

                myHigh = myHigh - 1000;

    This is strange, because in my opinion the second line should include 'activeStep' as well, but now it reports the high value.

    The only thing is the value is 1000 too high (or has a '1' before it, my target values are always below 1000 watt).

    Perhaps this is of help to you.

    And perhaps someone can explain to me how this should be addressed without getting a value that is 1000 too high.

  • Yes, it should be currentStepInfo.step.activeStep.targetValueHigh. I'm surprised that code runs without giving you a Symbol Not Found error.

    That said, it looks like there is a bug in the documentation. The step field can be either a WorkoutStep or a WorkoutIntervalStep. You have to use instanceof or has to tell which. I've got a fix for the documentation issue, so that should be taken care of, but it will take a little while to roll out.

    Here is some test code that I wrote to verify things are working properly on a fenix6xpro.

    using Toybox.Application;
    using Toybox.WatchUi;
    using Toybox.Graphics;
    
    class DataFieldView extends WatchUi.DataField {
    
        hidden var _width;
        hidden var _height;
        hidden var _font;
        hidden var _justification;
        hidden var _step;
    
        function initialize() {
            DataField.initialize();
            _step = 0;
        }
    
        function onLayout(dc) {
            _width = dc.getWidth();
            _height = dc.getHeight();
            _font = Graphics.FONT_TINY;
            _justification = Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER;
        }
    
        function dumpValue(depth, label, value) {
            var s = "";
    
           for (var i = 0; i < depth; ++i) {
                s += "    ";
            }
    
            System.println(Lang.format("$1$$2$: $3$", [ s, label, value ]));
        }
    
        function dumpWorkoutStep(depth, field, workoutStep) {
            dumpValue(depth, field, workoutStep);
            depth += 1;
    
            if (workoutStep != null) {
                if (workoutStep has :durationType) {
                    dumpValue(depth, "durationType", workoutStep.durationType);
                }
                if (workoutStep has :durationValue) {
                    dumpValue(depth, "durationValue", workoutStep.durationValue);
                }
                if (workoutStep has :targetType) {
                    dumpValue(depth, "targetType", workoutStep.targetType);
                }
                if (workoutStep has :targetValueLow) {
                    dumpValue(depth, "targetValueLow", workoutStep.targetValueLow);
                }
                if (workoutStep has :targetValueHigh) {
                    dumpValue(depth, "targetValueHigh", workoutStep.targetValueHigh);
                }
            }
        }
    
        function dumpWorkoutIntervalStep(depth, name, intervalStep) {
            dumpValue(depth, name, intervalStep);
            depth += 1;
    
            if (intervalStep != null) {
                if (intervalStep has :activeStep) {
                    dumpWorkoutStep(depth, "activeStep", intervalStep.activeStep);
                }
                if (intervalStep has :repititionNumber) {
                    dumpValue(depth, "repititionNumber", intervalStep.repititionNumber);
                }
                if (intervalStep has :restStep) {
                    dumpWorkoutStep(depth, "restStep", intervalStep.restStep);
                }
            }
        }
    
        function dumpWorkoutStepInfo(depth, workoutStepInfo) {
            dumpValue(depth, "workoutStepInfo", workoutStepInfo);
            depth += 1;
    
            if (workoutStepInfo != null) {
                if (workoutStepInfo has :intensity) {
                    dumpValue(depth, "intensity", workoutStepInfo.intensity);
                }
                if (workoutStepInfo has :name) {
                    dumpValue(depth, "name", workoutStepInfo.name);
                }
                if (workoutStepInfo has :notes) {
                    dumpValue(depth, "notes", workoutStepInfo.notes);
                }
                if (workoutStepInfo has :sport) {
                    dumpValue(depth, "sport", workoutStepInfo.sport);
                }
                if (workoutStepInfo has :subSport) {
                    dumpValue(depth, "subSport", workoutStepInfo.subSport);
                }
                if (workoutStepInfo has :step) {
                    if (workoutStepInfo.step instanceof Activity.WorkoutStep) {
                        dumpWorkoutStep(depth, "step", workoutStepInfo.step);
                    }
                    else {
                        dumpWorkoutIntervalStep(depth, "step", workoutStepInfo.step);
                    }
                }
            }
    
            System.println("");
        }
    
        function onWorkoutStarted() {
            _step = 1;
            System.println("onWorkoutStarted");
            if (Activity has :getCurrentWorkoutStep) {
                var workoutStepInfo = Activity.getCurrentWorkoutStep();
                dumpWorkoutStepInfo(1, workoutStepInfo);
            }
        }
    
        function onWorkoutStepComplete() {
            ++_step;
    
            System.println("onWorkoutStepComplete");
            if (Activity has :getCurrentWorkoutStep) {
                var workoutStepInfo = Activity.getCurrentWorkoutStep();
                dumpWorkoutStepInfo(1, workoutStepInfo);
            }
        }
    
        function onUpdate(dc) {
            dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
            dc.clear();
    
            dc.drawText(_width / 2, _height / 2, _font, _step, _justification);
        }
    
    }
    
    class DataFieldApp extends Application.AppBase {
    
        function initialize() {
            AppBase.initialize();
        }
    
        function getInitialView() {
            return [ new DataFieldView() ];
        }
    
    }
    

    I used that code to run a simple test workout. Here is the output it produced.

    onWorkoutStarted
        workoutStepInfo: Obj: 132
            intensity: 2
            name: 
            notes: Warm Up Notes
            sport: 1
            subSport: 255
            step: Obj: 143
                durationType: 0
                durationValue: 5.000000
                targetType: 2
                targetValueLow: 0
                targetValueHigh: 0
    
    onWorkoutStepComplete
        workoutStepInfo: Obj: 132
            intensity: 0
            name: 
            notes: Run Notes
            sport: 1
            subSport: 255
            step: Obj: 143
                durationType: 0
                durationValue: 10.000000
                targetType: 0
                targetValueLow: 0.447000
                targetValueHigh: 4.470000
    
    onWorkoutStepComplete
        workoutStepInfo: Obj: 132
            intensity: 3
            name: 
            notes: Cool Down Notes
            sport: 1
            subSport: 255
            step: Obj: 143
                durationType: 0
                durationValue: 5.000000
                targetType: 2
                targetValueLow: 0
                targetValueHigh: 0
    
    

    Run_Workout_Test.FIT

  • I've not been able to create a workout that provides a step of type WorkoutIntervalStep. I'm looking at the code and everything seems correct. I see the code that creates them, but I'm just not seeing how you would go about creating such a FIT file.

    I'll need to ask around.