From 553f6171e6fff74e3da3aa2ca6623d5ca7e17b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sun, 5 May 2024 14:42:04 +0100 Subject: [PATCH] Garmin: Map all supported workout types --- .../devices/garmin/fit/FitImporter.java | 70 +++++++++++----- .../devices/garmin/fit/enums/GarminSport.java | 83 +++++++++++++++++++ .../gadgetbridge/util/Optional.java | 4 + .../garmin/fit/enums/GarminSportTest.java | 31 +++++++ 4 files changed, 165 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSport.java create mode 100644 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSportTest.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java index 68ebf5fec..11f63ed4b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java @@ -49,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminTimeUtils; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums.GarminSport; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitEvent; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitFileId; @@ -61,6 +62,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages. import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitStressLevel; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitTimeInZone; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.Optional; public class FitImporter { private static final Logger LOG = LoggerFactory.getLogger(FitImporter.class); @@ -301,29 +303,51 @@ public class FitImporter { } private int getActivityKind(final Integer sport, final Integer subsport) { - // TODO map all sports - if (sport != null) { - switch (sport) { - case 2: - return ActivityKind.TYPE_CYCLING; - case 4: // fitness_equipment - case 10: // training - if (subsport != null) { - switch (subsport) { - case 15: - return ActivityKind.TYPE_ELLIPTICAL_TRAINER; - case 20: - return ActivityKind.TYPE_STRENGTH_TRAINING; - default: - LOG.warn("Unknown sub sport {} for {}", subsport, sport); - } - break; - } - case 11: - return ActivityKind.TYPE_WALKING; - default: - LOG.warn("Unknown sport {}", sport); - } + final Optional garminSport = GarminSport.fromCodes(sport, subsport); + if (garminSport.isEmpty()) { + LOG.warn("Unknown garmin sport {}/{}", sport, subsport); + return ActivityKind.TYPE_UNKNOWN; + } + + switch (garminSport.get()) { + case RUN: + case PUSH_RUN_SPEED: + case INDOOR_PUSH_RUN_SPEED: + case INDOOR_TRACK: + return ActivityKind.TYPE_RUNNING; + case TREADMILL: + return ActivityKind.TYPE_TREADMILL; + case E_BIKE: + case BIKE: + return ActivityKind.TYPE_CYCLING; + case BIKE_INDOOR: + return ActivityKind.TYPE_INDOOR_CYCLING; + case ELLIPTICAL: + return ActivityKind.TYPE_ELLIPTICAL_TRAINER; + case STAIR_STEPPER: + case PILATES: + case CARDIO: + return ActivityKind.TYPE_EXERCISE; + case POOL_SWIM: + return ActivityKind.TYPE_SWIMMING; + case OPEN_WATER: + return ActivityKind.TYPE_SWIMMING_OPENWATER; + case SOCCER: + return ActivityKind.TYPE_SOCCER; + case STRENGTH: + return ActivityKind.TYPE_STRENGTH_TRAINING; + case YOGA: + return ActivityKind.TYPE_YOGA; + case WALK: + case WALK_INDOOR: + case PUSH_WALK_SPEED: + case INDOOR_PUSH_WALK_SPEED: + return ActivityKind.TYPE_WALKING; + case HIKE: + return ActivityKind.TYPE_HIKING; + case CLIMB_INDOOR: + case BOULDERING: + return ActivityKind.TYPE_CLIMBING; } return ActivityKind.TYPE_UNKNOWN; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSport.java new file mode 100644 index 000000000..b1706241b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSport.java @@ -0,0 +1,83 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums; + +import nodomain.freeyourgadget.gadgetbridge.util.Optional; + +// Taken from CHANGELOG.fit of a Venu 3 +public enum GarminSport { + NAVIGATE(0, 50), + RUN(1, 0), + TREADMILL(1, 1), + INDOOR_TRACK(1, 45), + BIKE(2, 0), + BIKE_INDOOR(2, 6), + HANDCYCLING(2, 12), + E_BIKE(2, 28), + HANDCYCLING_INDOOR(2, 88), + ELLIPTICAL(4, 15), + STAIR_STEPPER(4, 16), + PILATES(4, 44), + POOL_SWIM(5, 17), + OPEN_WATER(5, 18), + SOCCER(7, 0), + TENNIS(8, 0), + PLATFORM_TENNIS(8, 93), + TABLE_TENNIS(8, 97), + STRENGTH(10, 20), + CARDIO(10, 26), + YOGA(10, 43), + BREATHWORK(10, 62), + WALK(11, 0), + WALK_INDOOR(11, 27), + XC_CLASSIC_SKI(12, 0), + SKI(13, 0), + SNOWBOARD(14, 0), + HIKE(17, 0), + CLIMB_INDOOR(31, 68), + BOULDERING(31, 69), + SNOWSHOE(35, 0), + STAND_UP_PADDLEBOARDING(37, 0), + SOFTBALL(50, 0), + HEALTH_SNAPSHOT(60, 0), + HIIT(62, 0), + PICKLEBALL(64, 84), + PADEL(64, 85), + SQUASH(64, 94), + RACQUETBALL(64, 96), + PUSH_WALK_SPEED(65, 0), + INDOOR_PUSH_WALK_SPEED(65, 86), + PUSH_RUN_SPEED(66, 0), + INDOOR_PUSH_RUN_SPEED(66, 87), + DISC_GOLF(69, 0), + ULTIMATE_DISC(69, 92), + RUGBY(72, 0), + LACROSSE(74, 0), + VOLLEYBALL(75, 0), + MIXED_MARTIAL_ARTS(80, 0), // aka MMA + ; + + private final int type; + private final int subtype; + + GarminSport(final int type, final int subtype) { + this.type = type; + this.subtype = subtype; + } + + public int getType() { + return type; + } + + public int getSubtype() { + return subtype; + } + + public static Optional fromCodes(final int type, final int subtype) { + for (final GarminSport value : GarminSport.values()) { + if (value.getType() == type && value.getSubtype() == subtype) { + return Optional.of(value); + } + } + + return Optional.empty(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Optional.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Optional.java index 0c038606c..1d1bf4e34 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Optional.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Optional.java @@ -40,6 +40,10 @@ public final class Optional { return value; } + public boolean isEmpty() { + return value == null; + } + public boolean isPresent() { return value != null; } diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSportTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSportTest.java new file mode 100644 index 000000000..c74f69aa6 --- /dev/null +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSportTest.java @@ -0,0 +1,31 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums; + +import static org.junit.Assert.assertTrue; + +import android.util.Pair; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import nodomain.freeyourgadget.gadgetbridge.test.TestBase; + +public class GarminSportTest extends TestBase { + @Test + public void testNoDuplicates() { + // Ensure there are no duplicated sports with the same type and subtype + final Set duplicates = new HashSet<>(); + final Set> seen = new HashSet<>(); + + for (final GarminSport sport : GarminSport.values()) { + final Pair codePair = Pair.create(sport.getType(), sport.getSubtype()); + if (seen.contains(codePair)) { + duplicates.add(sport); + } + seen.add(codePair); + } + + assertTrue("Duplicated sport codes: " + duplicates, duplicates.isEmpty()); + } +}