dbc_rs/dbc/impls.rs
1use super::{ExtMuxIndex, ExtendedMultiplexings, Messages, ValueDescriptionsMap};
2use crate::{
3 BitTiming, Dbc, ExtendedMultiplexing, Nodes, ValueDescriptions, Version, compat::Comment,
4};
5
6impl Dbc {
7 pub(crate) fn new(
8 version: Option<Version>,
9 bit_timing: Option<BitTiming>,
10 nodes: Nodes,
11 messages: Messages,
12 value_descriptions: ValueDescriptionsMap,
13 extended_multiplexing: ExtendedMultiplexings,
14 comment: Option<Comment>,
15 ) -> Self {
16 // Build index for fast extended multiplexing lookup
17 let ext_mux_index = ExtMuxIndex::build(extended_multiplexing.as_slice());
18
19 // Validation should have been done prior (by builder)
20 Self {
21 version,
22 bit_timing,
23 nodes,
24 messages,
25 value_descriptions,
26 extended_multiplexing,
27 ext_mux_index,
28 comment,
29 }
30 }
31
32 /// Get the version of the DBC file
33 ///
34 /// # Examples
35 ///
36 /// ```rust,no_run
37 /// use dbc_rs::Dbc;
38 ///
39 /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
40 /// if let Some(version) = dbc.version() {
41 /// // Version is available
42 /// let _ = version.as_str();
43 /// }
44 /// # Ok::<(), dbc_rs::Error>(())
45 /// ```
46 #[inline]
47 #[must_use = "return value should be used"]
48 pub fn version(&self) -> Option<&Version> {
49 self.version.as_ref()
50 }
51
52 /// Get the bit timing configuration
53 ///
54 /// The BS_ section in DBC files specifies CAN bus timing parameters.
55 /// Returns `None` if the BS_ section was empty or not present.
56 /// Returns `Some(&BitTiming)` if timing parameters were specified.
57 ///
58 /// # Examples
59 ///
60 /// ```rust,no_run
61 /// use dbc_rs::Dbc;
62 ///
63 /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBS_: 500000 : 1,2\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
64 /// if let Some(bit_timing) = dbc.bit_timing() {
65 /// println!("Baudrate: {:?}", bit_timing.baudrate());
66 /// }
67 /// # Ok::<(), dbc_rs::Error>(())
68 /// ```
69 #[inline]
70 #[must_use = "return value should be used"]
71 pub fn bit_timing(&self) -> Option<&BitTiming> {
72 self.bit_timing.as_ref()
73 }
74
75 /// Get a reference to the nodes collection
76 ///
77 /// # Examples
78 ///
79 /// ```rust,no_run
80 /// use dbc_rs::Dbc;
81 ///
82 /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM TCM\n\nBO_ 256 Engine : 8 ECM")?;
83 /// let nodes = dbc.nodes();
84 /// assert_eq!(nodes.len(), 2);
85 /// // Iterate over nodes
86 /// let mut iter = nodes.iter();
87 /// assert_eq!(iter.next(), Some("ECM"));
88 /// assert_eq!(iter.next(), Some("TCM"));
89 /// assert_eq!(iter.next(), None);
90 /// # Ok::<(), dbc_rs::Error>(())
91 /// ```
92 #[inline]
93 #[must_use = "return value should be used"]
94 pub fn nodes(&self) -> &Nodes {
95 &self.nodes
96 }
97
98 /// Get a reference to the messages collection
99 ///
100 /// # Examples
101 ///
102 /// ```rust,no_run
103 /// use dbc_rs::Dbc;
104 ///
105 /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
106 /// let messages = dbc.messages();
107 /// assert_eq!(messages.len(), 1);
108 /// let message = messages.at(0).unwrap();
109 /// assert_eq!(message.name(), "Engine");
110 /// assert_eq!(message.id(), 256);
111 /// # Ok::<(), dbc_rs::Error>(())
112 /// ```
113 #[inline]
114 #[must_use = "return value should be used"]
115 pub fn messages(&self) -> &Messages {
116 &self.messages
117 }
118
119 /// Get value descriptions for a specific signal
120 ///
121 /// Value descriptions map numeric signal values to human-readable text.
122 /// Returns `None` if the signal has no value descriptions.
123 ///
124 /// **Global Value Descriptions**: According to the Vector DBC specification,
125 /// a message_id of `-1` (0xFFFFFFFF) in a `VAL_` statement means the value
126 /// descriptions apply to all signals with that name in ANY message. This
127 /// method will first check for a message-specific entry, then fall back to
128 /// a global entry if one exists.
129 ///
130 /// # Examples
131 ///
132 /// ```rust,no_run
133 /// # use dbc_rs::Dbc;
134 /// # let dbc = Dbc::parse(r#"VERSION "1.0"\n\nBU_: ECM\n\nBO_ 100 Engine : 8 ECM\n SG_ Gear : 0|8@1+ (1,0) [0|5] "" *\n\nVAL_ 100 Gear 0 "Park" 1 "Reverse" ;"#)?;
135 /// if let Some(value_descriptions) = dbc.value_descriptions_for_signal(100, "Gear") {
136 /// if let Some(desc) = value_descriptions.get(0) {
137 /// println!("Value 0 means: {}", desc);
138 /// }
139 /// }
140 /// # Ok::<(), dbc_rs::Error>(())
141 /// ```
142 /// Get a reference to the value descriptions list
143 ///
144 /// # Examples
145 ///
146 /// ```rust,no_run
147 /// use dbc_rs::Dbc;
148 ///
149 /// let dbc = Dbc::parse(r#"VERSION "1.0"
150 ///
151 /// BU_: ECM
152 ///
153 /// BO_ 100 Engine : 8 ECM
154 /// SG_ Gear : 0|8@1+ (1,0) [0|5] "" *
155 ///
156 /// VAL_ 100 Gear 0 "Park" 1 "Drive" ;"#)?;
157 /// let value_descriptions_list = dbc.value_descriptions();
158 /// assert_eq!(value_descriptions_list.len(), 1);
159 /// # Ok::<(), dbc_rs::Error>(())
160 /// ```
161 #[inline]
162 #[must_use = "return value should be used"]
163 pub fn value_descriptions(&self) -> &ValueDescriptionsMap {
164 &self.value_descriptions
165 }
166
167 #[must_use = "return value should be used"]
168 pub fn value_descriptions_for_signal(
169 &self,
170 message_id: u32,
171 signal_name: &str,
172 ) -> Option<&ValueDescriptions> {
173 self.value_descriptions.for_signal(message_id, signal_name)
174 }
175
176 /// Get all extended multiplexing entries
177 ///
178 /// Returns a reference to all extended multiplexing (SG_MUL_VAL_) entries
179 /// in the DBC file.
180 ///
181 /// # Examples
182 ///
183 /// ```rust,no_run
184 /// use dbc_rs::Dbc;
185 ///
186 /// let dbc = Dbc::parse(r#"VERSION "1.0"
187 ///
188 /// BU_: ECM
189 ///
190 /// BO_ 500 MuxMessage : 8 ECM
191 /// SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
192 /// SG_ Signal_A m0 : 16|16@1+ (0.1,0) [0|100] ""
193 ///
194 /// SG_MUL_VAL_ 500 Signal_A Mux1 0-5 ;
195 /// "#)?;
196 ///
197 /// let ext_mux = dbc.extended_multiplexing();
198 /// assert_eq!(ext_mux.len(), 1);
199 /// # Ok::<(), dbc_rs::Error>(())
200 /// ```
201 #[inline]
202 #[must_use = "return value should be used"]
203 pub fn extended_multiplexing(&self) -> &[ExtendedMultiplexing] {
204 self.extended_multiplexing.as_slice()
205 }
206
207 /// Get extended multiplexing entries for a specific message
208 ///
209 /// Extended multiplexing (SG_MUL_VAL_) entries define which multiplexer switch values
210 /// activate specific multiplexed signals. This method returns an iterator over
211 /// references to extended multiplexing entries for the given message ID.
212 ///
213 /// # Performance
214 ///
215 /// Returns an iterator of references (zero allocation) instead of cloning entries.
216 /// This is optimized for the decode hot path where extended multiplexing is checked
217 /// on every CAN frame.
218 ///
219 /// # Examples
220 ///
221 /// ```rust,no_run
222 /// use dbc_rs::Dbc;
223 ///
224 /// let dbc = Dbc::parse(r#"VERSION "1.0"
225 ///
226 /// BU_: ECM
227 ///
228 /// BO_ 500 ComplexMux : 8 ECM
229 /// SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
230 /// SG_ Signal_A m0 : 16|16@1+ (0.1,0) [0|100] ""
231 ///
232 /// SG_MUL_VAL_ 500 Signal_A Mux1 0-5,10-15 ;
233 /// "#)?;
234 /// let extended: Vec<_> = dbc.extended_multiplexing_for_message(500).collect();
235 /// assert_eq!(extended.len(), 1);
236 /// # Ok::<(), dbc_rs::Error>(())
237 /// ```
238 #[inline]
239 #[must_use = "iterator is lazy and does nothing unless consumed"]
240 pub fn extended_multiplexing_for_message(
241 &self,
242 message_id: u32,
243 ) -> impl Iterator<Item = &ExtendedMultiplexing> + '_ {
244 self.extended_multiplexing
245 .iter()
246 .filter(move |ext_mux| ext_mux.message_id() == message_id)
247 }
248
249 /// Returns the database-level comment from CM_ (general comment), if present.
250 ///
251 /// This is the general comment for the entire DBC file, not associated with
252 /// any specific node, message, or signal.
253 ///
254 /// # Examples
255 ///
256 /// ```rust,no_run
257 /// use dbc_rs::Dbc;
258 ///
259 /// let dbc = Dbc::parse(r#"VERSION "1.0"
260 ///
261 /// BU_: ECM
262 ///
263 /// BO_ 256 Engine : 8 ECM
264 ///
265 /// CM_ "CAN database for powertrain";"#)?;
266 /// assert_eq!(dbc.comment(), Some("CAN database for powertrain"));
267 /// # Ok::<(), dbc_rs::Error>(())
268 /// ```
269 #[inline]
270 #[must_use = "return value should be used"]
271 pub fn comment(&self) -> Option<&str> {
272 self.comment.as_ref().map(|c| c.as_ref())
273 }
274
275 /// Returns the comment for a specific node from CM_ BU_ entry, if present.
276 ///
277 /// # Examples
278 ///
279 /// ```rust,no_run
280 /// use dbc_rs::Dbc;
281 ///
282 /// let dbc = Dbc::parse(r#"VERSION "1.0"
283 ///
284 /// BU_: ECM
285 ///
286 /// BO_ 256 Engine : 8 ECM
287 ///
288 /// CM_ BU_ ECM "Engine Control Module";"#)?;
289 /// assert_eq!(dbc.node_comment("ECM"), Some("Engine Control Module"));
290 /// # Ok::<(), dbc_rs::Error>(())
291 /// ```
292 #[inline]
293 #[must_use = "return value should be used"]
294 pub fn node_comment(&self, node_name: &str) -> Option<&str> {
295 self.nodes.node_comment(node_name)
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use crate::Dbc;
302
303 #[test]
304 fn test_parse_extended_multiplexing() {
305 let dbc = Dbc::parse(
306 r#"VERSION "1.0"
307
308BU_: ECM
309
310BO_ 500 ComplexMux : 8 ECM
311 SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
312 SG_ Signal_A m0 : 16|16@1+ (0.1,0) [0|100] "unit" *
313
314SG_MUL_VAL_ 500 Signal_A Mux1 5-10 ;
315"#,
316 )
317 .unwrap();
318
319 let ext_entries: crate::compat::Vec<_, { crate::MAX_EXTENDED_MULTIPLEXING }> =
320 dbc.extended_multiplexing_for_message(500).collect();
321 assert_eq!(
322 ext_entries.len(),
323 1,
324 "Extended multiplexing entry should be parsed"
325 );
326 assert_eq!(ext_entries[0].signal_name(), "Signal_A");
327 assert_eq!(ext_entries[0].multiplexer_switch(), "Mux1");
328 assert_eq!(ext_entries[0].value_ranges(), [(5, 10)]);
329 }
330
331 #[test]
332 fn test_version() {
333 let dbc = Dbc::parse(
334 r#"VERSION "1.0"
335
336BU_: ECM
337
338BO_ 256 Engine : 8 ECM
339"#,
340 )
341 .unwrap();
342 assert_eq!(dbc.version().map(|v| v.as_str()), Some("1.0"));
343 }
344
345 #[test]
346 fn test_nodes() {
347 let dbc = Dbc::parse(
348 r#"VERSION "1.0"
349
350BU_: ECM TCM
351
352BO_ 256 Engine : 8 ECM
353"#,
354 )
355 .unwrap();
356 assert_eq!(dbc.nodes().len(), 2);
357 assert!(dbc.nodes().contains("ECM"));
358 assert!(dbc.nodes().contains("TCM"));
359 }
360
361 #[test]
362 fn test_messages() {
363 let dbc = Dbc::parse(
364 r#"VERSION "1.0"
365
366BU_: ECM
367
368BO_ 256 Engine : 8 ECM
369"#,
370 )
371 .unwrap();
372 assert_eq!(dbc.messages().len(), 1);
373 let message = dbc.messages().at(0).unwrap();
374 assert_eq!(message.name(), "Engine");
375 assert_eq!(message.id(), 256);
376 }
377}