Initial commit

Signed-off-by: Thomas Klaehn <thomas.klaehn@perinet.io>
This commit is contained in:
Thomas Klaehn
2026-02-08 07:28:32 +00:00
commit 2945d90d24
25 changed files with 3177 additions and 0 deletions

282
example/main.go Normal file
View File

@@ -0,0 +1,282 @@
package main
import (
"fmt"
"log"
"os"
"strings"
"git.blackfinn.de/go/fit-parser/fitparser"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run main.go <fit-file>")
fmt.Println("Example: go run main.go testdata/Activity.fit")
fmt.Println()
fmt.Println("Available test files:")
fmt.Println(" testdata/Activity.fit - Full activity with GPS and sensors")
fmt.Println(" testdata/Settings.fit - Device settings file")
fmt.Println(" testdata/MonitoringFile.fit - Daily monitoring data")
fmt.Println(" testdata/WorkoutIndividualSteps.fit - Workout definition")
fmt.Println(" testdata/RealWorld_Cycling.fit - Real-world cycling activity")
os.Exit(1)
}
filePath := os.Args[1]
// Read FIT file
data, err := os.ReadFile(filePath)
if err != nil {
log.Fatalf("Error reading file: %v", err)
}
fmt.Printf("File: %s (%d bytes)\n", filePath, len(data))
fmt.Println(strings.Repeat("=", 60))
// Check if it's a valid FIT file
if !fitparser.IsFIT(data) {
log.Fatal("Not a valid FIT file")
}
// Create decoder
decoder, err := fitparser.NewDecoder(data)
if err != nil {
log.Fatalf("Error creating decoder: %v", err)
}
// Optionally disable CRC checking for faster parsing
// decoder.EnableCRCCheck(false)
// Get header info
header := decoder.GetHeader()
if header != nil {
fmt.Println("\nHeader Information:")
fmt.Printf(" %s\n", header)
} else {
// Parse header to get info
header, _ := fitparser.ParseHeader(data)
fmt.Println("\nHeader Information:")
fmt.Printf(" %s\n", header)
}
// Decode all messages
messages, err := decoder.Decode()
if err != nil {
// CRC errors are common, continue anyway
fmt.Printf("Warning: %v\n", err)
fmt.Println("Continuing with decoded messages...")
}
if len(messages) == 0 {
fmt.Println("No messages found in file")
return
}
fmt.Printf("\nDecoded %d messages\n", len(messages))
// Count message types
msgCounts := make(map[uint16]int)
for _, msg := range messages {
msgCounts[msg.Num]++
}
fmt.Println("\nMessage Type Summary:")
for mesgNum, count := range msgCounts {
fmt.Printf(" %-20s: %5d messages\n", fitparser.GetMessageName(mesgNum), count)
}
// Process key messages
fmt.Println(strings.Repeat("=", 60))
fmt.Println("\nFile Details:")
for _, msg := range messages {
switch msg.Num {
case fitparser.MesgNumFileId:
printFileId(msg)
case fitparser.MesgNumActivity:
printActivity(msg)
case fitparser.MesgNumSession:
printSession(msg)
case fitparser.MesgNumLap:
printLap(msg)
}
}
// Print sample records
printSampleRecords(messages)
}
func printFileId(msg *fitparser.Message) {
fmt.Println("\n📄 File ID:")
if fileType, ok := msg.GetFieldValueUint8(fitparser.FieldFileIdType); ok {
fmt.Printf(" Type: %s\n", fitparser.GetFileTypeName(fileType))
}
if manufacturer, ok := msg.GetFieldValueUint16(fitparser.FieldFileIdManufacturer); ok {
fmt.Printf(" Manufacturer: %d\n", manufacturer)
}
if product, ok := msg.GetFieldValueUint16(fitparser.FieldFileIdProduct); ok {
fmt.Printf(" Product: %d\n", product)
}
if serial, ok := msg.GetFieldValueUint32(fitparser.FieldFileIdSerialNumber); ok && serial != fitparser.Uint32Invalid {
fmt.Printf(" Serial Number: %d\n", serial)
}
if timeCreated, ok := msg.GetFieldValueUint32(fitparser.FieldFileIdTimeCreated); ok {
t := fitparser.ConvertFITTimestamp(timeCreated)
fmt.Printf(" Created: %s\n", t.Format("2006-01-02 15:04:05 MST"))
}
}
func printActivity(msg *fitparser.Message) {
fmt.Println("\n🏃 Activity Summary:")
if timestamp, ok := msg.GetFieldValueUint32(fitparser.FieldActivityTimestamp); ok {
t := fitparser.ConvertFITTimestamp(timestamp)
fmt.Printf(" Timestamp: %s\n", t.Format("2006-01-02 15:04:05 MST"))
}
if totalTime, ok := msg.GetFieldValueUint32(fitparser.FieldActivityTotalTimerTime); ok {
minutes := float64(totalTime) / 1000.0 / 60.0
fmt.Printf(" Total Time: %.2f minutes\n", minutes)
}
if numSessions, ok := msg.GetFieldValueUint16(fitparser.FieldActivityNumSessions); ok {
fmt.Printf(" Number of Sessions: %d\n", numSessions)
}
}
func printSession(msg *fitparser.Message) {
fmt.Println("\n📊 Session 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(" Total Time: %.2f minutes\n", minutes)
}
if totalDist, ok := msg.GetFieldValueUint32(fitparser.FieldSessionTotalDistance); ok {
km := float64(totalDist) / 100000.0
fmt.Printf(" Total Distance: %.2f km\n", km)
}
if avgSpeed, ok := msg.GetFieldValueUint16(fitparser.FieldSessionAvgSpeed); ok && avgSpeed != fitparser.Uint16Invalid {
speed := float64(avgSpeed) / 1000.0
fmt.Printf(" Average Speed: %.2f m/s (%.2f km/h)\n", speed, speed*3.6)
}
if maxSpeed, ok := msg.GetFieldValueUint16(fitparser.FieldSessionMaxSpeed); ok && maxSpeed != fitparser.Uint16Invalid {
speed := float64(maxSpeed) / 1000.0
fmt.Printf(" Max Speed: %.2f m/s (%.2f km/h)\n", speed, speed*3.6)
}
if avgHR, ok := msg.GetFieldValueUint8(fitparser.FieldSessionAvgHeartRate); ok && avgHR != fitparser.Uint8Invalid {
fmt.Printf(" Average Heart Rate: %d bpm\n", avgHR)
}
if maxHR, ok := msg.GetFieldValueUint8(fitparser.FieldSessionMaxHeartRate); ok && maxHR != fitparser.Uint8Invalid {
fmt.Printf(" Max Heart Rate: %d bpm\n", maxHR)
}
if totalCalories, ok := msg.GetFieldValueUint16(fitparser.FieldSessionTotalCalories); ok {
fmt.Printf(" Total Calories: %d kcal\n", totalCalories)
}
if avgPower, ok := msg.GetFieldValueUint16(fitparser.FieldSessionAvgPower); ok && avgPower != fitparser.Uint16Invalid {
fmt.Printf(" Average Power: %d watts\n", avgPower)
}
if maxPower, ok := msg.GetFieldValueUint16(fitparser.FieldSessionMaxPower); ok && maxPower != fitparser.Uint16Invalid {
fmt.Printf(" Max Power: %d watts\n", maxPower)
}
}
func printLap(msg *fitparser.Message) {
fmt.Println("\n⏱ Lap:")
if timestamp, ok := msg.GetFieldValueUint32(fitparser.FieldLapTimestamp); ok {
t := fitparser.ConvertFITTimestamp(timestamp)
fmt.Printf(" Timestamp: %s\n", t.Format("15:04:05"))
}
if totalTime, ok := msg.GetFieldValueUint32(fitparser.FieldLapTotalTimerTime); ok {
seconds := float64(totalTime) / 1000.0
fmt.Printf(" Time: %.2f seconds\n", seconds)
}
if totalDist, ok := msg.GetFieldValueUint32(fitparser.FieldLapTotalDistance); ok {
meters := float64(totalDist) / 100.0
fmt.Printf(" Distance: %.2f meters\n", meters)
}
if avgHR, ok := msg.GetFieldValueUint8(fitparser.FieldLapAvgHeartRate); ok && avgHR != fitparser.Uint8Invalid {
fmt.Printf(" Avg HR: %d bpm\n", avgHR)
}
}
func printSampleRecords(messages []*fitparser.Message) {
fmt.Println(strings.Repeat("=", 60))
fmt.Println("\nSample Records (first 5):")
count := 0
for _, msg := range messages {
if msg.Num == fitparser.MesgNumRecord && count < 5 {
count++
fmt.Printf("\n📍 Record #%d:\n", count)
if timestamp, ok := msg.GetFieldValueUint32(fitparser.FieldRecordTimestamp); ok {
t := fitparser.ConvertFITTimestamp(timestamp)
fmt.Printf(" Time: %s\n", t.Format("15:04:05"))
}
// Position
if lat, ok := msg.Fields[fitparser.FieldRecordPositionLat]; ok {
if latInt, ok := lat.(int32); ok && latInt != fitparser.Sint32Invalid {
latDeg := fitparser.ConvertSemicirclesToDegrees(latInt)
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)
}
}
}
}
if altitude, ok := msg.GetFieldValueUint16(fitparser.FieldRecordAltitude); ok && altitude != fitparser.Uint16Invalid {
alt := float64(altitude)/5.0 - 500.0
fmt.Printf(" Altitude: %.1f m\n", alt)
}
if hr, ok := msg.GetFieldValueUint8(fitparser.FieldRecordHeartRate); ok && hr != fitparser.Uint8Invalid {
fmt.Printf(" Heart Rate: %d bpm\n", hr)
}
if cadence, ok := msg.GetFieldValueUint8(fitparser.FieldRecordCadence); ok && cadence != fitparser.Uint8Invalid {
fmt.Printf(" Cadence: %d rpm\n", cadence)
}
if speed, ok := msg.GetFieldValueUint16(fitparser.FieldRecordSpeed); ok && speed != fitparser.Uint16Invalid {
spd := float64(speed) / 1000.0
fmt.Printf(" Speed: %.2f m/s (%.2f km/h)\n", spd, spd*3.6)
}
if power, ok := msg.GetFieldValueUint16(fitparser.FieldRecordPower); ok && power != fitparser.Uint16Invalid {
fmt.Printf(" Power: %d watts\n", power)
}
if temp, ok := msg.GetFieldValueUint8(fitparser.FieldRecordTemperature); ok && temp != fitparser.Sint8Invalid {
fmt.Printf(" Temperature: %d °C\n", int(temp))
}
}
}
}