### Install fitactivity CLI Source: https://github.com/muktihari/fit/blob/master/cmd/fitactivity/README.md Installs the fitactivity program directly from the remote repository. The executable is saved in $GOPATH/bin. ```sh go install github.com/muktihari/fit/cmd/fitactivity@latest ``` -------------------------------- ### Install FIT Dump CLI Source: https://github.com/muktihari/fit/blob/master/cmd/fitdump/README.md Install the FIT dump tool globally using 'go install'. ```sh go install . ``` -------------------------------- ### Run the installed FIT Print CLI binary Source: https://github.com/muktihari/fit/blob/master/cmd/fitprint/README.md Execute the 'fitprint' command after installation to process a FIT file. The output is similar to running it directly with 'go run'. ```sh fitprint DeveloperData.fit # Output: # ๐Ÿ“„ "DeveloperData.fit" -> "DeveloperData-fitprint.txt" ``` -------------------------------- ### Run Installed FIT Dump Binary Source: https://github.com/muktihari/fit/blob/master/cmd/fitdump/README.md Execute the installed FIT dump tool directly to process a FIT file. ```sh fitdump triathlon_summary_last.fit # Output: # FIT dumped: "triathlon_summary_last.fit" -> "triathlon_summary_last-fitdump.txt" ``` -------------------------------- ### Install CSV Diff Tool Source: https://github.com/muktihari/fit/blob/master/internal/cmd/csvdiff/README.md Install the CSV diff tool globally using 'go install' for command-line access. After installation, it can be run as a standalone command. ```bash $ go install . ``` -------------------------------- ### Reusable Decoder with sync.Pool Source: https://github.com/muktihari/fit/blob/master/docs/usage.md This example shows how to use a sync.Pool to manage Decoder instances, reducing memory allocation overhead. Decoders are retrieved from the pool, used, and then returned to the pool. Ensure to reset the decoder and close the listener and request body. ```go package main import ( "fmt" "log" "net/http" "sync" "github.com/muktihari/fit/decoder" "github.com/muktihari/fit/profile/mesgdef" ) var ( pool = sync.Pool{New: func() any { return new(decoder.Decoder) }} lispool = sync.Pool{New: func() any { return new(filedef.Listener) }} ) func main() { srv := http.NewServeMux() srv.HandleFunc("/decode", func(w http.ResponseWriter, r *http.Request) { dec := pool.Get().(*decoder.Decoder) defer pool.Put(dec) // put decoder back to the pool lis := lispool.Get().(*filedef.Listener) defer lispool.Put(lis) // put listener back to the pool defer lis.Close() // release channels used by listener // Assign reader and options just like // when using decoder.New(). dec.Reset(r.Body, decoder.WithMesgListener(lis), decoder.WithBroadcastOnly(), ) defer r.Body.Close() for dec.Next() { _, err := dec.Decode() if err != nil { writeError(w, err) return } activity, ok := lis.File().(*filedef.Activity) if !ok { writeError(w, fmt.Errorf("not an activity file")) } fmt.Fprintf(w, "%+v\n", activity.FileId) } }) log.Fatal(http.ListenAndServe(":8080", srv)) } func writeError(w http.ResponseWriter, err error) { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("{\"err\":%q}", err))) } ``` -------------------------------- ### Example FIT File Dump Output Source: https://github.com/muktihari/fit/blob/master/cmd/fitdump/README.md This is an example of the segmented bytes output from a FIT file. It shows file headers, message definitions, and message data. ```txt SEGMENT |LOCAL NUM| HEADER BYTES file_header [14 32 133 82 230 44 4 0 46 70 73 84 12 58] message_definition | 0| 01000000 [64 0 1 0 0 7 3 4 140 4 4 134 7 4 134 1 2 132 2 2 132 5 2 132 0 1 0] message_data | 0| 00000000 [0 203 230 191 170 63 85 233 108 255 255 255 255 0 1 15 152 255 255 4] message_definition | 1| 01000001 [65 0 1 0 49 5 2 20 7 0 2 132 1 1 2 3 1 0 4 1 0] message_data | 1| 00000001 [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 166 255 255 255] ... ``` -------------------------------- ### Run Installed CSV Diff Tool Source: https://github.com/muktihari/fit/blob/master/internal/cmd/csvdiff/README.md Execute the installed CSV diff command-line tool. Provide two CSV file paths as arguments and redirect the output to a diff.txt file. ```bash $ csvdiff file1.csv file2.csv > diff.txt ``` -------------------------------- ### Encode FIT Protocol Messages in Go Source: https://github.com/muktihari/fit/blob/master/docs/usage.md Use this example to encode FIT protocol messages by self-declaring them. This demonstrates composing messages using the SDK. Ensure you do not wrap the writer with a buffer like bufio.Writer, as the encoder implements efficient buffering. ```go package main import ( "os" "time" "github.com/muktihari/fit/encoder" "github.com/muktihari/fit/kit/datetime" "github.com/muktihari/fit/profile/factory" "github.com/muktihari/fit/profile/typedef" "github.com/muktihari/fit/profile/untyped/fieldnum" "github.com/muktihari/fit/profile/untyped/mesgnum" "github.com/muktihari/fit/proto" ) func main() { now := time.Now() fit := proto.FIT{ Messages: []proto.Message{ {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity.Byte()), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerBryton.Uint16()), factory.CreateField(mesgnum.FileId, fieldnum.FileIdProduct).WithValue(uint16(1901)), // Bryton Rider 420 factory.CreateField(mesgnum.FileId, fieldnum.FileIdProductName).WithValue("Bryton Rider 420"), }}, {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now)), factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32(100)), factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(1000)), factory.CreateField(mesgnum.Record, fieldnum.RecordCadence).WithValue(uint8(78)), factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate).WithValue(uint8(100)), }}, {Num: mesgnum.Session, Fields: []proto.Field{ factory.CreateField(mesgnum.Session, fieldnum.SessionTimestamp).WithValue(datetime.ToUint32(now)), factory.CreateField(mesgnum.Session, fieldnum.SessionTotalDistance).WithValue(uint32(100)), factory.CreateField(mesgnum.Session, fieldnum.SessionAvgSpeed).WithValue(uint16(1000)), factory.CreateField(mesgnum.Session, fieldnum.SessionAvgCadence).WithValue(uint8(78)), factory.CreateField(mesgnum.Session, fieldnum.SessionAvgHeartRate).WithValue(uint8(100)), }}, {Num: mesgnum.Activity, Fields: []proto.Field{ factory.CreateField(mesgnum.Activity, fieldnum.ActivityTimestamp).WithValue(datetime.ToUint32(now)), factory.CreateField(mesgnum.Activity, fieldnum.ActivityType).WithValue(typedef.ActivityManual.Byte()), factory.CreateField(mesgnum.Activity, fieldnum.ActivityLocalTimestamp).WithValue(datetime.ToUint32(now.Add(7 * time.Hour))), // GMT+7 factory.CreateField(mesgnum.Activity, fieldnum.ActivityNumSessions).WithValue(uint16(1)), }}, }, } f, err := os.OpenFile("CoffeeRide.fit", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) if err != nil { panic(err) } defer f.Close() enc := encoder.New(f) if err := enc.Encode(&fit); err != nil { panic(err) } } ``` -------------------------------- ### Process Chained FIT Files with Custom Listener in Go Source: https://github.com/muktihari/fit/blob/master/docs/usage.md Decode chained FIT files using a custom listener and process each file sequence. This example demonstrates how to extract specific file types like Activity files. ```go ... lis := filedef.NewListener() defer lis.Close() dec := decoder.New(f, decoder.WithMesgListener(lis), decoder.WithBroadcastOnly(), ) for dec.Next() { _, err := dec.Decode() if err != nil { return err } file, ok := lis.File().(*filedef.Activity) if !ok { return } // do something with file variable } ... ``` -------------------------------- ### Run FIT Dump CLI with Hexadecimal Output Source: https://github.com/muktihari/fit/blob/master/cmd/fitdump/README.md Use the '-hex' option with 'go run' or the installed binary to display FIT file bytes in hexadecimal format. ```sh go run main.go -hex triathlon_summary_last.fit ``` ```sh fitdump -hex triathlon_summary_last.fit ``` -------------------------------- ### Run Fitconv with Options Source: https://github.com/muktihari/fit/blob/master/cmd/fitconv/README.md Demonstrates using fitconv with various options for FIT to CSV conversion, including degree output for GPS, raw values, and trimming trailing commas. ```sh fitconv -deg activity.fit activity2.fit ``` ```sh fitconv -raw activity.fit activity2.fit ``` ```sh fitconv -deg -no-expand -trim activity.fit activity2.fit ``` -------------------------------- ### Run fitactivity CLI Source: https://github.com/muktihari/fit/blob/master/cmd/fitactivity/README.md Run the CLI without arguments to display help text and available commands. ```sh fitactivity ``` -------------------------------- ### Build fitactivity for Windows Source: https://github.com/muktihari/fit/blob/master/cmd/fitactivity/README.md Manually builds the fitactivity executable for Windows. Ensure you have cloned the repository and are in the cmd/fitactivity folder. ```sh GOOS=windows GOARCH=amd64 go build -o fitactivity.exe main.go ``` -------------------------------- ### Run FIT Print CLI with a FIT file Source: https://github.com/muktihari/fit/blob/master/cmd/fitprint/README.md Execute the Go program directly to process a FIT file. The output will be a text file with the same base name and a .txt extension. ```sh go run main.go DeveloperData.fit # Output: # ๐Ÿ“„ "DeveloperData.fit" -> "DeveloperData-fitprint.txt" ``` -------------------------------- ### Fitgen CLI Help Source: https://github.com/muktihari/fit/blob/master/docs/generating_code.md Command to display the help information for the `fitgen` CLI tool, providing details on its usage and available options. ```sh go run main.go --help ``` -------------------------------- ### Sample FIT file data output Source: https://github.com/muktihari/fit/blob/master/cmd/fitprint/README.md This sample output demonstrates the detailed information extracted from a FIT file, including file headers, message types, field names, data types, and values. It shows how developer-specific fields are also represented. ```clojure File generated by https://github.com/muktihari/fit/tree/master/cmd/fitprint Filepath: "testdata/from_official_sdk/DeveloperData.fit" File Header: - Size: 14 - Protocol Version: 2.0 (32) - Profile Version: 1640 - DataSize: 162 - DataType: ".FIT" - CRC: 53438 file_id (num: 0, arch: 1, size: 9, fields[-]: 4, expandedFields[x]: 0, developerFields[+]: 0) [0]: - manufacturer (num: 1, type: uint16 | manufacturer, size: 2): 15 - type (num: 0, type: enum | file, size: 1): 4 - garmin_product (num: 2, type: uint16 | garmin_product, size: 2): 9001 <> - serial_number (num: 3, type: uint32z, size: 4): 1701 developer_data_id (num: 207, arch: 1, size: 17, fields[-]: 2, expandedFields[x]: 0, developerFields[+]: 0) [1]: - application_id (num: 1, type: byte array, size: 16): [1 1 2 3 5 8 13 21 34 55 89 144 233 121 98 219] - developer_data_index (num: 3, type: uint8, size: 1): 0 field_description (num: 206, arch: 1, size: 30, fields[-]: 5, expandedFields[x]: 0, developerFields[+]: 0) [2]: - developer_data_index (num: 0, type: uint8, size: 1): 0 - field_definition_number (num: 1, type: uint8, size: 1): 0 - fit_base_type_id (num: 2, type: uint8 | fit_base_type, size: 1): 1 - field_name (num: 3, type: string array, size: 17): ["doughnuts_earned"] - units (num: 8, type: string array, size: 10): ["doughnuts"] record (num: 20, arch: 1, size: 9, fields[-]: 4, expandedFields[x]: 1, developerFields[+]: 1) [3]: - heart_rate (num: 3, type: uint8, size: 1): 140 bpm - cadence (num: 4, type: uint8, size: 1): 88 rpm - distance (num: 5, type: uint32, size: 4): 510 m ((51000 / 100) - 0) - speed (num: 6, type: uint16, size: 2): 47.488 m/s ((47488 / 1000) - 0) x enhanced_speed (num: 73, type: uint32, size: ?): 47.488 m/s ((47488 / 1000) - 0) + doughnuts_earned (num: 0, type: sint8, size: 1): 1 doughnuts record (num: 20, arch: 1, size: 9, fields[-]: 4, expandedFields[x]: 1, developerFields[+]: 1) [4]: - heart_rate (num: 3, type: uint8, size: 1): 143 bpm - cadence (num: 4, type: uint8, size: 1): 90 rpm - distance (num: 5, type: uint32, size: 4): 2080 m ((208000 / 100) - 0) - speed (num: 6, type: uint16, size: 2): 36.416 m/s ((36416 / 1000) - 0) x enhanced_speed (num: 73, type: uint32, size: ?): 36.416 m/s ((36416 / 1000) - 0) + doughnuts_earned (num: 0, type: sint8, size: 1): 2 doughnuts record (num: 20, arch: 1, size: 9, fields[-]: 4, expandedFields[x]: 1, developerFields[+]: 1) [5]: - heart_rate (num: 3, type: uint8, size: 1): 144 bpm - cadence (num: 4, type: uint8, size: 1): 92 rpm - distance (num: 5, type: uint32, size: 4): 3710 m ((371000 / 100) - 0) - speed (num: 6, type: uint16, size: 2): 35.344 m/s ((35344 / 1000) - 0) x enhanced_speed (num: 73, type: uint32, size: ?): 35.344 m/s ((35344 / 1000) - 0) + doughnuts_earned (num: 0, type: sint8, size: 1): 3 doughnuts File CRC: 40659 ``` -------------------------------- ### Run FIT SDK Benchmark Source: https://github.com/muktihari/fit/blob/master/README.md Execute the benchmark tests for the FIT SDK. This command compares decoding and encoding performance against another Go FIT library. ```shell cd internal/cmd/benchfit go test -bench=. -benchmem ``` -------------------------------- ### Create and Use a Custom Message Listener Source: https://github.com/muktihari/fit/blob/master/docs/usage.md Implement a custom listener to count 'Record' messages. Use `decoder.WithMesgListener` to register the listener and `decoder.WithBroadcastOnly` to optimize for broadcasting without retaining messages. Note that `mesg.Fields` and `mesg.DeveloperFields` are short-lived and must be copied if retention is needed, unless `WithBroadcastMesgCopy` is used. ```go package main import ( "fmt" "os" "github.com/muktihari/fit/decoder" "github.com/muktihari/fit/profile/untyped/mesgnum" "github.com/muktihari/fit/proto" ) type RecordCounter struct{ Count int } var _ decoder.MesgListener = (*RecordCounter)(nil) func (c *RecordCounter) OnMesg(mesg proto.Message) { if mesg.Num == mesgnum.Record { c.Count++ } } func main() { f, err := os.Open("Activity.fit") if err != nil { panic(err) } defer f.Close() rc := new(RecordCounter) dec := decoder.New(f, // Add activity listener to the decoder: decoder.WithMesgListener(rc), // Direct the decoder to only broadcast // the messages without retaining them: decoder.WithBroadcastOnly(), ) _, err = dec.Decode() if err != nil { panic(err) } fmt.Printf("We have %d records\n", rc.Count) } ``` -------------------------------- ### Run FIT Dump CLI Source: https://github.com/muktihari/fit/blob/master/cmd/fitdump/README.md Execute the FIT dump tool using 'go run' to process a FIT file and generate a text output file. ```sh go run main.go triathlon_summary_last.fit # FIT dumped: "triathlon_summary_last.fit" -> "triathlon_summary_last-fitdump.txt" ``` -------------------------------- ### Build fitactivity for Linux Source: https://github.com/muktihari/fit/blob/master/cmd/fitactivity/README.md Manually builds the fitactivity executable for Linux. Ensure you have cloned the repository and are in the cmd/fitactivity folder. ```sh GOOS=linux GOARCH=amd64 go build -o fitactivity main.go ``` -------------------------------- ### Generate FIT SDK with Custom Profile Source: https://github.com/muktihari/fit/blob/master/internal/cmd/fitgen/README.md Use the fitgen CLI to generate Go files for the FIT SDK. Specify the path to your custom profile file using the --profile-file or -f option, and the output directory with --path or -p. The --builders option can be set to 'all' to generate all builders. ```bash ./fitgen --profile-file Profile-copy.xlsx --path ../../../ --builders all --profile-version 21.115 -v -y ``` ```bash ./fitgen -f Profile-copy.xlsx -p ../../../ -b all --profile-version 21.115 -v -y ``` -------------------------------- ### Build FIT Print CLI executable Source: https://github.com/muktihari/fit/blob/master/cmd/fitprint/README.md Compile the Go source code to create a standalone executable file named 'fitprint'. This allows you to run the tool without needing to execute it via 'go run'. ```sh go build -o fitprint main.go ``` -------------------------------- ### Register Custom FIT Messages in Go Source: https://github.com/muktihari/fit/blob/master/docs/runtime_registration.md Demonstrates how to create and register custom FIT messages for different manufacturers at runtime. This involves creating a new factory, registering messages with their respective numbers and fields, and then using these factories with services or decoders. Note that by default, only RAW messages are supported for custom messages without code generation. ```go package main import ( "app/bryton" "app/garmin" "os" "github.com/muktihari/fit/decoder" "github.com/muktihari/fit/encoder" "github.com/muktihari/fit/profile" "github.com/muktihari/fit/profile/factory" "github.com/muktihari/fit/profile/filedef" "github.com/muktihari/fit/proto" ) func main() { brytonFactory := factory.New() brytonFactory.RegisterMesg(proto.Message{ Num: 68, // I found this mesg num is used by Bryton & Garmin in their FIT file. Fields: []proto.Field{ { FieldBase: &proto.FieldBase{ Num: 0, Name: "Software Version", Type: profile.Uint16, BaseType: basetype.Uint16, Array: false, Accumulate: false, Scale: 1, Offset: 0, Units: "", }, }, }, }) brytonFactory.RegisterMesg(proto.Message{ Num: 65290, Fields: []proto.Field{ { FieldBase: &proto.FieldBase{ Num: 0, Name: "Max Heart Rate", Type: profile.Uint8, BaseType: basetype.Uint8, Array: false, Accumulate: false, Scale: 1, Offset: 0, Units: "", }, }, }, }) garminFactory := factory.New() garminFactory.RegisterMesg(proto.Message{ Num: 65282, // I found this mesg num is used by Garmin in their FIT file. Fields: []proto.Field{ { FieldBase: &proto.FieldBase{ Num: 0, Name: "Region", Type: profile.String, BaseType: basetype.String, Array: false, Accumulate: false, Scale: 1, Offset: 0, Units: "", }, }, { FieldBase: &proto.FieldBase{ Num: 1, Name: "Product Year", Type: profile.Uint16, BaseType: basetype.Uint16, Array: false, Accumulate: false, Scale: 1, Offset: 0, Units: "", }, }, }, }) // Add the factory to your service brytonService := bryton.NewService(brytonFactory) garminService := garmin.NewService(garminFactory) ... // Or if you just want to decode FIT files right away, add it to decoder brytonDec := decoder.New(f, decoder.WithFactory(brytonFactory)) garminDec := decoder.New(f, decoder.WithFactory(garminFactory)) ... } ``` -------------------------------- ### Decode FIT File and Create Activity Type Source: https://github.com/muktihari/fit/blob/master/docs/usage.md This snippet demonstrates the basic approach to decoding a FIT file and creating an Activity file type from its protocol messages. It's suitable for simpler use cases but less efficient for large files. ```Go package main import ( "fmt" "os" "github.com/muktihari/fit/decoder" "github.com/muktihari/fit/profile/filedef" ) func main() { f, err := os.Open("Activity.fit") if err != nil { panic(err) } defer f.Close() dec := decoder.New(f) fit, err := dec.Decode() if err != nil { panic(err) } activity := filedef.NewActivity(fit.Messages...) fmt.Printf("File Type: %s\n", activity.FileId.Type) fmt.Printf("Sessions count: %d\n", len(activity.Sessions)) fmt.Printf("Laps count: %d\n", len(activity.Laps)) fmt.Printf("Records count: %d\n", len(activity.Records)) i := 100 fmt.Printf("\nSample value of record[%d]:\n", i) fmt.Printf(" Distance: %g m\n", activity.Records[i].DistanceScaled()) fmt.Printf(" Lat: %d semicircles\n", activity.Records[i].PositionLat) fmt.Printf(" Long: %d semicircles\n", activity.Records[i].PositionLong) fmt.Printf(" Speed: %g m/s\n", activity.Records[i].SpeedScaled()) fmt.Printf(" HeartRate: %d bpm\n", activity.Records[i].HeartRate) fmt.Printf(" Cadence: %d rpm\n", activity.Records[i].Cadence) // Output: // File Type: activity // Sessions count: 1 // Laps count: 1 // Records count: 3601 // // Sample value of record[100]: // Distance: 100 m // Lat: 0 semicircles // Long: 10717 semicircles // Speed: 1 m/s // HeartRate: 126 bpm // Cadence: 100 rpm } ``` -------------------------------- ### Build Fitconv Executable Source: https://github.com/muktihari/fit/blob/master/cmd/fitconv/README.md Build the fitconv program from source code into an executable file named 'fitconv'. ```sh go build -o fitconv main.go ``` -------------------------------- ### Build FIT Dump CLI Binary Source: https://github.com/muktihari/fit/blob/master/cmd/fitdump/README.md Compile the FIT dump tool into an executable binary named 'fitdump'. ```sh go build -o fitdump main.go ``` -------------------------------- ### Build fitactivity for MacOS Source: https://github.com/muktihari/fit/blob/master/cmd/fitactivity/README.md Manually builds the fitactivity executable for MacOS. Ensure you have cloned the repository and are in the cmd/fitactivity folder. ```sh GOOS=darwin GOARCH=amd64 go build -o fitactivity main.go ``` -------------------------------- ### Generate Custom FIT SDK CLI Source: https://github.com/muktihari/fit/blob/master/docs/generating_code.md Command to run the `fitgen` CLI tool for generating custom FIT SDKs. Point it to your modified profile file and specify output path and profile version. ```sh go run main.go --profile-file Profile-copy.xlsx --path ../../../../ --builders all --profile-version 21.115 --verbose -y ``` -------------------------------- ### Run CSV Diff from Source Source: https://github.com/muktihari/fit/blob/master/internal/cmd/csvdiff/README.md Execute the CSV diff program directly from the source code directory using 'go run'. The output is redirected to a diff.txt file. ```bash $ go run main.go file1.csv file2.csv > diff.txt ``` -------------------------------- ### Retrieve Specific Messages with a Custom Listener Source: https://github.com/muktihari/fit/blob/master/docs/usage.md Create a listener to specifically retrieve 'Record' messages from a FIT file. When `WithBroadcastOnly` is enabled, it's crucial to clone `mesg.Fields` and `mesg.DeveloperFields` within the `OnMesg` function to preserve them, as the message object is short-lived. ```go package main import ( "fmt" "os" "slices" "github.com/muktihari/fit/decoder" "github.com/muktihari/fit/profile/untyped/mesgnum" "github.com/muktihari/fit/proto" ) type RecordRetriever struct{ Records []proto.Message } var _ decoder.MesgListener = (*RecordRetriever)(nil) func (c *RecordRetriever) OnMesg(mesg proto.Message) { if mesg.Num == mesgnum.Record { // Unless WithBroadcastMesgCopy is used, we must copy // the slices since mesg is a short-lived object. mesg.Fields = slices.Clone(mesg.Fields) mesg.DeveloperFields = slices.Clone(mesg.DeveloperFields) c.Records = append(c.Records, mesg) } } func main() { f, err := os.Open("Activity.fit") if err != nil { panic(err) } defer f.Close() rr := new(RecordRetriever) dec := decoder.New(f, // Add activity listener to the decoder: decoder.WithMesgListener(rr), // Direct the decoder to only broadcast // the messages without retaining them: decoder.WithBroadcastOnly(), ) _, err = dec.Decode() if err != nil { panic(err) } fmt.Printf("Records: %d\n", len(rr.Records)) } ``` -------------------------------- ### Efficiently Decode FIT Files with a Listener Source: https://github.com/muktihari/fit/blob/master/docs/usage.md This snippet uses a `filedef.Listener` with the `decoder` for concurrent and efficient processing of FIT file messages. It's the recommended approach for better performance. ```Go package main import ( "fmt" "os" "github.com/muktihari/fit/decoder" "github.com/muktihari/fit/profile/filedef" ) func main() { f, err := os.Open("Activity.fit") if err != nil { panic(err) } defer f.Close() // The listener will receive every message from the decoder // as soon as it is decoded and transform it into an filedef.File. lis := filedef.NewListener() defer lis.Close() // release channel used by listener dec := decoder.New(f, // Add activity listener to the decoder: decoder.WithMesgListener(lis), // Direct the decoder to only broadcast // the messages without retaining them: decoder.WithBroadcastOnly(), ) _, err = dec.Decode() if err != nil { panic(err) } // The resulting File can be retrieved after decoding process completed. // filedef.File is just an interface, we can do type assertion like this. file, ok := lis.File().(*filedef.Activity) if !ok { fmt.Printf("%T is not an Activity File\n", lis.File()) return } fmt.Printf("File Type: %s\n", file.FileId.Type) fmt.Printf("Sessions count: %d\n", len(file.Sessions)) fmt.Printf("Laps count: %d\n", len(file.Laps)) fmt.Printf("Records count: %d\n", len(file.Records)) i := 100 fmt.Printf("\nSample value of record[%d]:\n", i) fmt.Printf(" Distance: %g m\n", file.Records[i].DistanceScaled()) fmt.Printf(" Lat: %g degrees\n", file.Records[i].PositionLatDegrees()) fmt.Printf(" Long: %g degrees\n", file.Records[i].PositionLongDegrees()) fmt.Printf(" Speed: %g m/s\n", file.Records[i].SpeedScaled()) fmt.Printf(" HeartRate: %d bpm\n", file.Records[i].HeartRate) fmt.Printf(" Cadence: %d rpm\n", file.Records[i].Cadence) // Output: // File Type: activity // Sessions count: 1 // Laps count: 1 // Records count: 3601 // // Sample value of record[100]: // Distance: 100 m // Lat: 0 degrees // Long: 0.0008982885628938675 degrees // Speed: 1 m/s // HeartRate: 126 bpm // Cadence: 100 rpm } ``` -------------------------------- ### Encode FIT File from Common File Types Source: https://github.com/muktihari/fit/blob/master/docs/usage.md This Go code snippet shows how to create a FIT file representing an activity. It populates various message types like FileId, Record, Lap, and Session, then encodes the data into a FIT file named 'NewActivity.fit'. ```go package main import ( "os" "time" "github.com/muktihari/fit/encoder" "github.com/muktihari/fit/profile/filedef" "github.com/muktihari/fit/profile/mesgdef" "github.com/muktihari/fit/profile/typedef" ) func main() { now := time.Now() activity := filedef.NewActivity() activity.FileId. SetType(typedef.FileActivity). SetTimeCreated(now). SetManufacturer(typedef.ManufacturerSuunto). SetProduct(56). // Suunto 5 Peak SetProductName("Suunto 5 Peak") activity.Records = append(activity.Records, mesgdef.NewRecord(nil). SetTimestamp(now.Add(1*time.Second)). SetSpeed(1000). SetCadence(90). SetHeartRate(100), mesgdef.NewRecord(nil). SetTimestamp(now.Add(2*time.Second)). SetSpeed(1010). SetCadence(100). SetHeartRate(110), ) activity.Laps = append(activity.Laps, mesgdef.NewLap(nil). SetTimestamp(now.Add(3*time.Second)). SetStartTime(now.Add(1*time.Second)). SetAvgSpeed(1000). SetAvgCadence(95). SetAvgHeartRate(105), ) activity.Sessions = append(activity.Sessions, mesgdef.NewSession(nil). SetTimestamp(now.Add(3*time.Second)). SetStartTime(now.Add(1*time.Second)). SetAvgSpeed(1000). SetAvgCadence(95). SetAvgHeartRate(105), ) activity.Activity = mesgdef.NewActivity(nil). SetType(typedef.ActivityManual). SetTimestamp(now.Add(4 * time.Second)). SetNumSessions(1) // Convert back to FIT protocol messages fit := activity.ToFIT(nil) f, err := os.OpenFile("NewActivity.fit", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) if err != nil { panic(err) } defer f.Close() enc := encoder.New(f) if err := enc.Encode(&fit); err != nil { panic(err) } } ``` -------------------------------- ### Peek FIT File Header Source: https://github.com/muktihari/fit/blob/master/docs/usage.md Use PeekFileHeader to inspect the first 12-14 bytes of a FIT file without decoding the entire reader. This is useful for verifying if a file is a FIT file. ```go package main import ( "fmt" "os" "github.com/muktihari/fit/decoder" ) func main() { f, err := os.Open("Activity.fit") if err != nil { panic(err) } defer f.Close() dec := decoder.New(f) fileHeader, err := dec.PeekFileHeader() if err != nil { panic(err) } fmt.Printf("%v\n", fileHeader) // Output: // {14 32 2147 94080 .FIT 17310} } ``` -------------------------------- ### Register Custom MesgNum and File Types Source: https://github.com/muktihari/fit/blob/master/docs/runtime_registration.md Use `typedef.MesgNumRegister` to add custom message numbers and their string representations, and `typedef.FileRegister` for custom file types. Note that `typedef` is globally shared, limiting its use to one manufacturer at a time. ```go package main import "github.com/muktihari/fit/profile/typedef" func main() { // Register specific MesgNum typedef.MesgNumRegister(68, "Internal Message") typedef.MesgNumRegister(65282, "Product Information") // If your company have specific File Type, you can register it as well. typedef.FileRegister(247, "Internal File") ... } ``` -------------------------------- ### Count FIT Messages with RawDecoder Source: https://github.com/muktihari/fit/blob/master/docs/usage.md Demonstrates using RawDecoder to count messages within a FIT file, measuring the execution time. This is useful for analyzing file structure and performance. ```go package main import ( "bufio" "fmt" "os" "time" "github.com/muktihari/fit/decoder" ) func main() { defer func(begin time.Time) { fmt.Printf("took: %s\n", time.Since(begin)) }(time.Now()) f, err := os.Open("./testdata/from_garmin_forums/triathlon_summary_first.fit") if err != nil { panic(err) } defer f.Close() dec := decoder.NewRaw() var lenMesgsPerSequence []int var lenMesgs int _, err = dec.Decode(bufio.NewReader(f), func(flag decoder.RawFlag, b []byte) error { switch flag { case decoder.RawFlagFileHeader: lenMesgs = 0 case decoder.RawFlagMesgData: lenMesgs++ case decoder.RawFlagCRC: lenMesgsPerSequence = append(lenMesgsPerSequence, lenMesgs) } return nil }) if err != nil { panic(err) } for i := range lenMesgsPerSequence { fmt.Printf("seq[%d] has %d messages\n", i, lenMesgsPerSequence[i]) } // Output: // seq[0] has 4177 messages // took: 312.651ยตs } ``` -------------------------------- ### FIT SDK Benchmark Results Source: https://github.com/muktihari/fit/blob/master/README.md Benchmark results for decoding and encoding FIT files using the FIT SDK and tormoder/fit. The results show performance metrics including operations per second, bytes per operation, and allocations per operation. ```text goos: darwin; goarch: amd64; pkg: benchfit; cpu: Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz BenchmarkDecode/muktihari/fit_raw-4 12 94936751 ns/op 77087248 B/op 100043 allocs/op BenchmarkDecode/muktihari/fit-4 14 82229838 ns/op 39400001 B/op 100187 allocs/op BenchmarkDecode/tormoder/fit-4 10 107181982 ns/op 84109019 B/op 700051 allocs/op BenchmarkEncode/muktihari/fit_raw-4 20 56309898 ns/op 135602 B/op 14 allocs/op BenchmarkEncode/muktihari/fit-4 9 112494366 ns/op 44142141 B/op 100018 allocs/op BenchmarkEncode/tormoder/fit-4 1 1301167388 ns/op 101592672 B/op 12100313 allocs/op PASS; ok benchfit 9.737s ``` -------------------------------- ### Use Custom Factory with Decoder Source: https://github.com/muktihari/fit/blob/master/docs/usage.md Configure the decoder with a custom factory to handle specific manufacturer messages. Register custom message types with their fields. ```go fac := factory.New() fac.RegisterMesg(proto.Message{ Num: 65281, Fields: []proto.Field{ { FieldBase: &proto.FieldBase{ Num: 253, Name: "Timestamp", Type: profile.Uint32, BaseType: basetype.Uint32, Array: false, Accumulate: false, Scale: 1, Offset: 0, Units: "s", }, }, }, }) dec := decoder.New(f, decoder.WithFactory(fac)) ``` -------------------------------- ### Combine FIT Activity Files Source: https://github.com/muktihari/fit/blob/master/cmd/fitactivity/README.md Combine multiple FIT activity files into a single file. Requires the -o flag for the output file. ```sh fitactivity combine ``` ```sh fitactivity combine -o result.fit part1.fit part2.fit ``` ```sh fitactivity combine reduce -o result.fit --rdp 0.0001 part1.fit part2.fit ``` ```sh fitactivity combine conceal -o result.fit --first 1000 part1.fit part2.fit ``` ```sh fitactivity combine remove -o result.fit --unknown --mesgnums 160,164 part1.fit part2.fit ``` ```sh fitactivity combine conceal reduce -o result.fit --last 1000 --time 5 part1.fit part2.fit ``` -------------------------------- ### Configure Header Options for Encoder Source: https://github.com/muktihari/fit/blob/master/docs/usage.md Use WithHeaderOption to specify header options and local message type values. This can optimize for file size or RAM usage on embedded devices. ```go enc := encoder.New(f, encoder.WithHeaderOption(encoder.HeaderOptionNormal, 15)) // valid 0-15 ``` ```go enc := encoder.New(f, encoder.WithHeaderOption(encoder.HeaderOptionCompressedTimestamp, 3)) // valid 0-3 ``` -------------------------------- ### Define Custom Activity File Type in Go Source: https://github.com/muktihari/fit/blob/master/docs/usage.md Implement a custom Activity file by satisfying the filedef.File interface. This allows for selective message retrieval and custom handling. ```go package main import ( "os" "github.com/muktihari/fit/decoder" "github.com/muktihari/fit/profile/filedef" "github.com/muktihari/fit/profile/mesgdef" "github.com/muktihari/fit/profile/typedef" "github.com/muktihari/fit/profile/untyped/mesgnum" "github.com/muktihari/fit/proto" ) var _ filedef.File = (*Activity)(nil) type Activity struct { FileId mesgdef.FileId Activity *mesgdef.Activity Sessions []*mesgdef.Session Laps []*mesgdef.Lap Records []*mesgdef.Record } func (f *Activity) Add(mesg proto.Message) { switch mesg.Num { case mesgnum.FileId: f.FileId.Reset(&mesg) case mesgnum.Activity: f.Activity = mesgdef.NewActivity(&mesg) case mesgnum.Session: f.Sessions = append(f.Sessions, mesgdef.NewSession(&mesg)) case mesgnum.Lap: f.Laps = append(f.Laps, mesgdef.NewLap(&mesg)) case mesgnum.Record: f.Records = append(f.Records, mesgdef.NewRecord(&mesg)) } } func (f *Activity) ToFIT(options *mesgdef.Options) proto.FIT { size := 2 + len(f.Sessions) + len(f.Laps) + len(f.Records) fit := proto.FIT{Messages: make([]proto.Message, 0, size)} fit.Messages = append(fit.Messages, f.FileId.ToMesg(options)) if f.Activity != nil { fit.Messages = append(fit.Messages, f.Activity.ToMesg(options)) } for i := range f.Sessions { fit.Messages = append(fit.Messages, f.Sessions[i].ToMesg(options)) } for i := range f.Laps { fit.Messages = append(fit.Messages, f.Laps[i].ToMesg(options)) } for i := range f.Records { fit.Messages = append(fit.Messages, f.Records[i].ToMesg(options)) } filedef.SortMessagesByTimestamp(fit.Messages) return fit } func main() { f, err := os.Open("Activity.fit") if err != nil { panic(err) } defer f.Close() lis := filedef.NewListener( // Replace default filedef.Activity with our custom Activity. filedef.WithFileFunc(typedef.FileActivity, func() filedef.File { return new(Activity) }), ) defer lis.Close() dec := decoder.New(f, decoder.WithMesgListener(lis), decoder.WithBroadcastOnly(), ) _, err = dec.Decode() if err != nil { panic(err) } // The resulting File will be *Activity instead of *filedef.Activity. file := lis.File().(*Activity) fmt.Printf("Distance: %.2f km\n", file.Sessions[0].TotalDistanceScaled()/1000) } ``` -------------------------------- ### Add Message Definition Listener to Decoder Source: https://github.com/muktihari/fit/blob/master/docs/usage.md Configure the decoder to receive message definitions as they are decoded, useful for data conversion tasks. ```go conv := fitcsv.NewFITToCSVConv(bw) defer conv.Wait() dec := decoder.New(f, decoder.WithMesgDefListener(conv), decoder.WithMesgListener(conv), ) ``` -------------------------------- ### Convert FIT to CSV and CSV to FIT Source: https://github.com/muktihari/fit/blob/master/cmd/fitconv/README.md Perform a basic conversion from a FIT file to a CSV file, and then convert a CSV file back to a FIT file. Unknown messages are skipped by default during CSV to FIT conversion. ```sh fitconv activity.fit activity2.csv # Output: # ๐Ÿ“„ "activity.fit" -> "activity.csv" # ๐Ÿš€ "activity2.csv" -> "activity2.fit". [Info: 2 unknown messages are skipped] ls # activity.fit activity.csv activity2.fit activity2.csv ``` -------------------------------- ### Decode Chained FIT Files with Next() in Go Source: https://github.com/muktihari/fit/blob/master/docs/usage.md Iterate through multiple FIT sequences within a single file by repeatedly calling Decode() or DecodeWithContext(). The Next() method provides a convenient way to manage this loop. ```go ... dec := decoder.New(f) for dec.Next() { fit, err := dec.Decode() if err != nil { return err } // do something with fit variable } ... ```