1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-10-06 05:07:18 +02:00

Garmin: Parse HR time in zone, sweat loss, avg and max HR

This commit is contained in:
José Rebelo 2024-08-21 15:03:30 +01:00
parent 880c85abcf
commit 10e27c88c6
12 changed files with 175 additions and 17 deletions

View File

@ -105,6 +105,7 @@ public class ActivitySummaryEntries {
public static final String WORKOUT_LOAD = "currentWorkoutLoad";
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 CYCLING_POWER_AVERAGE = "cyclingPowerAverage";
public static final String CYCLING_POWER_MIN = "cyclingPowerMin";
@ -114,6 +115,7 @@ public class ActivitySummaryEntries {
public static final String UNIT_CM = "cm";
public static final String UNIT_UNIX_EPOCH_SECONDS = "unix_epoch_seconds";
public static final String UNIT_KCAL = "calories_unit";
public static final String UNIT_ML = "ml";
public static final String UNIT_LAPS = "laps_unit";
public static final String UNIT_KILOMETERS = "km";
public static final String UNIT_METERS = "meters";

View File

@ -7,6 +7,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefi
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionFileType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalSource;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrTimeInZone;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrZoneHighBoundary;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrvStatus;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionLanguage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem;
@ -34,6 +36,10 @@ public class FieldDefinitionFactory {
return new FieldDefinitionGoalType(localNumber, size, baseType, name);
case HRV_STATUS:
return new FieldDefinitionHrvStatus(localNumber, size, baseType, name);
case HR_TIME_IN_ZONE:
return new FieldDefinitionHrTimeInZone(localNumber, size, baseType, name);
case HR_ZONE_HIGH_BOUNDARY:
return new FieldDefinitionHrZoneHighBoundary(localNumber, size, baseType, name);
case MEASUREMENT_SYSTEM:
return new FieldDefinitionMeasurementSystem(localNumber, size, baseType, name);
case TEMPERATURE:
@ -62,6 +68,8 @@ public class FieldDefinitionFactory {
GOAL_SOURCE,
GOAL_TYPE,
HRV_STATUS,
HR_TIME_IN_ZONE,
HR_ZONE_HIGH_BOUNDARY,
MEASUREMENT_SYSTEM,
TEMPERATURE,
TIMESTAMP,

View File

@ -5,8 +5,19 @@ import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.CALORIES_BURNT;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.DESCENT_DISTANCE;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.DISTANCE_METERS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ESTIMATED_SWEAT_LOSS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_AVG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_MAX;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_AEROBIC;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_ANAEROBIC;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_EXTREME;
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.UNIT_BPM;
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_SECONDS;
import android.content.Context;
@ -336,6 +347,15 @@ public class FitImporter {
if (session.getTotalCalories() != null) {
summaryData.add(CALORIES_BURNT, session.getTotalCalories(), UNIT_KCAL);
}
if (session.getEstimatedSweatLoss() != null) {
summaryData.add(ESTIMATED_SWEAT_LOSS, session.getEstimatedSweatLoss(), UNIT_ML);
}
if (session.getAverageHeartRate() != null) {
summaryData.add(HR_AVG, session.getAverageHeartRate(), UNIT_BPM);
}
if (session.getMaxHeartRate() != null) {
summaryData.add(HR_MAX, session.getMaxHeartRate(), UNIT_BPM);
}
if (session.getTotalAscent() != null) {
summaryData.add(ASCENT_DISTANCE, session.getTotalAscent(), UNIT_METERS);
}
@ -343,16 +363,21 @@ public class FitImporter {
summaryData.add(DESCENT_DISTANCE, session.getTotalDescent(), UNIT_METERS);
}
//FitTimeInZone timeInZone = null;
//for (final FitTimeInZone fitTimeInZone : timesInZone) {
// // Find the firt time in zone for the session (assumes single-session)
// if (fitTimeInZone.getReferenceMessage() != null && fitTimeInZone.getReferenceMessage() == 18) {
// timeInZone = fitTimeInZone;
// break;
// }
//}
//if (timeInZone != null) {
//}
for (final FitTimeInZone fitTimeInZone : timesInZone) {
// Find the firt time in zone for the session (assumes single-session)
if (fitTimeInZone.getReferenceMessage() != null && fitTimeInZone.getReferenceMessage() == 18) {
final Double[] timeInZone = fitTimeInZone.getTimeInZone();
if (timeInZone != null && timeInZone.length == 6) {
summaryData.add(HR_ZONE_NA, timeInZone[0].floatValue(), UNIT_SECONDS);
summaryData.add(HR_ZONE_WARM_UP, timeInZone[1].floatValue(), UNIT_SECONDS);
summaryData.add(HR_ZONE_FAT_BURN, timeInZone[2].floatValue(), UNIT_SECONDS);
summaryData.add(HR_ZONE_AEROBIC, timeInZone[3].floatValue(), UNIT_SECONDS);
summaryData.add(HR_ZONE_ANAEROBIC, timeInZone[4].floatValue(), UNIT_SECONDS);
summaryData.add(HR_ZONE_EXTREME, timeInZone[5].floatValue(), UNIT_SECONDS);
}
break;
}
}
summary.setSummaryData(summaryData.toString());
if (file != null) {

View File

@ -99,9 +99,12 @@ public class GlobalFITMessage {
new FieldDefinitionPrimitive(8, BaseType.UINT32, "total_timer_time"), // no pauses
new FieldDefinitionPrimitive(9, BaseType.UINT32, "total_distance"),
new FieldDefinitionPrimitive(11, BaseType.UINT16, "total_calories"),
new FieldDefinitionPrimitive(16, BaseType.UINT8, "average_heart_rate"),
new FieldDefinitionPrimitive(17, BaseType.UINT8, "max_heart_rate"),
new FieldDefinitionPrimitive(22, BaseType.UINT16, "total_ascent"),
new FieldDefinitionPrimitive(23, BaseType.UINT16, "total_descent"),
new FieldDefinitionPrimitive(110, BaseType.STRING, 64, "sport_profile_name"),
new FieldDefinitionPrimitive(178, BaseType.UINT16, "estimated_sweat_loss"),
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
));
@ -231,8 +234,8 @@ public class GlobalFITMessage {
public static GlobalFITMessage TIME_IN_ZONE = new GlobalFITMessage(216, "TIME_IN_ZONE", Arrays.asList(
new FieldDefinitionPrimitive(0, BaseType.UINT16, "reference_message"),
new FieldDefinitionPrimitive(1, BaseType.UINT16, "reference_index"),
new FieldDefinitionPrimitive(2, BaseType.UINT32, "time_in_zone"), // seconds
new FieldDefinitionPrimitive(6, BaseType.UINT8, "hr_zone_high_boundary"), // bpm
new FieldDefinitionPrimitive(2, BaseType.UINT32, "time_in_zone", FieldDefinitionFactory.FIELD.HR_TIME_IN_ZONE), // seconds
new FieldDefinitionPrimitive(6, BaseType.UINT8, "hr_zone_high_boundary", FieldDefinitionFactory.FIELD.HR_ZONE_HIGH_BOUNDARY), // bpm
new FieldDefinitionPrimitive(10, BaseType.ENUM, "hr_calc_type"), // 1 percent max hr
new FieldDefinitionPrimitive(11, BaseType.UINT8, "max_heart_rate"),
new FieldDefinitionPrimitive(12, BaseType.UINT8, "resting_heart_rate"),
@ -244,6 +247,14 @@ public class GlobalFITMessage {
new FieldDefinitionPrimitive(0, BaseType.UINT16, "time", FieldDefinitionFactory.FIELD.ALARM)
));
public static GlobalFITMessage SET = new GlobalFITMessage(225, "SET", Arrays.asList(
new FieldDefinitionPrimitive(0, BaseType.UINT32, "duration"),
new FieldDefinitionPrimitive(5, BaseType.UINT8, "set_type"), // 1 active 0 rest
new FieldDefinitionPrimitive(6, BaseType.UINT32, "start_time", FieldDefinitionFactory.FIELD.TIMESTAMP),
new FieldDefinitionPrimitive(10, BaseType.UINT16, "message_index"),
new FieldDefinitionPrimitive(254, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
));
public static GlobalFITMessage STRESS_LEVEL = new GlobalFITMessage(227, "STRESS_LEVEL", Arrays.asList(
new FieldDefinitionPrimitive(0, BaseType.SINT16, "stress_level_value"),
new FieldDefinitionPrimitive(1, BaseType.UINT32, "stress_level_time", FieldDefinitionFactory.FIELD.TIMESTAMP),
@ -309,6 +320,7 @@ public class GlobalFITMessage {
put(207, DEVELOPER_DATA);
put(216, TIME_IN_ZONE);
put(222, ALARM_SETTINGS);
put(225, SET);
put(227, STRESS_LEVEL);
put(269, SPO2);
put(275, SLEEP_STAGE);

View File

@ -190,7 +190,19 @@ public class FitCodeGen {
sb.append("\n");
sb.append(" @Nullable\n");
sb.append(" public ").append(fieldTypeName).append(method(" get", primitive)).append("() {\n");
sb.append(" return (").append(fieldTypeName).append(") getFieldByNumber(").append(primitive.getNumber()).append(");\n");
if (fieldTypeName.endsWith("[]")) {
// Special case for arrays, since these are decoded in RecordData and we can't easily decode them with the correct type
// FIXME this should be refactored...
final String simpleTypeName = fieldTypeName.replace("[]", "");
sb.append(" final Object[] objectsArray = (Object[]) getFieldByNumber(").append(primitive.getNumber()).append(");\n");
sb.append(" final ").append(fieldTypeName).append(" ret = new ").append(simpleTypeName).append("[objectsArray.length];\n");
sb.append(" for (int i = 0; i < objectsArray.length; i++) {\n");
sb.append(" ret[i] = (").append(simpleTypeName).append(") objectsArray[i];\n");
sb.append(" }\n");
sb.append(" return ret;\n");
} else {
sb.append(" return (").append(fieldTypeName).append(") getFieldByNumber(").append(primitive.getNumber()).append(");\n");
}
sb.append(" }\n");
}
@ -238,6 +250,10 @@ public class FitCodeGen {
return FieldDefinitionGoalType.Type.class;
case HRV_STATUS:
return FieldDefinitionHrvStatus.HrvStatus.class;
case HR_TIME_IN_ZONE:
return Double[].class;
case HR_ZONE_HIGH_BOUNDARY:
return Integer[].class;
case MEASUREMENT_SYSTEM:
return FieldDefinitionMeasurementSystem.Type.class;
case TEMPERATURE:

View File

@ -0,0 +1,10 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
public class FieldDefinitionHrTimeInZone extends FieldDefinition {
public FieldDefinitionHrTimeInZone(final int localNumber, final int size, final BaseType baseType, final String name) {
super(localNumber, size, baseType, name, 1000, 0);
}
}

View File

@ -0,0 +1,10 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
public class FieldDefinitionHrZoneHighBoundary extends FieldDefinition {
public FieldDefinitionHrZoneHighBoundary(final int localNumber, final int size, final BaseType baseType, final String name) {
super(localNumber, size, baseType, name, 1, 0);
}
}

View File

@ -59,6 +59,8 @@ public class FitRecordDataFactory {
return new FitTimeInZone(recordDefinition, recordHeader);
case 222:
return new FitAlarmSettings(recordDefinition, recordHeader);
case 225:
return new FitSet(recordDefinition, recordHeader);
case 227:
return new FitStressLevel(recordDefinition, recordHeader);
case 269:

View File

@ -75,6 +75,16 @@ public class FitSession extends RecordData {
return (Integer) getFieldByNumber(11);
}
@Nullable
public Integer getAverageHeartRate() {
return (Integer) getFieldByNumber(16);
}
@Nullable
public Integer getMaxHeartRate() {
return (Integer) getFieldByNumber(17);
}
@Nullable
public Integer getTotalAscent() {
return (Integer) getFieldByNumber(22);
@ -90,6 +100,11 @@ public class FitSession extends RecordData {
return (String) getFieldByNumber(110);
}
@Nullable
public Integer getEstimatedSweatLoss() {
return (Integer) getFieldByNumber(178);
}
@Nullable
public Long getTimestamp() {
return (Long) getFieldByNumber(253);

View File

@ -0,0 +1,47 @@
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 FitSet extends RecordData {
public FitSet(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
super(recordDefinition, recordHeader);
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
if (globalNumber != 225) {
throw new IllegalArgumentException("FitSet expects global messages of " + 225 + ", got " + globalNumber);
}
}
@Nullable
public Long getDuration() {
return (Long) getFieldByNumber(0);
}
@Nullable
public Integer getSetType() {
return (Integer) getFieldByNumber(5);
}
@Nullable
public Long getStartTime() {
return (Long) getFieldByNumber(6);
}
@Nullable
public Integer getMessageIndex() {
return (Integer) getFieldByNumber(10);
}
@Nullable
public Long getTimestamp() {
return (Long) getFieldByNumber(254);
}
}

View File

@ -31,13 +31,23 @@ public class FitTimeInZone extends RecordData {
}
@Nullable
public Long getTimeInZone() {
return (Long) getFieldByNumber(2);
public Double[] getTimeInZone() {
final Object[] objectsArray = (Object[]) getFieldByNumber(2);
final Double[] ret = new Double[objectsArray.length];
for (int i = 0; i < objectsArray.length; i++) {
ret[i] = (Double) objectsArray[i];
}
return ret;
}
@Nullable
public Integer getHrZoneHighBoundary() {
return (Integer) getFieldByNumber(6);
public Integer[] getHrZoneHighBoundary() {
final Object[] objectsArray = (Object[]) getFieldByNumber(6);
final Integer[] ret = new Integer[objectsArray.length];
for (int i = 0; i < objectsArray.length; i++) {
ret[i] = (Integer) objectsArray[i];
}
return ret;
}
@Nullable

View File

@ -2092,6 +2092,7 @@
<string name="anaerobicTrainingEffect">Anaerobic Effect</string>
<string name="currentWorkoutLoad">Workout Load</string>
<string name="maximumOxygenUptake">Maximum Oxygen Uptake</string>
<string name="estimatedSweatLoss">Estimated Sweat Loss</string>
<string name="averageStride">Average Stride</string>
<string name="maxStride">Max Stride</string>
<string name="minStride">Min Stride</string>