// Package fitparser provides functionality to decode FIT (Flexible and Interoperable Data Transfer) files. // FIT is a binary file format used by Garmin and other fitness device manufacturers. package fitparser import "time" // Protocol and Profile versions const ( ProtocolVersionMajorShift = 4 ProtocolVersionMajorMask = 0xF0 ProtocolVersionMinorMask = 0x0F ProtocolVersion10 = 0x10 ProtocolVersion20 = 0x20 ProfileVersionMajor = 21 ProfileVersionMinor = 188 ) // Base type definitions const ( BaseTypeEnum = 0x00 BaseTypeSint8 = 0x01 BaseTypeUint8 = 0x02 BaseTypeSint16 = 0x83 BaseTypeUint16 = 0x84 BaseTypeSint32 = 0x85 BaseTypeUint32 = 0x86 BaseTypeString = 0x07 BaseTypeFloat32 = 0x88 BaseTypeFloat64 = 0x89 BaseTypeUint8z = 0x0A BaseTypeUint16z = 0x8B BaseTypeUint32z = 0x8C BaseTypeByte = 0x0D BaseTypeSint64 = 0x8E BaseTypeUint64 = 0x8F BaseTypeUint64z = 0x90 ) // Base type flags const ( BaseTypeEndianFlag = 0x80 BaseTypeNumMask = 0x1F ) // Invalid values for each type const ( EnumInvalid = 0xFF Sint8Invalid = 0x7F Uint8Invalid = 0xFF Sint16Invalid = 0x7FFF Uint16Invalid = 0xFFFF Sint32Invalid = 0x7FFFFFFF Uint32Invalid = 0xFFFFFFFF StringInvalid = 0x00 Float32Invalid = 0xFFFFFFFF Float64Invalid = 0xFFFFFFFFFFFFFFFF Uint8zInvalid = 0x00 Uint16zInvalid = 0x0000 Uint32zInvalid = 0x00000000 ByteInvalid = 0xFF Sint64Invalid = 0x7FFFFFFFFFFFFFFF Uint64Invalid = 0xFFFFFFFFFFFFFFFF Uint64zInvalid = 0x0000000000000000 ) // Header sizes const ( HeaderWithCRCSize = 14 HeaderWithoutCRCSize = 12 HeaderMinSize = 12 CRCSize = 2 ) // Record header bits const ( RecordHeaderMask = 0x80 RecordHeaderNormal = 0x00 RecordHeaderCompressedMask = 0x80 RecordHeaderLocalMesgNumMask = 0x0F RecordHeaderTimeMask = 0x1F ) // Message definition bits const ( MesgDefinitionMask = 0x40 MesgHeaderMask = 0xF0 LocalMesgNum = 0x0F MesgDefinitionReserved = 0x00 DevDataMask = 0x20 ) // FIT file signature var FITSignature = [4]byte{'.', 'F', 'I', 'T'} // FIT Epoch - Number of seconds between Unix Epoch and FIT Epoch (Dec 31, 1989 00:00:00 UTC) const FITEpochSeconds = 631065600 // ConvertFITTimestamp converts a FIT timestamp to a Go time.Time func ConvertFITTimestamp(fitTime uint32) time.Time { if fitTime == Uint32Invalid { return time.Time{} } return time.Unix(int64(fitTime)+FITEpochSeconds, 0).UTC() } // BaseTypeSize returns the size in bytes of the given base type func BaseTypeSize(baseType byte) int { switch baseType & BaseTypeNumMask { case BaseTypeEnum, BaseTypeSint8, BaseTypeUint8, BaseTypeByte, BaseTypeUint8z: return 1 case BaseTypeSint16, BaseTypeUint16, BaseTypeUint16z: return 2 case BaseTypeSint32, BaseTypeUint32, BaseTypeUint32z, BaseTypeFloat32: return 4 case BaseTypeSint64, BaseTypeUint64, BaseTypeUint64z, BaseTypeFloat64: return 8 case BaseTypeString: return 1 // Variable length, but each character is 1 byte default: return 0 } } // IsEndianType returns true if the base type requires endian conversion func IsEndianType(baseType byte) bool { return (baseType & BaseTypeEndianFlag) != 0 } // IsInvalidValue checks if a value is invalid for its type func IsInvalidValue(baseType byte, value interface{}) bool { switch baseType & BaseTypeNumMask { case BaseTypeEnum, BaseTypeUint8: if v, ok := value.(uint8); ok { return v == Uint8Invalid } case BaseTypeSint8: if v, ok := value.(int8); ok { return v == Sint8Invalid } case BaseTypeUint16: if v, ok := value.(uint16); ok { return v == Uint16Invalid } case BaseTypeSint16: if v, ok := value.(int16); ok { return v == Sint16Invalid } case BaseTypeUint32: if v, ok := value.(uint32); ok { return v == Uint32Invalid } case BaseTypeSint32: if v, ok := value.(int32); ok { return v == Sint32Invalid } case BaseTypeUint64: if v, ok := value.(uint64); ok { return v == Uint64Invalid } case BaseTypeSint64: if v, ok := value.(int64); ok { return v == Sint64Invalid } case BaseTypeUint8z: if v, ok := value.(uint8); ok { return v == Uint8zInvalid } case BaseTypeUint16z: if v, ok := value.(uint16); ok { return v == Uint16zInvalid } case BaseTypeUint32z: if v, ok := value.(uint32); ok { return v == Uint32zInvalid } case BaseTypeUint64z: if v, ok := value.(uint64); ok { return v == Uint64zInvalid } } return false }