#![warn(missing_docs)]
use chrono::{DateTime, Local};
use serde::Serialize;
use std::convert;
use std::fmt;
mod de;
mod error;
pub mod profile;
pub use de::{from_bytes, from_reader};
pub use error::{Error, ErrorKind, Result};
#[derive(Clone, Debug, Serialize)]
pub struct FitDataRecord {
kind: profile::MesgNum,
fields: Vec<FitDataField>,
}
impl FitDataRecord {
pub fn new(kind: profile::MesgNum) -> Self {
FitDataRecord {
kind,
fields: Vec::new(),
}
}
pub fn kind(&self) -> profile::MesgNum {
self.kind
}
pub fn fields(&self) -> &[FitDataField] {
&self.fields
}
pub fn push(&mut self, field: FitDataField) {
self.fields.push(field)
}
pub fn into_vec(self) -> Vec<FitDataField> {
self.fields
}
}
#[derive(Clone, Debug, Serialize)]
pub struct FitDataField {
name: String,
number: u8,
value: Value,
units: String,
}
impl FitDataField {
pub fn new(name: String, number: u8, value: Value, units: String) -> Self {
FitDataField {
name,
number,
value,
units,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn number(&self) -> u8 {
self.number
}
pub fn value(&self) -> &Value {
&self.value
}
pub fn units(&self) -> &str {
&self.units
}
pub fn into_value(self) -> Value {
self.value
}
}
impl fmt::Display for FitDataField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.units.is_empty() {
write!(f, "{}", self.value)
} else {
write!(f, "{} {}", self.value, self.units)
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize)]
#[serde(untagged)]
pub enum Value {
Timestamp(DateTime<Local>),
Byte(u8),
Enum(u8),
SInt8(i8),
UInt8(u8),
SInt16(i16),
UInt16(u16),
SInt32(i32),
UInt32(u32),
String(String),
Float32(f32),
Float64(f64),
UInt8z(u8),
UInt16z(u16),
UInt32z(u32),
SInt64(i64),
UInt64(u64),
UInt64z(u64),
Array(Vec<Self>),
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
Value::Timestamp(val) => write!(f, "{}", val),
Value::Byte(val) => write!(f, "{}", val),
Value::Enum(val) => write!(f, "{}", val),
Value::SInt8(val) => write!(f, "{}", val),
Value::UInt8(val) => write!(f, "{}", val),
Value::UInt8z(val) => write!(f, "{}", val),
Value::SInt16(val) => write!(f, "{}", val),
Value::UInt16(val) => write!(f, "{}", val),
Value::UInt16z(val) => write!(f, "{}", val),
Value::SInt32(val) => write!(f, "{}", val),
Value::UInt32(val) => write!(f, "{}", val),
Value::UInt32z(val) => write!(f, "{}", val),
Value::SInt64(val) => write!(f, "{}", val),
Value::UInt64(val) => write!(f, "{}", val),
Value::UInt64z(val) => write!(f, "{}", val),
Value::Float32(val) => write!(f, "{}", val),
Value::Float64(val) => write!(f, "{}", val),
Value::String(val) => write!(f, "{}", val),
Value::Array(vals) => write!(f, "{:?}", vals),
}
}
}
impl convert::TryInto<f64> for Value {
type Error = error::Error;
fn try_into(self) -> Result<f64> {
match self {
Value::Timestamp(val) => Ok(val.timestamp() as f64),
Value::Byte(val) => Ok(val as f64),
Value::Enum(val) => Ok(val as f64),
Value::SInt8(val) => Ok(val as f64),
Value::UInt8(val) => Ok(val as f64),
Value::UInt8z(val) => Ok(val as f64),
Value::SInt16(val) => Ok(val as f64),
Value::UInt16(val) => Ok(val as f64),
Value::UInt16z(val) => Ok(val as f64),
Value::SInt32(val) => Ok(val as f64),
Value::UInt32(val) => Ok(val as f64),
Value::UInt32z(val) => Ok(val as f64),
Value::SInt64(val) => Ok(val as f64),
Value::UInt64(val) => Ok(val as f64),
Value::UInt64z(val) => Ok(val as f64),
Value::Float32(val) => Ok(val as f64),
Value::Float64(val) => Ok(val),
Value::String(_) => {
Err(ErrorKind::ValueError(format!("cannot convert {} into an f64", self)).into())
}
Value::Array(_) => {
Err(ErrorKind::ValueError(format!("cannot convert {} into an f64", self)).into())
}
}
}
}
impl convert::TryInto<i64> for Value {
type Error = error::Error;
fn try_into(self) -> Result<i64> {
match self {
Value::Timestamp(val) => Ok(val.timestamp()),
Value::Byte(val) => Ok(val as i64),
Value::Enum(val) => Ok(val as i64),
Value::SInt8(val) => Ok(val as i64),
Value::UInt8(val) => Ok(val as i64),
Value::UInt8z(val) => Ok(val as i64),
Value::SInt16(val) => Ok(val as i64),
Value::UInt16(val) => Ok(val as i64),
Value::UInt16z(val) => Ok(val as i64),
Value::SInt32(val) => Ok(val as i64),
Value::UInt32(val) => Ok(val as i64),
Value::UInt32z(val) => Ok(val as i64),
Value::SInt64(val) => Ok(val),
Value::UInt64(val) => Ok(val as i64),
Value::UInt64z(val) => Ok(val as i64),
Value::Float32(_) => {
Err(ErrorKind::ValueError(format!("cannot convert {} into an i64", self)).into())
}
Value::Float64(_) => {
Err(ErrorKind::ValueError(format!("cannot convert {} into an i64", self)).into())
}
Value::String(_) => {
Err(ErrorKind::ValueError(format!("cannot convert {} into an i64", self)).into())
}
Value::Array(_) => {
Err(ErrorKind::ValueError(format!("cannot convert {} into an i64", self)).into())
}
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct ValueWithUnits {
value: Value,
units: String,
}
impl ValueWithUnits {
pub fn new(value: Value, units: String) -> Self {
ValueWithUnits { value, units }
}
}
impl convert::From<FitDataField> for ValueWithUnits {
fn from(field: FitDataField) -> Self {
ValueWithUnits::new(field.value, field.units)
}
}
impl fmt::Display for ValueWithUnits {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.units.is_empty() {
write!(f, "{}", self.value)
} else {
write!(f, "{} {}", self.value, self.units)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_activity() {
let data = include_bytes!("../tests/fixtures/Activity.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 22);
}
#[test]
fn parse_developer_data() {
let data = include_bytes!("../tests/fixtures/DeveloperData.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 6);
}
#[test]
fn parse_monitoring_file() {
let data = include_bytes!("../tests/fixtures/MonitoringFile.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 355);
}
#[test]
fn parse_settings() {
let data = include_bytes!("../tests/fixtures/Settings.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 3);
}
#[test]
fn parse_weight_scale_multi_user() {
let data = include_bytes!("../tests/fixtures/WeightScaleMultiUser.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 7);
}
#[test]
fn parse_weight_scale_single_user() {
let data = include_bytes!("../tests/fixtures/WeightScaleSingleUser.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 6);
}
#[test]
fn parse_workout_custom_target_values() {
let data = include_bytes!("../tests/fixtures/WorkoutCustomTargetValues.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 6);
}
#[test]
fn parse_workout_individual_steps() {
let data = include_bytes!("../tests/fixtures/WorkoutIndividualSteps.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 6);
}
#[test]
fn parse_workout_repeat_greater_than_step() {
let data = include_bytes!("../tests/fixtures/WorkoutRepeatGreaterThanStep.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 7);
}
#[test]
fn parse_workout_repeat_steps() {
let data = include_bytes!("../tests/fixtures/WorkoutRepeatSteps.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 7);
}
#[test]
fn parse_garmin_fenix_5_bike() {
let data = include_bytes!("../tests/fixtures/garmin-fenix-5-bike.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 143);
}
#[test]
fn parse_sample_mulitple_header() {
let data = include_bytes!("../tests/fixtures/sample_mulitple_header.fit").to_vec();
let fit_data = from_bytes(&data).unwrap();
assert_eq!(fit_data.len(), 3023);
}
}