Garmin: Parse some swimming stats

This commit is contained in:
José Rebelo 2025-01-24 22:29:12 +00:00
parent b4dc1d2358
commit bcc6c23097
9 changed files with 253 additions and 0 deletions

View File

@ -28,6 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums.GarminSport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitLap;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitPhysiologicalMetrics;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSession;
@ -47,6 +48,7 @@ public class GarminWorkoutParser implements ActivitySummaryParser {
private FitSport sport = null;
private FitPhysiologicalMetrics physiologicalMetrics = null;
private final List<FitSet> sets = new ArrayList<>();
private final List<FitLap> laps = new ArrayList<>();
public GarminWorkoutParser(final Context context) {
this.context = context;
@ -102,6 +104,7 @@ public class GarminWorkoutParser implements ActivitySummaryParser {
sport = null;
physiologicalMetrics = null;
sets.clear();
laps.clear();
}
public boolean handleRecord(final RecordData record) {
@ -132,6 +135,9 @@ public class GarminWorkoutParser implements ActivitySummaryParser {
} else if (record instanceof FitSet) {
LOG.trace("Set: {}", record);
sets.add((FitSet) record);
} else if (record instanceof FitLap) {
LOG.trace("Lap: {}", record);
laps.add((FitLap) record);
} else {
return false;
}
@ -166,6 +172,12 @@ public class GarminWorkoutParser implements ActivitySummaryParser {
if (session.getTotalDistance() != null) {
summaryData.add(DISTANCE_METERS, session.getTotalDistance() / 100f, UNIT_METERS);
}
if (session.getPoolLength() != null) {
summaryData.add(POOL_LENGTH, session.getPoolLength(), UNIT_METERS);
}
if (session.getAvgSwolf() != null) {
summaryData.add(SWOLF_AVG, session.getAvgSwolf(), UNIT_NONE);
}
if (session.getTotalSteps() != null) {
summaryData.add(STEPS, session.getTotalSteps(), UNIT_STEPS);
}
@ -214,6 +226,9 @@ public class GarminWorkoutParser implements ActivitySummaryParser {
if (session.getTotalDescent() != null) {
summaryData.add(DESCENT_DISTANCE, session.getTotalDescent(), UNIT_METERS);
}
if (session.getAvgSwimCadence() != null) {
summaryData.add(SWIM_AVG_CADENCE, session.getAvgSwimCadence(), UNIT_STROKES_PER_LENGTH);
}
if (session.getEnhancedAvgSpeed() != null) {
if (ActivityKind.isPaceActivity(activityKind)) {
@ -343,6 +358,10 @@ public class GarminWorkoutParser implements ActivitySummaryParser {
}
}
if (!laps.isEmpty()) {
final boolean anySwolf = laps.stream().anyMatch(l -> l.getAvgSwolf() != null);
}
summaryData.add(
INTERNAL_HAS_GPS,
String.valueOf(activityPoints.stream().anyMatch(p -> p.getLocation() != null))

View File

@ -67,6 +67,7 @@ public class ActivitySummaryEntries {
public static final String EVERSION_ANGLE_MAX = "eversionAngleMax";
public static final String DISTANCE_METERS = "distanceMeters";
public static final String POOL_LENGTH = "poolLength";
public static final String ELEVATION_GAIN = "elevationGain";
public static final String ELEVATION_LOSS = "elevationLoss";
@ -119,6 +120,7 @@ public class ActivitySummaryEntries {
public static final String SWOLF_AVG = "swolfAvg";
public static final String SWOLF_MAX = "swolfMax";
public static final String SWOLF_MIN = "swolfMin";
public static final String SWIM_AVG_CADENCE = "swim_avg_cadence";
public static final String CALORIES_BURNT = "caloriesBurnt";
public static final String TRAINING_EFFECT_AEROBIC = "aerobicTrainingEffect";
@ -159,6 +161,7 @@ public class ActivitySummaryEntries {
public static final String UNIT_STROKES = "strokes_unit";
public static final String UNIT_STROKES_PER_MINUTE = "strokes_minute";
public static final String UNIT_STROKES_PER_SECOND = "strokes_second";
public static final String UNIT_STROKES_PER_LENGTH = "strokes_per_length";
public static final String UNIT_JUMPS = "jumps_unit";
public static final String UNIT_JUMPS_PER_MINUTE = "jumps_minute";
public static final String UNIT_YARD = "yard";

View File

@ -190,6 +190,7 @@ public class ActivitySummaryJsonSummary {
JUMPS, JUMP_RATE_AVG, JUMP_RATE_MAX
));
put(GROUP_SWIMMING, Arrays.asList(
POOL_LENGTH, SWIM_AVG_CADENCE,
SWOLF_INDEX, SWOLF_AVG, SWOLF_MAX, SWOLF_MIN, SWIM_STYLE
));
put(GROUP_TRAINING_EFFECT, Arrays.asList(

View File

@ -10,6 +10,7 @@ import java.util.Map;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
/** @noinspection ArraysAsListWithZeroOrOneArgument*/
public class GlobalFITMessage {
public static GlobalFITMessage FILE_ID = new GlobalFITMessage(0, "FILE_ID", Arrays.asList(
new FieldDefinitionPrimitive(0, BaseType.ENUM, "type", FieldDefinitionFactory.FIELD.FILE_TYPE),
@ -112,8 +113,13 @@ public class GlobalFITMessage {
new FieldDefinitionPrimitive(30, BaseType.SINT32, "nec_longitude", FieldDefinitionFactory.FIELD.COORDINATE),
new FieldDefinitionPrimitive(31, BaseType.SINT32, "swc_latitude", FieldDefinitionFactory.FIELD.COORDINATE),
new FieldDefinitionPrimitive(32, BaseType.SINT32, "swc_longitude", FieldDefinitionFactory.FIELD.COORDINATE),
new FieldDefinitionPrimitive(33, BaseType.UINT16, "num_lengths"),
new FieldDefinitionPrimitive(38, BaseType.SINT32, "end_latitude", FieldDefinitionFactory.FIELD.COORDINATE),
new FieldDefinitionPrimitive(39, BaseType.SINT32, "end_longitude", FieldDefinitionFactory.FIELD.COORDINATE),
new FieldDefinitionPrimitive(44, BaseType.UINT16, "pool_length", 100, 0), // m
new FieldDefinitionPrimitive(47, BaseType.UINT16, "num_active_lengths"),
new FieldDefinitionPrimitive(79, BaseType.UINT16, "avg_swim_cadence", 10, 0), // rpm
new FieldDefinitionPrimitive(80, BaseType.UINT16, "avg_swolf"),
new FieldDefinitionPrimitive(110, BaseType.STRING, 64, "sport_profile_name"),
new FieldDefinitionPrimitive(124, BaseType.UINT32, "enhanced_avg_speed", 1000, 0), // m/s
new FieldDefinitionPrimitive(125, BaseType.UINT32, "enhanced_max_speed", 1000, 0), // m/s
@ -121,14 +127,19 @@ public class GlobalFITMessage {
new FieldDefinitionPrimitive(170, BaseType.UINT16, "enhanced_max_respiration_rate", 100, 0 ), // breaths/min
new FieldDefinitionPrimitive(178, BaseType.UINT16, "estimated_sweat_loss"), // ml
new FieldDefinitionPrimitive(180, BaseType.UINT16, "enhanced_min_respiration_rate", 100, 0 ), // breaths/min
new FieldDefinitionPrimitive(188, BaseType.ENUM, "primary_benefit"), // 1 recovery
new FieldDefinitionPrimitive(194, BaseType.UINT8, "avg_spo2"),
new FieldDefinitionPrimitive(195, BaseType.UINT8, "avg_stress"),
new FieldDefinitionPrimitive(196, BaseType.UINT16, "resting_calories"), // kcal
new FieldDefinitionPrimitive(197, BaseType.UINT8, "hrv_sdrr"), // ms
new FieldDefinitionPrimitive(198, BaseType.UINT8, "hrv_rmssd"), // ms
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
));
public static GlobalFITMessage LAP = new GlobalFITMessage(19, "LAP", Arrays.asList(
new FieldDefinitionPrimitive(0, BaseType.ENUM, "event"), // 9 lap
new FieldDefinitionPrimitive(1, BaseType.ENUM, "event_type"), // 1 stop
new FieldDefinitionPrimitive(2, BaseType.UINT32, "start_time"),
new FieldDefinitionPrimitive(3, BaseType.SINT32, "start_lat", FieldDefinitionFactory.FIELD.COORDINATE),
new FieldDefinitionPrimitive(4, BaseType.SINT32, "start_long", FieldDefinitionFactory.FIELD.COORDINATE),
new FieldDefinitionPrimitive(5, BaseType.SINT32, "end_lat", FieldDefinitionFactory.FIELD.COORDINATE),
@ -136,8 +147,24 @@ public class GlobalFITMessage {
new FieldDefinitionPrimitive(7, BaseType.UINT32, "total_elapsed_time", 1000, 0), // s
new FieldDefinitionPrimitive(8, BaseType.UINT32, "total_timer_time", 1000, 0), // s
new FieldDefinitionPrimitive(9, BaseType.UINT32, "total_distance", 100, 0), // m
new FieldDefinitionPrimitive(10, BaseType.UINT32, "total_cycles"),
new FieldDefinitionPrimitive(11, BaseType.UINT16, "total_calores"),
new FieldDefinitionPrimitive(15, BaseType.UINT8, "avg_heart_rate"),
new FieldDefinitionPrimitive(16, BaseType.UINT8, "max_heart_rate"),
new FieldDefinitionPrimitive(17, BaseType.UINT8, "avg_cadence"), // rpm
new FieldDefinitionPrimitive(21, BaseType.UINT16, "total_ascent"), // m
new FieldDefinitionPrimitive(22, BaseType.UINT16, "total_descent"), // m
new FieldDefinitionPrimitive(24, BaseType.ENUM, "lap_trigger"), // 0 manual
new FieldDefinitionPrimitive(25, BaseType.ENUM, "sport"), // 5 swimming
new FieldDefinitionPrimitive(32, BaseType.UINT16, "num_lengths"),
new FieldDefinitionPrimitive(35, BaseType.UINT16, "first_length_index"),
new FieldDefinitionPrimitive(37, BaseType.UINT16, "avg_stroke_distance"),
new FieldDefinitionPrimitive(38, BaseType.ENUM, "swim_style"), // 0 freestyle 2 breaststroke 5 mixed
new FieldDefinitionPrimitive(39, BaseType.ENUM, "sub_sport"), // 17 = lap swimming
new FieldDefinitionPrimitive(40, BaseType.UINT16, "num_active_lengths"),
new FieldDefinitionPrimitive(73, BaseType.UINT16, "avg_swolf"),
new FieldDefinitionPrimitive(110, BaseType.UINT32, "enhanced_avg_speed", 100, 0), // m/s
new FieldDefinitionPrimitive(111, BaseType.UINT32, "enhanced_max_speed", 100, 0), // m/s
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
));
@ -193,6 +220,16 @@ public class GlobalFITMessage {
new FieldDefinitionPrimitive(5, BaseType.STRING, 16, "name")
));
public static GlobalFITMessage ACTIVITY = new GlobalFITMessage(34, "ACTIVITY", Arrays.asList(
new FieldDefinitionPrimitive(0, BaseType.UINT32, "total_timer_time"),
new FieldDefinitionPrimitive(1, BaseType.UINT16, "num_sessions"),
new FieldDefinitionPrimitive(2, BaseType.ENUM, "type"), // 0
new FieldDefinitionPrimitive(3, BaseType.ENUM, "event"), // 26 activity
new FieldDefinitionPrimitive(4, BaseType.ENUM, "event_type"), // 1 stop
new FieldDefinitionPrimitive(5, BaseType.UINT32, "local_timestamp"), // garmin timestamp, but in user timezone
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
));
public static GlobalFITMessage FILE_CREATOR = new GlobalFITMessage(49, "FILE_CREATOR", Arrays.asList(
new FieldDefinitionPrimitive(0, BaseType.UINT16, "software_version"),
new FieldDefinitionPrimitive(1, BaseType.UINT8, "hardware_version")
@ -427,6 +464,7 @@ public class GlobalFITMessage {
put(23, DEVICE_INFO);
put(26, WORKOUT);
put(31, COURSE);
put(34, ACTIVITY);
put(49, FILE_CREATOR);
put(55, MONITORING);
put(103, MONITORING_INFO);

View File

@ -0,0 +1,57 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
import androidx.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
//
// WARNING: This class was auto-generated, please avoid modifying it directly.
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
//
public class FitActivity extends RecordData {
public FitActivity(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
super(recordDefinition, recordHeader);
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
if (globalNumber != 34) {
throw new IllegalArgumentException("FitActivity expects global messages of " + 34 + ", got " + globalNumber);
}
}
@Nullable
public Long getTotalTimerTime() {
return (Long) getFieldByNumber(0);
}
@Nullable
public Integer getNumSessions() {
return (Integer) getFieldByNumber(1);
}
@Nullable
public Integer getType() {
return (Integer) getFieldByNumber(2);
}
@Nullable
public Integer getEvent() {
return (Integer) getFieldByNumber(3);
}
@Nullable
public Integer getEventType() {
return (Integer) getFieldByNumber(4);
}
@Nullable
public Long getLocalTimestamp() {
return (Long) getFieldByNumber(5);
}
@Nullable
public Long getTimestamp() {
return (Long) getFieldByNumber(253);
}
}

View File

@ -20,6 +20,21 @@ public class FitLap extends RecordData {
}
}
@Nullable
public Integer getEvent() {
return (Integer) getFieldByNumber(0);
}
@Nullable
public Integer getEventType() {
return (Integer) getFieldByNumber(1);
}
@Nullable
public Long getStartTime() {
return (Long) getFieldByNumber(2);
}
@Nullable
public Double getStartLat() {
return (Double) getFieldByNumber(3);
@ -55,6 +70,31 @@ public class FitLap extends RecordData {
return (Double) getFieldByNumber(9);
}
@Nullable
public Long getTotalCycles() {
return (Long) getFieldByNumber(10);
}
@Nullable
public Integer getTotalCalores() {
return (Integer) getFieldByNumber(11);
}
@Nullable
public Integer getAvgHeartRate() {
return (Integer) getFieldByNumber(15);
}
@Nullable
public Integer getMaxHeartRate() {
return (Integer) getFieldByNumber(16);
}
@Nullable
public Integer getAvgCadence() {
return (Integer) getFieldByNumber(17);
}
@Nullable
public Integer getTotalAscent() {
return (Integer) getFieldByNumber(21);
@ -65,6 +105,61 @@ public class FitLap extends RecordData {
return (Integer) getFieldByNumber(22);
}
@Nullable
public Integer getLapTrigger() {
return (Integer) getFieldByNumber(24);
}
@Nullable
public Integer getSport() {
return (Integer) getFieldByNumber(25);
}
@Nullable
public Integer getNumLengths() {
return (Integer) getFieldByNumber(32);
}
@Nullable
public Integer getFirstLengthIndex() {
return (Integer) getFieldByNumber(35);
}
@Nullable
public Integer getAvgStrokeDistance() {
return (Integer) getFieldByNumber(37);
}
@Nullable
public Integer getSwimStyle() {
return (Integer) getFieldByNumber(38);
}
@Nullable
public Integer getSubSport() {
return (Integer) getFieldByNumber(39);
}
@Nullable
public Integer getNumActiveLengths() {
return (Integer) getFieldByNumber(40);
}
@Nullable
public Integer getAvgSwolf() {
return (Integer) getFieldByNumber(73);
}
@Nullable
public Double getEnhancedAvgSpeed() {
return (Double) getFieldByNumber(110);
}
@Nullable
public Double getEnhancedMaxSpeed() {
return (Double) getFieldByNumber(111);
}
@Nullable
public Long getTimestamp() {
return (Long) getFieldByNumber(253);

View File

@ -41,6 +41,8 @@ public class FitRecordDataFactory {
return new FitWorkout(recordDefinition, recordHeader);
case 31:
return new FitCourse(recordDefinition, recordHeader);
case 34:
return new FitActivity(recordDefinition, recordHeader);
case 49:
return new FitFileCreator(recordDefinition, recordHeader);
case 55:

View File

@ -140,6 +140,11 @@ public class FitSession extends RecordData {
return (Double) getFieldByNumber(32);
}
@Nullable
public Integer getNumLengths() {
return (Integer) getFieldByNumber(33);
}
@Nullable
public Double getEndLatitude() {
return (Double) getFieldByNumber(38);
@ -150,6 +155,26 @@ public class FitSession extends RecordData {
return (Double) getFieldByNumber(39);
}
@Nullable
public Float getPoolLength() {
return (Float) getFieldByNumber(44);
}
@Nullable
public Integer getNumActiveLengths() {
return (Integer) getFieldByNumber(47);
}
@Nullable
public Float getAvgSwimCadence() {
return (Float) getFieldByNumber(79);
}
@Nullable
public Integer getAvgSwolf() {
return (Integer) getFieldByNumber(80);
}
@Nullable
public String getSportProfileName() {
return (String) getFieldByNumber(110);
@ -185,6 +210,11 @@ public class FitSession extends RecordData {
return (Float) getFieldByNumber(180);
}
@Nullable
public Integer getPrimaryBenefit() {
return (Integer) getFieldByNumber(188);
}
@Nullable
public Integer getAvgSpo2() {
return (Integer) getFieldByNumber(194);
@ -195,6 +225,11 @@ public class FitSession extends RecordData {
return (Integer) getFieldByNumber(195);
}
@Nullable
public Integer getRestingCalories() {
return (Integer) getFieldByNumber(196);
}
@Nullable
public Integer getHrvSdrr() {
return (Integer) getFieldByNumber(197);

View File

@ -2208,6 +2208,9 @@
<string name="sonyswr12_settings_alarm_interval_summary">Smart alarm interval is interval before of installed alarm. In this interval device is trying to detect lightest phase of sleep to awake user</string>
<!-- activity summary labels-->
<string name="distanceMeters">Distance</string>
<string name="poolLength">Pool length</string>
<string name="swim_avg_cadence">Avg Swim Cadence</string>
<string name="strokes_per_length">strokes/length</string>
<string name="ascentMeters">Uphill</string>
<string name="descentMeters">Downhill</string>
<string name="ascentDistance">Uphill distance</string>