183 lines
4.6 KiB
Go
183 lines
4.6 KiB
Go
// 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
|
|
}
|