6.7 KiB
Quick Start Guide
Installation
# Get the module
go get git.blackfinn.de/go/fit-parser/fitparser
# Or clone and use locally
git clone https://git.blackfinn.de/go/fit-parser.git
Quick Test
# Run the example
cd fitparser/example
go run main.go testdata/Activity.fit
# Run tests
cd fitparser
go test -v
Basic Usage
1. Simple File Parsing
package main
import (
"fmt"
"log"
"os"
"git.blackfinn.de/go/fit-parser/fitparser"
)
func main() {
// Read the FIT file
data, err := os.ReadFile("activity.fit")
if err != nil {
log.Fatal(err)
}
// Create decoder
decoder, err := fitparser.NewDecoder(data)
if err != nil {
log.Fatal(err)
}
// Decode messages
messages, err := decoder.Decode()
if err != nil {
log.Printf("Warning: %v", err)
// Continue anyway - CRC errors are non-fatal
}
fmt.Printf("Decoded %d messages\n", len(messages))
}
2. Processing Activity Data
for _, msg := range messages {
switch msg.Num {
case fitparser.MesgNumFileId:
// File information
if fileType, ok := msg.GetFieldValueUint8(fitparser.FieldFileIdType); ok {
fmt.Printf("File Type: %s\n", fitparser.GetFileTypeName(fileType))
}
case fitparser.MesgNumRecord:
// GPS and sensor data points
if timestamp, ok := msg.GetFieldValueUint32(fitparser.FieldRecordTimestamp); ok {
t := fitparser.ConvertFITTimestamp(timestamp)
fmt.Printf("Time: %s\n", t)
}
if hr, ok := msg.GetFieldValueUint8(fitparser.FieldRecordHeartRate); ok {
if hr != fitparser.Uint8Invalid {
fmt.Printf("Heart Rate: %d bpm\n", hr)
}
}
case fitparser.MesgNumSession:
// Activity summary
if sport, ok := msg.GetFieldValueUint8(fitparser.FieldSessionSport); ok {
fmt.Printf("Sport: %s\n", fitparser.GetSportName(sport))
}
if totalTime, ok := msg.GetFieldValueUint32(fitparser.FieldSessionTotalTimerTime); ok {
minutes := float64(totalTime) / 1000.0 / 60.0
fmt.Printf("Duration: %.2f minutes\n", minutes)
}
}
}
3. GPS Coordinates
for _, msg := range messages {
if msg.Num == fitparser.MesgNumRecord {
// Get latitude
if lat, ok := msg.Fields[fitparser.FieldRecordPositionLat]; ok {
if latInt, ok := lat.(int32); ok && latInt != fitparser.Sint32Invalid {
latDeg := fitparser.ConvertSemicirclesToDegrees(latInt)
// Get longitude
if lon, ok := msg.Fields[fitparser.FieldRecordPositionLong]; ok {
if lonInt, ok := lon.(int32); ok && lonInt != fitparser.Sint32Invalid {
lonDeg := fitparser.ConvertSemicirclesToDegrees(lonInt)
fmt.Printf("Position: %.6f, %.6f\n", latDeg, lonDeg)
}
}
}
}
}
}
Running the Example
This repository includes a complete example application:
# From the example directory
cd example
go run main.go testdata/Activity.fit
go run main.go testdata/Settings.fit
go run main.go testdata/RealWorld_Cycling.fit
# Or from the repository root
go run example/main.go example/testdata/Activity.fit
Testing
Run the test suite:
cd fitparser
go test -v
Run benchmarks:
cd fitparser
go test -bench=.
Common Message Types
- FileId (0) - File metadata (type, manufacturer, product, serial number)
- Record (20) - Data points (GPS, heart rate, power, cadence, etc.)
- Lap (19) - Lap summaries
- Session (18) - Activity session summaries
- Activity (34) - Overall activity summaries
- Event (21) - Events (start, stop, lap button, etc.)
- DeviceInfo (23) - Device information
Field Access Patterns
Type-Safe Accessors
// For common types
uint8Val, ok := msg.GetFieldValueUint8(fieldNum)
uint16Val, ok := msg.GetFieldValueUint16(fieldNum)
uint32Val, ok := msg.GetFieldValueUint32(fieldNum)
stringVal, ok := msg.GetFieldValueString(fieldNum)
Direct Field Access
// Access any field by number
if value, ok := msg.Fields[fieldNum]; ok {
// Type assert as needed
switch v := value.(type) {
case uint8:
fmt.Printf("Uint8: %d\n", v)
case uint32:
fmt.Printf("Uint32: %d\n", v)
case int32:
fmt.Printf("Sint32: %d\n", v)
case string:
fmt.Printf("String: %s\n", v)
case []uint8:
fmt.Printf("Byte array: %v\n", v)
}
}
Performance Tips
-
Disable CRC for faster parsing (if you trust your files):
decoder.EnableCRCCheck(false) -
Filter messages early if you only need specific types:
for _, msg := range messages { if msg.Num != fitparser.MesgNumRecord { continue // Skip non-record messages } // Process only records } -
Use type-safe accessors when possible - they're optimized
Handling Invalid Values
FIT files use special "invalid" values to indicate missing data:
// Always check for invalid values
if hr, ok := msg.GetFieldValueUint8(fitparser.FieldRecordHeartRate); ok {
if hr != fitparser.Uint8Invalid {
fmt.Printf("Heart Rate: %d bpm\n", hr)
} else {
fmt.Println("Heart Rate: not available")
}
}
Invalid value constants:
Uint8Invalid= 0xFFUint16Invalid= 0xFFFFUint32Invalid= 0xFFFFFFFFSint8Invalid= 0x7FSint16Invalid= 0x7FFFSint32Invalid= 0x7FFFFFFF
Next Steps
- Read the full README.md for comprehensive documentation
- Explore the example/main.go for a complete working example
- Check the fitparser/ directory for implementation details
- Try parsing your own FIT files!
Troubleshooting
CRC Validation Failures: If CRC validation fails, the file may be corrupted. CRC checking is enabled by default and validates data integrity. You can disable it with decoder.EnableCRCCheck(false) if you trust the file source.
Invalid Field Values: Always check for invalid values using the provided constants (e.g., Uint8Invalid, Uint32Invalid). These indicate missing or unavailable data in the FIT file.
Unknown Message Types: Some proprietary message types may not have names in the profile. They'll show as "Unknown" but can still be accessed by their message number.