1use crate::error::{CanbusError, CanbusResult};
21use std::collections::HashMap;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25pub enum ByteOrder {
26 #[default]
28 LittleEndian,
29 BigEndian,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
35pub enum ValueType {
36 #[default]
38 Unsigned,
39 Signed,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Default)]
45pub enum MultiplexerType {
46 #[default]
48 None,
49 Multiplexer,
51 Multiplexed(u32),
53}
54
55#[derive(Debug, Clone)]
57pub struct DbcSignal {
58 pub name: String,
60 pub start_bit: u32,
62 pub bit_length: u32,
64 pub byte_order: ByteOrder,
66 pub value_type: ValueType,
68 pub factor: f64,
70 pub offset: f64,
72 pub min: f64,
74 pub max: f64,
76 pub unit: String,
78 pub receivers: Vec<String>,
80 pub multiplexer: MultiplexerType,
82 pub value_descriptions: HashMap<i64, String>,
84 pub comment: Option<String>,
86}
87
88impl DbcSignal {
89 pub fn new(name: &str) -> Self {
91 Self {
92 name: name.to_string(),
93 start_bit: 0,
94 bit_length: 1,
95 byte_order: ByteOrder::default(),
96 value_type: ValueType::default(),
97 factor: 1.0,
98 offset: 0.0,
99 min: 0.0,
100 max: 0.0,
101 unit: String::new(),
102 receivers: Vec::new(),
103 multiplexer: MultiplexerType::default(),
104 value_descriptions: HashMap::new(),
105 comment: None,
106 }
107 }
108
109 pub fn to_physical(&self, raw: i64) -> f64 {
111 raw as f64 * self.factor + self.offset
112 }
113
114 pub fn to_raw(&self, physical: f64) -> i64 {
116 ((physical - self.offset) / self.factor).round() as i64
117 }
118
119 pub fn get_value_description(&self, raw: i64) -> Option<&str> {
121 self.value_descriptions.get(&raw).map(|s| s.as_str())
122 }
123}
124
125#[derive(Debug, Clone)]
127pub struct DbcMessage {
128 pub id: u32,
130 pub is_extended: bool,
132 pub name: String,
134 pub dlc: u8,
136 pub transmitter: String,
138 pub signals: Vec<DbcSignal>,
140 pub comment: Option<String>,
142 pub attributes: HashMap<String, AttributeValue>,
144}
145
146impl DbcMessage {
147 pub fn new(id: u32, name: &str, dlc: u8) -> Self {
149 Self {
150 id,
151 is_extended: id > 0x7FF, name: name.to_string(),
153 dlc,
154 transmitter: String::new(),
155 signals: Vec::new(),
156 comment: None,
157 attributes: HashMap::new(),
158 }
159 }
160
161 pub fn get_signal(&self, name: &str) -> Option<&DbcSignal> {
163 self.signals.iter().find(|s| s.name == name)
164 }
165
166 pub fn get_signal_mut(&mut self, name: &str) -> Option<&mut DbcSignal> {
168 self.signals.iter_mut().find(|s| s.name == name)
169 }
170}
171
172#[derive(Debug, Clone)]
174pub struct DbcNode {
175 pub name: String,
177 pub comment: Option<String>,
179 pub attributes: HashMap<String, AttributeValue>,
181}
182
183impl DbcNode {
184 pub fn new(name: &str) -> Self {
186 Self {
187 name: name.to_string(),
188 comment: None,
189 attributes: HashMap::new(),
190 }
191 }
192}
193
194#[derive(Debug, Clone, PartialEq)]
196pub enum AttributeValue {
197 Int(i64),
199 Float(f64),
201 String(String),
203 Enum(String),
205}
206
207#[derive(Debug, Clone)]
209pub struct AttributeDefinition {
210 pub name: String,
212 pub object_type: AttributeObjectType,
214 pub value_type: AttributeValueType,
216 pub default_value: Option<AttributeValue>,
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub enum AttributeObjectType {
223 Database,
225 Node,
227 Message,
229 Signal,
231}
232
233#[derive(Debug, Clone)]
235pub enum AttributeValueType {
236 Int {
238 min: i64,
240 max: i64,
242 },
243 Float {
245 min: f64,
247 max: f64,
249 },
250 String,
252 Enum {
254 values: Vec<String>,
256 },
257}
258
259#[derive(Debug, Clone)]
261pub struct DbcDatabase {
262 pub version: String,
264 pub nodes: Vec<DbcNode>,
266 pub messages: Vec<DbcMessage>,
268 pub attribute_definitions: Vec<AttributeDefinition>,
270 pub value_tables: HashMap<String, HashMap<i64, String>>,
272 pub attributes: HashMap<String, AttributeValue>,
274}
275
276impl DbcDatabase {
277 pub fn new() -> Self {
279 Self {
280 version: String::new(),
281 nodes: Vec::new(),
282 messages: Vec::new(),
283 attribute_definitions: Vec::new(),
284 value_tables: HashMap::new(),
285 attributes: HashMap::new(),
286 }
287 }
288
289 pub fn get_message(&self, id: u32) -> Option<&DbcMessage> {
291 self.messages.iter().find(|m| m.id == id)
292 }
293
294 pub fn get_message_by_name(&self, name: &str) -> Option<&DbcMessage> {
296 self.messages.iter().find(|m| m.name == name)
297 }
298
299 pub fn get_node(&self, name: &str) -> Option<&DbcNode> {
301 self.nodes.iter().find(|n| n.name == name)
302 }
303
304 pub fn all_signals(&self) -> impl Iterator<Item = (&DbcMessage, &DbcSignal)> {
306 self.messages
307 .iter()
308 .flat_map(|m| m.signals.iter().map(move |s| (m, s)))
309 }
310}
311
312impl Default for DbcDatabase {
313 fn default() -> Self {
314 Self::new()
315 }
316}
317
318pub struct DbcParser {
320 line_number: usize,
322 database: DbcDatabase,
324}
325
326impl DbcParser {
327 pub fn new() -> Self {
329 Self {
330 line_number: 0,
331 database: DbcDatabase::new(),
332 }
333 }
334
335 pub fn parse(&mut self, content: &str) -> CanbusResult<DbcDatabase> {
337 self.database = DbcDatabase::new();
338 self.line_number = 0;
339
340 let mut lines = content.lines().peekable();
341
342 while let Some(line) = lines.next() {
343 self.line_number += 1;
344 let trimmed = line.trim();
345
346 if trimmed.is_empty() || trimmed.starts_with("//") {
347 continue;
348 }
349
350 let needs_semicolon = Self::directive_needs_semicolon(trimmed);
352
353 let mut full_line = trimmed.to_string();
354
355 if needs_semicolon {
356 while !full_line.ends_with(';') {
358 if let Some(next) = lines.next() {
359 self.line_number += 1;
360 let next_trimmed = next.trim();
361 full_line.push(' ');
362 full_line.push_str(next_trimmed);
363 } else {
364 break;
365 }
366 }
367 }
368
369 self.parse_line(&full_line)?;
370 }
371
372 Ok(std::mem::take(&mut self.database))
373 }
374
375 fn directive_needs_semicolon(line: &str) -> bool {
377 line.starts_with("CM_ ")
380 || line.starts_with("BA_DEF_ ")
381 || line.starts_with("BA_DEF_DEF_ ")
382 || line.starts_with("BA_ ")
383 || line.starts_with("VAL_TABLE_ ")
384 || line.starts_with("VAL_ ")
385 }
386
387 fn parse_line(&mut self, line: &str) -> CanbusResult<()> {
389 let trimmed = line.trim();
391
392 if trimmed.starts_with("VERSION") {
393 self.parse_version(trimmed)
394 } else if trimmed.starts_with("NS_") {
395 Ok(())
397 } else if trimmed.starts_with("BS_") {
398 Ok(())
400 } else if trimmed.starts_with("BU_") {
401 self.parse_nodes(trimmed)
402 } else if trimmed.starts_with("BO_ ") {
403 self.parse_message(trimmed)
404 } else if trimmed.starts_with("SG_ ") {
405 self.parse_standalone_signal(trimmed)
406 } else if trimmed.starts_with("CM_ ") {
407 self.parse_comment(line) } else if trimmed.starts_with("BA_DEF_ ") {
409 self.parse_attribute_definition(line)
410 } else if trimmed.starts_with("BA_DEF_DEF_ ") {
411 self.parse_attribute_default(line)
412 } else if trimmed.starts_with("BA_ ") {
413 self.parse_attribute_value(line)
414 } else if trimmed.starts_with("VAL_TABLE_ ") {
415 self.parse_value_table(line)
416 } else if trimmed.starts_with("VAL_ ") {
417 self.parse_value_descriptions(line)
418 } else {
419 Ok(())
421 }
422 }
423
424 fn parse_version(&mut self, line: &str) -> CanbusResult<()> {
426 if let Some(start) = line.find('"') {
428 if let Some(end) = line[start + 1..].find('"') {
429 self.database.version = line[start + 1..start + 1 + end].to_string();
430 }
431 }
432 Ok(())
433 }
434
435 fn parse_nodes(&mut self, line: &str) -> CanbusResult<()> {
437 let content = line.strip_prefix("BU_:").unwrap_or("").trim();
439 for name in content.split_whitespace() {
440 if !name.is_empty() {
441 self.database.nodes.push(DbcNode::new(name));
442 }
443 }
444 Ok(())
445 }
446
447 fn parse_message(&mut self, line: &str) -> CanbusResult<()> {
449 let parts: Vec<&str> = line.split_whitespace().collect();
452 if parts.len() < 4 {
453 return Err(self.error("Invalid message format"));
454 }
455
456 let id = parts[1]
457 .parse::<u32>()
458 .map_err(|_| self.error("Invalid message ID"))?;
459
460 let name = parts[2].trim_end_matches(':');
461 let dlc = parts[3]
462 .parse::<u8>()
463 .map_err(|_| self.error("Invalid DLC"))?;
464
465 let mut message = DbcMessage::new(id, name, dlc);
466
467 if parts.len() > 4 {
468 message.transmitter = parts[4].to_string();
469 }
470
471 let signal_start = line.find("SG_");
473 if let Some(pos) = signal_start {
474 let signal_part = &line[pos..];
475 if let Ok(signal) = self.parse_signal_definition(signal_part) {
476 message.signals.push(signal);
477 }
478 }
479
480 self.database.messages.push(message);
481 Ok(())
482 }
483
484 fn parse_standalone_signal(&mut self, line: &str) -> CanbusResult<()> {
486 if let Ok(signal) = self.parse_signal_definition(line) {
487 if let Some(msg) = self.database.messages.last_mut() {
489 msg.signals.push(signal);
490 }
491 }
492 Ok(())
493 }
494
495 fn parse_signal_definition(&self, line: &str) -> CanbusResult<DbcSignal> {
497 let parts: Vec<&str> = line.split_whitespace().collect();
502 if parts.len() < 7 {
503 return Err(self.error("Invalid signal format"));
504 }
505
506 let name = parts[1];
507 let mut signal = DbcSignal::new(name);
508
509 let mut bit_spec_idx = 3;
511 if parts[2] == "M" || parts[2] == "m" {
512 signal.multiplexer = MultiplexerType::Multiplexer;
513 bit_spec_idx = 4;
514 } else if parts[2].starts_with('m') && parts[2].len() > 1 {
515 if let Ok(val) = parts[2][1..].parse::<u32>() {
516 signal.multiplexer = MultiplexerType::Multiplexed(val);
517 }
518 bit_spec_idx = 4;
519 } else if parts[2] != ":" {
520 bit_spec_idx = 3;
522 }
523
524 let bit_spec = parts
527 .get(bit_spec_idx)
528 .ok_or_else(|| self.error("Missing bit spec"))?;
529 self.parse_bit_spec(bit_spec, &mut signal)?;
530
531 for part in &parts[bit_spec_idx + 1..] {
533 if part.starts_with('(') && part.contains(',') {
534 self.parse_factor_offset(part, &mut signal)?;
535 } else if part.starts_with('[') && part.contains('|') {
536 self.parse_min_max(part, &mut signal)?;
537 } else if part.starts_with('"') {
538 signal.unit = part.trim_matches('"').to_string();
539 } else if !part.starts_with('[') && !part.starts_with('(') && !part.starts_with('"') {
540 let receiver = part.trim_matches(',');
542 if !receiver.is_empty() && receiver != "Vector__XXX" {
543 signal.receivers.push(receiver.to_string());
544 }
545 }
546 }
547
548 Ok(signal)
549 }
550
551 fn parse_bit_spec(&self, spec: &str, signal: &mut DbcSignal) -> CanbusResult<()> {
553 let at_pos = spec
557 .find('@')
558 .ok_or_else(|| self.error("Invalid bit spec: missing @"))?;
559 let pipe_pos = spec
560 .find('|')
561 .ok_or_else(|| self.error("Invalid bit spec: missing |"))?;
562
563 signal.start_bit = spec[..pipe_pos]
564 .parse()
565 .map_err(|_| self.error("Invalid start bit"))?;
566
567 signal.bit_length = spec[pipe_pos + 1..at_pos]
568 .parse()
569 .map_err(|_| self.error("Invalid bit length"))?;
570
571 let order_sign = &spec[at_pos + 1..];
572 signal.byte_order = if order_sign.starts_with('1') {
573 ByteOrder::LittleEndian
574 } else {
575 ByteOrder::BigEndian
576 };
577
578 signal.value_type = if order_sign.ends_with('-') {
579 ValueType::Signed
580 } else {
581 ValueType::Unsigned
582 };
583
584 Ok(())
585 }
586
587 fn parse_factor_offset(&self, spec: &str, signal: &mut DbcSignal) -> CanbusResult<()> {
589 let inner = spec.trim_matches(|c| c == '(' || c == ')');
590 let parts: Vec<&str> = inner.split(',').collect();
591 if parts.len() == 2 {
592 signal.factor = parts[0].parse().unwrap_or(1.0);
593 signal.offset = parts[1].parse().unwrap_or(0.0);
594 }
595 Ok(())
596 }
597
598 fn parse_min_max(&self, spec: &str, signal: &mut DbcSignal) -> CanbusResult<()> {
600 let inner = spec.trim_matches(|c| c == '[' || c == ']');
601 let parts: Vec<&str> = inner.split('|').collect();
602 if parts.len() == 2 {
603 signal.min = parts[0].parse().unwrap_or(0.0);
604 signal.max = parts[1].parse().unwrap_or(0.0);
605 }
606 Ok(())
607 }
608
609 fn parse_comment(&mut self, line: &str) -> CanbusResult<()> {
611 let comment_start = line.find('"');
617 let comment_end = line.rfind('"');
618
619 if let (Some(start), Some(end)) = (comment_start, comment_end) {
620 if start < end {
621 let comment = line[start + 1..end].to_string();
622
623 if line.starts_with("CM_ SG_") {
624 let parts: Vec<&str> = line[7..start].split_whitespace().collect();
626 if parts.len() >= 2 {
627 if let Ok(msg_id) = parts[0].parse::<u32>() {
628 let sig_name = parts[1];
629 if let Some(msg) =
630 self.database.messages.iter_mut().find(|m| m.id == msg_id)
631 {
632 if let Some(sig) =
633 msg.signals.iter_mut().find(|s| s.name == sig_name)
634 {
635 sig.comment = Some(comment);
636 }
637 }
638 }
639 }
640 } else if line.starts_with("CM_ BO_") {
641 let parts: Vec<&str> = line[7..start].split_whitespace().collect();
643 if !parts.is_empty() {
644 if let Ok(msg_id) = parts[0].parse::<u32>() {
645 if let Some(msg) =
646 self.database.messages.iter_mut().find(|m| m.id == msg_id)
647 {
648 msg.comment = Some(comment);
649 }
650 }
651 }
652 } else if line.starts_with("CM_ BU_") {
653 let parts: Vec<&str> = line[7..start].split_whitespace().collect();
655 if !parts.is_empty() {
656 let node_name = parts[0];
657 if let Some(node) =
658 self.database.nodes.iter_mut().find(|n| n.name == node_name)
659 {
660 node.comment = Some(comment);
661 }
662 }
663 }
664 }
665 }
666
667 Ok(())
668 }
669
670 fn parse_attribute_definition(&mut self, line: &str) -> CanbusResult<()> {
672 let object_type = if line.contains(" BO_ ") {
678 AttributeObjectType::Message
679 } else if line.contains(" SG_ ") {
680 AttributeObjectType::Signal
681 } else if line.contains(" BU_ ") {
682 AttributeObjectType::Node
683 } else {
684 AttributeObjectType::Database
685 };
686
687 let name_start = line.find('"');
689 let name_end = line[name_start.unwrap_or(0) + 1..].find('"');
690
691 if let (Some(start), Some(end)) = (name_start, name_end) {
692 let name = line[start + 1..start + 1 + end].to_string();
693 let remaining = &line[start + 2 + end..];
694
695 let value_type = self.parse_attribute_value_type(remaining)?;
696
697 self.database
698 .attribute_definitions
699 .push(AttributeDefinition {
700 name,
701 object_type,
702 value_type,
703 default_value: None,
704 });
705 }
706
707 Ok(())
708 }
709
710 fn parse_attribute_value_type(&self, spec: &str) -> CanbusResult<AttributeValueType> {
712 let parts: Vec<&str> = spec.split_whitespace().collect();
713 if parts.is_empty() {
714 return Ok(AttributeValueType::String);
715 }
716
717 match parts[0] {
718 "INT" => {
719 let min = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
720 let max = parts
721 .get(2)
722 .and_then(|s| s.trim_end_matches(';').parse().ok())
723 .unwrap_or(0);
724 Ok(AttributeValueType::Int { min, max })
725 }
726 "FLOAT" | "HEX" => {
727 let min = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0.0);
728 let max = parts
729 .get(2)
730 .and_then(|s| s.trim_end_matches(';').parse().ok())
731 .unwrap_or(0.0);
732 Ok(AttributeValueType::Float { min, max })
733 }
734 "STRING" => Ok(AttributeValueType::String),
735 "ENUM" => {
736 let values: Vec<String> = parts[1..]
737 .iter()
738 .map(|s| {
739 s.trim_matches(|c| c == '"' || c == ',' || c == ';')
740 .to_string()
741 })
742 .filter(|s| !s.is_empty())
743 .collect();
744 Ok(AttributeValueType::Enum { values })
745 }
746 _ => Ok(AttributeValueType::String),
747 }
748 }
749
750 fn parse_attribute_default(&mut self, line: &str) -> CanbusResult<()> {
752 let name_start = line.find('"');
754 let name_end = line[name_start.unwrap_or(0) + 1..].find('"');
755
756 if let (Some(start), Some(end)) = (name_start, name_end) {
757 let name = &line[start + 1..start + 1 + end];
758 let remaining = line[start + 2 + end..].trim().trim_end_matches(';');
759
760 let value_type = self
762 .database
763 .attribute_definitions
764 .iter()
765 .find(|d| d.name == name)
766 .map(|d| d.value_type.clone());
767
768 if let Some(vt) = value_type {
770 let default_value = Self::parse_attribute_literal_static(remaining, &vt);
771 if let Some(def) = self
772 .database
773 .attribute_definitions
774 .iter_mut()
775 .find(|d| d.name == name)
776 {
777 def.default_value = Some(default_value);
778 }
779 }
780 }
781
782 Ok(())
783 }
784
785 fn parse_attribute_literal_static(
787 value: &str,
788 value_type: &AttributeValueType,
789 ) -> AttributeValue {
790 let trimmed = value.trim().trim_matches('"');
791 match value_type {
792 AttributeValueType::Int { .. } => AttributeValue::Int(trimmed.parse().unwrap_or(0)),
793 AttributeValueType::Float { .. } => {
794 AttributeValue::Float(trimmed.parse().unwrap_or(0.0))
795 }
796 AttributeValueType::Enum { .. } => AttributeValue::Enum(trimmed.to_string()),
797 AttributeValueType::String => AttributeValue::String(trimmed.to_string()),
798 }
799 }
800
801 fn parse_attribute_value(&mut self, line: &str) -> CanbusResult<()> {
803 let name_start = line.find('"');
807 let name_end = line[name_start.unwrap_or(0) + 1..].find('"');
808
809 if let (Some(start), Some(end)) = (name_start, name_end) {
810 let attr_name = line[start + 1..start + 1 + end].to_string();
811 let remaining = &line[start + 2 + end..];
812 let parts: Vec<&str> = remaining.split_whitespace().collect();
813
814 let value_type = self
816 .database
817 .attribute_definitions
818 .iter()
819 .find(|d| d.name == attr_name)
820 .map(|d| d.value_type.clone())
821 .unwrap_or(AttributeValueType::String);
822
823 if parts.len() >= 2 && parts[0] == "BO_" {
824 if let Ok(msg_id) = parts[1].parse::<u32>() {
826 if let Some(value_str) = parts.get(2) {
827 let value = Self::parse_attribute_literal_static(
828 value_str.trim_end_matches(';'),
829 &value_type,
830 );
831 if let Some(msg) =
832 self.database.messages.iter_mut().find(|m| m.id == msg_id)
833 {
834 msg.attributes.insert(attr_name, value);
835 }
836 }
837 }
838 } else if parts.len() >= 3 && parts[0] == "SG_" {
839 let _msg_id = parts[1].parse::<u32>().ok();
842 let _sig_name = parts[2];
843 }
844 }
845
846 Ok(())
847 }
848
849 fn parse_value_table(&mut self, line: &str) -> CanbusResult<()> {
851 let parts: Vec<&str> = line.split_whitespace().collect();
853 if parts.len() < 2 {
854 return Ok(());
855 }
856
857 let name = parts[1].to_string();
858 let mut values = HashMap::new();
859
860 let mut i = 2;
861 while i + 1 < parts.len() {
862 if let Ok(key) = parts[i].parse::<i64>() {
863 let value = parts[i + 1].trim_matches(|c| c == '"' || c == ';');
864 values.insert(key, value.to_string());
865 i += 2;
866 } else {
867 break;
868 }
869 }
870
871 self.database.value_tables.insert(name, values);
872 Ok(())
873 }
874
875 fn parse_value_descriptions(&mut self, line: &str) -> CanbusResult<()> {
877 let parts: Vec<&str> = line.split_whitespace().collect();
879 if parts.len() < 3 {
880 return Ok(());
881 }
882
883 let msg_id: u32 = match parts[1].parse() {
884 Ok(id) => id,
885 Err(_) => return Ok(()),
886 };
887
888 let sig_name = parts[2];
889 let mut values = HashMap::new();
890
891 let mut i = 3;
892 while i + 1 < parts.len() {
893 if let Ok(key) = parts[i].parse::<i64>() {
894 let value = parts[i + 1].trim_matches(|c| c == '"' || c == ';');
895 values.insert(key, value.to_string());
896 i += 2;
897 } else {
898 i += 1;
899 }
900 }
901
902 if let Some(msg) = self.database.messages.iter_mut().find(|m| m.id == msg_id) {
904 if let Some(sig) = msg.signals.iter_mut().find(|s| s.name == sig_name) {
905 sig.value_descriptions = values;
906 }
907 }
908
909 Ok(())
910 }
911
912 fn error(&self, message: &str) -> CanbusError {
914 CanbusError::DbcParseError {
915 line: self.line_number,
916 message: message.to_string(),
917 }
918 }
919}
920
921impl Default for DbcParser {
922 fn default() -> Self {
923 Self::new()
924 }
925}
926
927pub fn parse_dbc(content: &str) -> CanbusResult<DbcDatabase> {
929 DbcParser::new().parse(content)
930}
931
932pub fn parse_dbc_file(path: impl AsRef<std::path::Path>) -> CanbusResult<DbcDatabase> {
934 let content = std::fs::read_to_string(path.as_ref()).map_err(CanbusError::Io)?;
935 parse_dbc(&content)
936}
937
938#[cfg(test)]
939mod tests {
940 use super::*;
941
942 const TEST_DBC: &str = r#"
943VERSION ""
944
945NS_ :
946 NS_DESC_
947 CM_
948 BA_DEF_
949 BA_
950
951BS_:
952
953BU_: Engine Dashboard Transmission
954
955BO_ 2024 EngineData: 8 Engine
956 SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8031.875] "rpm" Dashboard
957 SG_ EngineTemp : 16|8@1+ (1,-40) [-40|215] "degC" Dashboard
958 SG_ ThrottlePos : 24|8@1+ (0.392157,0) [0|100] "%" Dashboard
959
960BO_ 2028 VehicleSpeed: 4 Transmission
961 SG_ Speed : 0|16@1+ (0.01,0) [0|655.35] "km/h" Dashboard
962
963CM_ BO_ 2024 "Engine data message containing RPM, temperature and throttle";
964CM_ SG_ 2024 EngineSpeed "Engine rotational speed in RPM";
965CM_ SG_ 2024 EngineTemp "Engine coolant temperature";
966CM_ BU_ Engine "Engine control unit";
967
968BA_DEF_ BO_ "GenMsgCycleTime" INT 0 10000;
969BA_DEF_DEF_ "GenMsgCycleTime" 100;
970BA_ "GenMsgCycleTime" BO_ 2024 50;
971
972VAL_ 2024 ThrottlePos 0 "Closed" 100 "WOT";
973"#;
974
975 #[test]
976 fn test_parse_dbc() {
977 let db = parse_dbc(TEST_DBC).unwrap();
978
979 assert_eq!(db.nodes.len(), 3);
980 assert_eq!(db.messages.len(), 2);
981 }
982
983 #[test]
984 fn test_parse_message() {
985 let db = parse_dbc(TEST_DBC).unwrap();
986
987 let engine_msg = db.get_message(2024).unwrap();
988 assert_eq!(engine_msg.name, "EngineData");
989 assert_eq!(engine_msg.dlc, 8);
990 assert_eq!(engine_msg.transmitter, "Engine");
991 assert_eq!(engine_msg.signals.len(), 3);
992 }
993
994 #[test]
995 fn test_parse_signal() {
996 let db = parse_dbc(TEST_DBC).unwrap();
997
998 let engine_msg = db.get_message(2024).unwrap();
999 let speed_sig = engine_msg.get_signal("EngineSpeed").unwrap();
1000
1001 assert_eq!(speed_sig.start_bit, 0);
1002 assert_eq!(speed_sig.bit_length, 16);
1003 assert_eq!(speed_sig.factor, 0.125);
1004 assert_eq!(speed_sig.offset, 0.0);
1005 assert_eq!(speed_sig.unit, "rpm");
1006 assert_eq!(speed_sig.byte_order, ByteOrder::LittleEndian);
1007 assert_eq!(speed_sig.value_type, ValueType::Unsigned);
1008 }
1009
1010 #[test]
1011 fn test_parse_signal_with_offset() {
1012 let db = parse_dbc(TEST_DBC).unwrap();
1013
1014 let engine_msg = db.get_message(2024).unwrap();
1015 let temp_sig = engine_msg.get_signal("EngineTemp").unwrap();
1016
1017 assert_eq!(temp_sig.start_bit, 16);
1018 assert_eq!(temp_sig.bit_length, 8);
1019 assert_eq!(temp_sig.factor, 1.0);
1020 assert_eq!(temp_sig.offset, -40.0);
1021 assert_eq!(temp_sig.min, -40.0);
1022 assert_eq!(temp_sig.max, 215.0);
1023 }
1024
1025 #[test]
1026 fn test_physical_value_conversion() {
1027 let db = parse_dbc(TEST_DBC).unwrap();
1028
1029 let engine_msg = db.get_message(2024).unwrap();
1030
1031 let speed_sig = engine_msg.get_signal("EngineSpeed").unwrap();
1033 assert!((speed_sig.to_physical(16000) - 2000.0).abs() < 0.001);
1034
1035 let temp_sig = engine_msg.get_signal("EngineTemp").unwrap();
1037 assert!((temp_sig.to_physical(125) - 85.0).abs() < 0.001);
1038 }
1039
1040 #[test]
1041 fn test_parse_comments() {
1042 let db = parse_dbc(TEST_DBC).unwrap();
1043
1044 let engine_msg = db.get_message(2024).unwrap();
1045 assert!(engine_msg.comment.is_some());
1046 assert!(engine_msg
1047 .comment
1048 .as_ref()
1049 .unwrap()
1050 .contains("Engine data message"));
1051
1052 let speed_sig = engine_msg.get_signal("EngineSpeed").unwrap();
1053 assert!(speed_sig.comment.is_some());
1054 assert!(speed_sig
1055 .comment
1056 .as_ref()
1057 .unwrap()
1058 .contains("rotational speed"));
1059 }
1060
1061 #[test]
1062 fn test_parse_value_descriptions() {
1063 let db = parse_dbc(TEST_DBC).unwrap();
1064
1065 let engine_msg = db.get_message(2024).unwrap();
1066 let throttle_sig = engine_msg.get_signal("ThrottlePos").unwrap();
1067
1068 assert_eq!(throttle_sig.get_value_description(0), Some("Closed"));
1069 assert_eq!(throttle_sig.get_value_description(100), Some("WOT"));
1070 }
1071
1072 #[test]
1073 fn test_parse_attributes() {
1074 let db = parse_dbc(TEST_DBC).unwrap();
1075
1076 assert_eq!(db.attribute_definitions.len(), 1);
1077 let attr_def = &db.attribute_definitions[0];
1078 assert_eq!(attr_def.name, "GenMsgCycleTime");
1079
1080 let engine_msg = db.get_message(2024).unwrap();
1081 assert!(engine_msg.attributes.contains_key("GenMsgCycleTime"));
1082 }
1083
1084 #[test]
1085 fn test_parse_nodes() {
1086 let db = parse_dbc(TEST_DBC).unwrap();
1087
1088 assert!(db.get_node("Engine").is_some());
1089 assert!(db.get_node("Dashboard").is_some());
1090 assert!(db.get_node("Transmission").is_some());
1091
1092 let engine_node = db.get_node("Engine").unwrap();
1093 assert!(engine_node.comment.is_some());
1094 }
1095
1096 #[test]
1097 fn test_vehicle_speed_message() {
1098 let db = parse_dbc(TEST_DBC).unwrap();
1099
1100 let speed_msg = db.get_message(2028).unwrap();
1101 assert_eq!(speed_msg.name, "VehicleSpeed");
1102 assert_eq!(speed_msg.dlc, 4);
1103
1104 let speed_sig = speed_msg.get_signal("Speed").unwrap();
1105 assert_eq!(speed_sig.factor, 0.01);
1106
1107 assert!((speed_sig.to_physical(10000) - 100.0).abs() < 0.001);
1109 }
1110
1111 #[test]
1112 fn test_byte_order_big_endian() {
1113 let dbc = r#"
1114BO_ 100 TestMsg: 8 ECU
1115 SG_ BigEndianSig : 7|16@0+ (1,0) [0|65535] "" Vector__XXX
1116"#;
1117 let db = parse_dbc(dbc).unwrap();
1118
1119 let msg = db.get_message(100).unwrap();
1120 let sig = msg.get_signal("BigEndianSig").unwrap();
1121 assert_eq!(sig.byte_order, ByteOrder::BigEndian);
1122 }
1123
1124 #[test]
1125 fn test_signed_signal() {
1126 let dbc = r#"
1127BO_ 100 TestMsg: 8 ECU
1128 SG_ SignedSig : 0|16@1- (1,0) [-32768|32767] "" Vector__XXX
1129"#;
1130 let db = parse_dbc(dbc).unwrap();
1131
1132 let msg = db.get_message(100).unwrap();
1133 let sig = msg.get_signal("SignedSig").unwrap();
1134 assert_eq!(sig.value_type, ValueType::Signed);
1135 }
1136
1137 #[test]
1138 fn test_all_signals() {
1139 let db = parse_dbc(TEST_DBC).unwrap();
1140
1141 let all_sigs: Vec<_> = db.all_signals().collect();
1142 assert_eq!(all_sigs.len(), 4); }
1144
1145 #[test]
1146 fn test_empty_dbc() {
1147 let dbc = "VERSION \"\"";
1148 let db = parse_dbc(dbc).unwrap();
1149 assert!(db.messages.is_empty());
1150 }
1151}