use std::io::Read; use std::time::Instant; use crate::serial_core::frame::{FrameHandler}; use crate::serial_core::{codec::Codec, error::CodecError, frame::TestFrame}; use anyhow::anyhow; use async_trait::async_trait; use csv::StringRecord; use crate::serial_core::record::{write_csv, CsvExporter, CsvImporter, RecordedFrame, Recording}; use crate::serial_core::utils::{ elapsed_millis, usize_to_u16_be_bytes }; pub struct TestCodec { buffer: Vec, } pub struct TestHandler; impl TestCodec { pub fn new() -> TestCodec { Self { buffer: Vec::new() } } } impl Codec for TestCodec { fn decode(&mut self, input: &[u8], session_started_at: Instant) -> Result, CodecError> { self.buffer.extend_from_slice(input); let mut frames = Vec::new(); loop { if self.buffer.len() < 6 { break; } let header_pos = self.buffer.windows(2).position(|w| w == [0xAA, 0x55]); let Some(pos) = header_pos else { self.buffer.clear(); break; }; if pos > 0 { self.buffer.drain(0..pos); } if self.buffer.len() < 6 { break; } let cmd = self.buffer[2]; let length_bytes = [self.buffer[3], self.buffer[4]]; let length = u16::from_be_bytes(length_bytes) as usize; let frame_length = (length + 6) as usize; if self.buffer.len() < frame_length { break; } let payload = self.buffer[5..5 + length].to_vec(); // let checksum = crc8(payload.as_slice()); let crc8_alg = crc::Crc::::new(&crc::CRC_8_SMBUS); let checksum = crc8_alg.checksum(payload.as_slice()); if self.buffer[frame_length - 1] != checksum { self.buffer.drain(0..1); continue; } let dts = elapsed_millis(session_started_at); println!("dts_ms: {dts}"); frames.push(TestFrame { header: [0xAA, 0x55], cmd: cmd, length: length, payload: payload, checksum: checksum, dts_ms: dts, }); self.buffer.drain(0..frame_length); } Ok(frames) } fn encode(&self, frame: &TestFrame) -> Result, CodecError> { let _ = u16::try_from(frame.payload.len()).map_err(|_| CodecError::PayloadTooLarge)?; let mut out = Vec::with_capacity(6 + frame.length); out.extend_from_slice(&frame.header); out.push(frame.cmd); out.extend_from_slice(&usize_to_u16_be_bytes(frame.length)); out.extend_from_slice(&frame.payload); out.push(frame.checksum); Ok(out) } } #[async_trait] impl FrameHandler for TestHandler { async fn on_frame(&mut self, frame: &TestFrame) -> anyhow::Result>> { match frame.cmd { 0x01 => { let vals = parse_data_frame(&frame.payload)?; Ok(Some(vals)) } _ => Ok(None), } } } fn parse_data_frame(data: &[u8]) -> Result, CodecError> { if data.len() % 2 != 0 { return Err(CodecError::InvalidLength); } let vals: Vec = data .chunks_exact(2) .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]) as i32) .collect::>(); Ok(vals) } pub struct TestCsvExporter; pub struct TestCsvImporter { channels: usize, data_row: usize, packets: Vec, } #[derive(Clone)] pub struct TestDataPacket { pub data: Vec, pub dts_ms: u64 } impl TryFrom<&TestFrame> for TestDataPacket { type Error = CodecError; fn try_from(frame: &TestFrame) -> Result { let data = parse_data_frame(&frame.payload)?; let dts = frame.dts_ms; Ok(TestDataPacket { data: data, dts_ms: dts }) } } // impl From for TestDataPacket { // fn from(frame: TestFrame) -> Self { // let data = parse_data_frame(&frame.payload)?; // let dts = frame.dts_ms; // TestDataPacket { data: data, dts_ms: dts } // } // } impl CsvExporter for TestCsvExporter { type Error = CodecError; fn csv_header(&self, recording: &Recording) -> Vec { let channel_nb = recording .frames .iter() .find_map(|frame| parse_data_frame(&frame.frame.payload).ok().map(|vals| vals.len())) .unwrap_or(0); let mut header: Vec = Vec::new(); for i in 0..channel_nb { header.push(format!("channel{}", i + 1)); } header.push("dts".to_string()); header } fn csv_row(&self, item: &RecordedFrame) -> anyhow::Result> { let packet: TestDataPacket = TestDataPacket::try_from(&item.frame)?; let mut row: Vec = packet.data.iter().map(|&x| x.to_string()).collect(); row.push(packet.dts_ms.to_string()); Ok(row) } } impl TestCsvImporter { pub fn new(_path: &str) -> TestCsvImporter { Self { channels: 0, data_row: 0, packets: Vec::new(), } } fn parse_record(&mut self, record: StringRecord) -> anyhow::Result{ if self.channels == 0 { return Err(anyhow!("csv header is missing channel columns")); } if record.len() < self.channels + 1 { return Err(anyhow!("csv row has insufficient columns")); } let mut data = Vec::with_capacity(self.channels); for index in 0..self.channels { let cell = record.get(index).ok_or_else(|| anyhow!("missing channel cell"))?; data.push(cell.parse::()?); } let dts_cell = record .get(self.channels) .ok_or_else(|| anyhow!("missing dts cell"))?; let dts_ms = dts_cell.parse::()?; Ok(TestDataPacket { data: data, dts_ms: dts_ms, }) } } impl CsvImporter for TestCsvImporter { fn load(&mut self, reader: R) -> anyhow::Result> { let mut rdr = csv::Reader::from_reader(reader); let headers = rdr.headers()?.clone(); self.channels = headers.len().saturating_sub(1); self.data_row = 0; self.packets.clear(); for record in rdr.records() { let record = record?; let packet = self.parse_record(record)?; self.packets.push(packet); self.data_row += 1; } Ok(self.packets.clone()) } } pub fn export_recording_csv(recording: &Recording, writer: W) -> anyhow::Result<()> where W: std::io::Write, { write_csv(recording, &TestCsvExporter, writer) } #[cfg(test)] mod tests { use super::*; use csv::Reader; use std::io::Cursor; #[test] fn test_read_csv_basic() -> anyhow::Result<()> { let mut rdr = Reader::from_path("recording_20260329_125238.csv")?; let headers = rdr.headers()?; println!("headers: {:?}", headers); for result in rdr.records() { let record = result?; println!("record: {:?}", record); } Ok(()) } }