308
fitparser/message.go
Normal file
308
fitparser/message.go
Normal file
@@ -0,0 +1,308 @@
|
||||
package fitparser
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// FieldDefinition represents a field definition in a message definition
|
||||
type FieldDefinition struct {
|
||||
Num uint8 // Field definition number
|
||||
Size uint8 // Size in bytes
|
||||
BaseType uint8 // Base type
|
||||
}
|
||||
|
||||
// DeveloperFieldDefinition represents a developer field definition
|
||||
type DeveloperFieldDefinition struct {
|
||||
Num uint8
|
||||
Size uint8
|
||||
DeveloperDataIndex uint8
|
||||
}
|
||||
|
||||
// MessageDefinition represents a message definition record
|
||||
type MessageDefinition struct {
|
||||
Reserved uint8
|
||||
Architecture uint8 // 0 = little endian, 1 = big endian
|
||||
GlobalMesgNum uint16
|
||||
NumFields uint8
|
||||
FieldDefinitions []FieldDefinition
|
||||
NumDevFields uint8
|
||||
DevFieldDefinitions []DeveloperFieldDefinition
|
||||
}
|
||||
|
||||
// Message represents a decoded FIT message
|
||||
type Message struct {
|
||||
Num uint16 // Global message number
|
||||
Fields map[uint8]interface{} // Field number -> value
|
||||
DevFields map[uint8]interface{} // Developer field number -> value
|
||||
}
|
||||
|
||||
// NewMessage creates a new message
|
||||
func NewMessage(num uint16) *Message {
|
||||
return &Message{
|
||||
Num: num,
|
||||
Fields: make(map[uint8]interface{}),
|
||||
DevFields: make(map[uint8]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// GetFieldValue returns the value of a field by field number
|
||||
func (m *Message) GetFieldValue(fieldNum uint8) (interface{}, bool) {
|
||||
val, ok := m.Fields[fieldNum]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// GetFieldValueUint8 returns a field value as uint8
|
||||
func (m *Message) GetFieldValueUint8(fieldNum uint8) (uint8, bool) {
|
||||
if val, ok := m.Fields[fieldNum]; ok {
|
||||
if v, ok := val.(uint8); ok {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// GetFieldValueUint16 returns a field value as uint16
|
||||
func (m *Message) GetFieldValueUint16(fieldNum uint8) (uint16, bool) {
|
||||
if val, ok := m.Fields[fieldNum]; ok {
|
||||
if v, ok := val.(uint16); ok {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// GetFieldValueUint32 returns a field value as uint32
|
||||
func (m *Message) GetFieldValueUint32(fieldNum uint8) (uint32, bool) {
|
||||
if val, ok := m.Fields[fieldNum]; ok {
|
||||
if v, ok := val.(uint32); ok {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// GetFieldValueString returns a field value as string
|
||||
func (m *Message) GetFieldValueString(fieldNum uint8) (string, bool) {
|
||||
if val, ok := m.Fields[fieldNum]; ok {
|
||||
if v, ok := val.(string); ok {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// IsLittleEndian returns true if the message definition uses little endian byte order
|
||||
func (md *MessageDefinition) IsLittleEndian() bool {
|
||||
return md.Architecture == 0
|
||||
}
|
||||
|
||||
// DecodeField decodes a single field value from raw bytes
|
||||
func DecodeField(data []byte, fieldDef FieldDefinition, littleEndian bool) (interface{}, error) {
|
||||
if len(data) < int(fieldDef.Size) {
|
||||
return nil, fmt.Errorf("insufficient data for field: need %d bytes, got %d", fieldDef.Size, len(data))
|
||||
}
|
||||
|
||||
// Extract the base type number by masking (removes endian flag)
|
||||
baseTypeNum := fieldDef.BaseType & BaseTypeNumMask
|
||||
fieldData := data[:fieldDef.Size]
|
||||
|
||||
switch baseTypeNum {
|
||||
case 0x00, 0x02, 0x0A, 0x0D: // Enum (0x00), Uint8 (0x02), Uint8z (0x0A), Byte (0x0D)
|
||||
if fieldDef.Size == 1 {
|
||||
return uint8(fieldData[0]), nil
|
||||
}
|
||||
// Array of bytes
|
||||
result := make([]uint8, fieldDef.Size)
|
||||
copy(result, fieldData)
|
||||
return result, nil
|
||||
|
||||
case 0x01: // Sint8
|
||||
if fieldDef.Size == 1 {
|
||||
return int8(fieldData[0]), nil
|
||||
}
|
||||
// Array of int8
|
||||
result := make([]int8, fieldDef.Size)
|
||||
for i := range result {
|
||||
result[i] = int8(fieldData[i])
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case 0x04, 0x0B: // Uint16 (0x84 masked), Uint16z (0x8B masked)
|
||||
if fieldDef.Size == 2 {
|
||||
if littleEndian {
|
||||
return binary.LittleEndian.Uint16(fieldData), nil
|
||||
}
|
||||
return binary.BigEndian.Uint16(fieldData), nil
|
||||
}
|
||||
// Array of uint16
|
||||
count := fieldDef.Size / 2
|
||||
result := make([]uint16, count)
|
||||
for i := range result {
|
||||
if littleEndian {
|
||||
result[i] = binary.LittleEndian.Uint16(fieldData[i*2:])
|
||||
} else {
|
||||
result[i] = binary.BigEndian.Uint16(fieldData[i*2:])
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case 0x03: // Sint16 (0x83 masked)
|
||||
if fieldDef.Size == 2 {
|
||||
if littleEndian {
|
||||
return int16(binary.LittleEndian.Uint16(fieldData)), nil
|
||||
}
|
||||
return int16(binary.BigEndian.Uint16(fieldData)), nil
|
||||
}
|
||||
// Array of int16
|
||||
count := fieldDef.Size / 2
|
||||
result := make([]int16, count)
|
||||
for i := range result {
|
||||
if littleEndian {
|
||||
result[i] = int16(binary.LittleEndian.Uint16(fieldData[i*2:]))
|
||||
} else {
|
||||
result[i] = int16(binary.BigEndian.Uint16(fieldData[i*2:]))
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case 0x06, 0x0C: // Uint32 (0x86 masked), Uint32z (0x8C masked)
|
||||
if fieldDef.Size == 4 {
|
||||
if littleEndian {
|
||||
return binary.LittleEndian.Uint32(fieldData), nil
|
||||
}
|
||||
return binary.BigEndian.Uint32(fieldData), nil
|
||||
}
|
||||
// Array of uint32
|
||||
count := fieldDef.Size / 4
|
||||
result := make([]uint32, count)
|
||||
for i := range result {
|
||||
if littleEndian {
|
||||
result[i] = binary.LittleEndian.Uint32(fieldData[i*4:])
|
||||
} else {
|
||||
result[i] = binary.BigEndian.Uint32(fieldData[i*4:])
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case 0x05: // Sint32 (0x85 masked)
|
||||
if fieldDef.Size == 4 {
|
||||
if littleEndian {
|
||||
return int32(binary.LittleEndian.Uint32(fieldData)), nil
|
||||
}
|
||||
return int32(binary.BigEndian.Uint32(fieldData)), nil
|
||||
}
|
||||
// Array of int32
|
||||
count := fieldDef.Size / 4
|
||||
result := make([]int32, count)
|
||||
for i := range result {
|
||||
if littleEndian {
|
||||
result[i] = int32(binary.LittleEndian.Uint32(fieldData[i*4:]))
|
||||
} else {
|
||||
result[i] = int32(binary.BigEndian.Uint32(fieldData[i*4:]))
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case 0x08: // Float32 (0x88 masked)
|
||||
if fieldDef.Size == 4 {
|
||||
var bits uint32
|
||||
if littleEndian {
|
||||
bits = binary.LittleEndian.Uint32(fieldData)
|
||||
} else {
|
||||
bits = binary.BigEndian.Uint32(fieldData)
|
||||
}
|
||||
return math.Float32frombits(bits), nil
|
||||
}
|
||||
// Array of float32
|
||||
count := fieldDef.Size / 4
|
||||
result := make([]float32, count)
|
||||
for i := range result {
|
||||
var bits uint32
|
||||
if littleEndian {
|
||||
bits = binary.LittleEndian.Uint32(fieldData[i*4:])
|
||||
} else {
|
||||
bits = binary.BigEndian.Uint32(fieldData[i*4:])
|
||||
}
|
||||
result[i] = math.Float32frombits(bits)
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case 0x09: // Float64 (0x89 masked)
|
||||
if fieldDef.Size == 8 {
|
||||
var bits uint64
|
||||
if littleEndian {
|
||||
bits = binary.LittleEndian.Uint64(fieldData)
|
||||
} else {
|
||||
bits = binary.BigEndian.Uint64(fieldData)
|
||||
}
|
||||
return math.Float64frombits(bits), nil
|
||||
}
|
||||
// Array of float64
|
||||
count := fieldDef.Size / 8
|
||||
result := make([]float64, count)
|
||||
for i := range result {
|
||||
var bits uint64
|
||||
if littleEndian {
|
||||
bits = binary.LittleEndian.Uint64(fieldData[i*8:])
|
||||
} else {
|
||||
bits = binary.BigEndian.Uint64(fieldData[i*8:])
|
||||
}
|
||||
result[i] = math.Float64frombits(bits)
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case 0x0F, 0x10: // Uint64 (0x8F masked), Uint64z (0x90 masked)
|
||||
if fieldDef.Size == 8 {
|
||||
if littleEndian {
|
||||
return binary.LittleEndian.Uint64(fieldData), nil
|
||||
}
|
||||
return binary.BigEndian.Uint64(fieldData), nil
|
||||
}
|
||||
// Array of uint64
|
||||
count := fieldDef.Size / 8
|
||||
result := make([]uint64, count)
|
||||
for i := range result {
|
||||
if littleEndian {
|
||||
result[i] = binary.LittleEndian.Uint64(fieldData[i*8:])
|
||||
} else {
|
||||
result[i] = binary.BigEndian.Uint64(fieldData[i*8:])
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case 0x0E: // Sint64 (0x8E masked)
|
||||
if fieldDef.Size == 8 {
|
||||
if littleEndian {
|
||||
return int64(binary.LittleEndian.Uint64(fieldData)), nil
|
||||
}
|
||||
return int64(binary.BigEndian.Uint64(fieldData)), nil
|
||||
}
|
||||
// Array of int64
|
||||
count := fieldDef.Size / 8
|
||||
result := make([]int64, count)
|
||||
for i := range result {
|
||||
if littleEndian {
|
||||
result[i] = int64(binary.LittleEndian.Uint64(fieldData[i*8:]))
|
||||
} else {
|
||||
result[i] = int64(binary.BigEndian.Uint64(fieldData[i*8:]))
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case 0x07: // String
|
||||
// String field - find null terminator
|
||||
end := len(fieldData)
|
||||
for i, b := range fieldData {
|
||||
if b == 0 {
|
||||
end = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return string(fieldData[:end]), nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported base type: 0x%02X (num: 0x%02X)", fieldDef.BaseType, baseTypeNum)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user