use can_dbc::{
AccessNode, AccessType, AttributeDefault, AttributeDefinition, AttributeValue,
AttributeValueForDatabase, AttributeValueForEnvVariable, AttributeValueForMessage,
AttributeValueForNode, AttributeValueForSignal, AttributeValueType, Baudrate, ByteOrder,
Comment, Dbc, EnvType, EnvironmentVariable, EnvironmentVariableData, Message, Node,
NumericValue, Signal, Symbol, Transmitter, ValueType, Version,
};
use strum::{Display, EnumIter, EnumString, IntoEnumIterator};
#[derive(Debug, Copy, Clone, PartialEq, Eq, EnumIter, EnumString, Display)]
pub enum DbcSection {
Version,
NewSymbols,
BitTiming,
Nodes,
ValueTables,
Messages,
MessageTransmitters,
EnvironmentVariables,
EnvironmentVariableData,
SignalTypes,
Comments,
AttributeDefinitions,
RelationAttributeDefinitions,
AttributeDefaults,
RelationAttributeDefaults,
RelationAttributeValues,
AttributeValuesDatabase,
AttributeValuesNode,
AttributeValuesMessage,
AttributeValuesSignal,
AttributeValuesEnv,
ValueDescriptions,
SignalTypeRefs,
SignalGroups,
SignalExtendedValueTypeList,
ExtendedMultiplex,
}
pub fn dbc_to_string(dbc: &Dbc) -> String {
dbc_sections_to_string(dbc, &Vec::from_iter(DbcSection::iter()))
}
pub fn dbc_sections_to_string(dbc: &Dbc, sections: &[DbcSection]) -> String {
let mut lines = Vec::new();
for section in sections {
let section_text = match section {
DbcSection::Version => version_to_string(&dbc.version),
DbcSection::NewSymbols => new_symbols_to_string(&dbc.new_symbols),
DbcSection::BitTiming => maybe_bit_timings_to_string(dbc.bit_timing.as_deref()),
DbcSection::Nodes => nodes_to_string(&dbc.nodes),
DbcSection::ValueTables => String::default(),
DbcSection::Messages => messages_to_string(&dbc.messages),
DbcSection::MessageTransmitters => String::default(),
DbcSection::EnvironmentVariables => {
environment_variables_to_string(&dbc.environment_variables)
}
DbcSection::EnvironmentVariableData => {
environment_variable_datas_to_string(&dbc.environment_variable_data)
}
DbcSection::SignalTypes => String::default(),
DbcSection::Comments => comments_to_string(&dbc.comments),
DbcSection::AttributeDefinitions => {
attribute_definitions_to_string(&dbc.attribute_definitions)
}
DbcSection::RelationAttributeDefinitions => String::default(),
DbcSection::AttributeDefaults => attribute_defaults_to_string(&dbc.attribute_defaults),
DbcSection::RelationAttributeDefaults => String::default(),
DbcSection::RelationAttributeValues => String::default(),
DbcSection::AttributeValuesDatabase => {
attribute_values_database_to_string(&dbc.attribute_values_database)
}
DbcSection::AttributeValuesNode => {
attribute_values_node_to_string(&dbc.attribute_values_node)
}
DbcSection::AttributeValuesMessage => {
attribute_values_message_to_string(&dbc.attribute_values_message)
}
DbcSection::AttributeValuesSignal => {
attribute_values_signal_to_string(&dbc.attribute_values_signal)
}
DbcSection::AttributeValuesEnv => {
attribute_values_env_to_string(&dbc.attribute_values_env)
}
DbcSection::ValueDescriptions => String::default(),
DbcSection::SignalTypeRefs => String::default(),
DbcSection::SignalGroups => String::default(),
DbcSection::SignalExtendedValueTypeList => String::default(),
DbcSection::ExtendedMultiplex => String::default(),
};
if !section_text.is_empty() {
lines.push(format!("{}\n", section_text));
}
}
lines.join("\n")
}
pub fn version_to_string(version: &Version) -> String {
format!("VERSION \"{}\"", version.0)
}
pub fn new_symbols_to_string(symbols: &[Symbol]) -> String {
let mut lines = Vec::new();
lines.push(String::from("NS_:"));
for symbol in symbols {
lines.push(format!("\t{}", symbol.0));
}
lines.join("\n")
}
pub fn maybe_bit_timings_to_string(maybe_bit_timings: Option<&[Baudrate]>) -> String {
if let Some(bit_timings) = maybe_bit_timings {
bit_timings_to_string(bit_timings)
} else {
String::from("BS_:")
}
}
pub fn bit_timings_to_string(bit_timings: &[Baudrate]) -> String {
slice_to_string(bit_timings, bit_timing_to_string)
}
pub fn bit_timing_to_string(bit_timing: &Baudrate) -> String {
format!("BS_: {} : 0,0", bit_timing.0)
}
pub fn nodes_to_string(nodes: &[Node]) -> String {
let mut lines = Vec::new();
lines.push(String::from("BU_:"));
for node in nodes {
lines.push(format!("\t{}", node.0));
}
lines.join("\n")
}
pub fn messages_to_string(messages: &[Message]) -> String {
slice_to_string(messages, |message| format!("{}\n", message_to_string(message)))
}
pub fn message_to_string(message: &Message) -> String {
let mut lines = Vec::new();
lines.push(format!(
"BO_ {} {}: {} {}",
message.id.raw(),
message.name,
message.size,
match &message.transmitter {
Transmitter::NodeName(node_name) => {
node_name.clone()
}
Transmitter::VectorXXX => {
String::from("Vector__XXX")
}
}
));
for signal in &message.signals {
lines.push(format!("\t{}", signal_to_string(&signal)));
}
lines.join("\n")
}
pub fn signal_to_string(signal: &Signal) -> String {
format!(
"SG_ {} : {}|{}@{}{} ({},{}) [{}|{}] \"{}\" {}",
signal.name,
signal.start_bit,
signal.size,
match signal.byte_order {
ByteOrder::BigEndian => 0,
ByteOrder::LittleEndian => 1,
},
match signal.value_type {
ValueType::Signed => "-",
ValueType::Unsigned => "+",
},
signal.factor,
signal.offset,
signal.min,
signal.max,
signal.unit,
signal.receivers.join(","),
)
}
pub fn comments_to_string(comments: &[Comment]) -> String {
slice_to_string(comments, comment_to_string)
}
pub fn comment_to_string(comment: &Comment) -> String {
match comment {
Comment::EnvVar { name, comment } => {
format!("CM_ EV_ {} \"{}\";", name, comment,)
}
Comment::Message { id, comment } => {
format!("CM_ BO_ {} \"{}\";", id.raw(), comment,)
}
Comment::Node { name, comment } => {
format!("CM_ BU_ {} \"{}\";", name, comment,)
}
Comment::Plain { comment } => {
format!("CM_ \"{}\";", comment,)
}
Comment::Signal {
message_id,
name,
comment,
} => {
format!("CM_ SG_ {} {} \"{}\";", message_id.raw(), name, comment,)
}
}
}
pub fn environment_variables_to_string(environment_variables: &[EnvironmentVariable]) -> String {
slice_to_string(environment_variables, environment_variable_to_string)
}
pub fn environment_variable_to_string(environment_variable: &EnvironmentVariable) -> String {
format!(
"EV_ {} : {} [{}|{}] \"{}\" {} {} {} {};",
environment_variable.name,
match environment_variable.typ {
EnvType::Integer => 0,
EnvType::Float => 1,
EnvType::String => 2,
},
environment_variable.min,
environment_variable.max,
environment_variable.unit,
environment_variable.initial_value,
environment_variable.ev_id,
match environment_variable.access_type {
AccessType::DummyNodeVector0 => "DUMMY_NODE_VECTOR0",
AccessType::DummyNodeVector1 => "DUMMY_NODE_VECTOR1",
AccessType::DummyNodeVector2 => "DUMMY_NODE_VECTOR2",
AccessType::DummyNodeVector3 => "DUMMY_NODE_VECTOR3",
},
environment_variable
.access_nodes
.iter()
.map(|access_node| match access_node {
AccessNode::Name(name) => name.clone(),
AccessNode::VectorXXX => String::from("VECTOR__XXX"),
})
.collect::<Vec<_>>()
.join(", ")
)
}
pub fn environment_variable_datas_to_string(
environment_variable_datas: &[EnvironmentVariableData],
) -> String {
slice_to_string(
environment_variable_datas,
environment_variable_data_to_string,
)
}
pub fn environment_variable_data_to_string(
environment_variable_data: &EnvironmentVariableData,
) -> String {
format!(
"ENVVAR_DATA_ {} : {};",
environment_variable_data.env_var_name, environment_variable_data.data_size
)
}
pub fn attribute_definitions_to_string(attribute_definitions: &[AttributeDefinition]) -> String {
slice_to_string(attribute_definitions, attribute_definition_to_string)
}
pub fn attribute_definition_to_string(attribute_definition: &AttributeDefinition) -> String {
match attribute_definition {
AttributeDefinition::EnvironmentVariable(name, attribute_value_type) => {
format!(
"BA_DEF_ EV_ \"{}\" {};",
name,
attribute_value_type_to_string(attribute_value_type)
)
}
AttributeDefinition::Message(name, attribute_value_type) => {
format!(
"BA_DEF_ BO_ \"{}\" {};",
name,
attribute_value_type_to_string(attribute_value_type)
)
}
AttributeDefinition::Node(name, attribute_value_type) => {
format!(
"BA_DEF_ BU_ \"{}\" {};",
name,
attribute_value_type_to_string(attribute_value_type)
)
}
AttributeDefinition::Plain(name, attribute_value_type) => {
format!(
"BA_DEF_ EV_ \"{}\" {};",
name,
attribute_value_type_to_string(attribute_value_type)
)
}
AttributeDefinition::Signal(name, attribute_value_type) => {
format!(
"BA_DEF_ SG_ \"{}\" {};",
name,
attribute_value_type_to_string(attribute_value_type)
)
}
}
}
pub fn attribute_defaults_to_string(attribute_defaults: &[AttributeDefault]) -> String {
slice_to_string(attribute_defaults, attribute_default_to_string)
}
pub fn attribute_default_to_string(attribute_default: &AttributeDefault) -> String {
format!(
"BA_DEF_DEF \"{}\" {};",
attribute_default.name,
attribute_value_to_string(&attribute_default.value)
)
}
pub fn attribute_value_type_to_string(attribute_value_type: &AttributeValueType) -> String {
match attribute_value_type {
AttributeValueType::Enum(values) => {
format!(
"ENUM {}",
values
.iter()
.map(|value| format!("\"{}\"", value))
.collect::<Vec<_>>()
.join(", ")
)
}
AttributeValueType::Float(min, max) => {
format!(
"FLOAT {} {}",
numeric_value_to_string(min),
numeric_value_to_string(max)
)
}
AttributeValueType::Hex(min, max) => {
format!(
"HEX {} {}",
numeric_value_to_string(min),
numeric_value_to_string(max)
)
}
AttributeValueType::Int(min, max) => {
format!(
"INT {} {}",
numeric_value_to_string(min),
numeric_value_to_string(max)
)
}
AttributeValueType::String => String::from("STRING"),
}
}
pub fn attribute_values_database_to_string(
attribute_values: &[AttributeValueForDatabase],
) -> String {
slice_to_string(attribute_values, attribute_value_database_to_string)
}
pub fn attribute_value_database_to_string(
attribute_value_database: &AttributeValueForDatabase,
) -> String {
format!(
"BA_ \"{}\" {};",
attribute_value_database.name,
attribute_value_to_string(&attribute_value_database.value)
)
}
pub fn attribute_values_env_to_string(attribute_values: &[AttributeValueForEnvVariable]) -> String {
slice_to_string(attribute_values, attribute_value_env_to_string)
}
pub fn attribute_value_env_to_string(attribute_value_env: &AttributeValueForEnvVariable) -> String {
format!(
"BA_ \"{}\" EV_ {} {};",
attribute_value_env.name,
attribute_value_env.variable_name,
attribute_value_to_string(&attribute_value_env.value)
)
}
pub fn attribute_values_message_to_string(attribute_values: &[AttributeValueForMessage]) -> String {
slice_to_string(attribute_values, attribute_value_message_to_string)
}
pub fn attribute_value_message_to_string(
attribute_value_message: &AttributeValueForMessage,
) -> String {
format!(
"BA_ \"{}\" BO_ {} {};",
attribute_value_message.name,
attribute_value_message.message_id.raw(),
attribute_value_to_string(&attribute_value_message.value)
)
}
pub fn attribute_values_node_to_string(attribute_values: &[AttributeValueForNode]) -> String {
slice_to_string(attribute_values, attribute_value_node_to_string)
}
pub fn attribute_value_node_to_string(attribute_value_node: &AttributeValueForNode) -> String {
format!(
"BA_ \"{}\" BU_ {} {};",
attribute_value_node.name,
attribute_value_node.node_name,
attribute_value_to_string(&attribute_value_node.value)
)
}
pub fn attribute_values_signal_to_string(attribute_values: &[AttributeValueForSignal]) -> String {
slice_to_string(attribute_values, attribute_value_signal_to_string)
}
pub fn attribute_value_signal_to_string(
attribute_value_signal: &AttributeValueForSignal,
) -> String {
format!(
"BA_ \"{}\" SG_ {} {} {};",
attribute_value_signal.name,
attribute_value_signal.message_id.raw(),
attribute_value_signal.signal_name,
attribute_value_to_string(&attribute_value_signal.value)
)
}
pub fn attribute_value_to_string(attribute_value: &AttributeValue) -> String {
match attribute_value {
AttributeValue::Double(value) => numeric_value_to_string(&NumericValue::Double(*value)),
AttributeValue::Int(value) => numeric_value_to_string(&NumericValue::Int(*value)),
AttributeValue::String(value) => format!("\"{}\"", value),
AttributeValue::Uint(value) => numeric_value_to_string(&NumericValue::Uint(*value)),
}
}
pub fn numeric_value_to_string(numeric_value: &NumericValue) -> String {
match numeric_value {
NumericValue::Double(value) => value.to_string(),
NumericValue::Int(value) => value.to_string(),
NumericValue::Uint(value) => value.to_string(),
}
}
pub fn slice_to_string<T, F>(slice: &[T], map: F) -> String
where
F: Fn(&T) -> String,
{
slice.iter().map(map).collect::<Vec<String>>().join("\n")
}
#[cfg(test)]
mod tests {
use can_dbc::{MessageId, MultiplexIndicator, Transmitter};
use super::*;
#[test]
fn test_comments() {
assert_eq!(
comment_to_string(&Comment::EnvVar {
name: String::from("name"),
comment: String::from("comment"),
}),
r#"CM_ EV_ name "comment";"#
);
assert_eq!(
comment_to_string(&Comment::Node {
name: String::from("name"),
comment: String::from("comment"),
}),
r#"CM_ BU_ name "comment";"#
);
assert_eq!(
comment_to_string(&Comment::Message {
id: MessageId::Extended(0),
comment: String::from("comment"),
}),
r#"CM_ BO_ 2147483648 "comment";"#
);
assert_eq!(
comment_to_string(&Comment::Plain {
comment: String::from("comment"),
}),
r#"CM_ "comment";"#
);
assert_eq!(
comment_to_string(&Comment::Signal {
message_id: MessageId::Extended(0),
name: String::from("name"),
comment: String::from("comment"),
}),
r#"CM_ SG_ 2147483648 name "comment";"#
);
}
#[test]
pub fn test_signal() {
assert_eq!(
signal_to_string(&Signal {
name: String::from("name"),
multiplexer_indicator: MultiplexIndicator::Plain,
start_bit: 0,
size: 1,
byte_order: ByteOrder::BigEndian,
value_type: ValueType::Unsigned,
factor: 1.5,
offset: 2.5,
min: -10.0,
max: 10.0,
unit: String::from("unit"),
receivers: vec![String::from("Vector__XXX")]
}),
r#"SG_ name : 0|1@0+ (1.5,2.5) [-10|10] "unit" Vector__XXX"#
);
}
#[test]
pub fn test_message() {
assert_eq!(
message_to_string(&Message {
id: MessageId::Extended(0),
name: String::from("name"),
size: 0,
transmitter: Transmitter::VectorXXX,
signals: Vec::default()
}),
r#"BO_ 2147483648 name: 0 Vector__XXX"#
);
}
#[test]
pub fn test_nodes() {
assert_eq!(
nodes_to_string(&[Node(String::from("name"))]),
r#"BU_:
name"#
);
}
}