diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java
index ec615dfda..002b77f10 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java
@@ -106,6 +106,7 @@ public class ActivitySummaryEntries {
public static final String MAXIMUM_OXYGEN_UPTAKE = "maximumOxygenUptake";
public static final String RECOVERY_TIME = "recoveryTime";
public static final String ESTIMATED_SWEAT_LOSS = "estimatedSweatLoss";
+ public static final String LACTATE_THRESHOLD_HR = "lactateThresholdHeartRate";
public static final String CYCLING_POWER_AVERAGE = "cyclingPowerAverage";
public static final String CYCLING_POWER_MIN = "cyclingPowerMin";
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java
index e214cfbc2..82238d142 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java
@@ -226,7 +226,7 @@ public class ActivitySummaryJsonSummary {
));
put("TrainingEffect", Arrays.asList(
TRAINING_EFFECT_AEROBIC, TRAINING_EFFECT_ANAEROBIC, WORKOUT_LOAD,
- MAXIMUM_OXYGEN_UPTAKE
+ MAXIMUM_OXYGEN_UPTAKE, RECOVERY_TIME, LACTATE_THRESHOLD_HR
));
put("laps", Arrays.asList(
LAP_PACE_AVERAGE, LAPS, LANE_LENGTH
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 f56cf0bde..f93d5da4c 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
@@ -14,11 +14,20 @@ import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_FAT_BURN;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_NA;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_WARM_UP;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.LACTATE_THRESHOLD_HR;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.MAXIMUM_OXYGEN_UPTAKE;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.RECOVERY_TIME;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TRAINING_EFFECT_AEROBIC;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TRAINING_EFFECT_ANAEROBIC;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_BPM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_HOURS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_KCAL;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_METERS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_ML;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_ML_KG_MIN;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_NONE;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.WORKOUT_LOAD;
import android.content.Context;
import android.widget.Toast;
@@ -76,6 +85,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitHrvSummary;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitHrvValue;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitMonitoring;
+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;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepStage;
@@ -105,6 +115,7 @@ public class FitImporter {
private FitFileId fileId = null;
private FitSession session = null;
private FitSport sport = null;
+ private FitPhysiologicalMetrics physiologicalMetrics = null;
public FitImporter(final Context context, final GBDevice gbDevice) {
this.context = context;
@@ -201,6 +212,9 @@ public class FitImporter {
// We only support 1 session
session = (FitSession) record;
}
+ } else if (record instanceof FitPhysiologicalMetrics) {
+ LOG.debug("Physiological Metrics: {}", record);
+ physiologicalMetrics = (FitPhysiologicalMetrics) record;
} else if (record instanceof FitSport) {
LOG.debug("Sport: {}", record);
if (sport != null) {
@@ -379,6 +393,25 @@ public class FitImporter {
}
}
+ if (physiologicalMetrics != null) {
+ // TODO lactate_threshold_heart_rate
+ if (physiologicalMetrics.getAerobicEffect() != null) {
+ summaryData.add(TRAINING_EFFECT_AEROBIC, physiologicalMetrics.getAerobicEffect(), UNIT_NONE);
+ }
+ if (physiologicalMetrics.getAnaerobicEffect() != null) {
+ summaryData.add(TRAINING_EFFECT_ANAEROBIC, physiologicalMetrics.getAnaerobicEffect(), UNIT_NONE);
+ }
+ if (physiologicalMetrics.getMetMax() != null) {
+ summaryData.add(MAXIMUM_OXYGEN_UPTAKE, physiologicalMetrics.getMetMax().floatValue() * 3.5f, UNIT_ML_KG_MIN);
+ }
+ if (physiologicalMetrics.getRecoveryTime() != null) {
+ summaryData.add(RECOVERY_TIME, physiologicalMetrics.getRecoveryTime() / 60f, UNIT_HOURS);
+ }
+ if (physiologicalMetrics.getLactateThresholdHeartRate() != null) {
+ summaryData.add(LACTATE_THRESHOLD_HR, physiologicalMetrics.getLactateThresholdHeartRate(), UNIT_BPM);
+ }
+ }
+
summary.setSummaryData(summaryData.toString());
if (file != null) {
summary.setRawDetailsPath(file.getAbsolutePath());
@@ -457,6 +490,7 @@ public class FitImporter {
fileId = null;
session = null;
sport = null;
+ physiologicalMetrics = null;
}
private void persistActivitySamples() {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java
index 9273f1a44..abc197a36 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java
@@ -208,6 +208,17 @@ public class GlobalFITMessage {
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
));
+ // https://github.com/GoldenCheetah/GoldenCheetah/blob/71e3928bc614f3209d9977d90cc50b942999b855/src/FileIO/FitRideFile.cpp#L1998
+ public static GlobalFITMessage PHYSIOLOGICAL_METRICS = new GlobalFITMessage(140, "PHYSIOLOGICAL_METRICS", Arrays.asList(
+ new FieldDefinitionPrimitive(4, BaseType.UINT8, "aerobic_effect", 10, 0),
+ new FieldDefinitionPrimitive(7, BaseType.SINT32, "met_max", 65536, 0),
+ new FieldDefinitionPrimitive(9, BaseType.UINT16, "recovery_time", 1, 0), // minutes
+ new FieldDefinitionPrimitive(14, BaseType.UINT16, "lactate_threshold_heart_rate", 1, 0), // bpm
+ //new FieldDefinitionPrimitive(15, BaseType.UINT16, "lactate_threshold_speed", 1, 0), // m/s // TODO confirm scale
+ new FieldDefinitionPrimitive(20, BaseType.UINT8, "anaerobic_effect", 10, 0),
+ new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+ ));
+
public static GlobalFITMessage WATCHFACE_SETTINGS = new GlobalFITMessage(159, "WATCHFACE_SETTINGS", Arrays.asList(
new FieldDefinitionPrimitive(0, BaseType.ENUM, "mode"), //1=analog
new FieldDefinitionPrimitive(1, BaseType.BASE_TYPE_BYTE, "layout")
@@ -314,6 +325,7 @@ public class GlobalFITMessage {
put(55, MONITORING);
put(127, CONNECTIVITY);
put(128, WEATHER);
+ put(140, PHYSIOLOGICAL_METRICS);
put(159, WATCHFACE_SETTINGS);
put(160, GPS_METADATA);
put(206, FIELD_DESCRIPTION);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitPhysiologicalMetrics.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitPhysiologicalMetrics.java
new file mode 100644
index 000000000..6410c27a5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitPhysiologicalMetrics.java
@@ -0,0 +1,52 @@
+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 FitPhysiologicalMetrics extends RecordData {
+ public FitPhysiologicalMetrics(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+ super(recordDefinition, recordHeader);
+
+ final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+ if (globalNumber != 140) {
+ throw new IllegalArgumentException("FitPhysiologicalMetrics expects global messages of " + 140 + ", got " + globalNumber);
+ }
+ }
+
+ @Nullable
+ public Float getAerobicEffect() {
+ return (Float) getFieldByNumber(4);
+ }
+
+ @Nullable
+ public Double getMetMax() {
+ return (Double) getFieldByNumber(7);
+ }
+
+ @Nullable
+ public Integer getRecoveryTime() {
+ return (Integer) getFieldByNumber(9);
+ }
+
+ @Nullable
+ public Integer getLactateThresholdHeartRate() {
+ return (Integer) getFieldByNumber(14);
+ }
+
+ @Nullable
+ public Float getAnaerobicEffect() {
+ return (Float) getFieldByNumber(20);
+ }
+
+ @Nullable
+ public Long getTimestamp() {
+ return (Long) getFieldByNumber(253);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java
index e2a33e988..e6977b802 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java
@@ -47,6 +47,8 @@ public class FitRecordDataFactory {
return new FitConnectivity(recordDefinition, recordHeader);
case 128:
return new FitWeather(recordDefinition, recordHeader);
+ case 140:
+ return new FitPhysiologicalMetrics(recordDefinition, recordHeader);
case 159:
return new FitWatchfaceSettings(recordDefinition, recordHeader);
case 160:
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1cc2f6a87..83581ae34 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2131,6 +2131,8 @@
Workout Load
Maximum Oxygen Uptake
Estimated Sweat Loss
+ Lactate Threshold Heart Rate
+ Recovery Time
Average Stride
Max Stride
Min Stride