1use crate::de::DecodeOption;
4use crate::error::{ErrorKind, Result};
5use crate::{FitDataField, Value};
6use chrono::{DateTime, Duration, Local, NaiveDate, TimeZone};
7use std::collections::{HashMap, HashSet};
8use std::convert::TryInto;
9
10pub mod field_types;
11pub use field_types::{get_field_variant_as_string, FieldDataType, MesgNum};
12
13pub mod decode;
14pub use decode::VERSION;
15
16impl Value {
17 fn to_ne_bytes(&self) -> Vec<u8> {
19 match self {
20 Value::Byte(val) => vec![*val],
21 Value::Enum(val) => vec![*val],
22 Value::SInt8(val) => vec![*val as u8],
23 Value::UInt8(val) => vec![*val],
24 Value::SInt16(val) => val.to_ne_bytes().to_vec(),
25 Value::UInt16(val) => val.to_ne_bytes().to_vec(),
26 Value::SInt32(val) => val.to_ne_bytes().to_vec(),
27 Value::UInt32(val) => val.to_ne_bytes().to_vec(),
28 Value::String(val) => val.as_bytes().to_vec(),
29 Value::Timestamp(val) => val.timestamp().to_ne_bytes().to_vec(),
30 Value::Float32(val) => val.to_ne_bytes().to_vec(),
31 Value::Float64(val) => val.to_ne_bytes().to_vec(),
32 Value::UInt8z(val) => vec![*val],
33 Value::UInt16z(val) => val.to_ne_bytes().to_vec(),
34 Value::UInt32z(val) => val.to_ne_bytes().to_vec(),
35 Value::SInt64(val) => val.to_ne_bytes().to_vec(),
36 Value::UInt64(val) => val.to_ne_bytes().to_vec(),
37 Value::UInt64z(val) => val.to_ne_bytes().to_vec(),
38 Value::Array(vals) => vals.iter().flat_map(|v| v.to_ne_bytes()).collect(),
39 Value::Invalid => Vec::new(),
40 }
41 }
42}
43
44#[derive(Debug, Copy, Clone)]
46pub enum TimestampField {
47 Local(i64),
49 Utc(i64),
51}
52
53impl TimestampField {
54 pub fn as_i64(&self) -> i64 {
56 match self {
57 Self::Local(value) => *value,
58 Self::Utc(value) => *value,
59 }
60 }
61
62 fn to_date_time(self) -> DateTime<Local> {
64 let ref_date = NaiveDate::from_ymd_opt(1989, 12, 31)
66 .and_then(|d: NaiveDate| d.and_hms_opt(0, 0, 0))
67 .unwrap();
68 match self {
69 Self::Local(value) => {
70 TimeZone::from_local_datetime(&Local, &ref_date).unwrap() + Duration::seconds(value)
71 }
72 Self::Utc(value) => {
73 TimeZone::from_utc_datetime(&Local, &ref_date) + Duration::seconds(value)
74 }
75 }
76 }
77}
78
79impl From<TimestampField> for Value {
80 fn from(timestamp: TimestampField) -> Value {
81 Value::Timestamp(timestamp.to_date_time())
82 }
83}
84
85fn extract_component(input: &[u8], mut offset: usize, nbits: usize) -> ((&[u8], usize), Value) {
88 let bit_mask = [1u8, 2u8, 4u8, 8u8, 16u8, 32u8, 64u8, 128u8];
89 let mut bytes = input.iter().copied();
90 let mut idx = 0;
91 let mut byte = bytes.next().unwrap_or(0);
92 let mut acc: u64 = 0;
93
94 for pos in 0..nbits {
95 acc |= (((byte & bit_mask[offset]) >> offset) as u64) << pos;
96 if offset == 7 {
97 byte = bytes.next().unwrap_or(0);
98 idx += 1;
99 offset = 0;
100 } else {
101 offset += 1;
102 }
103 }
104 if input.len() > idx {
105 ((&input[idx..], offset), Value::UInt64(acc))
106 } else {
107 ((&[], offset), Value::UInt64(acc))
108 }
109}
110
111pub fn calculate_cumulative_value(
113 accumulate_fields: &mut HashMap<u32, Value>,
114 msg_num: u16,
115 def_num: u8,
116 value: Value,
117) -> Result<Value> {
118 macro_rules! only_add_like_values {
120 ($key:ident, $val:ident, $stored_value:ident, $variant:ident) => {
121 if let Value::$variant(other) = value {
122 let value = Value::$variant($val + other);
123 accumulate_fields.insert($key, value.clone());
124 Ok(value)
125 } else {
126 Err(ErrorKind::ValueError(format!(
127 "Mixed type addition {} and {} cannot be combined",
128 $stored_value, value
129 ))
130 .into())
131 }
132 };
133 }
134
135 let key = (msg_num as u32) << 8 | def_num as u32;
136 if let Some(stored_value) = accumulate_fields.get(&key) {
137 match stored_value {
138 Value::Timestamp(_) => {
139 Err(ErrorKind::ValueError("Cannot accumlate timestamp fields".to_string()).into())
142 }
143 Value::Byte(val) => only_add_like_values!(key, val, stored_value, Byte),
144 Value::Enum(_) => {
145 Err(ErrorKind::ValueError("Cannot accumlate enum fields".to_string()).into())
146 }
147 Value::SInt8(val) => only_add_like_values!(key, val, stored_value, SInt8),
148 Value::UInt8(val) => only_add_like_values!(key, val, stored_value, UInt8),
149 Value::UInt8z(val) => only_add_like_values!(key, val, stored_value, UInt8z),
150 Value::SInt16(val) => only_add_like_values!(key, val, stored_value, SInt16),
151 Value::UInt16(val) => only_add_like_values!(key, val, stored_value, UInt16),
152 Value::UInt16z(val) => only_add_like_values!(key, val, stored_value, UInt16z),
153 Value::SInt32(val) => only_add_like_values!(key, val, stored_value, SInt32),
154 Value::UInt32(val) => only_add_like_values!(key, val, stored_value, UInt32),
155 Value::UInt32z(val) => only_add_like_values!(key, val, stored_value, UInt32z),
156 Value::SInt64(val) => only_add_like_values!(key, val, stored_value, SInt64),
157 Value::UInt64(val) => only_add_like_values!(key, val, stored_value, UInt64),
158 Value::UInt64z(val) => only_add_like_values!(key, val, stored_value, UInt64z),
159 Value::Float32(val) => only_add_like_values!(key, val, stored_value, Float32),
160 Value::Float64(val) => only_add_like_values!(key, val, stored_value, Float64),
161 Value::String(_) => {
162 Err(ErrorKind::ValueError("Cannot accumlate string fields".to_string()).into())
163 }
164 Value::Array(vals) => {
170 if let Value::Array(other_vals) = value {
171 if vals.len() == other_vals.len() {
172 let mut new_vals = Vec::with_capacity(vals.len());
173 for (v1, v2) in vals.iter().zip(other_vals.iter()) {
174 let v1_i64: i64 = v1.try_into()?;
175 let v2_i64: i64 = v2.try_into()?;
176 new_vals.push(Value::SInt64(v1_i64 + v2_i64));
177 }
178 accumulate_fields.insert(key, Value::Array(new_vals.clone()));
179 Ok(Value::Array(new_vals))
180 } else {
181 Err(ErrorKind::ValueError(format!(
182 "Array lengths differ, {} != {}",
183 vals.len(),
184 other_vals.len()
185 ))
186 .into())
187 }
188 } else {
189 Err(ErrorKind::ValueError(format!(
190 "Mixed type addition {} and {} cannot be combined",
191 stored_value, value
192 ))
193 .into())
194 }
195 }
196 Value::Invalid => {
197 Err(ErrorKind::ValueError("Cannot accumlate invalid fields".to_string()).into())
198 }
199 }
200 } else {
201 accumulate_fields.insert(key, value.clone());
202 Ok(value)
203 }
204}
205
206#[allow(clippy::too_many_arguments)]
208pub fn data_field_with_info(
209 def_number: u8,
210 developer_data_index: Option<u8>,
211 name: &str,
212 data_type: FieldDataType,
213 scale: f64,
214 offset: f64,
215 units: &str,
216 value: Value,
217 options: &HashSet<DecodeOption>,
218) -> Result<FitDataField> {
219 let value = convert_value(data_type, scale, offset, value, options)?;
220 Ok(FitDataField::new(
221 name.to_string(),
222 def_number,
223 developer_data_index,
224 value,
225 units.to_string(),
226 ))
227}
228
229pub fn unknown_field(field_def_num: u8, value: Value) -> FitDataField {
231 FitDataField::new(
232 format!("unknown_field_{}", field_def_num),
233 field_def_num,
234 None,
235 value,
236 String::new(),
237 )
238}
239
240fn convert_value(
242 field_type: FieldDataType,
243 scale: f64,
244 offset: f64,
245 value: Value,
246 options: &HashSet<DecodeOption>,
247) -> Result<Value> {
248 if let Value::Array(vals) = value {
250 let vals: Result<Vec<Value>> = vals
251 .into_iter()
252 .map(|v| apply_scale_and_offset(v, scale, offset))
253 .collect();
254 return vals.map(Value::Array);
255 }
256
257 match field_type {
260 FieldDataType::DateTime => {
261 return Ok(Value::from(TimestampField::Utc(
262 value.try_into().unwrap_or(0),
263 )));
264 }
265 FieldDataType::LocalDateTime => {
266 return Ok(Value::from(TimestampField::Local(
267 value.try_into().unwrap_or(0),
268 )));
269 }
270 _ => (),
271 }
272
273 if field_type.is_enum_type() {
275 let val: i64 = value.try_into()?;
276 if options.contains(&DecodeOption::ReturnNumericEnumValues) {
277 Ok(Value::SInt64(val))
278 } else if field_type.is_named_variant(val) {
279 Ok(Value::String(get_field_variant_as_string(field_type, val)))
280 } else {
281 Ok(Value::SInt64(val))
282 }
283 } else {
284 apply_scale_and_offset(value, scale, offset)
285 }
286}
287
288fn apply_scale_and_offset(value: Value, scale: f64, offset: f64) -> Result<Value> {
289 if value != Value::Invalid
290 && (((scale - 1.0).abs() > f64::EPSILON) || ((offset - 0.0).abs() > f64::EPSILON))
291 {
292 let val: f64 = value.try_into()?;
293 Ok(Value::Float64(val / scale - offset))
294 } else {
295 Ok(value)
296 }
297}