fit

repository·master·Indexed 41 days ago

https://github.com/muktihari/fit

A Go implementation of the Garmin FIT (Flexible and Interoperable Data Transfer) protocol supporting Protocol V2.0 and custom developer fields. It includes the fitactivity CLI for merging, concealing GPS data, reducing record density (via RDP, distance, or time intervals), and removing specific messages, as well as the fitconv tool for bidirectional conversion between FIT and CSV files.

Tokens
27K
Snippets
83
Records
109
Agent score
50%

What's inside fit

  1. Overview of FIT SDK for Go

    master

    The FIT SDK for Go is a Go implementation of the Flexible and Interoperable Data Transfer (FIT) Protocol. It is designed to handle sports, fitness, and health data stored in .fit files.

    Unlike many third-party libraries, this SDK provides semantics similar to the official Garmin SDK, allowing users to:

    • Retrieve protocol messages in their original arrival order.
    • Handle 'unknown messages' without losing data.
    • Support FIT Protocol Version 2.0, including Developer Fields (custom data fields added to existing messages).
    • Create FIT files with advanced options like compressed timestamps or multiple local message types to reduce file size.
  2. Available commands in FIT Activity CLI

    master

    The fitactivity CLI provides four primary functions for managing FIT activity files:

    1. combine: Merges multiple FIT activity files into a single continuous activity.
    2. conceal: Protects privacy by concealing the first or last $X$ meters of GPS positions.
    3. reduce: Decreases file size by reducing the density of record messages using one of three methods (RDP, distance interval, or time interval).
    4. remove: Deletes specific messages based on message numbers or other parameters.
  3. How record messages are reduced

    master

    The reduce command decreases the number of record messages in a file. You must choose exactly one of the following three methods:

    1. RDP (Ramer-Douglas-Peucker): Simplifies the curve using the RDP algorithm. Precision is controlled by a provided epsilon (tolerance) value.
    2. Distance Interval: Ensures the distance gap between two consecutive records does not exceed a specified number of meters. If the gap is already larger than the interval, no records are removed.
    3. Time Interval: Ensures the timestamp gap between two consecutive records does not exceed a specified number of seconds. If the gap is already larger than the interval, no records are removed.

    Note: Reduction is performed by removing messages; no data aggregation is performed during this process.

  4. How messages are removed

    master

    The remove command deletes messages based on their message numbers.

    • Specific Messages: Providing a message number (e.g., 160 for gps_metadata) will trigger its removal.
    • Developer Data: The tool automatically removes developer_data_id and field_description messages, as well as all DeveloperFields within all messages.
    • Unknown Messages: Any message not defined by the Global Profile is treated as an unknown message.
  5. How FIT activity files are combined

    master

    The combine command merges multiple activities into one continuous activity by ordering files by FileId.TimeCreated. The first file serves as the base.

    Aggregation Logic:

    • Activity: Uses the message from the first file and updates it.
    • Session: Fields are aggregated with corresponding sessions from subsequent files.
    • Lap/Event: Appended as they are.
    • Record: Accumulable fields (like distance and accumulated_power) are summed before appending; others are appended as is.
    • SplitSummary: Aggregated with split summaries of the same split_type.

    Field Aggregation Rules:

    • total* (e.g., total_elapsed_time): Sum of values.
    • num*s (e.g., num_splits): Sum of values.
    • max* or enhanced_max*: Maximum value.
    • min* or enhanced_min*: Minimum value.
    • avg* or enhanced_avg*: Average value.
    • Exception: For total_training_effect and total_anaerobic_training_effect, the tool picks the highest value from the combined sessions rather than summing them.

    Session Matching: If the last session sport type of a file matches the first session of the next file, they are combined. If they do not match, the next session is appended as a new session.

  6. How runtime registration compares to code generation

    master

    The FIT SDK provides two ways to handle manufacturer-specific data:

    1. Code Generation (fitgen): The preferred method. It uses a custom Profile.xlsx to generate types and messages. This allows for full support, including mesgdef (message definitions).
    2. Runtime Registration: A simpler approach using typedef and factory packages.
      • Use typedef for global constant string representations (limited to one manufacturer per app).
      • Use factory to support multiple manufacturers simultaneously by creating independent factory instances.
      • Constraint: Runtime registration is limited to RAW messages only; it does not support mesgdef.

    Choose runtime registration for simple cases where you want to avoid the maintenance overhead of a custom generated SDK.

  7. Conventions for generated Go code

    master

    The project follows specific conventions for generated code to ensure compatibility with Go tooling and maintainability:

    • Header Requirement: All generated files must include a line matching the regex ^// Code generated .* DO NOT EDIT\.$ to signal to humans and machine tools that the file should not be edited manually.
    • Naming Convention: Generated Go files use the _gen suffix (e.g., activity_gen.go) to distinguish them from manually written code.
    • Manual Implementation: Items that are inadequately defined in Profile.xlsx or lack sufficient detail are implemented manually to improve robustness (e.g., FIT Base Type is located in profile/basetype/basetype.go).
  8. Decode with a Message Listener

    master

    The decoder can broadcast messages to registered listeners as soon as they are decoded. This allows for streaming-style consumption.

    To use a listener:

    1. Implement the decoder.MesgListener interface (specifically the OnMesg(mesg proto.Message) method).
    2. Register it using the decoder.WithMesgListener(listener) option in decoder.New().

    Important Performance/Memory Notes:

    • By default, the mesg object passed to OnMesg is short-lived. If you need to retain the data, you must manually copy mesg.Fields and mesg.DeveloperFields (e.g., using slices.Clone), or use the decoder.WithBroadcastMesgCopy() option to instruct the decoder to perform the copy for you.
    • Use decoder.WithBroadcastOnly() if you only want to consume messages via listeners and do not want the decoder to retain the full message set in memory.
    type RecordCounter struct{ Count int }
    
    var _ decoder.MesgListener = (*RecordCounter)(nil)
    
    func (c *RecordCounter) OnMesg(mesg proto.Message) {
        if mesg.Num == mesgnum.Record {
            c.Count++
        }
    }
    
    // Usage
    dec := decoder.New(f, 
        decoder.WithMesgListener(rc), 
        decoder.WithBroadcastOnly(),
    )
  9. How to handle manufacturer-specific messages

    master

    FIT files may contain Product Profile messages that are specific to a manufacturer and not defined in the Global Profile. To decode or create these messages using this SDK, you have two options:

    1. Register Manufacturer Specific Messages at Runtime: Use the factory package to register profile messages dynamically without generating new code. This is ideal if you want to use the SDK as-is.
    2. Generate a Custom FIT SDK: Generate custom code specifically tailored to your manufacturer's message definitions. This is better for performance and type safety in production environments.
  10. How GPS positions are concealed

    master

    The conceal command removes privacy-sensitive GPS data from the start or end of an activity.

    1. Conceal Start Position: Iterates from the beginning of FIT messages up to the desired distance, removing position_lat and position_long fields from every record found.
    2. Conceal End Position: Iterates backward from the end of the FIT messages up to the desired distance, removing position_lat and position_long fields.

    For Laps and Sessions, the tool automatically updates start_position_lat, start_position_long, end_position_lat, and end_position_long to match the new boundaries.

  11. Reuse Decoders with sync.Pool

    master

    The Decoder is designed to be reusable to reduce memory allocations. It provides a Reset(reader, ...options) method. In high-throughput environments like a web server, you can use sync.Pool to manage a pool of Decoder instances and reuse them with different readers.

    var pool = sync.Pool{
        New: func() any { return new(decoder.Decoder) },
    }
    
    // In a handler:
    dec := pool.Get().(*decoder.Decoder)
    defer pool.Put(dec)
    
    dec.Reset(r.Body, decoder.WithMesgListener(lis), decoder.WithBroadcastOnly())