#fit #filesystem #file #decoding #reading #sport #devices

fit_no_io

A small crate used for reading and decoding FIT files generated by sports devices that works without a file system

1 unstable release

0.1.0 Jul 13, 2019

#2933 in Parser implementations

MIT license

4.5MB
1K SLoC

Fit (no IO)

A WebAssembly-friendly adaptation of fit by Richard Brodie. The original crate depended on the existence of a file system, meaning it could not be used in environments like WebAssembly that lack file system support. This crate requires you to pass the data directly to the parser instead of passing the file path to the parser, therefore eliminating the need for a file system.

Original description (by Brodie):

Fit is a small crate used for reading and decoding FIT files generated by various sports devices. Currently it has only been designed specifically to read files produced by Garmin Edges 1000 and 520, and a Wahoo Elemnt. It will probably mostly work out of the box with other file sources, but will probably not work 100% perfectly unless it's a Garmin cycling computer. It currently does not support FIT files using custom developer fields.

For more information about FIT, refer to the SDK from ANT (a zip archive is included in this repository).

Installation

[dependencies]
fit_no_io = "0.1"

Usage

extern crate env_logger;
extern crate fit_no_io;

use std::path::PathBuf;
use fit::{self as fit, Message};

fn main() {
    let file = File::open("fits/2913547417.fit").expect("should be able to open file");
    let mut buf = Vec::new();
    file.read(&mut buf).expect("should be able to read file");
    let f: Vec<Message> = fit::run(&buf);
    println!("{:#?}", f);
}

A typical Message will look something like this:

Message {
  kind: Record,
  values: [
    DataField { field_num: 253, value: Time(1480856114) },
    DataField { field_num: 0,   value: F32(57.710945)   },
    DataField { field_num: 1,   value: F32(11.9945755)  },
    DataField { field_num: 5,   value: U32(1151)        },
    DataField { field_num: 29,  value: U32(0)           },
    DataField { field_num: 2,   value: U16(2394)        },
    DataField { field_num: 6,   value: U16(0)           },
    DataField { field_num: 7,   value: U16(0)           },
    DataField { field_num: 61,  value: U16(2234)        },
    DataField { field_num: 66,  value: I16(442)         },
    DataField { field_num: 3,   value: U8(113)          },
    DataField { field_num: 13,  value: I8(21)           }
  ],
  dev_values: []
}

FIT Crash Course

If you are already intimately familiar with the FIT file format, you should probably skip this section. Otherwise, read on.

Caveat

This is a crash course (i.e., a dramatic over-simplification), not a comprehensive guide to FIT. For a more thorough guide, refer to the ANT FIT SDK.

Now that you've been warned...

A FIT file is basically a list of Messages. Each Message is basically just an object/hash map/dictionary/whatever you call something that associates keys to values.

There are many kinds of Messages. Each kind has a different purpose, and each has its own shape (i.e., what properties they have). For example, a UserProfile Message will have different properties than a Record Message. That is, UserProfile Message probably contains information such as the user's height and weight, while the Record Message probably contains information about the user's heart rate and speed at a given point in time.

Ideally, Message in Rust would look something like this:

struct Message {
  kind: MessageType,
  values: HashMap<Attribute, Value>,
}

enum Attribute {
  Timestamp,
  Distance,
  Speed,
  HeartRate,
  Cadence,
  // ...
}

// `Value` will be explained later

In reality, because of the way FIT works, it looks something more like this:

struct Message {
  kind: MessageType,
  values: Vec<DataField>,
}

A DataField is a key-value pair. It's similar to the key-value pair you use to build a HashMap (i.e., when you build a HashMap from an iterator of tuples vec![(key1, value1), (key2, value2), ...].into_iter().collect()), but the difference is that instead of having the actual key, DataField has a field_num member that can be used in conjuction with the kind of the Message to determine the actual key. This is done because different kinds of Messages have different rules for what key a given field_num refers to. For instance, field 0 of a Record Message is the user's latitude, while field 0 of a DeviceInfo Message is the device index. For a table mapping field_nums and Message kinds to properties, refer to the ANT FIT SDK.

A Value enum is a simple wrapper around most rust primitive types, such as u16 or i64 or f32.

Some things to watch out for:

  • Speed is recorded as m/s, rather than kph.
  • For running, cadence is recorded as cycles per minute, as opposed to strides per minute, where one cycle is defined as a stride with the left foot plus a stride with the right foot. If your cadence is listed as 90, it means it is 90 cycles per minute, which equals 180 strides per minute.

Contributing

Repository of original crate: https://github.com/richardbrodie/fit-rs.

Repository of this crate: https://github.com/kylejlin/fit_no_io.

Thanks

Thanks to Richard Brodie for developing the original crate. 99% of this crate was taken from his fit.

Dependencies