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

  • I have not been able to simulate a workout in the Garmin simulator (using version 2.3.5). When I select a fit file with a workout and then set simulate data all matrics stay at zero and a dump of workoutdata delivers a null.

    Has anyone succeeded?

  • No, the simulator does not support workouts. You have to test workouts 'in the field', sideloading a device.

  • I'm almost certain I've been able to do this, but it is limited. You can play back a FIT file workout and simulate data together. I can't recall exactly how to do it.

  • I just used this code to do it.

    using Toybox.Application;
    using Toybox.WatchUi;
    using Toybox.Graphics;
    using Toybox.System;
    using Toybox.Lang;
    
    const INTENSITY_NAMES = [
        "ACTIVE",
        "REST",
        "WARMUP",
        "COOLDOWN",
        "RECOVERY",
        "INTERVAL"
    ];
    
    const DURATION_NAMES = [
        "TIME",
        "DISTANCE",
        "HR_LESS_THAN",
        "HR_GREATER_THAN",
        "CALORIES",
        "OPEN",
        "REPEAT_UNTIL_STEPS_COMPLETE",
        "REPEAT_UNTIL_TIME",
        "REPEAT_UNTIL_DISTANCE",
        "REPEAT_UNTIL_CALORIES",
        "REPEAT_UNTIL_HR_LESS_THAN",
        "REPEAT_UNTIL_HR_GREATER_THAN",
        "REPEAT_UNTIL_POWER_LESS_THAN",
        "REPEAT_UNTIL_POWER_GREATER_THAN",
        "POWER_LESS_THAN",
        "POWER_GREATER_THAN",
        "TRAINING_PEAKS_TRAINING_STRESS_SCORE",
        "REPEAT_UNTIL_POWER_LAST_LAP_LESS_THAN",
        "REPEAT_UNTIL_MAX_POWER_LAST_LAP_LESS_THAN",
        "POWER_3S_LESS_THAN",
        "POWER_10S_LESS_THAN",
        "POWER_30S_LESS_THAN",
        "POWER_3S_GREATER_THAN",
        "POWER_10S_GREATER_THAN",
        "POWER_30S_GREATER_THAN",
        "POWER_LAP_LESS_THAN",
        "POWER_LAP_GREATER_THAN",
        "REPEAT_UNTIL_TRAINING_PEAKS_TRAINING_STRESS_SCORE",
        "REPETITION_TIME",
        "REPS",
    ];
    
    const TARGET_NAMES = [
        "SPEED",
        "HEART_RATE",
        "OPEN",
        "CADENCE",
        "POWER",
        "GRADE",
        "RESISTANCE",
        "POWER_3S",
        "POWER_10S",
        "POWER_30S",
        "POWER_LAP",
        "SWIM_STROKE",
        "SPEED_LAP",
        "HEART_RATE_LAP",
        "INHALE_DURATION",
        "INHALE_HOLD_DURATION",
        "EXHALE_DURATION",
        "EXHALE_HOLD_DURATION",
        "POWER_CURVE",
    ];
    
    const SPORT_NAMES = [
        "GENERIC",
        "RUNNING",
        "CYCLING",
        "TRANSITION",
        "FITNESS_EQUIPMENT",
        "SWIMMING",
        "BASKETBALL",
        "SOCCER",
        "TENNIS",
        "AMERICAN_FOOTBALL",
        "TRAINING",
        "WALKING",
        "CROSS_COUNTRY_SKIING",
        "ALPINE_SKIING",
        "SNOWBOARDING",
        "ROWING",
        "MOUNTAINEERING",
        "HIKING",
        "MULTISPORT",
        "PADDLING",
        "FLYING",
        "E_BIKING",
        "MOTORCYCLING",
        "BOATING",
        "DRIVING",
        "GOLF",
        "HANG_GLIDING",
        "HORSEBACK_RIDING",
        "HUNTING",
        "FISHING",
        "INLINE_SKATING",
        "ROCK_CLIMBING",
        "SAILING",
        "ICE_SKATING",
        "SKY_DIVING",
        "SNOWSHOEING",
        "SNOWMOBILING",
        "STAND_UP_PADDLEBOARDING",
        "SURFING",
        "WAKEBOARDING",
        "WATER_SKIING",
        "KAYAKING",
        "RAFTING",
        "WINDSURFING",
        "KITESURFING",
        "TACTICAL",
        "JUMPMASTER",
        "BOXING",
        "FLOOR_CLIMBING",
        "BASEBALL",
        "SOFTBALL_FAST_PITCH",
        "SOFTBALL_SLOW_PITCH",
        "SHOOTING",
        "AUTO_RACING",
    ];
    
    const SUB_SPORT_NAMES = [
        "GENERIC",
        "TREADMILL",
        "STREET",
        "TRAIL",
        "TRACK",
        "SPIN",
        "INDOOR_CYCLING",
        "ROAD",
        "MOUNTAIN",
        "DOWNHILL",
        "RECUMBENT",
        "CYCLOCROSS",
        "HAND_CYCLING",
        "TRACK_CYCLING",
        "INDOOR_ROWING",
        "ELLIPTICAL",
        "STAIR_CLIMBING",
        "LAP_SWIMMING",
        "OPEN_WATER",
        "FLEXIBILITY_TRAINING",
        "STRENGTH_TRAINING",
        "WARM_UP",
        "MATCH",
        "EXERCISE",
        "CHALLENGE",
        "INDOOR_SKIING",
        "CARDIO_TRAINING",
    ];
    
    class WorkoutStepsView extends WatchUi.DataField {
    
        hidden var _cx;
        hidden var _cy;
        hidden var _font;
        hidden var _font_height;
        hidden var _justification;
        hidden var _step;
        hidden var _lines;
    
        function initialize() {
            DataField.initialize();
            _step = 0;
            _lines = [];
        }
    
        function onLayout(dc) {
            _cx = dc.getWidth() / 2;
            _cy = dc.getHeight() / 2;
            _font = Graphics.FONT_XTINY;
            _font_height = dc.getFontHeight(_font);
            _justification = Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER;
        }
    
        function dumpValue(depth, label, value, lines) {
            var s = "";
    
           for (var i = 0; i < depth; ++i) {
                s += " ";
            }
    
            lines.add(Lang.format("$1$$2$: $3$", [ s, label, value ]));
        }
    
        function dumpEnumValue(depth, label, value, names, lines) {
            if (0 <= value && value < names.size()) {
                value = names[value];
            }
            else {
                value = "INVALID";
            }
    
            dumpValue(depth, label, value, lines);
        }
    
        function dumpWorkoutStep(depth, field, workoutStep, lines) {
            dumpValue(depth, field, workoutStep, lines);
            depth += 1;
    
            if (workoutStep != null) {
                if (workoutStep has :durationType) {
                    dumpEnumValue(depth, "durationType", workoutStep.durationType, DURATION_NAMES, lines);
                }
                if (workoutStep has :durationValue) {
                    dumpValue(depth, "durationValue", workoutStep.durationValue, lines);
                }
                if (workoutStep has :targetType) {
                    dumpEnumValue(depth, "targetType", workoutStep.targetType, TARGET_NAMES, lines);
                }
                if (workoutStep has :targetValueLow) {
                    dumpValue(depth, "targetValueLow", workoutStep.targetValueLow, lines);
                }
                if (workoutStep has :targetValueHigh) {
                    dumpValue(depth, "targetValueHigh", workoutStep.targetValueHigh, lines);
                }
            }
        }
    
        function dumpWorkoutIntervalStep(depth, name, intervalStep, lines) {
            dumpValue(depth, name, intervalStep, lines);
            depth += 1;
    
            if (intervalStep != null) {
                if (intervalStep has :activeStep) {
                    dumpWorkoutStep(depth, "activeStep", intervalStep.activeStep, lines);
                }
                if (intervalStep has :repititionNumber) {
                    dumpValue(depth, "repititionNumber", intervalStep.repititionNumber, lines);
                }
                if (intervalStep has :restStep) {
                    dumpWorkoutStep(depth, "restStep", intervalStep.restStep, lines);
                }
            }
        }
    
        function dumpWorkoutStepInfo(depth, workoutStepInfo, lines) {
            dumpValue(depth, "workoutStepInfo", workoutStepInfo, lines);
            depth += 1;
    
            if (workoutStepInfo != null) {
                if (workoutStepInfo has :intensity) {
                    dumpEnumValue(depth, "intensity", workoutStepInfo.intensity, INTENSITY_NAMES, lines);
                }
                if (workoutStepInfo has :name) {
                    dumpValue(depth, "name", workoutStepInfo.name, lines);
                }
                if (workoutStepInfo has :notes) {
                    dumpValue(depth, "notes", workoutStepInfo.notes, lines);
                }
                if (workoutStepInfo has :sport) {
                    dumpEnumValue(depth, "sport", workoutStepInfo.sport, SPORT_NAMES, lines);
                }
                if (workoutStepInfo has :subSport) {
                    dumpEnumValue(depth, "subSport", workoutStepInfo.subSport, SUB_SPORT_NAMES, lines);
                }
                if (workoutStepInfo has :step) {
                    if (workoutStepInfo.step instanceof Activity.WorkoutStep) {
                        dumpWorkoutStep(depth, "workoutStep", workoutStepInfo.step, lines);
                    }
                    else {
                        dumpWorkoutIntervalStep(depth, "workoutIntervalStep", workoutStepInfo.step, lines);
                    }
                }
            }
        }
    
        function onWorkoutStarted() {
            _step = 1;
            _lines = [];
    
            if (Activity has :getCurrentWorkoutStep) {
                var workoutStepInfo = Activity.getCurrentWorkoutStep();
                dumpWorkoutStepInfo(1, workoutStepInfo, _lines);
            }
            if (Activity has :getNextWorkoutStep) {
                var workoutStepInfo = Activity.getNextWorkoutStep();
                dumpWorkoutStepInfo(1, workoutStepInfo, _lines);
            }
    
            WatchUi.requestUpdate();
        }
    
        function onWorkoutStepComplete() {
            ++_step;
            _lines = [];
    
            if (Activity has :getCurrentWorkoutStep) {
                var workoutStepInfo = Activity.getCurrentWorkoutStep();
                dumpWorkoutStepInfo(1, workoutStepInfo, _lines);
            }
            if (Activity has :getNextWorkoutStep) {
                var workoutStepInfo = Activity.getNextWorkoutStep();
                dumpWorkoutStepInfo(1, workoutStepInfo, _lines);
            }
    
            WatchUi.requestUpdate();
        }
    
        function onUpdate(dc) {
            dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
            dc.clear();
    
            var cy = _cy - (_lines.size() * _font_height) / 2;
    
            for (var i = 0; i < _lines.size(); ++i) {
                dc.drawText(_cx, cy + (_font_height * i), _font, _lines[i], _justification);
            }
        }
    
    }
    
    class WorkoutStepsApp extends Application.AppBase {
    
        function initialize() {
            AppBase.initialize();
        }
    
        function getInitialView() {
            return [ new WorkoutStepsView() ];
        }
    
    }

    You play back the fit file, then simulate data, then start the workout (Press Start/Stop on the simulated device, or click Data Fields > Timer > Start Activity).

  • Yep, this works in the simulator! Thanks!

  • yeah. this works. Is there also simulated data when playing back the FIT workout file? I'm not getting any simulated values in the simulator. No Cad. No Power etc

  • There should be simulated data. I'm pretty sure there was when I tested this using the code above.

  • I just tried it again. I loaded the FIT file you provided Run_Workout_Test.FIT. and 

    You play back the fit file, then simulate data, then start the workout (Press Start/Stop on the simulated device, or click Data Fields > Timer > Start Activity).

    I tried all these and while the fit file plays, Pressing the start/stop etc did not have any simulated values on the screen. When I mean simulated values, I mean things like HR keeping on looping like an actual outdoor run for example.