Garmin: fix .FIT file decoding for COROS generated files

example files for testing:
https://github.com/ThomasKuehne/FIT-test-files/tree/main/Activity/2021/Activity_20210630_001_Running_COROS-PACE-2_9677fe59cdc4684a89189dad1c288bb4.fit
https://github.com/ThomasKuehne/FIT-test-files/tree/main/Activity/2021/Activity_20210830_001_Running_COROS-APEX-46mm_11bb1b1e1c61bfed8bdd8ff0623391c5.fit
https://github.com/ThomasKuehne/FIT-test-files/tree/main/Activity/2023/Activity_20230626_043_Windsurfing_COROS-VERTIX_ff49a5a499ee66c4229038c547e32c8e.fit
https://github.com/ThomasKuehne/FIT-test-files/tree/main/Activity/2023/Activity_20230630_043_Windsurfing_COROS-VERTIX-2_d41760a0235a9b8037273b7651c4f532.fit
https://github.com/ThomasKuehne/FIT-test-files/tree/main/Activity/2023/Activity_20230821_043_Windsurfing_COROS-APEX-Pro_6d750dd6c2190bc6a0d89c441e708656.fit
https://github.com/ThomasKuehne/FIT-test-files/tree/main/Activity/2023/Activity_20231117_001_Running_COROS-PACE-3_badb78c49b7aef7b5e112f28da83604c.fit
https://github.com/ThomasKuehne/FIT-test-files/tree/main/Activity/2024/Activity_20240107_017_Hiking_COROS-APEX-2-Pro_0a7a9b95b09df66ffa48ea97bb27209d.fit
https://github.com/ThomasKuehne/FIT-test-files/tree/main/Activity/2025/Activity_20251119_002_008_Cycling_Mountain_COROS-DURA_4e04b1f1bad2e57127e9a2affd4cd7d0.fit
https://github.com/ThomasKuehne/FIT-test-files/tree/main/Activity/2025/Activity_20250722_001_Running_COROS-PACE-Pro_13d6d1e2a93a1ed855a068145ae80110.fit
https://github.com/ThomasKuehne/FIT-test-files/tree/main/Activity/2025/Activity_20250801_001_003_Running_Trail_COROS-Wearables_99901ef8f988756d25ac3afbbb1222eb.fit
This commit is contained in:
Thomas Kuehne
2026-04-26 09:43:39 +00:00
parent 16b29bd09d
commit bf5fde297d
3 changed files with 62 additions and 18 deletions
@@ -42,29 +42,20 @@ public class FieldDefinition implements FieldInterface {
int size = garminByteBufferReader.readByte();
int baseTypeIdentifier = garminByteBufferReader.readByte();
BaseType baseType = BaseType.fromIdentifier(baseTypeIdentifier);
FieldDefinition nativeFITMessageFieldDefinition = nativeFITMessage.getFieldDefinition(number, size);
FieldDefinition nativeFITMessageFieldDefinition = nativeFITMessage.getFieldDefinition(number, size, baseType);
if (nativeFITMessageFieldDefinition != null) {
if (nativeFITMessageFieldDefinition.getBaseType().equals(baseType)) {
return nativeFITMessageFieldDefinition;
} else {
if (baseType == BaseType.UINT8 && nativeFITMessageFieldDefinition.getBaseType() == BaseType.ENUM) {
// Allow uint8 -> enum, as older fit versions changed the type of some fields
return nativeFITMessageFieldDefinition;
}
LOG.warn(
"Native for {}[{}] is of type {}, but message declares {}",
nativeFITMessage.name(),
nativeFITMessageFieldDefinition.name,
nativeFITMessageFieldDefinition.getBaseType(),
baseType
);
}
return nativeFITMessageFieldDefinition;
}
if (number == 253 && size == 4 && baseType.equals(BaseType.UINT32)) {
return new FieldDefinitionTimestamp(number, size, baseType, "253_timestamp");
}
if (0 != (size % baseType.getSize())) {
LOG.warn("inconsistent size of field {} in record {}/{} - total size: {}, base size: {}, base type: {}",
number, nativeFITMessage.getNumber(), nativeFITMessage.name(), size,
baseType.getSize(), baseType);
}
return new FieldDefinition(number, size, baseType, "");
}
@@ -1,7 +1,12 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -2567,17 +2572,58 @@ public class NativeFITMessage {
}
@Nullable
FieldDefinition getFieldDefinition(int id, int size) {
FieldDefinition getFieldDefinition(int id, int size, @NonNull BaseType baseType) {
if (null == fieldDefinitionPrimitives)
return null;
for (NativeFITMessage.FieldDefinitionPrimitive fieldDefinitionPrimitive :
fieldDefinitionPrimitives) {
if (fieldDefinitionPrimitive.number == id) {
// some .FIT encoders don't strictly stick to current standard
// try to handle common cases gracefully
Level logLevel = null;
final BaseType stdBaseType = fieldDefinitionPrimitive.baseType;
if (stdBaseType == BaseType.ENUM && baseType == BaseType.UINT8) {
// very common issue
logLevel = Level.DEBUG;
} else if (stdBaseType == BaseType.UINT32Z && baseType == BaseType.UINT32) {
// quite common issue
logLevel = Level.INFO;
} else if (stdBaseType != baseType) {
logLevel = Level.WARN;
}
if ((size % baseType.getSize()) != 0) {
logLevel = Level.WARN;
}
if (logLevel != null) {
LOG.atLevel(logLevel).log(
"Native for {}[{}] is of type {} with size {} (base: {}), but message declares {} with size {} (base: {})",
name(),
fieldDefinitionPrimitive.name,
stdBaseType,
fieldDefinitionPrimitive.size,
stdBaseType.getSize(),
baseType,
size,
baseType.getSize()
);
if (size == 1 && (baseType == BaseType.UINT16 || baseType == BaseType.UINT32 || baseType == BaseType.UINT64)) {
// very common issue for COROS:
// Native for EVENT[data] is of type UINT32 with size 4 (base: 4), but message declares UINT32 with size 1 (base: 4)
LOG.warn("redefining field base type from {} to {} due to size",
baseType, BaseType.UINT8);
baseType = BaseType.UINT8;
}
}
return FieldDefinitionFactory.create(
fieldDefinitionPrimitive.number,
size,
fieldDefinitionPrimitive.type,
fieldDefinitionPrimitive.baseType,
baseType,
fieldDefinitionPrimitive.name,
fieldDefinitionPrimitive.scale,
fieldDefinitionPrimitive.offset
@@ -2690,4 +2736,6 @@ public class NativeFITMessage {
return result;
}
}
private static final Logger LOG = LoggerFactory.getLogger(NativeFITMessage.class);
}
@@ -234,6 +234,11 @@ public class RecordData {
if(clazz.equals(Integer.class)){
return clazz.cast(number.intValue());
}
// required for common COROS size mismatches like: FitEvent[event] type UINT32 with actual size 1
if (Long.class.equals(clazz)) {
return clazz.cast(number.longValue());
}
}
LOG.error(