FitFile

An Elixir wrapper for the Rust fitparser library (v0.10.0) to parse ANT FIT files.

FIT (Flexible and Interoperable Data Transfer) is a file format commonly used by fitness devices from Garmin and other manufacturers to store activity data such as runs, bike rides, and other workouts.

Features

Installation

Add fit_file to your list of dependencies in mix.exs:

def deps do
  [
    {:fit_file, "~> 0.1.4"}
  ]
end

Then run:

mix deps.get

Usage

Parse from a file

case FitFile.from_file("path/to/activity.fit") do
  {:ok, records} ->
    IO.puts("Successfully parsed #{length(records)} records")

  {:error, {reason, message}} ->
    IO.puts("Error: #{reason} - #{message}")
end

Parse from binary data

fit_data = File.read!("path/to/activity.fit")

case FitFile.from_binary(fit_data) do
  {:ok, records} ->
    # Process records

  {:error, {reason, message}} ->
    # Handle error
end

Convenience parse function

The parse/1 function automatically detects whether you're passing a file path or binary data:

# Parse from file
{:ok, records} = FitFile.parse("activity.fit")

# Parse from binary
{:ok, records} = FitFile.parse(binary_data)

Working with Records

Each record contains:

Example: Extract specific data

{:ok, records} = FitFile.from_file("activity.fit")

# Filter to get only "record" messages (the actual data points)
data_points = Enum.filter(records, fn r -> r.kind == "record" end)

# Get specific field values
Enum.each(data_points, fn record ->
  timestamp = FitFile.DataRecord.get_field_value(record, "timestamp")
  speed = FitFile.DataRecord.get_field_value(record, "speed")
  heart_rate = FitFile.DataRecord.get_field_value(record, "heart_rate")

  IO.puts("Time: #{timestamp}, Speed: #{speed}, HR: #{heart_rate}")
end)

Example: Get session summary

{:ok, records} = FitFile.from_file("activity.fit")

session = Enum.find(records, fn r -> r.kind == "session" end)

if session do
  total_distance = FitFile.DataRecord.get_field_value(session, "total_distance")
  total_time = FitFile.DataRecord.get_field_value(session, "total_elapsed_time")
  avg_speed = FitFile.DataRecord.get_field_value(session, "avg_speed")

  IO.puts("Distance: #{total_distance}m, Time: #{total_time}s, Avg Speed: #{avg_speed}m/s")
end

Data Structures

FitFile.DataRecord

%FitFile.DataRecord{
  kind: "record",
  fields: [%FitFile.DataField{}, ...]
}

FitFile.DataField

%FitFile.DataField{
  name: "speed",
  value: "5.2",
  units: "m/s"
}

Development

Building from source

If precompiled binaries aren't available for your platform, the library will automatically compile the Rust NIF. You'll need:

To force building from source:

export RUSTLER_PRECOMPILATION_FIT_FILE_BUILD=true
mix deps.get
mix compile

Running tests

mix test

Releasing (Maintainers)

This project uses GitHub Actions to automatically build precompiled NIFs for multiple platforms.

Release Process

  1. Update the version in mix.exs
  2. Update CHANGELOG.md with the new version and changes
  3. Commit the changes: git commit -am "Release v0.x.0"
  4. Create and push a git tag: git tag v0.x.0 && git push origin v0.x.0
  5. GitHub Actions will automatically:
    • Build NIFs for all supported platforms (macOS, Linux, Windows)
    • Create checksums for each binary
    • Create a GitHub release with all artifacts
  6. After the release is created, publish to Hex: mix hex.publish

Supported Platforms

Precompiled binaries are automatically built for:

CI/CD

The project includes two GitHub Actions workflows:

License

MIT

Credits

This library wraps the excellent fitparser Rust crate by Matthew Stadelman.