use alloc::string::String;
use alloc::vec::Vec;
use super::frame::{LinFlags, LinFrame};
use crate::bus_logging::{
BusLoggerConfig, TimestampedFrame, init_bus_channel_group, write_timestamped_frames,
};
const LIN_FRAME_SIZE: usize = 12;
pub struct RawLinLogger<W: crate::writer::MdfWrite> {
writer: crate::MdfWriter<W>,
source_name: String,
buffer: Vec<TimestampedFrame<LinFrame>>,
channel_group: Option<String>,
initialized: bool,
}
impl RawLinLogger<crate::writer::VecWriter> {
pub fn new() -> crate::Result<Self> {
Self::with_source_name("LIN")
}
pub fn with_source_name(source_name: &str) -> crate::Result<Self> {
let writer = crate::MdfWriter::from_writer(crate::writer::VecWriter::new());
Ok(Self {
writer,
source_name: String::from(source_name),
buffer: Vec::new(),
channel_group: None,
initialized: false,
})
}
pub fn with_bus_name(bus_name: &str) -> crate::Result<Self> {
Self::with_source_name(bus_name)
}
pub fn with_capacity(capacity: usize) -> crate::Result<Self> {
let writer =
crate::MdfWriter::from_writer(crate::writer::VecWriter::with_capacity(capacity));
Ok(Self {
writer,
source_name: String::from("LIN"),
buffer: Vec::with_capacity(capacity / LIN_FRAME_SIZE),
channel_group: None,
initialized: false,
})
}
pub fn finalize(mut self) -> crate::Result<Vec<u8>> {
self.flush_and_finalize()?;
Ok(self.writer.into_inner().into_inner())
}
}
#[cfg(feature = "std")]
impl RawLinLogger<crate::writer::FileWriter> {
pub fn new_file(path: &str) -> crate::Result<Self> {
Self::new_file_with_source_name(path, "LIN")
}
pub fn new_file_with_source_name(path: &str, source_name: &str) -> crate::Result<Self> {
let writer = crate::MdfWriter::new(path)?;
Ok(Self {
writer,
source_name: String::from(source_name),
buffer: Vec::new(),
channel_group: None,
initialized: false,
})
}
pub fn new_file_with_bus_name(path: &str, bus_name: &str) -> crate::Result<Self> {
Self::new_file_with_source_name(path, bus_name)
}
pub fn finalize_file(mut self) -> crate::Result<()> {
self.flush_and_finalize()
}
}
impl<W: crate::writer::MdfWrite> RawLinLogger<W> {
pub fn set_source_name(&mut self, name: &str) {
self.source_name = String::from(name);
}
pub fn set_bus_name(&mut self, name: &str) {
self.set_source_name(name);
}
pub fn log(&mut self, id: u8, timestamp_us: u64, data: &[u8]) -> bool {
self.log_frame(timestamp_us, LinFrame::with_enhanced_checksum(id, data))
}
pub fn log_classic(&mut self, id: u8, timestamp_us: u64, data: &[u8]) -> bool {
self.log_frame(timestamp_us, LinFrame::with_classic_checksum(id, data))
}
pub fn log_tx(&mut self, id: u8, timestamp_us: u64, data: &[u8]) -> bool {
self.log_frame(
timestamp_us,
LinFrame::with_enhanced_checksum(id, data).with_tx(),
)
}
pub fn log_rx(&mut self, id: u8, timestamp_us: u64, data: &[u8]) -> bool {
self.log_frame(
timestamp_us,
LinFrame::with_enhanced_checksum(id, data).with_rx(),
)
}
pub fn log_frame(&mut self, timestamp_us: u64, frame: LinFrame) -> bool {
self.buffer.push(TimestampedFrame::new(timestamp_us, frame));
true
}
pub fn log_frame_ref(&mut self, timestamp_us: u64, frame: &LinFrame) -> bool {
self.log_frame(timestamp_us, frame.clone())
}
pub fn log_with_flags(
&mut self,
id: u8,
timestamp_us: u64,
data: &[u8],
flags: LinFlags,
checksum: u8,
) -> bool {
let mut frame = LinFrame::new(id, data);
frame.flags = flags;
frame.checksum = checksum;
self.log_frame(timestamp_us, frame)
}
pub fn flush(&mut self) -> crate::Result<()> {
if !self.initialized {
self.initialize_mdf()?;
}
if self.buffer.is_empty() {
return Ok(());
}
self.write_frames()?;
self.buffer.clear();
Ok(())
}
fn initialize_mdf(&mut self) -> crate::Result<()> {
self.writer.init_mdf_file()?;
let config = BusLoggerConfig {
source_name: self.source_name.clone(),
group_name: alloc::format!("{}_LIN_Frame", self.source_name),
data_channel_name: String::from("LIN_Frame"),
data_channel_bits: (LIN_FRAME_SIZE * 8) as u32,
source_block: crate::blocks::SourceBlock::lin_bus(),
};
let (cg, _data_ch) = init_bus_channel_group(&mut self.writer, &config)?;
self.channel_group = Some(cg);
self.initialized = true;
Ok(())
}
fn write_frames(&mut self) -> crate::Result<()> {
let cg = match &self.channel_group {
Some(cg) => cg.clone(),
None => return Ok(()),
};
write_timestamped_frames(&mut self.writer, &cg, self.buffer.drain(..))?;
Ok(())
}
fn flush_and_finalize(&mut self) -> crate::Result<()> {
self.flush()?;
self.writer.finalize()
}
pub fn total_frame_count(&self) -> usize {
self.buffer.len()
}
pub fn unique_id_count(&self) -> usize {
let mut ids = alloc::collections::BTreeSet::new();
for entry in &self.buffer {
ids.insert(entry.frame.id);
}
ids.len()
}
pub fn frame_count_for_id(&self, id: u8) -> usize {
self.buffer
.iter()
.filter(|e| e.frame.id == (id & super::frame::MAX_LIN_ID))
.count()
}
pub fn tx_frame_count(&self) -> usize {
self.buffer.iter().filter(|e| e.frame.flags.is_tx()).count()
}
pub fn rx_frame_count(&self) -> usize {
self.buffer.iter().filter(|e| e.frame.flags.is_rx()).count()
}
pub fn error_frame_count(&self) -> usize {
self.buffer
.iter()
.filter(|e| e.frame.flags.has_error())
.count()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_raw_lin_logger_basic() {
let mut logger = RawLinLogger::new().unwrap();
assert!(logger.log(0x20, 1000, &[0x01, 0x02, 0x03, 0x04]));
assert!(logger.log(0x21, 2000, &[0x05, 0x06]));
assert!(logger.log(0x20, 3000, &[0x07, 0x08, 0x09, 0x0A]));
assert_eq!(logger.total_frame_count(), 3);
assert_eq!(logger.frame_count_for_id(0x20), 2);
assert_eq!(logger.frame_count_for_id(0x21), 1);
assert_eq!(logger.unique_id_count(), 2);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
assert_eq!(&mdf_bytes[0..3], b"MDF");
}
#[test]
fn test_raw_lin_logger_tx_rx() {
let mut logger = RawLinLogger::new().unwrap();
assert!(logger.log_tx(0x20, 1000, &[0x01, 0x02]));
assert!(logger.log_rx(0x20, 2000, &[0x03, 0x04]));
assert!(logger.log_tx(0x21, 3000, &[0x05, 0x06]));
assert_eq!(logger.tx_frame_count(), 2);
assert_eq!(logger.rx_frame_count(), 1);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
#[test]
fn test_raw_lin_logger_classic_checksum() {
let mut logger = RawLinLogger::new().unwrap();
assert!(logger.log_classic(0x20, 1000, &[0x01, 0x02, 0x03, 0x04]));
assert_eq!(logger.total_frame_count(), 1);
assert!(!logger.buffer[0].frame.flags.uses_enhanced_checksum());
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
#[test]
fn test_raw_lin_logger_empty() {
let logger = RawLinLogger::new().unwrap();
assert_eq!(logger.total_frame_count(), 0);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
#[test]
fn test_raw_lin_logger_frame_struct() {
let mut logger = RawLinLogger::new().unwrap();
let frame = LinFrame::with_enhanced_checksum(0x20, &[0x01, 0x02, 0x03, 0x04]).with_tx();
assert!(logger.log_frame(1000, frame));
assert_eq!(logger.total_frame_count(), 1);
assert_eq!(logger.tx_frame_count(), 1);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
#[test]
fn test_source_name() {
let logger = RawLinLogger::with_source_name("Body_LIN").unwrap();
assert_eq!(logger.source_name, "Body_LIN");
}
#[test]
fn test_bus_name_alias() {
let logger = RawLinLogger::with_bus_name("Body_LIN").unwrap();
assert_eq!(logger.source_name, "Body_LIN");
}
}