fit_rust/
lib.rs

1pub mod protocol;
2
3use crate::protocol::io::{skip_bytes, write_bin};
4use crate::protocol::macros::get_field_value;
5use crate::protocol::message_type::MessageType;
6use crate::protocol::value::Value;
7use crate::protocol::{
8    calculate_fit_crc, DataMessage, DefinitionMessage, FitDataMessage, FitDefinitionMessage,
9    FitHeader, FitMessage, FitMessageHeader,
10};
11use binrw::{BinReaderExt, BinResult, BinWrite, Endian, Error};
12use std::collections::VecDeque;
13use std::fmt;
14use std::fmt::{Debug, Formatter};
15use std::fs::{read, write};
16use std::io::{Cursor, Seek, SeekFrom, Write};
17use std::ops::Div;
18use std::path::Path;
19
20#[derive(Clone)]
21pub struct Fit {
22    pub header: FitHeader,
23
24    pub data: Vec<FitMessage>,
25}
26
27impl Debug for Fit {
28    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
29        f.debug_struct("Fit").field("header", &self.header).finish()
30    }
31}
32
33impl Fit {
34    pub fn read(buf: Vec<u8>) -> BinResult<Self> {
35        let mut cursor = Cursor::new(buf);
36        let header: FitHeader = cursor.read_ne()?;
37        let mut queue: VecDeque<(u8, FitDefinitionMessage)> = VecDeque::new();
38
39        let mut data: Vec<FitMessage> = Vec::new();
40        loop {
41            let message_header: FitMessageHeader = cursor.read_ne()?;
42            match message_header.definition {
43                true => {
44                    if message_header.dev_fields {
45                        unimplemented!("message_header.dev_fields is unimplemented");
46                    }
47                    let definition_message: DefinitionMessage =
48                        cursor.read_ne_args((message_header.dev_fields,))?;
49
50                    let local_num = message_header.local_num;
51                    let def = FitDefinitionMessage {
52                        header: message_header,
53                        data: definition_message,
54                    };
55                    data.push(FitMessage::Definition(def.clone()));
56                    queue.push_front((local_num, def));
57                }
58                false => {
59                    let definition = match queue.iter().find(|x| x.0 == message_header.local_num) {
60                        None => continue,
61                        Some((_, def)) => def,
62                    };
63                    let data_message: DataMessage = cursor.read_ne_args((definition,))?;
64                    if data_message.message_type == MessageType::None {
65                        continue;
66                    }
67                    data.push(FitMessage::Data(FitDataMessage {
68                        header: message_header,
69                        data: data_message,
70                    }));
71                    if cursor.position() >= (header.data_size + header.header_size as u32) as u64 {
72                        break;
73                    }
74                }
75            }
76        }
77        Ok(Fit { header, data })
78    }
79
80    pub fn write<P: AsRef<Path>>(&self, file: P) -> BinResult<()> {
81        let mut buf = Vec::with_capacity(
82            (self.header.data_size + self.header.header_size as u32 + 2) as usize,
83        );
84        let header = self.write_buf(&mut buf)?;
85        Fit::write_crc(header, &mut buf)?;
86        write(file, &buf)?;
87        Ok(())
88    }
89
90    fn write_crc(header: FitHeader, buf: &mut Vec<u8>) -> BinResult<()> {
91        let mut header_crc: Option<u16> = None;
92        if header.crc.is_some() {
93            let header = &buf[0..(header.header_size - 2) as usize];
94            header_crc = Some(calculate_fit_crc(&header));
95        }
96        let end_byte = header.header_size as u32 + header.data_size;
97        let body = &buf[header.header_size as usize..end_byte as usize];
98        let body_crc = calculate_fit_crc(&body);
99        let mut writer = Cursor::new(buf);
100        match header_crc {
101            None => {}
102            Some(crc) => {
103                writer.seek(SeekFrom::Start(header.header_size as u64 - 2))?;
104                write_bin(&mut writer, crc, Endian::Little)?;
105            }
106        }
107        writer.seek(SeekFrom::End(0))?;
108        write_bin(&mut writer, body_crc, Endian::Little)?;
109        writer.flush()?;
110        Ok(())
111    }
112
113    pub(crate) fn write_buf(&self, buf: &mut Vec<u8>) -> BinResult<FitHeader> {
114        let mut queue: VecDeque<(u8, FitDefinitionMessage)> = VecDeque::new();
115        let mut writer = Cursor::new(buf);
116        skip_bytes(&mut writer, self.header.header_size);
117        for massage in &self.data {
118            match massage {
119                FitMessage::Definition(msg) => {
120                    msg.header.write(&mut writer)?;
121                    msg.data.write(&mut writer)?;
122                    let local_num = msg.header.local_num;
123                    queue.push_front((local_num, msg.clone()));
124                }
125                FitMessage::Data(msg) => {
126                    let message_header = &msg.header;
127                    let definition = match queue.iter().find(|x| x.0 == message_header.local_num) {
128                        None => None,
129                        Some((_, def)) => Some(def),
130                    };
131                    match definition {
132                        None => {}
133                        Some(def) => {
134                            if &msg.data.message_type != &MessageType::None {
135                                msg.header.write(&mut writer)?;
136                                msg.data.write(&mut writer, &def.data)?;
137                            }
138                        }
139                    }
140                }
141            }
142        }
143        let mut header = self.header.clone();
144        header.data_size = writer.position() as u32 - header.header_size as u32;
145        writer.seek(SeekFrom::Start(0))?;
146        header.write(&mut writer)?;
147        writer.flush()?;
148        Ok(header)
149    }
150}
151
152impl Fit {
153    #[allow(unused)]
154    pub fn merge<P: AsRef<Path>>(files: Vec<P>, path: P) -> BinResult<()> {
155        if files.is_empty() || files.len() <= 1 {
156            eprintln!("Error files is empty: {:?}", files.len());
157            return Err(Error::Io(binrw::io::Error::new(
158                binrw::io::ErrorKind::UnexpectedEof,
159                "Error files is empty!",
160            )));
161        }
162        let file = read(files.get(0).unwrap()).unwrap();
163        let mut fit: Fit = Fit::read(file)?;
164        // find session
165        let session: Option<(usize, FitDataMessage)> = fit.get_session();
166        let mut sessions: Vec<Option<(usize, FitDataMessage)>> = vec![session];
167        for i in 1..=files.len() - 1 {
168            let f = files.get(i).unwrap();
169            let f = read(f).unwrap();
170            let mut tmp = Fit::read(f)?;
171            sessions.push(tmp.get_session());
172
173            let to_move: Vec<_> = tmp
174                .data
175                .iter()
176                .enumerate()
177                .filter(|(_, message)| match message {
178                    FitMessage::Definition(_) => false,
179                    FitMessage::Data(msg) => {
180                        return matches!(msg.data.message_type, MessageType::Record);
181                    }
182                })
183                .map(|(i, _)| i)
184                .collect();
185
186            for i in to_move.into_iter().rev() {
187                // Here, we directly take the message without wrapping it in an Option.
188                let message = tmp.data.swap_remove(i);
189                fit.data.push(message);
190            }
191        }
192
193        fit.replace_session(sessions);
194        fit.write(path)
195    }
196
197    fn replace_session(&mut self, sessions: Vec<Option<(usize, FitDataMessage)>>) {
198        let mut index = 0;
199        let mut session_vec = vec![];
200        for session in sessions {
201            match session {
202                None => {}
203                Some((i, s)) => {
204                    if index == 0 {
205                        index = i;
206                    }
207                    session_vec.push(s);
208                }
209            }
210        }
211
212        let session = Fit::merge_sessions(session_vec);
213        match session {
214            None => {}
215            Some(session) => {
216                self.data[index] = FitMessage::Data(session);
217            }
218        }
219    }
220
221    fn merge_sessions(mut sessions: Vec<FitDataMessage>) -> Option<FitDataMessage> {
222        if sessions.is_empty() {
223            return None;
224        }
225        let mut merged_session = sessions.remove(0);
226        // max
227        let mut max_stop_timestamp = Value::Time(u32::MIN);
228        let mut max_speed = Value::U16(u16::MIN);
229        let mut max_power = Value::U16(u16::MIN);
230        let mut max_altitude = Value::U16(u16::MIN);
231        let mut max_pos_grade = Value::I16(i16::MIN);
232        let mut max_neg_grade = Value::I16(i16::MIN);
233        let mut max_heart_rate = Value::U8(u8::MIN);
234        let mut max_cadence = Value::U8(u8::MIN);
235        let mut max_temperature = Value::U8(u8::MIN);
236        // min
237        let mut min_start_timestamp = Value::Time(u32::MAX);
238        let mut min_altitude = Value::U16(u16::MAX);
239        let mut min_heart_rate = Value::U8(u8::MAX);
240        // sum
241        let mut total_elapsed_time = Value::U32(0_u32);
242        let mut total_timer_time = Value::U32(0_u32);
243        let mut total_distance = Value::U32(0_u32);
244        let mut total_moving_time = Value::U32(0_u32);
245        let mut total_calories = Value::U16(0_u16);
246        let mut total_ascent = Value::U16(0_u16);
247        let mut total_descent = Value::U16(0_u16);
248        // avg
249        let mut avg_speed = Value::I32(0_i32);
250        let mut avg_speed_count = 0_i32;
251        let mut avg_power = Value::I32(0_i32);
252        let mut avg_power_count = 0_i32;
253        let mut avg_altitude = Value::I32(0_i32);
254        let mut avg_altitude_count = 0_i32;
255        let mut avg_grade = Value::I32(0_i32);
256        let mut avg_grade_count = 0_i32;
257        let mut avg_pos_grade = Value::I32(0_i32);
258        let mut avg_pos_grade_count = 0_i32;
259        let mut avg_neg_grade = Value::I32(0_i32);
260        let mut avg_neg_grade_count = 0_i32;
261        let mut avg_pos_vertical_speed = Value::I32(0_i32);
262        let mut avg_pos_vertical_speed_count = 0_i32;
263        let mut avg_neg_vertical_speed = Value::I32(0_i32);
264        let mut avg_neg_vertical_speed_count = 0_i32;
265        let mut avg_heart_rate = Value::I32(0_i32);
266        let mut avg_heart_rate_count = 0_i32;
267        let mut avg_cadence = Value::I32(0_i32);
268        let mut avg_cadence_count = 0_i32;
269        let mut avg_temperature = Value::I32(0_i32);
270        let mut avg_temperature_count = 0_i32;
271
272        for session in sessions {
273            merge_stats!(
274                // max
275                max 253, max_stop_timestamp, session,
276                max 15, max_speed, session,
277                max 21, max_power, session,
278                max 50, max_altitude, session,
279                max 55, max_pos_grade, session,
280                max 56, max_neg_grade, session,
281                max 17, max_heart_rate, session,
282                max 19, max_cadence, session,
283                max 58, max_temperature, session,
284                // min
285                min 2, min_start_timestamp, session,
286                min 71, min_altitude, session,
287                min 64, min_heart_rate, session,
288                // sum
289                sum 7, total_elapsed_time, session,
290                sum 8, total_timer_time, session,
291                sum 9, total_distance, session,
292                sum 59, total_moving_time, session,
293                sum 11, total_calories, session,
294                sum 22, total_ascent, session,
295                sum 23, total_descent, session,
296                sum 23, total_descent, session,
297                // avg
298                avg 14, avg_speed, avg_speed_count, session,
299                avg 20, avg_power, avg_power_count, session,
300                avg 49, avg_altitude, avg_altitude_count, session,
301                avg 52, avg_grade, avg_grade_count, session,
302                avg 53, avg_pos_grade, avg_pos_grade_count, session,
303                avg 54, avg_neg_grade, avg_neg_grade_count, session,
304                avg 60, avg_pos_vertical_speed, avg_pos_vertical_speed_count, session,
305                avg 61, avg_neg_vertical_speed, avg_neg_vertical_speed_count, session,
306                avg 16, avg_heart_rate, avg_heart_rate_count, session,
307                avg 18, avg_cadence, avg_cadence_count, session,
308                avg 57, avg_temperature, avg_temperature_count, session,
309            );
310        }
311
312        // Update merged session fields
313        // max
314        update_field!(merged_session.data.values, 253, max_stop_timestamp);
315        update_field!(merged_session.data.values, 15, max_speed);
316        update_field!(merged_session.data.values, 21, max_power);
317        update_field!(merged_session.data.values, 50, max_altitude);
318        update_field!(merged_session.data.values, 55, max_pos_grade);
319        update_field!(merged_session.data.values, 56, max_neg_grade);
320        update_field!(merged_session.data.values, 17, max_heart_rate);
321        update_field!(merged_session.data.values, 19, max_cadence);
322        update_field!(merged_session.data.values, 58, max_temperature);
323        // min
324        update_field!(merged_session.data.values, 2, min_start_timestamp);
325        update_field!(merged_session.data.values, 71, min_altitude);
326        update_field!(merged_session.data.values, 64, min_heart_rate);
327        // sum
328        update_field!(merged_session.data.values, 7, total_elapsed_time);
329        update_field!(merged_session.data.values, 8, total_timer_time);
330        update_field!(merged_session.data.values, 9, total_distance);
331        update_field!(merged_session.data.values, 59, total_moving_time);
332        update_field!(merged_session.data.values, 11, total_calories);
333        update_field!(merged_session.data.values, 22, total_ascent);
334        update_field!(merged_session.data.values, 23, total_descent);
335        // avg
336        if avg_speed_count > 0 {
337            let avg_speed = <Value as Into<i32>>::into(avg_speed).div(avg_speed_count);
338            update_field!(merged_session.data.values, 14, Value::U16(avg_speed as u16));
339        }
340        if avg_power_count > 0 {
341            let avg_power = <Value as Into<i32>>::into(avg_power).div(avg_power_count);
342            update_field!(merged_session.data.values, 20, Value::U16(avg_power as u16));
343        }
344        if avg_altitude_count > 0 {
345            let avg_altitude = <Value as Into<i32>>::into(avg_altitude).div(avg_altitude_count);
346            update_field!(
347                merged_session.data.values,
348                49,
349                Value::U16(avg_altitude as u16)
350            );
351        }
352        if avg_grade_count > 0 {
353            let avg_grade = <Value as Into<i32>>::into(avg_grade).div(avg_grade_count);
354            update_field!(merged_session.data.values, 52, Value::I16(avg_grade as i16));
355        }
356        if avg_pos_grade_count > 0 {
357            let avg_pos_grade = <Value as Into<i32>>::into(avg_pos_grade).div(avg_pos_grade_count);
358            update_field!(
359                merged_session.data.values,
360                53,
361                Value::I16(avg_pos_grade as i16)
362            );
363        }
364        if avg_neg_grade_count > 0 {
365            let avg_neg_grade = <Value as Into<i32>>::into(avg_neg_grade).div(avg_neg_grade_count);
366            update_field!(
367                merged_session.data.values,
368                54,
369                Value::I16(avg_neg_grade as i16)
370            );
371        }
372        if avg_pos_vertical_speed_count > 0 {
373            let avg_pos_vertical_speed = <Value as Into<i32>>::into(avg_pos_vertical_speed)
374                .div(avg_pos_vertical_speed_count);
375            update_field!(
376                merged_session.data.values,
377                60,
378                Value::I16(avg_pos_vertical_speed as i16)
379            );
380        }
381        if avg_neg_vertical_speed_count > 0 {
382            let avg_neg_vertical_speed = <Value as Into<i32>>::into(avg_neg_vertical_speed)
383                .div(avg_neg_vertical_speed_count);
384            update_field!(
385                merged_session.data.values,
386                61,
387                Value::I16(avg_neg_vertical_speed as i16)
388            );
389        }
390        if avg_heart_rate_count > 0 {
391            let avg_heart_rate =
392                <Value as Into<i32>>::into(avg_heart_rate).div(avg_heart_rate_count);
393            update_field!(
394                merged_session.data.values,
395                16,
396                Value::U8(avg_heart_rate as u8)
397            );
398        }
399        if avg_cadence_count > 0 {
400            let avg_cadence = <Value as Into<i32>>::into(avg_cadence).div(avg_cadence_count);
401            update_field!(merged_session.data.values, 18, Value::U8(avg_cadence as u8));
402        }
403        if avg_temperature_count > 0 {
404            let avg_temperature =
405                <Value as Into<i32>>::into(avg_temperature).div(avg_temperature_count);
406            update_field!(
407                merged_session.data.values,
408                57,
409                Value::I8(avg_temperature as i8)
410            );
411        }
412
413        Some(merged_session)
414    }
415
416    pub fn get_session(&self) -> Option<(usize, FitDataMessage)> {
417        for (index, message) in self.data.iter().enumerate() {
418            match message {
419                FitMessage::Definition(_) => {}
420                FitMessage::Data(msg) => match msg.data.message_type {
421                    MessageType::Session => {
422                        return Some((index, msg.clone()));
423                    }
424                    _ => {}
425                },
426            };
427        }
428        None
429    }
430}