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
This commit is contained in:
Daniele Gobbetti
2024-08-18 22:15:06 +02:00
parent 51a0745b06
commit fd1e81ff66
15 changed files with 112 additions and 54 deletions
@@ -206,12 +206,24 @@ public class FitImporter {
LOG.trace("HRV summary at {}: {}", ts, record); LOG.trace("HRV summary at {}: {}", ts, record);
final GarminHrvSummarySample sample = new GarminHrvSummarySample(); final GarminHrvSummarySample sample = new GarminHrvSummarySample();
sample.setTimestamp(ts * 1000L); sample.setTimestamp(ts * 1000L);
sample.setWeeklyAverage(hrvSummary.getWeeklyAverage()); if (hrvSummary.getWeeklyAverage() != null) {
sample.setLastNightAverage(hrvSummary.getLastNightAverage()); sample.setWeeklyAverage(Math.round(hrvSummary.getWeeklyAverage()));
sample.setLastNight5MinHigh(hrvSummary.getLastNight5MinHigh()); }
sample.setBaselineLowUpper(hrvSummary.getBaselineLowUpper()); if (hrvSummary.getLastNightAverage() != null) {
sample.setBaselineBalancedLower(hrvSummary.getBaselineBalancedLower()); sample.setLastNightAverage(Math.round(hrvSummary.getLastNightAverage()));
sample.setBaselineBalancedUpper(hrvSummary.getBaselineBalancedUpper()); }
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(); final FieldDefinitionHrvStatus.HrvStatus status = hrvSummary.getStatus();
if (status != null) { if (status != null) {
sample.setStatusNum(status.getId()); sample.setStatusNum(status.getId());
@@ -226,7 +238,7 @@ public class FitImporter {
LOG.trace("HRV value at {}: {}", ts, hrvValue.getValue()); LOG.trace("HRV value at {}: {}", ts, hrvValue.getValue());
final GarminHrvValueSample sample = new GarminHrvValueSample(); final GarminHrvValueSample sample = new GarminHrvValueSample();
sample.setTimestamp(ts * 1000L); sample.setTimestamp(ts * 1000L);
sample.setValue(hrvValue.getValue()); sample.setValue(Math.round(hrvValue.getValue()));
hrvValueSamples.add(sample); hrvValueSamples.add(sample);
} else { } else {
LOG.trace("Unknown record: {}", record); LOG.trace("Unknown record: {}", record);
@@ -49,7 +49,45 @@ public enum BaseType {
} }
public Object decode(ByteBuffer byteBuffer, double scale, int offset) { 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) { public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
@@ -34,7 +34,7 @@ public class BaseTypeByte implements BaseTypeInterface {
return null; return null;
if (b == invalid) if (b == invalid)
return null; return null;
return (int) (Math.round(b / scale) - offset); return (b / scale) - offset;
} }
@Override @Override
@@ -26,7 +26,7 @@ public class BaseTypeFloat implements BaseTypeInterface {
} }
if (Float.isNaN(f) || f == invalid) if (Float.isNaN(f) || f == invalid)
return null; return null;
return (float) (f / scale) - offset; return (f / scale) - offset;
} }
@Override @Override
@@ -32,7 +32,7 @@ public class BaseTypeInt implements BaseTypeInterface {
return null; return null;
if (i == invalid) if (i == invalid)
return null; return null;
return (Math.round(i / scale) - offset); return (i / scale) - offset;
} }
@Override @Override
@@ -32,7 +32,7 @@ public class BaseTypeShort implements BaseTypeInterface {
return null; return null;
if (s == invalid) if (s == invalid)
return null; return null;
return (int) Math.round(s / scale) - offset; return (s / scale) - offset;
} }
@Override @Override
@@ -268,14 +268,22 @@ public class FitCodeGen {
case UINT8Z: case UINT8Z:
case UINT16Z: case UINT16Z:
case BASE_TYPE_BYTE: case BASE_TYPE_BYTE:
return Integer.class; if (primitive.getScale() != 1) {
return Float.class;
} else {
return Integer.class;
}
case SINT32: case SINT32:
case UINT32: case UINT32:
case UINT32Z: case UINT32Z:
case SINT64: case SINT64:
case UINT64: case UINT64:
case UINT64Z: case UINT64Z:
return Long.class; if (primitive.getScale() != 1) {
return Double.class;
} else {
return Long.class;
}
case STRING: case STRING:
return String.class; return String.class;
case FLOAT32: case FLOAT32:
@@ -22,33 +22,33 @@ public class FitHrvSummary extends RecordData {
} }
@Nullable @Nullable
public Integer getWeeklyAverage() { public Float getWeeklyAverage() {
return (Integer) getFieldByNumber(0); return (Float) getFieldByNumber(0);
} }
@Nullable @Nullable
public Integer getLastNightAverage() { public Float getLastNightAverage() {
return (Integer) getFieldByNumber(1); return (Float) getFieldByNumber(1);
} }
@Nullable @Nullable
public Integer getLastNight5MinHigh() { public Float getLastNight5MinHigh() {
return (Integer) getFieldByNumber(2); return (Float) getFieldByNumber(2);
} }
@Nullable @Nullable
public Integer getBaselineLowUpper() { public Float getBaselineLowUpper() {
return (Integer) getFieldByNumber(3); return (Float) getFieldByNumber(3);
} }
@Nullable @Nullable
public Integer getBaselineBalancedLower() { public Float getBaselineBalancedLower() {
return (Integer) getFieldByNumber(4); return (Float) getFieldByNumber(4);
} }
@Nullable @Nullable
public Integer getBaselineBalancedUpper() { public Float getBaselineBalancedUpper() {
return (Integer) getFieldByNumber(5); return (Float) getFieldByNumber(5);
} }
@Nullable @Nullable
@@ -21,8 +21,8 @@ public class FitHrvValue extends RecordData {
} }
@Nullable @Nullable
public Integer getValue() { public Float getValue() {
return (Integer) getFieldByNumber(0); return (Float) getFieldByNumber(0);
} }
@Nullable @Nullable
@@ -41,18 +41,18 @@ public class FitLap extends RecordData {
} }
@Nullable @Nullable
public Long getTotalElapsedTime() { public Double getTotalElapsedTime() {
return (Long) getFieldByNumber(7); return (Double) getFieldByNumber(7);
} }
@Nullable @Nullable
public Long getTotalTimerTime() { public Double getTotalTimerTime() {
return (Long) getFieldByNumber(8); return (Double) getFieldByNumber(8);
} }
@Nullable @Nullable
public Long getTotalDistance() { public Double getTotalDistance() {
return (Long) getFieldByNumber(9); return (Double) getFieldByNumber(9);
} }
@Nullable @Nullable
@@ -36,8 +36,8 @@ public class FitRecord extends RecordData {
} }
@Nullable @Nullable
public Integer getAltitude() { public Float getAltitude() {
return (Integer) getFieldByNumber(2); return (Float) getFieldByNumber(2);
} }
@Nullable @Nullable
@@ -46,13 +46,13 @@ public class FitRecord extends RecordData {
} }
@Nullable @Nullable
public Long getDistance() { public Double getDistance() {
return (Long) getFieldByNumber(5); return (Double) getFieldByNumber(5);
} }
@Nullable @Nullable
public Integer getSpeed() { public Float getSpeed() {
return (Integer) getFieldByNumber(6); return (Float) getFieldByNumber(6);
} }
@Nullable @Nullable
@@ -61,8 +61,8 @@ public class FitRecord extends RecordData {
} }
@Nullable @Nullable
public Long getEnhancedAltitude() { public Double getEnhancedAltitude() {
return (Long) getFieldByNumber(78); return (Double) getFieldByNumber(78);
} }
@Nullable @Nullable
@@ -36,13 +36,13 @@ public class FitSession extends RecordData {
} }
@Nullable @Nullable
public Long getStartLatitude() { public Double getStartLatitude() {
return (Long) getFieldByNumber(3); return (Double) getFieldByNumber(3);
} }
@Nullable @Nullable
public Long getStartLongitude() { public Double getStartLongitude() {
return (Long) getFieldByNumber(4); return (Double) getFieldByNumber(4);
} }
@Nullable @Nullable
@@ -43,8 +43,8 @@ public class FitUserProfile extends RecordData {
} }
@Nullable @Nullable
public Integer getWeight() { public Float getWeight() {
return (Integer) getFieldByNumber(4); return (Float) getFieldByNumber(4);
} }
@Nullable @Nullable
@@ -45,8 +45,8 @@ public class FitWeather extends RecordData {
} }
@Nullable @Nullable
public Integer getWindSpeed() { public Float getWindSpeed() {
return (Integer) getFieldByNumber(4); return (Float) getFieldByNumber(4);
} }
@Nullable @Nullable
@@ -336,7 +336,7 @@ public class GarminSupportTest extends TestBase {
"FitFileId{serial_number=3889965805, manufacturer=1, product=1561, type=SETTINGS}, " + "FitFileId{serial_number=3889965805, manufacturer=1, product=1561, type=SETTINGS}, " +
"FitFileCreator{software_version=340}, " + "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}, " + "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_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/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}, " + "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}, " + "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}, " + "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}, " + "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=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, speed=36, doughnuts_earned=2}, " + "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, speed=35, doughnuts_earned=3}" + "FitRecord{heart_rate=144, unknown_4(UINT8/1)=92, distance=3710.0, speed=35.344, doughnuts_earned=3}" +
"]"; "]";
FitFile fitFile = FitFile.parseIncoming(fileContents); FitFile fitFile = FitFile.parseIncoming(fileContents);