use std::collections::HashMap;
use crate::candb::CanDatabase;
use crate::error::{Mf4Error, Result};
use crate::model::{Channel, ChannelGroup, SignalValues};
use crate::Mf4File;
const CAN_PREFIX: &str = "CAN_DataFrame";
const ID_MASK: u32 = 0x1FFF_FFFF;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CanFrame<'a> {
pub timestamp: f64,
pub id: u32,
pub extended: Option<bool>,
pub bus_channel: u8,
pub data: &'a [u8],
}
#[derive(Debug, Clone)]
pub struct CanFrames {
timestamps: Vec<f64>,
ids: Vec<u32>,
extended: Option<Vec<bool>>,
bus_channels: Vec<u8>,
payloads: SignalValues,
lengths: Option<Vec<u8>>,
}
impl CanFrames {
pub fn len(&self) -> usize {
self.ids.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, index: usize) -> Option<CanFrame<'_>> {
let id = *self.ids.get(index)?;
let data = self.payloads.bytes_at(index)?;
let data = match &self.lengths {
Some(lengths) => {
let len = usize::from(*lengths.get(index)?);
data.get(..len.min(data.len()))?
}
None => data,
};
Some(CanFrame {
timestamp: *self.timestamps.get(index)?,
id,
extended: match &self.extended {
Some(flags) => flags.get(index).copied(),
None => None,
},
bus_channel: self.bus_channels.get(index).copied().unwrap_or(0),
data,
})
}
pub fn iter(&self) -> impl Iterator<Item = CanFrame<'_>> + '_ {
(0..self.len()).filter_map(|index| self.get(index))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BusSignal<'a> {
pub message: &'a str,
pub name: &'a str,
pub unit: &'a str,
pub bus_channel: u8,
pub timestamps: Vec<f64>,
pub values: Vec<f64>,
texts: Option<Vec<Option<&'a str>>>,
}
impl<'a> BusSignal<'a> {
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn text_at(&self, index: usize) -> Option<&'a str> {
self.texts.as_ref()?.get(index).copied().flatten()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct BusSignals<'a> {
signals: Vec<BusSignal<'a>>,
}
impl<'a> BusSignals<'a> {
pub fn len(&self) -> usize {
self.signals.len()
}
pub fn is_empty(&self) -> bool {
self.signals.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &BusSignal<'a>> + '_ {
self.signals.iter()
}
pub fn find(&self, name: &str) -> Vec<&BusSignal<'a>> {
self.signals
.iter()
.filter(|signal| signal.name == name)
.collect()
}
}
pub(crate) struct Accumulator<'a> {
database: &'a CanDatabase,
signals: Vec<BusSignal<'a>>,
series: HashMap<(u8, usize, usize), usize>,
}
impl<'a> Accumulator<'a> {
pub(crate) fn new(database: &'a CanDatabase) -> Self {
Accumulator {
database,
signals: Vec::new(),
series: HashMap::new(),
}
}
pub(crate) fn push(&mut self, frame: CanFrame<'_>) {
let database = self.database;
let signals = &mut self.signals;
let series = &mut self.series;
database.decode_each(frame.id, frame.data, &mut |message, signal, decoded| {
let slot = *series
.entry((frame.bus_channel, message, signal))
.or_insert_with(|| {
let definition = &database.messages()[message].signals[signal];
signals.push(BusSignal {
message: &database.messages()[message].name,
name: decoded.name,
unit: decoded.unit,
bus_channel: frame.bus_channel,
timestamps: Vec::new(),
values: Vec::new(),
texts: (!definition.value_table.is_empty()).then(Vec::new),
});
signals.len() - 1
});
let series = &mut signals[slot];
series.timestamps.push(frame.timestamp);
series.values.push(decoded.value);
if let Some(texts) = &mut series.texts {
texts.push(decoded.text);
}
});
}
pub(crate) fn finish(self) -> BusSignals<'a> {
BusSignals {
signals: self.signals,
}
}
}
pub(crate) fn decode_bus_signals<'a>(
file: &Mf4File,
database: &'a CanDatabase,
) -> Result<BusSignals<'a>> {
let mut accumulator = Accumulator::new(database);
for group in file.can_frame_groups() {
for frame in file.can_frames(group)?.iter() {
accumulator.push(frame);
}
}
Ok(accumulator.finish())
}
pub(crate) fn is_can_frame_group(group: &ChannelGroup) -> bool {
field(group, "ID").is_some() && field(group, "DataBytes").is_some()
}
fn field<'a>(group: &'a ChannelGroup, suffix: &str) -> Option<&'a Channel> {
let qualified = format!("{CAN_PREFIX}.{suffix}");
group.find_channel(&qualified)
}
fn require<'a>(group: &'a ChannelGroup, suffix: &str) -> Result<&'a Channel> {
field(group, suffix).ok_or_else(|| Mf4Error::ChannelNotFound {
name: format!("{CAN_PREFIX}.{suffix}"),
})
}
fn scalars(file: &Mf4File, channel: &Channel) -> Result<Vec<f64>> {
Ok(file.signal(channel)?.values()?.to_f64())
}
pub(crate) fn read_can_frames(file: &Mf4File, group: &ChannelGroup) -> Result<CanFrames> {
let id_channel = require(group, "ID")?;
let payload_channel = require(group, "DataBytes")?;
let master = group
.master_channel()
.ok_or_else(|| Mf4Error::ChannelNotFound {
name: format!("master channel of bus group '{}'", group.acquisition_name),
})?;
let ids: Vec<u32> = scalars(file, id_channel)?
.into_iter()
.map(|id| id as u32 & ID_MASK)
.collect();
let timestamps = scalars(file, master)?;
let payloads = file.signal(payload_channel)?.values()?;
if timestamps.len() != ids.len() || payloads.len() != ids.len() {
return Err(Mf4Error::parse_error(format!(
"bus group '{}' has {} identifiers, {} timestamps and {} payloads; \
frame fields must agree sample for sample",
group.acquisition_name,
ids.len(),
timestamps.len(),
payloads.len()
)));
}
let extended = match field(group, "IDE") {
Some(channel) => Some(
scalars(file, channel)?
.into_iter()
.map(|flag| flag != 0.0)
.collect(),
),
None => None,
};
let bus_channels = match field(group, "BusChannel") {
Some(channel) => scalars(file, channel)?
.into_iter()
.map(|bus| bus as u8)
.collect(),
None => Vec::new(),
};
let lengths = match field(group, "DataLength") {
Some(channel) => Some(
scalars(file, channel)?
.into_iter()
.map(|len| len as u8)
.collect(),
),
None => None,
};
Ok(CanFrames {
timestamps,
ids,
extended,
bus_channels,
payloads,
lengths,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::candb::{MessageDef, Multiplexing, SignalDef};
fn signal(name: &str, start_bit: u64, value_table: Vec<(i64, String)>) -> SignalDef {
SignalDef {
name: name.into(),
start_bit,
size: 8,
big_endian: false,
signed: false,
factor: 1.0,
offset: 0.0,
unit: "u".into(),
multiplexing: Multiplexing::None,
value_table,
}
}
fn database() -> CanDatabase {
CanDatabase::new(vec![
MessageDef {
name: "First".into(),
id: 0x100,
extended: false,
length: 2,
signals: vec![
signal("Shared", 0, Vec::new()),
signal("Labelled", 8, vec![(1, "On".into()), (0, "Off".into())]),
],
},
MessageDef {
name: "Second".into(),
id: 0x200,
extended: false,
length: 1,
signals: vec![signal("Shared", 0, Vec::new())],
},
])
}
fn frame(timestamp: f64, id: u32, bus_channel: u8, data: &[u8]) -> CanFrame<'_> {
CanFrame {
timestamp,
id,
extended: Some(false),
bus_channel,
data,
}
}
fn collect<'a>(database: &'a CanDatabase, frames: Vec<CanFrame<'_>>) -> BusSignals<'a> {
let mut accumulator = Accumulator::new(database);
for frame in frames {
accumulator.push(frame);
}
accumulator.finish()
}
#[test]
fn one_identifier_on_two_buses_is_two_series() {
let db = database();
let signals = collect(
&db,
vec![
frame(0.0, 0x100, 1, &[10, 0]),
frame(0.1, 0x100, 9, &[20, 0]),
frame(0.2, 0x100, 1, &[11, 0]),
frame(0.3, 0x100, 9, &[21, 0]),
],
);
let shared = signals.find("Shared");
assert_eq!(shared.len(), 2, "one series per bus");
let mut by_bus: Vec<_> = shared.iter().map(|s| (s.bus_channel, &s.values)).collect();
by_bus.sort_by_key(|(bus, _)| *bus);
assert_eq!(by_bus[0], (1, &vec![10.0, 11.0]));
assert_eq!(by_bus[1], (9, &vec![20.0, 21.0]));
}
#[test]
fn one_name_in_two_messages_is_two_series() {
let db = database();
let signals = collect(
&db,
vec![frame(0.0, 0x100, 1, &[10, 0]), frame(0.1, 0x200, 1, &[99])],
);
let mut shared: Vec<_> = signals
.find("Shared")
.iter()
.map(|s| (s.message, s.values.clone()))
.collect();
shared.sort_by_key(|(message, _)| *message);
assert_eq!(
shared,
[("First", vec![10.0]), ("Second", vec![99.0])],
"the two Shared signals must not be merged"
);
}
#[test]
fn readings_keep_their_timestamps_in_order() {
let db = database();
let signals = collect(
&db,
vec![
frame(1.5, 0x100, 1, &[7, 0]),
frame(2.5, 0x200, 1, &[0]),
frame(3.5, 0x100, 1, &[8, 0]),
],
);
let first = signals
.iter()
.find(|s| s.message == "First" && s.name == "Shared")
.unwrap();
assert_eq!(
first.timestamps,
[1.5, 3.5],
"the 0x200 frame is not First's"
);
assert_eq!(first.values, [7.0, 8.0]);
assert_eq!(first.len(), 2);
}
#[test]
fn labels_stay_parallel_to_the_values_they_label() {
let db = database();
let signals = collect(
&db,
vec![
frame(0.0, 0x100, 1, &[0, 1]),
frame(0.1, 0x100, 1, &[0, 7]),
frame(0.2, 0x100, 1, &[0, 0]),
],
);
let labelled = signals
.iter()
.find(|s| s.name == "Labelled")
.expect("the labelled signal");
assert_eq!(labelled.values, [1.0, 7.0, 0.0]);
assert_eq!(labelled.text_at(0), Some("On"));
assert_eq!(labelled.text_at(1), None, "7 is not in the table");
assert_eq!(labelled.text_at(2), Some("Off"));
assert_eq!(labelled.text_at(3), None, "there is no fourth reading");
let plain = signals
.iter()
.find(|s| s.message == "First" && s.name == "Shared")
.unwrap();
assert_eq!(plain.text_at(0), None);
}
#[test]
fn unknown_identifiers_create_no_series() {
let db = database();
let signals = collect(
&db,
vec![frame(0.0, 0x555, 1, &[1, 2]), frame(0.1, 0x100, 1, &[3, 4])],
);
assert_eq!(signals.len(), 2);
assert!(signals.iter().all(|s| s.message == "First"));
assert!(signals.iter().all(|s| !s.is_empty()));
assert!(signals.find("Missing").is_empty());
assert!(collect(&db, Vec::new()).is_empty());
}
}