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 ") 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)) } } } }