From 0eb0d02e5a7eb8457a8c88546ce1317b12b8832c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Wed, 28 May 2025 20:34:01 +0100 Subject: [PATCH] Garmin: Parse max met data and training load --- .../devices/garmin/fit/GlobalFITMessage.java | 17 ++++++ .../garmin/fit/codegen/FitCodeGen.java | 2 + .../garmin/fit/messages/FitMaxMetData.java | 52 +++++++++++++++++++ .../fit/messages/FitRecordDataFactory.java | 4 ++ .../garmin/fit/messages/FitTrainingLoad.java | 37 +++++++++++++ 5 files changed, 112 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMaxMetData.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTrainingLoad.java 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 5a929a980d..a539998a72 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 @@ -436,6 +436,15 @@ public class GlobalFITMessage { new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP) )); + public static GlobalFITMessage MAX_MET_DATA = new GlobalFITMessage(229, "MAX_MET_DATA", Arrays.asList( + new FieldDefinitionPrimitive(0, BaseType.UINT32, "update_time", FieldDefinitionFactory.FIELD.TIMESTAMP), + new FieldDefinitionPrimitive(2, BaseType.UINT16, "vo2_max", 10, 0), + new FieldDefinitionPrimitive(5, BaseType.ENUM, "sport"), + new FieldDefinitionPrimitive(6, BaseType.ENUM, "sub_sport"), + new FieldDefinitionPrimitive(8, BaseType.ENUM, "max_met_category"), // 0 generic + new FieldDefinitionPrimitive(9, BaseType.ENUM, "calibrated_data") // 1? + )); + public static GlobalFITMessage RESPIRATION_RATE = new GlobalFITMessage(297, "RESPIRATION_RATE", Arrays.asList( new FieldDefinitionPrimitive(0, BaseType.SINT16, "respiration_rate", 100, 0), // breaths / min new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP) @@ -482,6 +491,12 @@ public class GlobalFITMessage { new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP) )); + public static GlobalFITMessage TRAINING_LOAD = new GlobalFITMessage(378, "TRAINING_LOAD", Arrays.asList( + new FieldDefinitionPrimitive(3, BaseType.UINT16, "training_load_acute"), + new FieldDefinitionPrimitive(4, BaseType.UINT16, "training_load_chronic"), + new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP) + )); + public static GlobalFITMessage SKIN_TEMP_OVERNIGHT = new GlobalFITMessage(398, "SKIN_TEMP_OVERNIGHT", Arrays.asList( new FieldDefinitionPrimitive(0, BaseType.UINT32, "local_timestamp"), // garmin timestamp, but in user timezone new FieldDefinitionPrimitive(1, BaseType.FLOAT32, "average_deviation"), @@ -532,6 +547,7 @@ public class GlobalFITMessage { put(222, ALARM_SETTINGS); put(225, SET); put(227, STRESS_LEVEL); + put(229, MAX_MET_DATA); put(269, SPO2); put(273, SLEEP_DATA_INFO); put(274, SLEEP_DATA_RAW); @@ -540,6 +556,7 @@ public class GlobalFITMessage { put(346, SLEEP_STATS); put(370, HRV_SUMMARY); put(371, HRV_VALUE); + put(378, TRAINING_LOAD); put(397, SKIN_TEMP_RAW); put(398, SKIN_TEMP_OVERNIGHT); put(412, NAP); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java index 31b970ef98..b29111bb19 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java @@ -42,6 +42,8 @@ import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; @RequiresApi(api = Build.VERSION_CODES.O) public class FitCodeGen { public static void main(final String[] args) throws Exception { + // To run this in Android Studio, right click and select "Run 'FitCodeGen.main()' with Coverage" + // for some reason, the classpath is broken otherwise. new FitCodeGen().generate(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMaxMetData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMaxMetData.java new file mode 100644 index 0000000000..0586957002 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMaxMetData.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 FitMaxMetData extends RecordData { + public FitMaxMetData(final RecordDefinition recordDefinition, final RecordHeader recordHeader) { + super(recordDefinition, recordHeader); + + final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber(); + if (globalNumber != 229) { + throw new IllegalArgumentException("FitMaxMetData expects global messages of " + 229 + ", got " + globalNumber); + } + } + + @Nullable + public Long getUpdateTime() { + return (Long) getFieldByNumber(0); + } + + @Nullable + public Float getVo2Max() { + return (Float) getFieldByNumber(2); + } + + @Nullable + public Integer getSport() { + return (Integer) getFieldByNumber(5); + } + + @Nullable + public Integer getSubSport() { + return (Integer) getFieldByNumber(6); + } + + @Nullable + public Integer getMaxMetCategory() { + return (Integer) getFieldByNumber(8); + } + + @Nullable + public Integer getCalibratedData() { + return (Integer) getFieldByNumber(9); + } +} 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 4b5fe7f8d3..a5850eabfa 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 @@ -75,6 +75,8 @@ public class FitRecordDataFactory { return new FitSet(recordDefinition, recordHeader); case 227: return new FitStressLevel(recordDefinition, recordHeader); + case 229: + return new FitMaxMetData(recordDefinition, recordHeader); case 269: return new FitSpo2(recordDefinition, recordHeader); case 273: @@ -91,6 +93,8 @@ public class FitRecordDataFactory { return new FitHrvSummary(recordDefinition, recordHeader); case 371: return new FitHrvValue(recordDefinition, recordHeader); + case 378: + return new FitTrainingLoad(recordDefinition, recordHeader); case 397: return new FitSkinTempRaw(recordDefinition, recordHeader); case 398: diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTrainingLoad.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTrainingLoad.java new file mode 100644 index 0000000000..7e19a9f37d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTrainingLoad.java @@ -0,0 +1,37 @@ +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 FitTrainingLoad extends RecordData { + public FitTrainingLoad(final RecordDefinition recordDefinition, final RecordHeader recordHeader) { + super(recordDefinition, recordHeader); + + final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber(); + if (globalNumber != 378) { + throw new IllegalArgumentException("FitTrainingLoad expects global messages of " + 378 + ", got " + globalNumber); + } + } + + @Nullable + public Integer getTrainingLoadAcute() { + return (Integer) getFieldByNumber(3); + } + + @Nullable + public Integer getTrainingLoadChronic() { + return (Integer) getFieldByNumber(4); + } + + @Nullable + public Long getTimestamp() { + return (Long) getFieldByNumber(253); + } +}