mod builder;
pub use builder::{CanDbcLoggerBuilder, CanDbcLoggerConfig};
use alloc::collections::BTreeMap;
use alloc::collections::BTreeSet;
use alloc::string::String;
use alloc::vec::Vec;
use super::dbc_compat::SignalInfo;
type BufferKey = (u32, Option<u64>);
#[derive(Debug)]
struct MessageBuffer {
signals: Vec<SignalInfo>,
signal_indices: Vec<usize>,
timestamps: Vec<u64>,
raw_values: Vec<Vec<i64>>,
physical_values: Vec<Vec<f64>>,
}
impl MessageBuffer {
fn new(signals: Vec<SignalInfo>, signal_indices: Vec<usize>) -> Self {
let num_signals = signals.len();
Self {
signals,
signal_indices,
timestamps: Vec::new(),
raw_values: (0..num_signals).map(|_| Vec::new()).collect(),
physical_values: (0..num_signals).map(|_| Vec::new()).collect(),
}
}
fn push_physical_indexed(&mut self, timestamp_us: u64, decode_buf: &[f64]) {
self.timestamps.push(timestamp_us);
for (out, &idx) in self
.physical_values
.iter_mut()
.zip(self.signal_indices.iter())
{
out.push(decode_buf.get(idx).copied().unwrap_or(0.0));
}
}
fn push_raw_indexed(&mut self, timestamp_us: u64, decode_buf: &[i64]) {
self.timestamps.push(timestamp_us);
for (out, &idx) in self.raw_values.iter_mut().zip(self.signal_indices.iter()) {
out.push(decode_buf.get(idx).copied().unwrap_or(0));
}
}
fn clear(&mut self) {
self.timestamps.clear();
for v in &mut self.raw_values {
v.clear();
}
for v in &mut self.physical_values {
v.clear();
}
}
fn frame_count(&self) -> usize {
self.timestamps.len()
}
}
#[allow(dead_code)]
struct ChannelIds {
time_channel: String,
signal_channels: Vec<String>,
}
#[derive(Debug)]
struct MultiplexInfo {
switch_index: usize,
mux_values: BTreeSet<u64>,
}
pub struct CanDbcLogger<W: crate::writer::MdfWrite> {
fast_dbc: dbc_rs::FastDbc,
config: CanDbcLoggerConfig,
buffers: BTreeMap<BufferKey, MessageBuffer>,
writer: crate::MdfWriter<W>,
channel_groups: BTreeMap<BufferKey, String>,
channel_ids: BTreeMap<BufferKey, ChannelIds>,
mux_info: BTreeMap<u32, MultiplexInfo>,
decode_buf: Vec<f64>,
decode_raw_buf: Vec<i64>,
initialized: bool,
}
impl CanDbcLogger<crate::writer::VecWriter> {
pub fn new(dbc: dbc_rs::Dbc) -> crate::Result<Self> {
let writer = crate::MdfWriter::from_writer(crate::writer::VecWriter::new());
Ok(Self::with_config(
dbc,
writer,
CanDbcLoggerConfig::default(),
))
}
pub fn with_capacity(dbc: dbc_rs::Dbc, capacity: usize) -> crate::Result<Self> {
let writer =
crate::MdfWriter::from_writer(crate::writer::VecWriter::with_capacity(capacity));
Ok(Self::with_config(
dbc,
writer,
CanDbcLoggerConfig::default(),
))
}
pub fn builder(dbc: dbc_rs::Dbc) -> CanDbcLoggerBuilder {
CanDbcLoggerBuilder::new(dbc)
}
pub fn finalize(mut self) -> crate::Result<Vec<u8>> {
self.flush_and_finalize()?;
Ok(self.writer.into_inner().into_inner())
}
#[cfg(feature = "std")]
pub fn from_raw_mdf4(path: &str, dbc: dbc_rs::Dbc) -> crate::Result<Self> {
Self::from_raw_mdf4_with_config(path, dbc, CanDbcLoggerConfig::default())
}
#[cfg(feature = "std")]
pub fn from_raw_mdf4_with_config(
path: &str,
dbc: dbc_rs::Dbc,
config: CanDbcLoggerConfig,
) -> crate::Result<Self> {
use crate::DecodedValue;
use crate::index::{FileRangeReader, MdfIndex};
let index = MdfIndex::from_file(path)?;
let mut reader = FileRangeReader::new(path)?;
let writer = crate::MdfWriter::from_writer(crate::writer::VecWriter::new());
let mut logger = Self::with_config(dbc, writer, config);
for (group_idx, group) in index.channel_groups.iter().enumerate() {
let mut timestamp_ch = None;
let mut dataframe_ch = None;
for (ch_idx, channel) in group.channels.iter().enumerate() {
if let Some(name) = &channel.name {
match name.as_str() {
"Timestamp" => timestamp_ch = Some(ch_idx),
"CAN_DataFrame" => dataframe_ch = Some(ch_idx),
_ => {}
}
}
}
let (ts_ch, df_ch) = match (timestamp_ch, dataframe_ch) {
(Some(t), Some(d)) => (t, d),
_ => continue,
};
let timestamps = index.read_channel_values(group_idx, ts_ch, &mut reader)?;
let dataframes = index.read_channel_values(group_idx, df_ch, &mut reader)?;
for (ts_val, df_val) in timestamps.iter().zip(dataframes.iter()) {
let timestamp_us = match ts_val {
Some(DecodedValue::Float(s)) => (*s * 1_000_000.0) as u64,
Some(DecodedValue::UnsignedInteger(us)) => *us,
_ => continue,
};
let bytes = match df_val {
Some(DecodedValue::ByteArray(b)) => b,
_ => continue,
};
if bytes.len() < 5 {
continue;
}
let raw_id = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let is_extended = (raw_id & 0x8000_0000) != 0;
let can_id = raw_id & 0x1FFF_FFFF;
let dlc = bytes[4];
let data_len = super::fd::dlc_to_len(dlc).min(bytes.len() - 5);
let data = &bytes[5..5 + data_len];
if is_extended {
logger.log_extended(can_id, timestamp_us, data);
} else {
logger.log(can_id, timestamp_us, data);
}
}
}
Ok(logger)
}
pub fn last_timestamp_us(&self) -> u64 {
self.buffers
.values()
.flat_map(|buf| buf.timestamps.iter())
.copied()
.max()
.unwrap_or(0)
}
pub fn total_frame_count(&self) -> usize {
self.buffers.values().map(|buf| buf.frame_count()).sum()
}
}
#[cfg(feature = "std")]
impl CanDbcLogger<crate::writer::FileWriter> {
pub fn new_file(dbc: dbc_rs::Dbc, path: &str) -> crate::Result<Self> {
let writer = crate::MdfWriter::new(path)?;
Ok(Self::with_config(
dbc,
writer,
CanDbcLoggerConfig::default(),
))
}
pub fn builder_file(dbc: dbc_rs::Dbc) -> CanDbcLoggerBuilder {
CanDbcLoggerBuilder::new(dbc)
}
pub fn finalize_file(mut self) -> crate::Result<()> {
self.flush_and_finalize()
}
}
impl<W: crate::writer::MdfWrite> CanDbcLogger<W> {
pub(crate) fn with_config(
dbc: dbc_rs::Dbc,
writer: crate::MdfWriter<W>,
config: CanDbcLoggerConfig,
) -> Self {
let fast_dbc = dbc_rs::FastDbc::new(dbc);
let mut buffers = BTreeMap::new();
let mut mux_info = BTreeMap::new();
for message in fast_dbc.dbc().messages().iter() {
let can_id = message.id();
let signals = message.signals();
let mut signal_name_to_idx: BTreeMap<&str, usize> = BTreeMap::new();
for (idx, signal) in signals.iter().enumerate() {
signal_name_to_idx.insert(signal.name(), idx);
}
let mut switch_name: Option<&str> = None;
let mut switch_index: Option<usize> = None;
let mut mux_values: BTreeSet<u64> = BTreeSet::new();
for (idx, signal) in signals.iter().enumerate() {
if signal.is_multiplexer_switch() {
switch_name = Some(signal.name());
switch_index = Some(idx);
}
if let Some(mux_val) = signal.multiplexer_switch_value() {
mux_values.insert(mux_val);
}
}
if let (Some(_switch), Some(sw_idx)) = (switch_name, switch_index) {
if !mux_values.is_empty() {
mux_info.insert(
can_id,
MultiplexInfo {
switch_index: sw_idx,
mux_values: mux_values.clone(),
},
);
for mux_val in &mux_values {
let mut mux_signals: Vec<SignalInfo> = Vec::new();
let mut signal_indices: Vec<usize> = Vec::new();
for (idx, signal) in signals.iter().enumerate() {
let include = signal.is_multiplexer_switch()
|| signal.multiplexer_switch_value() == Some(*mux_val)
|| (signal.multiplexer_switch_value().is_none()
&& !signal.is_multiplexer_switch());
if include {
mux_signals.push(SignalInfo::from_signal(signal));
signal_indices.push(idx);
}
}
if !mux_signals.is_empty() {
buffers.insert(
(can_id, Some(*mux_val)),
MessageBuffer::new(mux_signals, signal_indices),
);
}
}
continue;
}
}
let all_signals: Vec<SignalInfo> =
signals.iter().map(SignalInfo::from_signal).collect();
let signal_indices: Vec<usize> = (0..signals.len()).collect();
if !all_signals.is_empty() {
buffers.insert(
(can_id, None),
MessageBuffer::new(all_signals, signal_indices),
);
}
}
let max_signals = fast_dbc.max_signals();
let decode_buf = vec![0.0f64; max_signals];
let decode_raw_buf = vec![0i64; max_signals];
Self {
fast_dbc,
config,
buffers,
writer,
channel_groups: BTreeMap::new(),
channel_ids: BTreeMap::new(),
mux_info,
decode_buf,
decode_raw_buf,
initialized: false,
}
}
pub fn config(&self) -> &CanDbcLoggerConfig {
&self.config
}
pub fn fast_dbc(&self) -> &dbc_rs::FastDbc {
&self.fast_dbc
}
#[inline]
pub fn log(&mut self, can_id: u32, timestamp_us: u64, data: &[u8]) -> bool {
self.log_internal(can_id, timestamp_us, data, false)
}
#[inline]
pub fn log_extended(&mut self, can_id: u32, timestamp_us: u64, data: &[u8]) -> bool {
self.log_internal(can_id, timestamp_us, data, true)
}
#[inline]
pub fn log_fd(&mut self, can_id: u32, timestamp_us: u64, data: &[u8]) -> bool {
self.log_internal(can_id, timestamp_us, data, false)
}
#[inline]
pub fn log_fd_extended(&mut self, can_id: u32, timestamp_us: u64, data: &[u8]) -> bool {
self.log_internal(can_id, timestamp_us, data, true)
}
#[inline]
fn log_internal(
&mut self,
can_id: u32,
timestamp_us: u64,
data: &[u8],
is_extended: bool,
) -> bool {
let msg = if is_extended {
self.fast_dbc.get_extended(can_id)
} else {
self.fast_dbc.get(can_id)
};
let msg = match msg {
Some(m) => m,
None => return false,
};
let dbc_id = if is_extended {
can_id | 0x8000_0000
} else {
can_id
};
let decoded_count = if self.config.store_raw_values {
msg.decode_raw_into(data, &mut self.decode_raw_buf)
} else {
msg.decode_into(data, &mut self.decode_buf)
};
if decoded_count == 0 {
return false;
}
let buffer_key = if let Some(mux) = self.mux_info.get(&dbc_id) {
let mux_value = if self.config.store_raw_values {
self.decode_raw_buf[mux.switch_index] as u64
} else {
self.decode_buf[mux.switch_index] as u64
};
if mux.mux_values.contains(&mux_value) {
(dbc_id, Some(mux_value))
} else {
return false; }
} else {
(dbc_id, None)
};
let store_raw = self.config.store_raw_values;
if let Some(buffer) = self.buffers.get_mut(&buffer_key) {
if store_raw {
buffer.push_raw_indexed(timestamp_us, &self.decode_raw_buf);
} else {
buffer.push_physical_indexed(timestamp_us, &self.decode_buf);
}
return true;
}
false
}
#[cfg(feature = "can")]
#[inline]
pub fn log_frame<F: embedded_can::Frame>(&mut self, timestamp_us: u64, frame: &F) -> bool {
match frame.id() {
embedded_can::Id::Standard(id) => {
self.log(id.as_raw() as u32, timestamp_us, frame.data())
}
embedded_can::Id::Extended(id) => {
self.log_extended(id.as_raw(), timestamp_us, frame.data())
}
}
}
#[cfg(feature = "can")]
#[inline]
pub fn log_fd_frame<F: super::fd::FdFrame>(&mut self, timestamp_us: u64, frame: &F) -> bool {
match frame.id() {
embedded_can::Id::Standard(id) => {
self.log_fd(id.as_raw() as u32, timestamp_us, frame.data())
}
embedded_can::Id::Extended(id) => {
self.log_fd_extended(id.as_raw(), timestamp_us, frame.data())
}
}
}
pub fn flush(&mut self) -> crate::Result<()> {
if !self.initialized {
self.initialize_mdf()?;
}
for buffer_key in self.buffers.keys().copied().collect::<Vec<_>>() {
self.write_message_data(buffer_key)?;
}
for buffer in self.buffers.values_mut() {
buffer.clear();
}
Ok(())
}
fn initialize_mdf(&mut self) -> crate::Result<()> {
use crate::DataType;
self.writer.init_mdf_file()?;
for (&buffer_key, buffer) in &self.buffers {
let (can_id, mux_value) = buffer_key;
let cg = self.writer.add_channel_group(None, |_| {})?;
if let Some(message) = self.fast_dbc.get(can_id) {
let msg_name = message.name();
if !msg_name.is_empty() {
let group_name = match mux_value {
Some(val) => alloc::format!("{}_Mux{}", msg_name, val),
None => String::from(msg_name),
};
self.writer.set_channel_group_name(&cg, &group_name)?;
}
let sender = message.sender();
if !sender.is_empty() && sender != "Vector__XXX" {
self.writer.set_channel_group_source_name(&cg, sender)?;
}
}
let time_name = match mux_value {
Some(val) => alloc::format!("Time_0x{:X}_Mux{}", can_id, val),
None => alloc::format!("Time_0x{:X}", can_id),
};
let time_ch = self.writer.add_channel(&cg, None, |ch| {
ch.data_type = DataType::UnsignedIntegerLE;
ch.name = Some(time_name.clone());
ch.bit_count = 64;
})?;
self.writer.set_time_channel(&time_ch)?;
self.writer.set_channel_unit(&time_ch, "us")?;
let mut prev_ch = time_ch.clone();
let mut signal_channels = Vec::new();
for info in &buffer.signals {
let ch = if self.config.store_raw_values {
self.writer.add_channel(&cg, Some(&prev_ch), |ch| {
ch.data_type = info.data_type;
ch.name = Some(info.name.clone());
ch.bit_count = info.bit_count;
})?
} else {
self.writer.add_channel(&cg, Some(&prev_ch), |ch| {
ch.data_type = DataType::FloatLE;
ch.name = Some(info.name.clone());
ch.bit_count = 64;
})?
};
if self.config.include_units {
if let Some(ref unit) = info.unit {
self.writer.set_channel_unit(&ch, unit)?;
}
}
if self.config.include_limits && (info.min != 0.0 || info.max != 0.0) {
self.writer.set_channel_limits(&ch, info.min, info.max)?;
}
if self.config.store_raw_values {
let has_value_desc = self.config.include_value_descriptions
&& self
.fast_dbc
.dbc()
.value_descriptions_for_signal(can_id, &info.name)
.is_some();
if has_value_desc {
if let Some(vd) = self
.fast_dbc
.dbc()
.value_descriptions_for_signal(can_id, &info.name)
{
let mapping: Vec<(i64, &str)> =
vd.iter().map(|(v, desc)| (v as i64, desc)).collect();
if !mapping.is_empty() {
self.writer.add_value_to_text_conversion(
&mapping,
"", Some(&ch),
)?;
}
}
} else if self.config.include_conversions {
if let Some(ref conv) = info.conversion {
self.writer.set_channel_conversion(&ch, conv)?;
}
}
}
signal_channels.push(ch.clone());
prev_ch = ch;
}
self.channel_groups.insert(buffer_key, cg);
self.channel_ids.insert(
buffer_key,
ChannelIds {
time_channel: time_ch,
signal_channels,
},
);
}
self.initialized = true;
Ok(())
}
fn write_message_data(&mut self, buffer_key: BufferKey) -> crate::Result<()> {
use crate::DecodedValue;
let cg = match self.channel_groups.get(&buffer_key) {
Some(cg) => cg.clone(),
None => return Ok(()),
};
let buffer = match self.buffers.get(&buffer_key) {
Some(b) if !b.timestamps.is_empty() => b,
_ => return Ok(()),
};
self.writer.start_data_block_for_cg(&cg, 0)?;
if self.config.store_raw_values {
for (record_idx, &ts) in buffer.timestamps.iter().enumerate() {
let mut values = alloc::vec![DecodedValue::UnsignedInteger(ts)];
for (sig_idx, info) in buffer.signals.iter().enumerate() {
if record_idx < buffer.raw_values[sig_idx].len() {
let raw = buffer.raw_values[sig_idx][record_idx];
if info.unsigned {
values.push(DecodedValue::UnsignedInteger(raw as u64));
} else {
values.push(DecodedValue::SignedInteger(raw));
}
}
}
self.writer.write_record(&cg, &values)?;
}
} else {
for (record_idx, &ts) in buffer.timestamps.iter().enumerate() {
let mut values = alloc::vec![DecodedValue::UnsignedInteger(ts)];
for signal_values in &buffer.physical_values {
if record_idx < signal_values.len() {
values.push(DecodedValue::Float(signal_values[record_idx]));
}
}
self.writer.write_record(&cg, &values)?;
}
}
self.writer.finish_data_block(&cg)?;
Ok(())
}
fn flush_and_finalize(&mut self) -> crate::Result<()> {
self.flush()?;
self.writer.finalize()
}
pub fn frame_count(&self, can_id: u32) -> usize {
self.buffers
.iter()
.filter(|((id, _), _)| *id == can_id)
.map(|(_, b)| b.frame_count())
.sum()
}
pub fn frame_count_mux(&self, can_id: u32, mux_value: Option<u64>) -> usize {
self.buffers
.get(&(can_id, mux_value))
.map(|b| b.frame_count())
.unwrap_or(0)
}
pub fn can_ids(&self) -> impl Iterator<Item = u32> + '_ {
let mut ids: BTreeSet<u32> = BTreeSet::new();
for (id, _) in self.buffers.keys() {
ids.insert(*id);
}
ids.into_iter()
}
pub fn channel_group_count(&self) -> usize {
self.buffers.len()
}
pub fn total_signal_count(&self) -> usize {
self.buffers.values().map(|b| b.signals.len()).sum()
}
pub fn is_multiplexed(&self, can_id: u32) -> bool {
self.mux_info.contains_key(&can_id)
}
pub fn mux_values(&self, can_id: u32) -> Option<impl Iterator<Item = u64> + '_> {
self.mux_info
.get(&can_id)
.map(|info| info.mux_values.iter().copied())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dbc_mdf_logger() {
let dbc = dbc_rs::Dbc::parse(
r#"VERSION "1.0"
BU_: ECM
BO_ 256 Engine : 8 ECM
SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" Vector__XXX
"#,
)
.unwrap();
let mut logger = CanDbcLogger::new(dbc).unwrap();
let data = [0x40, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert!(logger.log(256, 1000, &data));
assert!(logger.log(256, 2000, &data));
assert_eq!(logger.frame_count(256), 2);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
assert_eq!(&mdf_bytes[0..3], b"MDF");
}
#[test]
fn test_dbc_mdf_logger_raw_mode() {
let dbc = dbc_rs::Dbc::parse(
r#"VERSION "1.0"
BU_: ECM
BO_ 256 Engine : 8 ECM
SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" Vector__XXX
SG_ Temp : 16|8@1- (1,-40) [-40|215] "C" Vector__XXX
"#,
)
.unwrap();
let mut logger = CanDbcLogger::builder(dbc)
.store_raw_values(true)
.build()
.unwrap();
let data = [0x40, 0x1F, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00];
assert!(logger.log(256, 1000, &data));
assert_eq!(logger.frame_count(256), 1);
assert_eq!(logger.total_signal_count(), 2);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
#[test]
fn test_dbc_mdf_logger_multiple_signals() {
let dbc = dbc_rs::Dbc::parse(
r#"VERSION "1.0"
BU_: ECM
BO_ 256 Engine : 8 ECM
SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" Vector__XXX
SG_ Temp : 16|8@1- (1,-40) [-40|215] "C" Vector__XXX
"#,
)
.unwrap();
let mut logger = CanDbcLogger::new(dbc).unwrap();
let data = [0x40, 0x1F, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00];
assert!(logger.log(256, 1000, &data));
assert_eq!(logger.frame_count(256), 1);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
#[test]
fn test_dbc_mdf_logger_unknown_message() {
let dbc = dbc_rs::Dbc::parse(
r#"VERSION "1.0"
BU_: ECM
BO_ 256 Engine : 8 ECM
SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" Vector__XXX
"#,
)
.unwrap();
let mut logger = CanDbcLogger::new(dbc).unwrap();
let data = [0x00; 8];
assert!(!logger.log(999, 1000, &data));
assert_eq!(logger.frame_count(999), 0);
}
#[test]
fn test_builder_configuration() {
let dbc = dbc_rs::Dbc::parse(
r#"VERSION "1.0"
BU_:
BO_ 100 TestMsg: 8 Vector__XXX
SG_ TestSig : 0|16@1+ (1,0) [0|65535] "units" Vector__XXX
"#,
)
.unwrap();
let logger = CanDbcLogger::builder(dbc)
.store_raw_values(true)
.include_units(true)
.include_limits(true)
.include_conversions(true)
.with_capacity(1024)
.build()
.unwrap();
assert!(logger.config().store_raw_values);
assert!(logger.config().include_units);
assert!(logger.config().include_limits);
assert!(logger.config().include_conversions);
}
#[test]
fn test_value_descriptions_to_text() {
let dbc = dbc_rs::Dbc::parse(
r#"VERSION "1.0"
BU_: ECM
BO_ 256 Transmission : 8 ECM
SG_ GearPosition : 0|8@1+ (1,0) [0|5] "" Vector__XXX
VAL_ 256 GearPosition 0 "Park" 1 "Reverse" 2 "Neutral" 3 "Drive" 4 "Sport" ;
"#,
)
.unwrap();
let vd = dbc.value_descriptions_for_signal(256, "GearPosition");
assert!(vd.is_some());
let vd = vd.unwrap();
assert_eq!(vd.get(0), Some("Park"));
assert_eq!(vd.get(3), Some("Drive"));
let mut logger = CanDbcLogger::builder(dbc)
.store_raw_values(true)
.include_value_descriptions(true)
.build()
.unwrap();
assert!(logger.log(256, 1000, &[0, 0, 0, 0, 0, 0, 0, 0])); assert!(logger.log(256, 2000, &[3, 0, 0, 0, 0, 0, 0, 0])); assert!(logger.log(256, 3000, &[2, 0, 0, 0, 0, 0, 0, 0]));
assert_eq!(logger.frame_count(256), 3);
assert!(logger.config().include_value_descriptions);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
assert_eq!(&mdf_bytes[0..3], b"MDF");
}
#[test]
fn test_channel_group_naming_and_source() {
let dbc = dbc_rs::Dbc::parse(
r#"VERSION "1.0"
BU_: ECM TCM
BO_ 256 Engine : 8 ECM
SG_ RPM : 0|16@1+ (1,0) [0|8000] "rpm" Vector__XXX
BO_ 512 Transmission : 8 TCM
SG_ Gear : 0|8@1+ (1,0) [0|5] "" Vector__XXX
"#,
)
.unwrap();
let mut logger = CanDbcLogger::new(dbc).unwrap();
assert!(logger.log(256, 1000, &[0x00, 0x10, 0, 0, 0, 0, 0, 0]));
assert!(logger.log(512, 1000, &[3, 0, 0, 0, 0, 0, 0, 0]));
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
#[test]
fn test_multiplexed_signals() {
let dbc = dbc_rs::Dbc::parse(
r#"VERSION "1.0"
BU_: ECM
BO_ 256 DiagResponse : 8 ECM
SG_ ServiceID M : 0|8@1+ (1,0) [0|255] "" Vector__XXX
SG_ SessionType m16 : 8|8@1+ (1,0) [0|255] "" Vector__XXX
SG_ DataLength m34 : 8|8@1+ (1,0) [0|255] "" Vector__XXX
SG_ DataValue m34 : 16|16@1+ (1,0) [0|65535] "" Vector__XXX
SG_ NormalSignal : 56|8@1+ (1,0) [0|255] "" Vector__XXX
"#,
)
.unwrap();
let mut logger = CanDbcLogger::new(dbc).unwrap();
assert!(logger.is_multiplexed(256));
let mux_vals: Vec<u64> = logger.mux_values(256).unwrap().collect();
assert_eq!(mux_vals.len(), 2); assert!(mux_vals.contains(&16));
assert!(mux_vals.contains(&34));
assert_eq!(logger.channel_group_count(), 2);
assert!(logger.log(256, 1000, &[16, 1, 0, 0, 0, 0, 0, 42]));
assert!(logger.log(256, 2000, &[16, 2, 0, 0, 0, 0, 0, 43]));
assert!(logger.log(256, 3000, &[34, 4, 0x34, 0x12, 0, 0, 0, 44]));
assert!(logger.log(256, 4000, &[34, 8, 0x78, 0x56, 0, 0, 0, 45]));
assert!(!logger.log(256, 5000, &[99, 0, 0, 0, 0, 0, 0, 0]));
assert_eq!(logger.frame_count_mux(256, Some(16)), 2);
assert_eq!(logger.frame_count_mux(256, Some(34)), 2);
assert_eq!(logger.frame_count(256), 4);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
assert_eq!(&mdf_bytes[0..3], b"MDF");
let temp_path = std::env::temp_dir().join("mux_test.mf4");
std::fs::write(&temp_path, &mdf_bytes).unwrap();
let mdf = crate::MDF::from_file(temp_path.to_str().unwrap()).unwrap();
let groups = mdf.channel_groups();
assert_eq!(groups.len(), 2);
for group in groups.iter() {
let name = group.name().unwrap().unwrap_or_default();
assert!(
name.starts_with("DiagResponse_Mux"),
"Expected DiagResponse_Mux*, got {}",
name
);
}
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_non_multiplexed_unchanged() {
let dbc = dbc_rs::Dbc::parse(
r#"VERSION "1.0"
BU_: ECM
BO_ 256 Engine : 8 ECM
SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" Vector__XXX
SG_ Temp : 16|8@1+ (1,-40) [-40|215] "C" Vector__XXX
"#,
)
.unwrap();
let mut logger = CanDbcLogger::new(dbc).unwrap();
assert!(!logger.is_multiplexed(256));
assert!(logger.mux_values(256).is_none());
assert_eq!(logger.channel_group_count(), 1);
assert!(logger.log(256, 1000, &[0x40, 0x1F, 0x5A, 0, 0, 0, 0, 0]));
assert!(logger.log(256, 2000, &[0x80, 0x3E, 0x64, 0, 0, 0, 0, 0]));
assert_eq!(logger.frame_count(256), 2);
assert_eq!(logger.frame_count_mux(256, None), 2);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
let temp_path = std::env::temp_dir().join("non_mux_test.mf4");
std::fs::write(&temp_path, &mdf_bytes).unwrap();
let mdf = crate::MDF::from_file(temp_path.to_str().unwrap()).unwrap();
let groups = mdf.channel_groups();
assert_eq!(groups.len(), 1);
let name = groups[0].name().unwrap().unwrap_or_default();
assert_eq!(name, "Engine");
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_streaming_with_flush_policy() {
use crate::FlushPolicy;
let dbc = dbc_rs::Dbc::parse(
r#"VERSION "1.0"
BU_: ECM
BO_ 256 Engine : 8 ECM
SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" Vector__XXX
"#,
)
.unwrap();
let mut logger = CanDbcLogger::builder(dbc)
.with_flush_policy(FlushPolicy::EveryNRecords(10))
.build()
.unwrap();
for i in 0..25 {
let rpm_raw = (i * 100) as u16;
let data = rpm_raw.to_le_bytes();
let mut frame = [0u8; 8];
frame[0..2].copy_from_slice(&data);
assert!(logger.log(256, i as u64 * 1000, &frame));
}
assert_eq!(logger.frame_count(256), 25);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
assert_eq!(&mdf_bytes[0..3], b"MDF");
}
#[test]
fn test_streaming_with_bytes_policy() {
use crate::FlushPolicy;
let dbc = dbc_rs::Dbc::parse(
r#"VERSION "1.0"
BU_: ECM
BO_ 256 Engine : 8 ECM
SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" Vector__XXX
"#,
)
.unwrap();
let mut logger = CanDbcLogger::builder(dbc)
.with_flush_policy(FlushPolicy::EveryNBytes(1024))
.build()
.unwrap();
for i in 0..100 {
let rpm_raw = (i * 100) as u16;
let data = rpm_raw.to_le_bytes();
let mut frame = [0u8; 8];
frame[0..2].copy_from_slice(&data);
assert!(logger.log(256, i as u64 * 1000, &frame));
}
assert_eq!(logger.frame_count(256), 100);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
}