From fd1e81ff66319f2b63ea2f5b32c9671e368d080e Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sun, 18 Aug 2024 15:08:26 +0200 Subject: [PATCH] Garmin: fix basetype handling and truncation Numeric BaseTypes now return fractional part if a scale is set, the return type for numeric values is unboxed from Number class so that existing comparisons and checks keep working, but values are not truncated at nearest integer value. The codegen class has been updated and some of the messages are re-generated with the new code, with the exception of Hrv* messages, since updating those needs updating the related sample types. Test cases expected values have been adjusted to keep into account the fractional part of some fields. Garmin: Fix HRV value and summary types --- .../devices/garmin/fit/FitImporter.java | 26 ++++++++---- .../garmin/fit/baseTypes/BaseType.java | 40 ++++++++++++++++++- .../garmin/fit/baseTypes/BaseTypeByte.java | 2 +- .../garmin/fit/baseTypes/BaseTypeFloat.java | 2 +- .../garmin/fit/baseTypes/BaseTypeInt.java | 2 +- .../garmin/fit/baseTypes/BaseTypeShort.java | 2 +- .../garmin/fit/codegen/FitCodeGen.java | 12 +++++- .../garmin/fit/messages/FitHrvSummary.java | 24 +++++------ .../garmin/fit/messages/FitHrvValue.java | 4 +- .../devices/garmin/fit/messages/FitLap.java | 12 +++--- .../garmin/fit/messages/FitRecord.java | 16 ++++---- .../garmin/fit/messages/FitSession.java | 8 ++-- .../garmin/fit/messages/FitUserProfile.java | 4 +- .../garmin/fit/messages/FitWeather.java | 4 +- .../devices/garmin/GarminSupportTest.java | 8 ++-- 15 files changed, 112 insertions(+), 54 deletions(-) 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 2d29522b4..8dd0e8e1f 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 @@ -206,12 +206,24 @@ public class FitImporter { LOG.trace("HRV summary at {}: {}", ts, record); final GarminHrvSummarySample sample = new GarminHrvSummarySample(); sample.setTimestamp(ts * 1000L); - sample.setWeeklyAverage(hrvSummary.getWeeklyAverage()); - sample.setLastNightAverage(hrvSummary.getLastNightAverage()); - sample.setLastNight5MinHigh(hrvSummary.getLastNight5MinHigh()); - sample.setBaselineLowUpper(hrvSummary.getBaselineLowUpper()); - sample.setBaselineBalancedLower(hrvSummary.getBaselineBalancedLower()); - sample.setBaselineBalancedUpper(hrvSummary.getBaselineBalancedUpper()); + if (hrvSummary.getWeeklyAverage() != null) { + sample.setWeeklyAverage(Math.round(hrvSummary.getWeeklyAverage())); + } + if (hrvSummary.getLastNightAverage() != null) { + sample.setLastNightAverage(Math.round(hrvSummary.getLastNightAverage())); + } + if (hrvSummary.getLastNight5MinHigh() != null) { + sample.setLastNight5MinHigh(Math.round(hrvSummary.getLastNight5MinHigh())); + } + if (hrvSummary.getBaselineLowUpper() != null) { + sample.setBaselineLowUpper(Math.round(hrvSummary.getBaselineLowUpper())); + } + if (hrvSummary.getBaselineBalancedLower() != null) { + sample.setBaselineBalancedLower(Math.round(hrvSummary.getBaselineBalancedLower())); + } + if (hrvSummary.getBaselineBalancedUpper() != null) { + sample.setBaselineBalancedUpper(Math.round(hrvSummary.getBaselineBalancedUpper())); + } final FieldDefinitionHrvStatus.HrvStatus status = hrvSummary.getStatus(); if (status != null) { sample.setStatusNum(status.getId()); @@ -226,7 +238,7 @@ public class FitImporter { LOG.trace("HRV value at {}: {}", ts, hrvValue.getValue()); final GarminHrvValueSample sample = new GarminHrvValueSample(); sample.setTimestamp(ts * 1000L); - sample.setValue(hrvValue.getValue()); + sample.setValue(Math.round(hrvValue.getValue())); hrvValueSamples.add(sample); } else { LOG.trace("Unknown record: {}", record); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java index a81a27e60..167d8123e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java @@ -49,7 +49,45 @@ public enum BaseType { } public Object decode(ByteBuffer byteBuffer, double scale, int offset) { - return baseTypeInterface.decode(byteBuffer, scale, offset); + Object raw = baseTypeInterface.decode(byteBuffer, scale, offset); + if (raw == null) + return null; + + //the following returns STRING basetype but also all specialized FieldDefinition classes + if (!Number.class.isAssignableFrom(raw.getClass())) + return raw; + + switch (this) { + case ENUM: + case SINT8: + case UINT8: + case SINT16: + case UINT16: + case UINT8Z: + case UINT16Z: + case BASE_TYPE_BYTE: + if (scale != 1) { + return ((Number) raw).floatValue(); + } else { + return ((Number) raw).intValue(); + } + case SINT32: + case UINT32: + case UINT32Z: + case SINT64: + case UINT64: + case UINT64Z: + if (scale != 1) { + return ((Number) raw).doubleValue(); + } else { + return ((Number) raw).longValue(); + } + case FLOAT32: + return ((Number) raw).floatValue(); + case FLOAT64: + return ((Number) raw).doubleValue(); + } + return raw; } public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java index c70f3c821..9d924862c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java @@ -34,7 +34,7 @@ public class BaseTypeByte implements BaseTypeInterface { return null; if (b == invalid) return null; - return (int) (Math.round(b / scale) - offset); + return (b / scale) - offset; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java index 2df247aec..b9f312046 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java @@ -26,7 +26,7 @@ public class BaseTypeFloat implements BaseTypeInterface { } if (Float.isNaN(f) || f == invalid) return null; - return (float) (f / scale) - offset; + return (f / scale) - offset; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java index 4f48cd776..dd998582a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java @@ -32,7 +32,7 @@ public class BaseTypeInt implements BaseTypeInterface { return null; if (i == invalid) return null; - return (Math.round(i / scale) - offset); + return (i / scale) - offset; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java index 0a76c6efe..61e5cee3e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java @@ -32,7 +32,7 @@ public class BaseTypeShort implements BaseTypeInterface { return null; if (s == invalid) return null; - return (int) Math.round(s / scale) - offset; + return (s / scale) - offset; } @Override 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 83c06ed83..57cb13398 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 @@ -268,14 +268,22 @@ public class FitCodeGen { case UINT8Z: case UINT16Z: case BASE_TYPE_BYTE: - return Integer.class; + if (primitive.getScale() != 1) { + return Float.class; + } else { + return Integer.class; + } case SINT32: case UINT32: case UINT32Z: case SINT64: case UINT64: case UINT64Z: - return Long.class; + if (primitive.getScale() != 1) { + return Double.class; + } else { + return Long.class; + } case STRING: return String.class; case FLOAT32: diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvSummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvSummary.java index ea6023428..c052ea270 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvSummary.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvSummary.java @@ -22,33 +22,33 @@ public class FitHrvSummary extends RecordData { } @Nullable - public Integer getWeeklyAverage() { - return (Integer) getFieldByNumber(0); + public Float getWeeklyAverage() { + return (Float) getFieldByNumber(0); } @Nullable - public Integer getLastNightAverage() { - return (Integer) getFieldByNumber(1); + public Float getLastNightAverage() { + return (Float) getFieldByNumber(1); } @Nullable - public Integer getLastNight5MinHigh() { - return (Integer) getFieldByNumber(2); + public Float getLastNight5MinHigh() { + return (Float) getFieldByNumber(2); } @Nullable - public Integer getBaselineLowUpper() { - return (Integer) getFieldByNumber(3); + public Float getBaselineLowUpper() { + return (Float) getFieldByNumber(3); } @Nullable - public Integer getBaselineBalancedLower() { - return (Integer) getFieldByNumber(4); + public Float getBaselineBalancedLower() { + return (Float) getFieldByNumber(4); } @Nullable - public Integer getBaselineBalancedUpper() { - return (Integer) getFieldByNumber(5); + public Float getBaselineBalancedUpper() { + return (Float) getFieldByNumber(5); } @Nullable diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvValue.java index c04d3a57b..10183bb3a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvValue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvValue.java @@ -21,8 +21,8 @@ public class FitHrvValue extends RecordData { } @Nullable - public Integer getValue() { - return (Integer) getFieldByNumber(0); + public Float getValue() { + return (Float) getFieldByNumber(0); } @Nullable diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitLap.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitLap.java index 34dafcee4..cb23ed9a8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitLap.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitLap.java @@ -41,18 +41,18 @@ public class FitLap extends RecordData { } @Nullable - public Long getTotalElapsedTime() { - return (Long) getFieldByNumber(7); + public Double getTotalElapsedTime() { + return (Double) getFieldByNumber(7); } @Nullable - public Long getTotalTimerTime() { - return (Long) getFieldByNumber(8); + public Double getTotalTimerTime() { + return (Double) getFieldByNumber(8); } @Nullable - public Long getTotalDistance() { - return (Long) getFieldByNumber(9); + public Double getTotalDistance() { + return (Double) getFieldByNumber(9); } @Nullable diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecord.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecord.java index a43038542..9c868f3a4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecord.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecord.java @@ -36,8 +36,8 @@ public class FitRecord extends RecordData { } @Nullable - public Integer getAltitude() { - return (Integer) getFieldByNumber(2); + public Float getAltitude() { + return (Float) getFieldByNumber(2); } @Nullable @@ -46,13 +46,13 @@ public class FitRecord extends RecordData { } @Nullable - public Long getDistance() { - return (Long) getFieldByNumber(5); + public Double getDistance() { + return (Double) getFieldByNumber(5); } @Nullable - public Integer getSpeed() { - return (Integer) getFieldByNumber(6); + public Float getSpeed() { + return (Float) getFieldByNumber(6); } @Nullable @@ -61,8 +61,8 @@ public class FitRecord extends RecordData { } @Nullable - public Long getEnhancedAltitude() { - return (Long) getFieldByNumber(78); + public Double getEnhancedAltitude() { + return (Double) getFieldByNumber(78); } @Nullable diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSession.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSession.java index c52c90d04..7b47c3d8b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSession.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSession.java @@ -36,13 +36,13 @@ public class FitSession extends RecordData { } @Nullable - public Long getStartLatitude() { - return (Long) getFieldByNumber(3); + public Double getStartLatitude() { + return (Double) getFieldByNumber(3); } @Nullable - public Long getStartLongitude() { - return (Long) getFieldByNumber(4); + public Double getStartLongitude() { + return (Double) getFieldByNumber(4); } @Nullable diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitUserProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitUserProfile.java index 6954f5d25..c0baa931e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitUserProfile.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitUserProfile.java @@ -43,8 +43,8 @@ public class FitUserProfile extends RecordData { } @Nullable - public Integer getWeight() { - return (Integer) getFieldByNumber(4); + public Float getWeight() { + return (Float) getFieldByNumber(4); } @Nullable diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWeather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWeather.java index 648c8c9b4..908c1145f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWeather.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWeather.java @@ -45,8 +45,8 @@ public class FitWeather extends RecordData { } @Nullable - public Integer getWindSpeed() { - return (Integer) getFieldByNumber(4); + public Float getWindSpeed() { + return (Float) getFieldByNumber(4); } @Nullable diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java index 1601dc0bb..38d717f36 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java @@ -336,7 +336,7 @@ public class GarminSupportTest extends TestBase { "FitFileId{serial_number=3889965805, manufacturer=1, product=1561, type=SETTINGS}, " + "FitFileCreator{software_version=340}, " + "FitDeviceSettings{utc_offset=0, time_offset=0, active_time_zone=0, unknown_3(ENUM/1)=0, time_mode=0, time_zone_offset=0, unknown_10(ENUM/1)=3, unknown_11(ENUM/1)=0, backlight_mode=2, unknown_13(UINT8/1)=0, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=50, unknown_21(ENUM/1)=1, unknown_22(ENUM/1)=0, unknown_26(ENUM/1)=254, unknown_27(ENUM/1)=0, unknown_29(ENUM/1)=0, unknown_52(ENUM/1)=0, unknown_53(ENUM/1)=1}, " + - "FitUserProfile{friendly_name=edge510, weight=78, gender=1, age=41, height=183, language=english, elev_setting=metric, weight_setting=metric, resting_heart_rate=60, default_max_biking_heart_rate=185, default_max_heart_rate=185, hr_setting=1, speed_setting=metric, dist_setting=metric, power_setting=1, activity_class=168, position_setting=2, temperature_setting=metric}, " + + "FitUserProfile{friendly_name=edge510, weight=78.0, gender=1, age=41, height=183, language=english, elev_setting=metric, weight_setting=metric, resting_heart_rate=60, default_max_biking_heart_rate=185, default_max_heart_rate=185, hr_setting=1, speed_setting=metric, dist_setting=metric, power_setting=1, activity_class=168, position_setting=2, temperature_setting=metric}, " + "RecordData{UNK_4, unknown_254(UINT16/2)=0, unknown_1(UINT16Z/2)=50008, unknown_0(UINT8/1)=1, unknown_3(UINT8Z/1)=1}, " + "RecordData{UNK_6, unknown_0(STRING/4)=EVO, unknown_3(UINT32/4)=45719172, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=0, unknown_7(UINT16Z/2)=47617, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=80, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=1, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=1, unknown_24(UINT8Z/1)=5, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " + "RecordData{UNK_6, unknown_0(STRING/5)=P2SL, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=1, unknown_7(UINT16Z/2)=28209, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=90, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=1, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=1, unknown_24(UINT8Z/1)=5, unknown_35(UINT8/3)=[0,118,190], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " + @@ -364,9 +364,9 @@ public class GarminSupportTest extends TestBase { "FitFileId{manufacturer=15, type=ACTIVITY, product=9001, serial_number=1701}, " + "FitDeveloperData{application_id=[1,1,2,3,5,8,13,21,34,55,89,144,233,121,98,219], developer_data_index=0}, " + "FitFieldDescription{developer_data_index=0, field_definition_number=0, fit_base_type_id=1, field_name=doughnuts_earned, units=doughnuts}, " + - "FitRecord{heart_rate=140, unknown_4(UINT8/1)=88, distance=510, speed=47, doughnuts_earned=1}, " + - "FitRecord{heart_rate=143, unknown_4(UINT8/1)=90, distance=2080, speed=36, doughnuts_earned=2}, " + - "FitRecord{heart_rate=144, unknown_4(UINT8/1)=92, distance=3710, speed=35, doughnuts_earned=3}" + + "FitRecord{heart_rate=140, unknown_4(UINT8/1)=88, distance=510.0, speed=47.488, doughnuts_earned=1}, " + + "FitRecord{heart_rate=143, unknown_4(UINT8/1)=90, distance=2080.0, speed=36.416, doughnuts_earned=2}, " + + "FitRecord{heart_rate=144, unknown_4(UINT8/1)=92, distance=3710.0, speed=35.344, doughnuts_earned=3}" + "]"; FitFile fitFile = FitFile.parseIncoming(fileContents);