Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Developer fields #44

Merged
merged 11 commits into from
Oct 14, 2024
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Fitparser

[![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Crates.io Version](https://img.shields.io/crates/v/fitparser.svg)](https://crates.io/crates/fitparser)
[![Docs.rs](https://docs.rs/fitparser/badge.svg)](https://docs.rs/fitparser)
[![Build Status](https://github.com/stadelmanma/fitparse-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/stadelmanma/fitparse-rs/actions/workflows/ci.yml)


## Overview

Parses FIT formatted files and exports their contents using
Expand All @@ -22,18 +22,20 @@ data in whatever format suits their needs. This library provides a
other serialization format implemented using Serde.

Notes:
* This library **does not** support writing FIT files at this time.
* Files with Developer Data fields can be parsed but the developer
fields are dropped.
* The FIT SDK is regularly updated by Garmin/Ant this library may not
be up to date; check the `src/profile/messages.rs` for the packaged version.
Submit an issue and I will gladly bump it!

- This library **does not** support writing FIT files at this time.
- Files with Developer Data fields can be parsed and the developer
fields are correctly extracted.
- The FIT SDK is regularly updated by Garmin/Ant this library may not
be up to date; check the `src/profile/messages.rs` for the packaged version.
Submit an issue and I will gladly bump it!

## Usage

See library documentation at [docs.rs/fitparser](https://docs.rs/fitparser)
for full usage information. Below is a basic example of calling the parser
on a FIT file.

```rust
use fitparser;
use std::fs::File;
Expand All @@ -57,7 +59,6 @@ parser. See the source code of the `fitparser/src/de/mod.rs` to view all options
(or view the crate docs). The `fit_to_json` example program demos all of the
currently available options as well.


## Updating the FIT profile

All FIT files are generated based on a customizable profile. The profile
Expand Down
61 changes: 55 additions & 6 deletions fitparser/src/de/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
use super::parser::FitDataMessage;
use super::DecodeOption;
use crate::error::Result;
use crate::profile::{MesgNum, TimestampField};
use crate::{FitDataField, FitDataRecord, Value};
use std::collections::{HashMap, HashSet};
use crate::profile::{data_field_with_info, FieldDataType, MesgNum, TimestampField};
use crate::{DeveloperFieldDescription, ErrorKind, FitDataField, FitDataRecord, Value};
use std::collections::{HashMap, HashSet, VecDeque};
use std::convert::{From, TryInto};

/// Decodes a raw FitDataMessage using the defined profile. Additional logic is used to handle
Expand All @@ -14,6 +14,7 @@ use std::convert::{From, TryInto};
pub struct Decoder {
base_timestamp: TimestampField,
accumulate_fields: HashMap<u32, Value>,
developer_field_descriptions: HashMap<(u8, u8), DeveloperFieldDescription>,
}

impl Decoder {
Expand All @@ -22,13 +23,15 @@ impl Decoder {
Decoder {
base_timestamp: TimestampField::Utc(0),
accumulate_fields: HashMap::new(),
developer_field_descriptions: HashMap::new(),
}
}

/// Reset accumation related fields
pub fn reset(&mut self) {
self.base_timestamp = TimestampField::Utc(0);
self.accumulate_fields = HashMap::new();
self.developer_field_descriptions = HashMap::new();
}

/// Decode a raw FIT data message by applying the defined profile
Expand All @@ -49,22 +52,33 @@ impl Decoder {

// process raw data
let mut fields =
mesg_num.decode_message(message.fields_mut(), &mut self.accumulate_fields, options)?;
mesg_num.decode_message(&mut message.fields, &mut self.accumulate_fields, options)?;
fields.sort_by_key(|f| f.number());
if mesg_num == MesgNum::FieldDescription {
// This message describes a new developer field
let description = DeveloperFieldDescription::try_from(&fields)?;
self.developer_field_descriptions.insert(
(
description.developer_data_index,
description.field_definition_number,
),
description,
);
}
record.extend(fields);
self.decode_developer_fields(&mut record, &message.developer_fields, options)?;

// Add a timestamp field if we have a time offset
if let Some(time_offset) = message.time_offset() {
record.push(FitDataField::new(
String::from("timestamp"),
253,
None,
self.update_timestamp(time_offset),
String::new(),
));
}

// TODO: process developer fields

Ok(record)
}

Expand All @@ -90,4 +104,39 @@ impl Decoder {

Value::from(self.base_timestamp)
}

pub fn developer_field_descriptions(&self) -> &HashMap<(u8, u8), DeveloperFieldDescription> {
&self.developer_field_descriptions
}

fn decode_developer_fields(
&self,
record: &mut FitDataRecord,
developer_data_map: &HashMap<(u8, u8), Value>,
options: &HashSet<DecodeOption>,
) -> Result<()> {
let mut entries: VecDeque<((u8, u8), Value)> = developer_data_map
.iter()
.map(|(k, v)| (*k, v.clone()))
.collect();
while let Some(((dev_data_idx, field_nr), value)) = entries.pop_front() {
let dev_definition = self
.developer_field_descriptions
.get(&(dev_data_idx, field_nr))
.ok_or(ErrorKind::MissingDeveloperDefinitionMessage())?;
record.push(data_field_with_info(
dev_definition.field_definition_number,
Some(dev_definition.developer_data_index),
&dev_definition.field_name,
FieldDataType::Byte,
dev_definition.scale,
dev_definition.offset,
&dev_definition.units,
value,
options,
)?);
}

Ok(())
}
}
24 changes: 17 additions & 7 deletions fitparser/src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
//! applying the packaged FIT profile to the data.
use crate::error::{ErrorKind, Result};
use crate::profile::MesgNum;
use crate::FitDataRecord;
use crate::{DeveloperFieldDescription, FitDataRecord};
use nom::number::complete::le_u16;
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use std::io::Read;
use std::sync::Arc;

Expand Down Expand Up @@ -97,7 +98,11 @@ impl Deserializer {

/// Advance the parser state returning one of four possible objects defined within the
/// FIT file.
fn deserialize_next<'de>(&mut self, input: &'de [u8]) -> Result<(&'de [u8], FitObject)> {
fn deserialize_next<'de>(
&mut self,
input: &'de [u8],
developer_field_descriptions: &HashMap<(u8, u8), DeveloperFieldDescription>,
) -> Result<(&'de [u8], FitObject)> {
if self.position > 0 && self.position == self.end_of_messages {
// extract the CRC
return self.deserialize_crc(input);
Expand All @@ -109,7 +114,7 @@ impl Deserializer {
}
// if we reach this point then we must be at some position: 0 < X < self.end_of_messages
// and a message should exist (either data or definition).
self.deserialize_message(input)
self.deserialize_message(input, developer_field_descriptions)
}

/// Parse the FIT header
Expand Down Expand Up @@ -164,11 +169,15 @@ impl Deserializer {
}

/// Parse a FIT data or definition message
fn deserialize_message<'de>(&mut self, input: &'de [u8]) -> Result<(&'de [u8], FitObject)> {
fn deserialize_message<'de>(
&mut self,
input: &'de [u8],
developer_fields: &HashMap<(u8, u8), DeveloperFieldDescription>,
) -> Result<(&'de [u8], FitObject)> {
// parse a single message of either variety
let init_len = input.len();
let (remaining, message) =
parser::fit_message(input, &self.definitions).map_err(|e| self.to_parse_err(e))?;
let (remaining, message) = parser::fit_message(input, &self.definitions, developer_fields)
.map_err(|e| self.to_parse_err(e))?;
// update CRC with the consumed bytes
self.crc = update_crc(self.crc, &input[0..(input.len() - remaining.len())]);

Expand Down Expand Up @@ -252,7 +261,8 @@ impl FitStreamProcessor {

/// Deserialize a FitObject from the byte stream.
pub fn deserialize_next<'de>(&mut self, input: &'de [u8]) -> Result<(&'de [u8], FitObject)> {
self.deserializer.deserialize_next(input)
self.deserializer
.deserialize_next(input, self.decoder.developer_field_descriptions())
}

/// Decode a FIT data message into a FIT data record using the defined FIT profile.
Expand Down
Loading
Loading