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