use clap::{Parser}; pub mod asotr_data { use std::{fs::File, io::Read}; use byteorder::{LittleEndian, ReadBytesExt}; use chrono::{DateTime, Utc}; use std::time::{SystemTime, UNIX_EPOCH, Duration}; use strum::FromRepr; use lazy_static::lazy_static; use regex::Regex; use walkdir::WalkDir; lazy_static! { pub static ref support_dtypes: String = String::from(".data01.asotr01(02), data02.asotr01(02), data06.asotr01(02)"); pub static ref patterns_fnames_csv_data: Vec<(String, String)> = { let mut patterns: Vec<(String, String)> = Vec::new(); patterns.push((String::from(".*data01.asotr01"), String::from("../data/asotr/asotr01_data_T.csv"))); patterns.push((String::from(".*data02.asotr01"), String::from("../data/asotr/asotr01_data_P.csv"))); patterns.push((String::from(".*data06.asotr01"), String::from("../data/asotr/asotr01_data_TSET.csv"))); patterns.push((String::from(".*data01.asotr02"), String::from("../data/asotr/asotr02_data_T.csv"))); patterns.push((String::from(".*data02.asotr02"), String::from("../data/asotr/asotr02_data_P.csv"))); patterns.push((String::from(".*data06.asotr02"), String::from("../data/asotr/asotr02_data_TSET.csv"))); patterns }; pub static ref patterns_disp: Vec = { let mut patterns: Vec = Vec::new(); patterns.push(String::from("ASOTR01 temperature")); patterns.push(String::from("ASOTR01 power")); patterns.push(String::from("ASOTR01 temperature setpoint")); patterns.push(String::from("ASOTR02 temperature")); patterns.push(String::from("ASOTR02 power")); patterns.push(String::from("ASOTR02 temperature setpoint")); patterns }; } #[derive(Debug, FromRepr, PartialEq)] enum AsotrDataType { Temp = 1, Pow = 2, TempSet = 6, } struct AsotrDataDesc { time_s: u64, time_mks: u32, date: String, time: String, data_type: AsotrDataType, // kit: u8, } impl AsotrDataDesc { pub fn new(time_s: u64, time_mks: u32, date: String, time: String, data_type: AsotrDataType) -> AsotrDataDesc { AsotrDataDesc { time_s, time_mks, date, time, data_type } } } pub fn read_data(filename_full: String) -> Result { let ch_u16: [u16; 6]; let ch_f32: [f32; 6]; let asotr_head = parse_filename(filename_full.clone())?; let mut buf = Vec::new(); let mut out = String::new(); let mut data = match File::open(filename_full.clone()) { Ok(file) => file, Err(msg) => { return Err(format!("Error opening data file {}: {}", filename_full, msg)) } }; match data.read_to_end(&mut buf) { Ok(stat) => stat, Err(msg) => { return Err(format!("Error reading data file {}: {}", filename_full, msg)) } }; out.push_str(&format!("{};{} {}.{:02};", &asotr_head.time_s, &asotr_head.date, &asotr_head.time, asotr_head.time_mks)); if asotr_head.data_type == AsotrDataType::Temp || asotr_head.data_type == AsotrDataType::TempSet { ch_f32 = parse_data_f32(buf)?; for elem in ch_f32 { out.push_str(&elem.to_string()); out.push(';'); } } else if asotr_head.data_type == AsotrDataType::Pow { ch_u16 = parse_data_u16(buf)?; for elem in ch_u16 { out.push_str(&elem.to_string()); out.push(';'); } } out.remove(out.len() - 1); return Ok(out); } pub fn parse_data_dir(dir: &str, disp: bool) -> Result<(), String> { let mut data: Vec = Vec::new(); println!("parse data from directory: {}", dir); for (i, (pattern, fname)) in patterns_fnames_csv_data.iter().enumerate() { let files = find_files_regex(dir, pattern)?; for elem in files { data.push(read_data(elem)?); } data.sort(); data.dedup(); if disp { disp_data(&data, &patterns_disp[i])?; } println!("save csv data to file: {}", fname); save_data_csv(data.clone(), fname)?; data.clear(); } return Ok(()); } fn parse_data_f32(buf: Vec) -> Result<[f32; 6], String> { let mut data = &buf[..]; let mut ch: [f32; 6] = [0.0; 6]; for i in 0..6 { ch[i] = match data.read_f32::() { Ok(val) => val, Err(msg) => { return Err(format!( "Error parsing file: failed parsing float32 data: {}", msg)); } } } return Ok(ch); } fn parse_data_u16(buf: Vec) -> Result<[u16; 6], String> { let mut data = &buf[..]; let mut ch: [u16; 6] = [0; 6]; for i in 0..6 { ch[i] = match data.read_u16::() { Ok(val) => val, Err(msg) => { return Err(format!( "Error parsing file: failed parsing uint16 data: {}", msg)); } } } return Ok(ch); } fn parse_filename(filename_full: String) -> Result { let mut fname = String::new(); let msg_prev = format!("Error parsing filename {}:", filename_full); match filename_full.rfind('/') { Some(val) => { fname = (filename_full[val+1..filename_full.len()]).to_string(); } _ => { fname = filename_full.clone(); } } if fname.len() != 32 { return Err(format!("{} unsupported file", msg_prev)); } let time_unix_ = fname[0..10].parse::(); let time_unix = match &time_unix_ { Ok(data) => data, Err(msg) => { return Err(format!("{} expected digits in timestamp sec part ({})", msg_prev, msg)); } }; let data_type_ = fname[22..24].parse::(); let data_type_u8 = match &data_type_ { Ok(data) => data, Err(msg) => { return Err(format!("{} expected digits in data type part ({})", msg_prev, msg)); } }; if *data_type_u8 == 1 || *data_type_u8 == 2 || *data_type_u8 == 6 { } else { return Err(format!("{} parser supports data types: {}", msg_prev, support_dtypes.to_string())); } let data_type = match AsotrDataType::from_repr(*data_type_u8 as usize) { Some(value) => value, _ => return Err(format!("{} expected digits in data type part", msg_prev)) }; // let _kit = filename[30..32].parse::(); // let kit = match &_kit { // Ok(data) => data, // Err(msg) => { return Err(format!("{}: expected digits in asotr kit part ({})", // msg_prev, msg)); } // }; let _time_str_mks = fname[11..14].parse::(); let time_mks = match &_time_str_mks { Ok(data) => data, Err(msg) => { return Err(format!("{}: expected digits in timestamp mks part ({})", msg_prev, msg)); } }; let time: SystemTime = UNIX_EPOCH + Duration::from_secs(*time_unix); let date_time = DateTime::::from(time); let date_s = date_time.format("%d.%m.%Y").to_string(); let time_s = date_time.format("%H:%M:%S").to_string(); let head = AsotrDataDesc::new(*time_unix, *time_mks, date_s, time_s, data_type); return Ok(head); } fn find_files_regex(dir: &str, template: &str) -> Result, String> { let mut path_vec: Vec = Vec::new(); let regex = match Regex::new(template) { Ok(val) => val, Err(msg) => { return Err(format!("Error create regex template ({}): {}", template, msg)); } }; let data = WalkDir::new(dir) .into_iter() .filter_map(|e| e.ok()) .filter(|e| regex.is_match(e.file_name().to_str().unwrap())) .map(|e| e.into_path()); for elem in data { path_vec.push(elem.display().to_string()); } if path_vec.len() == 0 { return Err(format!( "Error searching files for pattern ({}): files not found in directory {}", template, dir)); } return Ok(path_vec); } fn disp_data(data: &Vec, about: &str) -> Result<(), String> { println!("{}", about); println!("{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", "timestamp_sec", "timestamp", "ch1", "ch2", "ch3", "ch4", "ch5", "ch6"); for elem in data { println!("{}", elem.replace(';', "\t")); } return Ok(()) } fn save_data_csv(data: Vec, fname: &str) -> Result<(), String> { let mut data_w: Vec = Vec::new(); data_w.push(format!("{};{};{};{};{};{};{};{}", "timestamp_sec", "timestamp", "ch1", "ch2", "ch3", "ch4", "ch5", "ch6")); for elem in data { data_w.push(elem); } match std::fs::write(fname, data_w.join("\n")) { Ok(f) => f, Err(msg) => { return Err(format!("Error write data to csv file {}: {}", fname, msg)) } } return Ok(()); } } #[derive(Parser, Debug)] #[command(version, author, about, long_about = None)] struct Cli { /// file with ASOTR MVN data (.data01.asotr01(02), data02.asotr01(02), data06.asotr01(02)) #[arg(long, short = 'f', value_name = "FILE_DATA")] filename: Option, /// directory with ASOTR MVN data #[arg(long, short = 'd', value_name = "DIRECTORY_DATA")] directory: Option, /// show data in console #[arg(short = 's')] show: bool, } fn main() { use crate::asotr_data::*; let cli = Cli::parse(); let show = cli.show; if let Some(fname) = &cli.filename { let s = match read_data(fname.clone()) { Ok(elem) => elem, Err(msg) => { println!("{}", msg); return; } }; println!("{}", s); return; } if let Some(dir) = &cli.directory { match parse_data_dir(&dir.clone(), show) { Ok(elem) => elem, Err(msg) => { println!("{}", msg); return; } } return; } println!("Unexpected command. Type --help for more iformation"); }