use std::collections::BTreeMap;
use std::io::*;
use std::sync::{ Arc, atomic::AtomicBool };
use byteorder::{ ReadBytesExt, BigEndian, LittleEndian };
use crate::tags_impl::*;
use crate::*;
struct Format {
typ: u8,
_length: u8,
name: String,
format: String,
multipliers: Option<String>,
units: Option<String>,
labels: Vec<String>
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
pub enum FieldType {
u8(u8), i8(i8),
u16(u16), i16(i16),
u32(u32), i32(i32),
u64(u64), i64(i64),
f32(f32), f64(f64),
String(String),
Vec_i16(Vec<i16>), Vec_u16(Vec<u16>),
Vec_i32(Vec<i32>), Vec_u32(Vec<u32>),
}
#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
pub struct Field {
value: FieldType,
unit: Option<String>,
multiplier: Option<f64>
}
#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
pub struct LogItem {
typ: u8,
name: String,
data: BTreeMap<String, Field>
}
pub fn parse_full<T: Read + Seek, F: Fn(f64)>(stream: &mut T, size: usize, progress_cb: F, cancel_flag: Arc<AtomicBool>) -> Result<Vec<LogItem>> {
let mut units = BTreeMap::from([
( '-', "" .to_owned() ), ( '?', "UNKNOWN" .to_owned() ), ( 'A', "A" .to_owned() ), ( 'a', "Ah" .to_owned() ), ( 'd', "deg" .to_owned() ), ( 'b', "B" .to_owned() ), ( 'k', "deg/s" .to_owned() ), ( 'D', "deglatitude" .to_owned() ), ( 'e', "deg/s/s" .to_owned() ), ( 'E', "rad/s" .to_owned() ), ( 'G', "Gauss" .to_owned() ), ( 'h', "degheading" .to_owned() ), ( 'i', "A.s" .to_owned() ), ( 'J', "W.s" .to_owned() ), ( 'L', "rad/s/s" .to_owned() ), ( 'm', "m" .to_owned() ), ( 'n', "m/s" .to_owned() ), ( 'o', "m/s/s" .to_owned() ), ( 'O', "degC" .to_owned() ), ( '%', "%" .to_owned() ), ( 'S', "satellites" .to_owned() ), ( 's', "s" .to_owned() ), ( 'q', "rpm" .to_owned() ), ( 'r', "rad" .to_owned() ), ( 'U', "deglongitude" .to_owned() ), ( 'u', "ppm" .to_owned() ), ( 'v', "V" .to_owned() ), ( 'P', "Pa" .to_owned() ), ( 'w', "Ohm" .to_owned() ), ( 'W', "Watt" .to_owned() ), ( 'X', "W.h" .to_owned() ), ( 'Y', "us" .to_owned() ), ( 'z', "Hz" .to_owned() ), ( '#', "instance" .to_owned() ) ]);
let mut multipliers = BTreeMap::from([
( '-', 0.0 ), ( '?', 1.0 ), ( '2', 1e2 ),
( '1', 1e1 ),
( '0', 1e0 ),
( 'A', 1e-1 ),
( 'B', 1e-2 ),
( 'C', 1e-3 ),
( 'D', 1e-4 ),
( 'E', 1e-5 ),
( 'F', 1e-6 ),
( 'G', 1e-7 ),
( 'I', 1e-9 ),
( '!', 3.6 ), ( '/', 3600.0 ), ]);
let mut stream = std::io::BufReader::with_capacity(16*1024*1024, stream);
let mut formats = BTreeMap::new();
let mut log = Vec::<LogItem>::new();
while (size as i64 - stream.stream_position()? as i64) >= 3 {
let mut update_format = BTreeMap::new();
if cancel_flag.load(std::sync::atomic::Ordering::Relaxed) { break; }
if size > 0 {
progress_cb(stream.stream_position()? as f64 / size as f64);
}
if stream.read_u16::<BigEndian>()? == 0xA395 {
let id = stream.read_u8()?;
if id == 0x80 { let mut name = vec![0u8; 4];
let mut format = vec![0u8; 16];
let mut labels = vec![0u8; 64];
let typ = stream.read_u8()?;
let _length = stream.read_u8()?;
stream.read_exact(&mut name)?;
stream.read_exact(&mut format)?;
stream.read_exact(&mut labels)?;
formats.insert(typ, Format {
typ,
_length,
units: None,
multipliers: None,
name: String::from_utf8_lossy(&name).trim_matches('\0').to_string(),
format: String::from_utf8_lossy(&format).trim_matches('\0').to_string(),
labels: String::from_utf8_lossy(&labels).trim_matches('\0').split(',').map(str::to_string).collect(),
});
} else if let Some(desc) = formats.get(&id) {
if desc.format.len() > 0 && desc.format.len() == desc.labels.len() {
let mut msg = BTreeMap::new();
for (i, (f, label)) in desc.format.chars().zip(&desc.labels).enumerate() {
if let Err(e) = (|| -> Result<()> {
let unit = desc.units.as_ref().and_then(|v| v.chars().nth(i));
let unit = unit.map(|v| units.get(&v).cloned().unwrap_or_else(|| format!("{}", v)));
let mult = desc.multipliers.as_ref().and_then(|v| v.chars().nth(i));
let mult = mult.and_then(|v| multipliers.get(&v).copied());
let value = match f {
'a' => Some(FieldType::Vec_i16((0..32).filter_map(|_| stream.read_i16::<LittleEndian>().ok()).collect())),
'b' => Some(FieldType::i8(stream.read_i8()?)),
'B' => Some(FieldType::u8(stream.read_u8()?)),
'h' => Some(FieldType::i16(stream.read_i16::<LittleEndian>()?)),
'H' => Some(FieldType::u16(stream.read_u16::<LittleEndian>()?)),
'i' => Some(FieldType::i32(stream.read_i32::<LittleEndian>()?)),
'I' => Some(FieldType::u32(stream.read_u32::<LittleEndian>()?)),
'f' => Some(FieldType::f32(stream.read_f32::<LittleEndian>()?)),
'd' => Some(FieldType::f64(stream.read_f64::<LittleEndian>()?)),
'n' | 'N' | 'Z' => {
let s = match f { 'n' => 4, 'N' => 16, 'Z' => 64, _ => 0 };
let mut data = vec![0u8; s];
stream.read_exact(&mut data)?;
Some(FieldType::String(String::from_utf8_lossy(&data).trim_matches('\0').to_string()))
}
'c' => Some(FieldType::Vec_i16((0..100).filter_map(|_| stream.read_i16::<LittleEndian>().ok()).collect())),
'C' => Some(FieldType::Vec_u16((0..100).filter_map(|_| stream.read_u16::<LittleEndian>().ok()).collect())),
'e' => Some(FieldType::Vec_i32((0..100).filter_map(|_| stream.read_i32::<LittleEndian>().ok()).collect())),
'E' => Some(FieldType::Vec_u32((0..100).filter_map(|_| stream.read_u32::<LittleEndian>().ok()).collect())),
'L' => Some(FieldType::i32(stream.read_i32::<LittleEndian>()?)), 'M' => Some(FieldType::u8(stream.read_u8()?)), 'q' => Some(FieldType::i64(stream.read_i64::<LittleEndian>()?)),
'Q' => Some(FieldType::u64(stream.read_u64::<LittleEndian>()?)),
_ => {
log::error!("Invalid format {}", f);
None
}
};
if let Some(value) = value {
msg.insert(label.clone(), Field { value, unit, multiplier: mult });
}
Ok(())
})() {
log::error!("error parsing data: {e:?}")
}
}
match desc.name.as_ref() {
"UNIT" => match (msg.get("Id").map(|v| &v.value), msg.get("Label").map(|v| &v.value)) {
(Some(FieldType::i8(id)), Some(FieldType::String(label))) => {
units.insert(char::from(*id as u8), label.clone());
},
_ => { }
},
"MULT" => match (msg.get("Id").map(|v| &v.value), msg.get("Mult").map(|v| &v.value)) {
(Some(FieldType::i8(id)), Some(FieldType::f64(mult))) => {
multipliers.insert(char::from(*id as u8), *mult);
},
_ => { }
},
"FMTU" => match (msg.get("FmtType").map(|v| &v.value), msg.get("MultIds").map(|v| &v.value), msg.get("UnitIds").map(|v| &v.value)) {
(Some(FieldType::u8(id)), Some(FieldType::String(mult)), Some(FieldType::String(unit))) => {
update_format.insert(*id, (mult.clone(), unit.clone()));
},
_ => { }
},
_ => { }
}
log.push(LogItem {
typ: desc.typ,
name: desc.name.clone(),
data: msg
});
}
} else {
log::warn!("Unknown msg: {}", id);
}
}
if !update_format.is_empty() {
for (id, (mult, unit)) in update_format {
if let Some(desc) = formats.get_mut(&id) {
desc.units = Some(unit);
desc.multipliers = Some(mult);
}
}
}
}
Ok(log)
}
pub fn parse<T: Read + Seek, F: Fn(f64)>(stream: &mut T, size: usize, progress_cb: F, cancel_flag: Arc<AtomicBool>) -> Result<Vec<SampleInfo>> {
let log = parse_full(stream, size, progress_cb, cancel_flag)?;
let mut gyro = BTreeMap::from([ ("VSTB", vec![]), ("IMU", vec![]), ("GYR", vec![]) ]);
let mut accl = BTreeMap::from([ ("VSTB", vec![]), ("IMU", vec![]), ("ACC", vec![]) ]);
let mut quats = Vec::new();
let mut first_quat_ts = None;
for l in &log {
if let Some(FieldType::u64(time)) = l.data.get("SampleUS").or_else(|| l.data.get("TimeUS")).map(|v| &v.value) {
match l.name.as_ref() {
"IMU" | "GYR" | "ACC" | "VSTB" => {
match (l.data.get("AccX").map(|v| &v.value), l.data.get("AccY").map(|v| &v.value), l.data.get("AccZ").map(|v| &v.value)) {
(Some(FieldType::f32(x)), Some(FieldType::f32(y)), Some(FieldType::f32(z))) => {
accl.get_mut(l.name.as_str()).unwrap().push(TimeVector3 { t: *time as f64 / 1000000.0,
x: *x as f64,
y: *y as f64,
z: *z as f64
});
},
_ => { }
}
match (l.data.get("GyrX").map(|v| &v.value), l.data.get("GyrY").map(|v| &v.value), l.data.get("GyrZ").map(|v| &v.value)) {
(Some(FieldType::f32(x)), Some(FieldType::f32(y)), Some(FieldType::f32(z))) => {
gyro.get_mut(l.name.as_str()).unwrap().push(TimeVector3 { t: *time as f64 / 1000000.0,
x: *x as f64,
y: *y as f64,
z: *z as f64
});
},
_ => { }
}
match (l.data.get("Q1").map(|v| &v.value), l.data.get("Q2").map(|v| &v.value), l.data.get("Q3").map(|v| &v.value), l.data.get("Q4").map(|v| &v.value)) {
(Some(FieldType::f32(w)), Some(FieldType::f32(x)), Some(FieldType::f32(y)), Some(FieldType::f32(z))) => {
if first_quat_ts.is_none() {
first_quat_ts = Some(*time as i64);
}
quats.push(TimeQuaternion {
t: (*time as i64 - first_quat_ts.unwrap()) as f64 / 1000.0,
v: util::multiply_quats(
(*w as f64,
*x as f64,
*y as f64,
*z as f64),
(0.5, -0.5, -0.5, 0.5),
),
});
},
_ => { }
}
},
_ => { }
}
}
}
let gyro = [&gyro["VSTB"], &gyro["IMU"], &gyro["GYR"]].iter().find(|v| !v.is_empty()).map(|v| v.to_vec()).unwrap_or_default();
let accl = [&accl["VSTB"], &accl["IMU"], &accl["ACC"]].iter().find(|v| !v.is_empty()).map(|v| v.to_vec()).unwrap_or_default();
let mut map = GroupedTagMap::new();
util::insert_tag(&mut map, tag!(parsed GroupId::Accelerometer, TagId::Data, "Accelerometer data", Vec_TimeVector3_f64, |v| format!("{:?}", v), accl, vec![]));
util::insert_tag(&mut map, tag!(parsed GroupId::Gyroscope, TagId::Data, "Gyroscope data", Vec_TimeVector3_f64, |v| format!("{:?}", v), gyro, vec![]));
util::insert_tag(&mut map, tag!(parsed GroupId::Quaternion, TagId::Data, "Quaternion data", Vec_TimeQuaternion_f64, |v| format!("{:?}", v), quats, vec![]));
util::insert_tag(&mut map, tag!(parsed GroupId::Accelerometer, TagId::Unit, "Accelerometer unit", String, |v| v.to_string(), "m/s²".into(), Vec::new()));
util::insert_tag(&mut map, tag!(parsed GroupId::Gyroscope, TagId::Unit, "Gyroscope unit", String, |v| v.to_string(), "rad/s".into(), Vec::new()));
let imu_orientation = "zyx";
util::insert_tag(&mut map, tag!(parsed GroupId::Accelerometer, TagId::Orientation, "IMU orientation", String, |v| v.to_string(), imu_orientation.into(), Vec::new()));
util::insert_tag(&mut map, tag!(parsed GroupId::Gyroscope, TagId::Orientation, "IMU orientation", String, |v| v.to_string(), imu_orientation.into(), Vec::new()));
Ok(vec![
SampleInfo { tag_map: Some(map), ..Default::default() }
])
}