1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-08-31 19:45:27 +02:00

Garmin: Parse workout physiological metrics

This commit is contained in:
José Rebelo 2024-08-25 22:49:47 +01:00
parent 21de228204
commit 42dfb6ad4a
7 changed files with 104 additions and 1 deletions

View File

@ -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";

View File

@ -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

View File

@ -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() {

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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:

View File

@ -2131,6 +2131,8 @@
<string name="currentWorkoutLoad">Workout Load</string>
<string name="maximumOxygenUptake">Maximum Oxygen Uptake</string>
<string name="estimatedSweatLoss">Estimated Sweat Loss</string>
<string name="lactateThresholdHeartRate">Lactate Threshold Heart Rate</string>
<string name="recoveryTime">Recovery Time</string>
<string name="averageStride">Average Stride</string>
<string name="maxStride">Max Stride</string>
<string name="minStride">Min Stride</string>