From 86341aa75abafb38ef3a61b906462e0e5c56d611 Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Thu, 4 Jul 2024 19:05:34 +0200 Subject: [PATCH] fix: can parse many more things from `Record`; cooler code --- src/main.rs | 183 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 153 insertions(+), 30 deletions(-) diff --git a/src/main.rs b/src/main.rs index 55bfe24..7d659f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,114 @@ use fit_rust::{ - protocol::{message_type::MessageType, value::Value, FitDataMessage, FitMessage}, + protocol::{message_type::MessageType, value::Value, DataMessage, FitMessage}, Fit, }; use geo_types::{coord, Point}; use gpx::{Gpx, GpxVersion, Track, TrackSegment, Waypoint}; -use std::fs; -use std::{fs::File, io::BufWriter}; +use std::{fs, fs::File, io::BufWriter}; +use time::OffsetDateTime; + +#[derive(Clone, Copy, Default, Debug, PartialEq)] +struct RecordData { + /// latitude + pub lat: Option, + /// longitude + pub lon: Option, + /// altitude + pub alt: Option, + // heart-rate + pub hr: Option, + /// timestamp + pub time: Option, + + pub cadence: Option, + pub distance: Option, + pub speed: Option, + pub power: Option, + pub temperature: Option, + pub right_balance: Option, +} +impl RecordData { + // crazy check + fn invalid(&self) -> bool { + (self.lat.is_some_and(|lat| lat == 0.) && self.lon.is_some_and(|lon| lon == 0.)) + || (self.lat.is_none() && self.lon.is_none()) + } +} +impl From for RecordData { + fn from(value: DataMessage) -> Self { + if let MessageType::Record = value.message_type { + let lat = value_to_float(df_at(&value, 0)); + let lon = value_to_float(df_at(&value, 1)); + let alt = value_to_float(df_at(&value, 2)).map(|alt| alt / 5. - 500.); + + let hr = value_to_float(df_at(&value, 3)); + let hr: Option = hr.map(|hr| hr as u8); + + let cadence = value_to_float(df_at(&value, 4)); + let cadence: Option = cadence.map(|cad| cad as u8); + + let distance = value_to_float(df_at(&value, 5)).map(|d| d / 100000.); + + let speed = value_to_float(df_at(&value, 6)).map(|v| v / 1000. * 3.6); + + let power = value_to_float(df_at(&value, 7)); + let power = power.map(|power| power as u16); + + let temperature = value_to_float(df_at(&value, 13)); + let temperature = temperature.map(|temperature| temperature as i8); + + let right_balance = value_to_float(df_at(&value, 30)); + let right_balance = right_balance.map(|right_balance| right_balance as u8); + + let t = df_at(&value, 253); + let time = if let Some(Value::Time(t)) = t { + if let Ok(t) = OffsetDateTime::from_unix_timestamp((*t).into()) { + Some(t) + } else { + None + } + } else { + None + }; + + RecordData { + lat, + lon, + alt, + hr, + time, + cadence, + distance, + speed, + power, + temperature, + right_balance, + } + } else { + RecordData::default() + } + } +} +impl From for Waypoint { + fn from(value: RecordData) -> Self { + let geo_point: Point = + Point(coord! {x: value.lon.unwrap_or(0.), y: value.lat.unwrap_or(0.)}); + + let mut wp = Waypoint::new(geo_point); + wp.elevation = value.alt; + wp.time = value.time.map(|t| t.into()); + wp.speed = value.speed; + + wp + } +} fn main() { // collecting cli args let args = std::env::args().collect::>(); let mut handles = vec![]; - for file in args.iter().skip(1) { + for file in args.iter().skip(1).filter(|f| f.ends_with(".fit")) { let file = file.clone(); let jh = std::thread::spawn(move || { fit2gpx(&file); @@ -27,6 +123,7 @@ fn main() { fn fit2gpx(f_in: &String) { let file = fs::read(f_in).unwrap(); let fit: Fit = Fit::read(file).unwrap(); + // let mut log_file = File::create([f_in, ".log"].concat()).unwrap(); println!("\n\nHEADER:"); println!("\theader size: {}", &fit.header.header_size); @@ -38,6 +135,7 @@ fn fit2gpx(f_in: &String) { println!("-----------------------------\n"); let mut track_segment = TrackSegment { points: vec![] }; + let mut ongoing_activity = true; for data in &fit.data { match data { @@ -45,31 +143,36 @@ fn fit2gpx(f_in: &String) { // println!("\nDefinition: {:#?}", msg.data); } FitMessage::Data(msg) => { - // println!("\nData: {:#?}", msg.data); + // writeln!(log_file, "\nData: {msg:#?}").unwrap(); + // println!("\nData: {:#?}", msg); if let MessageType::Record = msg.data.message_type { - let y: f32 = match df_at(msg, 0) { - Value::F32(y) => *y, - y => panic!("invalid y coordinate: {y:?}"), - }; - let x: f32 = match df_at(msg, 1) { - Value::F32(x) => *x, - x => panic!("invalid x coordinate: {x:?}"), - }; - - // let elev = todo!(); TODO - - let t = match df_at(msg, 253) { - Value::Time(t) => t, - t => panic!("invalid time: {t:?}"), - }; - let t = time::OffsetDateTime::from_unix_timestamp((*t).into()).unwrap(); + let rec_dat: RecordData = msg.data.clone().into(); + if rec_dat.invalid() { + eprintln!("warn: guess it's invalid: {msg:#?}"); + continue; + } + // eprintln!("{rec_dat:#?}"); // Add track point - let geo_point: Point = Point(coord! {x: x as f64, y: y as f64}); - let mut wp = Waypoint::new(geo_point); - // wp.elevation = elev; // TODO - wp.time = Some(t.into()); - track_segment.points.push(wp); + let wp: Waypoint = rec_dat.into(); + // if wp.point().x_y() == (0., 0.) { + // eprintln!("warn: guess it's invalid: {msg:#?}"); + // } + if ongoing_activity { + track_segment.points.push(wp); + } else { + eprintln!("warn: NOT in an activity right now"); + // std::io::stdin().read_line(&mut String::new()).unwrap(); + } + } else if let MessageType::Activity = msg.data.message_type { + let start_stop = df_at(&msg.data, 4); + if let Some(Value::Enum(start_stop)) = start_stop { + if start_stop == &"start" { + ongoing_activity = true; + } else if start_stop == &"stop" { + ongoing_activity = false; + } + } } } } @@ -104,15 +207,35 @@ fn fit2gpx(f_in: &String) { gpx::write(&gpx, buf).unwrap(); } +fn value_to_float(val: Option<&Value>) -> Option { + match val { + Some(Value::U8(x)) => Some(*x as f64), + Some(Value::U16(x)) => Some(*x as f64), + Some(Value::U32(x)) => Some(*x as f64), + Some(Value::U64(x)) => Some(*x as f64), + + Some(Value::I8(x)) => Some(*x as f64), + Some(Value::I16(x)) => Some(*x as f64), + Some(Value::I32(x)) => Some(*x as f64), + Some(Value::I64(x)) => Some(*x as f64), + + Some(Value::F32(x)) => Some(*x as f64), + Some(Value::F64(x)) => Some(*x), + _x => { + // eprintln!("invalid f64: {x:?}"); + None + } + } +} + /// datafield at num -fn df_at(data_msg: &FitDataMessage, num: u8) -> &Value { +fn df_at(data_msg: &DataMessage, num: u8) -> Option<&Value> { + // eprintln!("data-msg: {data_msg:#?}"); let x = data_msg - .data .values .iter() .filter(|df| df.field_num == num) .collect::>(); - assert_eq!(1, x.len()); - &x[0].value + Some(&x.first()?.value) }