From af6dfa756b79c078a389b8a0d56e5807e8f5118a Mon Sep 17 00:00:00 2001 From: Markus Mayer Date: Sat, 6 Jul 2024 21:50:05 +0200 Subject: [PATCH] Add CSV row formatting --- src/main.rs | 154 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 113 insertions(+), 41 deletions(-) diff --git a/src/main.rs b/src/main.rs index 84ce7bb..5544ab6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,9 +10,12 @@ use async_compression::tokio::write::GzipEncoder; use async_compression::Level; use clap::Parser; use color_eyre::eyre::Result; +use num_traits::real::Real; pub use ratatui::prelude::*; use serial_sensors_proto::versions::Version1DataFrame; -use serial_sensors_proto::{deserialize, DeserializationError, SensorData, SensorId}; +use serial_sensors_proto::{ + deserialize, DeserializationError, IdentifierCode, SensorData, SensorId, ValueType, +}; use tokio::fs::File; use tokio::io::{self, AsyncReadExt, AsyncWriteExt, BufWriter}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; @@ -159,10 +162,14 @@ async fn dump_data(directory: PathBuf, mut rx: UnboundedReceiver continue, + Some(data) => data, + }; + match files.entry(target.clone()) { Entry::Occupied(mut entry) => { - let data = create_data_row(since_the_epoch, target, &data); - entry.get_mut().write_all(&data).await?; + entry.get_mut().write_all(&data_row).await?; } Entry::Vacant(entry) => { let file_name = format!( @@ -180,67 +187,132 @@ async fn dump_data(directory: PathBuf, mut rx: UnboundedReceiver Vec { - let mut row = String::from("host_time,sensor_tag,num_components,value_type"); +fn create_header_row(data: &Version1DataFrame) -> Option> { + let mut row = String::from("host_time,device_time,sensor_tag,num_components,value_type"); match data.value { - SensorData::SystemClockFrequency(_) => {} - SensorData::AccelerometerI16(_) => {} - SensorData::MagnetometerI16(_) => {} - SensorData::TemperatureI16(_) => {} - SensorData::GyroscopeI16(_) => {} - SensorData::HeadingI16(_) => {} - SensorData::EulerAnglesF32(_) => {} - SensorData::OrientationQuaternionF32(_) => {} - SensorData::LinearRanges(_) => {} - SensorData::Identification(_) => {} + SensorData::SystemClockFrequency(_) => row.push_str(",freq"), + SensorData::AccelerometerI16(_) => row.push_str(",x,y,z"), + SensorData::MagnetometerI16(_) => row.push_str(",x,y,z"), + SensorData::TemperatureI16(_) => row.push_str(",temp"), + SensorData::GyroscopeI16(_) => row.push_str(",x,y,z"), + SensorData::HeadingI16(_) => row.push_str(",heading"), + SensorData::EulerAnglesF32(_) => row.push_str(",x,y,z"), + SensorData::OrientationQuaternionF32(_) => row.push_str(",a,b,c,d"), + SensorData::LinearRanges(_) => row.push_str(",resolution_bits,scale_op,scale,scale_raw,scale_decimals,offset,offset_raw,offset_decimals"), + SensorData::Identification(_) => row.push_str(",code,value"), } row.push('\n'); - row.as_bytes().into() + Some(row.as_bytes().into()) } fn create_data_row( since_the_epoch: Duration, - target: SensorId, + target: &SensorId, data: &Version1DataFrame, -) -> Vec { +) -> Option> { + let device_time = decode_device_time(data); let mut row = format!( - "{},{:02X},{},{:02X}", + "{},{},{:02X},{},{},", since_the_epoch.as_secs_f64(), + device_time, target.tag(), target.num_components().unwrap_or(0), - target.value_type() as u8 + value_type_code(target.value_type()) ); match data.value { - SensorData::SystemClockFrequency(_) => {} - SensorData::AccelerometerI16(_) => {} - SensorData::MagnetometerI16(_) => {} - SensorData::TemperatureI16(_) => {} - SensorData::GyroscopeI16(_) => {} - SensorData::HeadingI16(_) => {} - SensorData::EulerAnglesF32(_) => {} - SensorData::OrientationQuaternionF32(_) => {} - SensorData::LinearRanges(_) => {} - SensorData::Identification(_) => {} + SensorData::SystemClockFrequency(data) => row.push_str(&format!("{}", data.value)), + SensorData::AccelerometerI16(vec) => { + row.push_str(&format!("{},{},{}", vec.x, vec.y, vec.z)) + } + SensorData::MagnetometerI16(vec) => row.push_str(&format!("{},{},{}", vec.x, vec.y, vec.z)), + SensorData::TemperatureI16(temp) => row.push_str(&format!("{}", temp.value)), + SensorData::GyroscopeI16(vec) => row.push_str(&format!("{},{},{}", vec.x, vec.y, vec.z)), + SensorData::HeadingI16(temp) => row.push_str(&format!("{}", temp.value)), + SensorData::EulerAnglesF32(vec) => row.push_str(&format!("{},{},{}", vec.x, vec.y, vec.z)), + SensorData::OrientationQuaternionF32(vec) => { + row.push_str(&format!("{},{},{},{}", vec.a, vec.b, vec.c, vec.d)) + } + SensorData::LinearRanges(ref lr) => row.push_str(&format!( + "{},{:02X},{},{},{},{},{},{}", + lr.resolution_bits, + lr.scale_op, + lr.scale as f32 * 10.0.powi(-(lr.scale_decimals as i32)), + lr.scale, + lr.scale_decimals, + lr.offset as f32 * 10.0.powi(-(lr.offset_decimals as i32)), + lr.offset, + lr.offset_decimals + )), + SensorData::Identification(ref ident) => row.push_str(&format!( + "{},{}", + ident_code(ident.code), + std::str::from_utf8(&ident.value).unwrap_or("").trim() + )), } row.push('\n'); - row.as_bytes().into() + Some(row.as_bytes().into()) +} + +fn decode_device_time(data: &Version1DataFrame) -> f32 { + if data.system_secs != u32::MAX { + data.system_secs as f32 + + if data.system_millis != u16::MAX { + data.system_millis as f32 / 1_000.0 + } else { + 0.0 + } + + if data.system_nanos != u16::MAX { + data.system_nanos as f32 / 1_000_000.0 + } else { + 0.0 + } + } else { + 0.0 + } +} + +fn ident_code(code: IdentifierCode) -> &'static str { + match code { + IdentifierCode::Generic => "generic", + IdentifierCode::Maker => "maker", + IdentifierCode::Product => "product", + IdentifierCode::Revision => "revision", + } +} + +fn value_type_code(vt: ValueType) -> &'static str { + match vt { + ValueType::UInt8 => "u8", + ValueType::SInt8 => "i8", + ValueType::UInt16 => "u16", + ValueType::SInt16 => "i16", + ValueType::UInt32 => "u32", + ValueType::SInt32 => "i32", + ValueType::UInt64 => "u64", + ValueType::SInt64 => "i64", + ValueType::UInt128 => "u128", + ValueType::SInt128 => "i128", + ValueType::Float32 => "f32", + ValueType::Float64 => "f64", + ValueType::Q8_8 => "Q8.8", + ValueType::Q16_16 => "Q16.1", + ValueType::Q32_32 => "Q32.32", + ValueType::LinearRange => "linear range", + ValueType::Identifier => "ident", + } } async fn decoder(