# RustyFIT

[](https://crates.io/crates/rustyfit)
[](https://crates.io/crates/rustyfit)
[](https://developer.garmin.com/fit/download)
Rewrite of [FIT SDK for Go](https://github.com/muktihari/fit) in Rust.
## Current State
This project serves as an exercise for me to learn Rust. I believe the fastest way to learn something new is by reinventing the wheel or rewriting something that already exists. Although this is a learning project, this library generally works, is usable, and quite fast.
Missing features, test completeness, and more robust documentation may be added later through release iteration.
## Usage
### Decoding
#### Decode
Decoder's `decode` allows us to interact with FIT files directly through their original protocol messages' structure.
```rust
use rustyfit::{
Decoder,
profile::{mesgdef, typedef},
};
use std::{fs::File, io::BufReader};
fn main() {
let name = "Activity.fit";
let f = File::open(name).unwrap();
let br = BufReader::new(f);
let mut dec = Decoder::new(br);
let fit = dec.decode().unwrap();
println!("file_header's data_size: {}", fit.file_header.data_size);
println!("messages count: {}", fit.messages.len());
for field in &fit.messages[0].fields { // first message: file_id
if field.num == mesgdef::FileId::TYPE {
println!("file type: {}", typedef::File(field.value.as_u8()));
}
}
// # Output:
// file_header's data_size: 94080
// messages count: 3611
// file type: activity
}
```
#### Decode with Closure
Decoder's `decode_fn` allow us to retrieve message definition or message data event as soon as it is being decoded. This way, users can have fine-grained control on how to interact with the data.
```rust
use rustyfit::{Decoder, DecoderEvent,profile::{mesgdef, typedef}};
use std::{fs::File, io::BufReader};
fn main() {
let name = "Activity.fit";
let f = File::open(name).unwrap();
let br = BufReader::new(f);
let mut dec = Decoder::new(br);
dec.decode_fn(|event| match event {
DecoderEvent::Message(mesg) => {
if mesg.num == typedef::MesgNum::SESSION {
// Convert mesg into Session struct
let ses = mesgdef::Session::from(mesg);
println!(
"session:\n start_time: {}\n sport: {}\n num_laps: {}",
ses.start_time.0, ses.sport, ses.num_laps
);
}
}
DecoderEvent::MessageDefinition(_) => {}
})
.unwrap();
// # Output
// session:
// start_time: 974894402
// sport: running
// num_laps: 53
}
```
#### DecoderBuilder
Create `Decoder` instance with options using `DecoderBuilder`.
```rust
let mut dec: Decoder = DecoderBuilder::new(br)
.checksum(false)
.expand_components(false)
.build();
```
### Encoding
#### Encode
Here is the example of manually encode FIT protocol using this library to give the idea how it works.
```rust
use std::{
fs::File,
io::{BufWriter, Write},
};
use rustyfit::{
Encoder,
profile::{
ProfileType, mesgdef,
typedef::{self},
},
proto::{FIT, Field, Message, Value},
};
fn main() {
let fout_name = "output.fit";
let fout = File::create(fout_name).unwrap();
let mut bw = BufWriter::new(fout);
let mut enc = Encoder::new(&mut bw);
let mut fit = FIT {
messages: vec![
Message {
num: typedef::MesgNum::FILE_ID,
fields: vec![
Field {
num: mesgdef::FileId::MANUFACTURER,
profile_type: ProfileType::MANUFACTURER,
value: Value::Uint16(typedef::Manufacturer::GARMIN.0),
is_expanded: false,
},
Field {
num: mesgdef::FileId::PRODUCT,
profile_type: ProfileType::UINT16,
value: Value::Uint16(typedef::GarminProduct::FENIX8_SOLAR.0),
is_expanded: false,
},
Field {
num: mesgdef::FileId::TYPE,
profile_type: ProfileType::UINT8,
value: Value::Uint8(typedef::File::ACTIVITY.0),
is_expanded: false,
},
],
..Default::default()
},
Message {
num: typedef::MesgNum::RECORD,
fields: vec![
Field {
num: mesgdef::Record::DISTANCE,
profile_type: ProfileType::UINT32,
value: Value::Uint16(100 * 100), // 100 m
is_expanded: false,
},
Field {
num: mesgdef::Record::HEART_RATE,
profile_type: ProfileType::UINT8,
value: Value::Uint8(70), // 70 bpm
is_expanded: false,
},
Field {
num: mesgdef::Record::SPEED,
profile_type: ProfileType::UINT16,
value: Value::Uint16(2 * 1000), // 2 m/s
is_expanded: false,
},
],
..Default::default()
},
],
..Default::default()
};
enc.encode(&mut fit).unwrap();
bw.flush().unwrap();
}
```
#### Encode using mesgdef module
Alternatively, users can create messages using the mesgdef module for convenience.
```rust
use std::{
fs::File,
io::{BufWriter, Write},
};
use rustyfit::{
Encoder,
profile::{
mesgdef,
typedef::{self},
},
proto::{FIT, Message},
};
fn main() {
let fout_name = "output.fit";
let fout = File::create(fout_name).unwrap();
let mut bw = BufWriter::new(fout);
let mut enc = Encoder::new(&mut bw);
let mut fit = FIT {
messages: vec![
{
let mut file_id = mesgdef::FileId::new();
file_id.manufacturer = typedef::Manufacturer::GARMIN;
file_id.product = typedef::GarminProduct::FENIX8_SOLAR.0;
file_id.r#type = typedef::File::ACTIVITY;
Message::from(file_id)
},
{
let mut record = mesgdef::Record::new();
record.distance = 100 * 100; // 100 m
record.heart_rate = 70; // 70 bpm
record.speed = 2 * 1000; // 2 m/s
Message::from(record)
},
],
..Default::default()
};
enc.encode(&mut fit).unwrap();
bw.flush().unwrap();
}
```
#### EncoderBuilder
Create `Encoder` instance with options using `EncoderBuilder`.
```rust
let mut enc: Encoder = EncoderBuilder::new(&mut bw)
.endianness(Endianness::BigEndian)
.protocol_version(ProtocolVersion::V2)
.header_option(HeaderOption::Compressed(3))
.omit_invalid_value(false)
.build();
```