dbc_rs/signal/impls.rs
1use super::Signal;
2use crate::{ByteOrder, Receivers};
3use core::hash::{Hash, Hasher};
4
5#[cfg(feature = "std")]
6use crate::MAX_NAME_SIZE;
7#[cfg(feature = "std")]
8use crate::compat::{Comment, String};
9
10impl Signal {
11 #[cfg(feature = "std")]
12 #[allow(clippy::too_many_arguments)] // Internal method, builder pattern is the public API
13 pub(crate) fn new(
14 name: String<{ MAX_NAME_SIZE }>,
15 start_bit: u16,
16 length: u16,
17 byte_order: ByteOrder,
18 unsigned: bool,
19 factor: f64,
20 offset: f64,
21 min: f64,
22 max: f64,
23 unit: Option<String<{ MAX_NAME_SIZE }>>,
24 receivers: Receivers,
25 comment: Option<Comment>,
26 ) -> Self {
27 // Validation should have been done prior (by builder or parse)
28 Self {
29 name,
30 start_bit,
31 length,
32 byte_order,
33 unsigned,
34 factor,
35 offset,
36 min,
37 max,
38 unit,
39 receivers,
40 is_multiplexer_switch: false,
41 multiplexer_switch_value: None,
42 comment,
43 }
44 }
45
46 /// Returns the signal name.
47 ///
48 /// # Examples
49 ///
50 /// ```rust,no_run
51 /// # use dbc_rs::Dbc;
52 /// # let dbc_content = "VERSION \"1.0\"\nBO_ 500 MSG_NAME: 8 ECU\n SG_ SIGNAL_NAME : 0|8@1+ (1,0) [0|0] \"\" ECU\n";
53 /// # let dbc = Dbc::parse(dbc_content).unwrap();
54 /// let message = dbc.messages().find("MSG_NAME").unwrap();
55 /// let signal = message.signals().find("SIGNAL_NAME").unwrap();
56 /// assert_eq!(signal.name(), "SIGNAL_NAME");
57 /// ```
58 #[inline]
59 #[must_use = "return value should be used"]
60 pub fn name(&self) -> &str {
61 self.name.as_ref()
62 }
63
64 /// Returns the start bit position of the signal in the CAN message payload.
65 ///
66 /// The start bit indicates where the signal begins in the message data.
67 /// For little-endian signals, this is the LSB position. For big-endian signals, this is the MSB position.
68 ///
69 /// # Examples
70 ///
71 /// ```rust,no_run
72 /// # use dbc_rs::Dbc;
73 /// # let dbc_content = "VERSION \"1.0\"\nBO_ 500 MSG_NAME: 8 ECU\n SG_ SIGNAL_NAME : 16|8@1+ (1,0) [0|0] \"\" ECU\n";
74 /// # let dbc = Dbc::parse(dbc_content).unwrap();
75 /// let message = dbc.messages().find("MSG_NAME").unwrap();
76 /// let signal = message.signals().find("SIGNAL_NAME").unwrap();
77 /// assert_eq!(signal.start_bit(), 16);
78 /// ```
79 #[inline]
80 #[must_use = "return value should be used"]
81 pub fn start_bit(&self) -> u16 {
82 self.start_bit
83 }
84
85 /// Returns the length of the signal in bits.
86 ///
87 /// # Examples
88 ///
89 /// ```rust,no_run
90 /// # use dbc_rs::Dbc;
91 /// # let dbc_content = "VERSION \"1.0\"\nBO_ 500 MSG_NAME: 8 ECU\n SG_ SIGNAL_NAME : 0|16@1+ (1,0) [0|0] \"\" ECU\n";
92 /// # let dbc = Dbc::parse(dbc_content).unwrap();
93 /// let message = dbc.messages().find("MSG_NAME").unwrap();
94 /// let signal = message.signals().find("SIGNAL_NAME").unwrap();
95 /// assert_eq!(signal.length(), 16);
96 /// ```
97 #[inline]
98 #[must_use = "return value should be used"]
99 pub fn length(&self) -> u16 {
100 self.length
101 }
102
103 /// Returns the byte order (endianness) of the signal.
104 ///
105 /// Returns either [`ByteOrder::LittleEndian`] (Intel format, `@1+`) or [`ByteOrder::BigEndian`] (Motorola format, `@0+`).
106 ///
107 /// # Examples
108 ///
109 /// ```rust,no_run
110 /// # use dbc_rs::{Dbc, ByteOrder};
111 /// # let dbc_content = "VERSION \"1.0\"\nBO_ 500 MSG_NAME: 8 ECU\n SG_ SIGNAL_NAME : 0|8@1+ (1,0) [0|0] \"\" ECU\n";
112 /// # let dbc = Dbc::parse(dbc_content).unwrap();
113 /// let message = dbc.messages().find("MSG_NAME").unwrap();
114 /// let signal = message.signals().find("SIGNAL_NAME").unwrap();
115 /// assert_eq!(signal.byte_order(), ByteOrder::LittleEndian);
116 /// ```
117 #[inline]
118 #[must_use = "return value should be used"]
119 pub fn byte_order(&self) -> ByteOrder {
120 self.byte_order
121 }
122
123 /// Returns `true` if the signal is unsigned, `false` if signed.
124 ///
125 /// In DBC format, `+` indicates unsigned and `-` indicates signed.
126 ///
127 /// # Examples
128 ///
129 /// ```rust,no_run
130 /// # use dbc_rs::Dbc;
131 /// # let dbc_content = "VERSION \"1.0\"\nBO_ 500 MSG_NAME: 8 ECU\n SG_ SIGNAL_NAME : 0|8@1+ (1,0) [0|0] \"\" ECU\n";
132 /// # let dbc = Dbc::parse(dbc_content).unwrap();
133 /// let message = dbc.messages().find("MSG_NAME").unwrap();
134 /// let signal = message.signals().find("SIGNAL_NAME").unwrap();
135 /// assert_eq!(signal.is_unsigned(), true);
136 /// ```
137 #[inline]
138 #[must_use = "return value should be used"]
139 pub fn is_unsigned(&self) -> bool {
140 self.unsigned
141 }
142
143 /// Returns the scaling factor applied to convert raw signal values to physical values.
144 ///
145 /// The physical value is calculated as: `physical_value = raw_value * factor + offset`
146 ///
147 /// # Examples
148 ///
149 /// ```rust,no_run
150 /// # use dbc_rs::Dbc;
151 /// # let dbc_content = "VERSION \"1.0\"\nBO_ 500 MSG_NAME: 8 ECU\n SG_ SIGNAL_NAME : 0|8@1+ (0.5,0) [0|0] \"\" ECU\n";
152 /// # let dbc = Dbc::parse(dbc_content).unwrap();
153 /// let message = dbc.messages().find("MSG_NAME").unwrap();
154 /// let signal = message.signals().find("SIGNAL_NAME").unwrap();
155 /// assert_eq!(signal.factor(), 0.5);
156 /// ```
157 #[inline]
158 #[must_use = "return value should be used"]
159 pub fn factor(&self) -> f64 {
160 self.factor
161 }
162
163 /// Returns the offset applied to convert raw signal values to physical values.
164 ///
165 /// The physical value is calculated as: `physical_value = raw_value * factor + offset`
166 ///
167 /// # Examples
168 ///
169 /// ```rust,no_run
170 /// # use dbc_rs::Dbc;
171 /// # let dbc_content = "VERSION \"1.0\"\nBO_ 500 MSG_NAME: 8 ECU\n SG_ SIGNAL_NAME : 0|8@1+ (1,-40) [0|0] \"\" ECU\n";
172 /// # let dbc = Dbc::parse(dbc_content).unwrap();
173 /// let message = dbc.messages().find("MSG_NAME").unwrap();
174 /// let signal = message.signals().find("SIGNAL_NAME").unwrap();
175 /// assert_eq!(signal.offset(), -40.0);
176 /// ```
177 #[inline]
178 #[must_use = "return value should be used"]
179 pub fn offset(&self) -> f64 {
180 self.offset
181 }
182
183 /// Returns the minimum physical value for this signal.
184 ///
185 /// # Examples
186 ///
187 /// ```rust,no_run
188 /// # use dbc_rs::Dbc;
189 /// # let dbc_content = "VERSION \"1.0\"\nBO_ 500 MSG_NAME: 8 ECU\n SG_ SIGNAL_NAME : 0|8@1+ (1,0) [-40|85] \"\" ECU\n";
190 /// # let dbc = Dbc::parse(dbc_content).unwrap();
191 /// let message = dbc.messages().find("MSG_NAME").unwrap();
192 /// let signal = message.signals().find("SIGNAL_NAME").unwrap();
193 /// assert_eq!(signal.min(), -40.0);
194 /// ```
195 #[inline]
196 #[must_use = "return value should be used"]
197 pub fn min(&self) -> f64 {
198 self.min
199 }
200
201 /// Returns the maximum physical value for this signal.
202 ///
203 /// # Examples
204 ///
205 /// ```rust,no_run
206 /// # use dbc_rs::Dbc;
207 /// # let dbc_content = "VERSION \"1.0\"\nBO_ 500 MSG_NAME: 8 ECU\n SG_ SIGNAL_NAME : 0|8@1+ (1,0) [-40|85] \"\" ECU\n";
208 /// # let dbc = Dbc::parse(dbc_content).unwrap();
209 /// let message = dbc.messages().find("MSG_NAME").unwrap();
210 /// let signal = message.signals().find("SIGNAL_NAME").unwrap();
211 /// assert_eq!(signal.max(), 85.0);
212 /// ```
213 #[inline]
214 #[must_use = "return value should be used"]
215 pub fn max(&self) -> f64 {
216 self.max
217 }
218
219 /// Returns the unit of measurement for this signal, if specified.
220 ///
221 /// Returns `None` if no unit was defined in the DBC file.
222 ///
223 /// # Examples
224 ///
225 /// ```rust,no_run
226 /// # use dbc_rs::Dbc;
227 /// # let dbc_content = "VERSION \"1.0\"\nBO_ 500 MSG_NAME: 8 ECU\n SG_ SIGNAL_NAME : 0|8@1+ (1,0) [0|0] \"km/h\" ECU\n";
228 /// # let dbc = Dbc::parse(dbc_content).unwrap();
229 /// let message = dbc.messages().find("MSG_NAME").unwrap();
230 /// let signal = message.signals().find("SIGNAL_NAME").unwrap();
231 /// assert_eq!(signal.unit(), Some("km/h"));
232 /// ```
233 #[inline]
234 #[must_use = "return value should be used"]
235 pub fn unit(&self) -> Option<&str> {
236 self.unit.as_ref().map(|u| u.as_ref())
237 }
238
239 /// Returns the receivers (ECU nodes) that subscribe to this signal.
240 ///
241 /// Returns a reference to a [`Receivers`] enum which can be either a list of node names or `None`.
242 ///
243 /// # Examples
244 ///
245 /// ```rust,no_run
246 /// # use dbc_rs::Dbc;
247 /// # let dbc_content = "VERSION \"1.0\"\nBO_ 500 MSG_NAME: 8 ECU1\n SG_ SIGNAL_NAME : 0|8@1+ (1,0) [0|0] \"\" ECU2,ECU3\n";
248 /// # let dbc = Dbc::parse(dbc_content).unwrap();
249 /// let message = dbc.messages().find("MSG_NAME").unwrap();
250 /// let signal = message.signals().find("SIGNAL_NAME").unwrap();
251 /// assert_eq!(signal.receivers().len(), 2);
252 /// assert!(signal.receivers().contains("ECU2"));
253 /// ```
254 #[inline]
255 #[must_use = "return value should be used"]
256 pub fn receivers(&self) -> &Receivers {
257 &self.receivers
258 }
259
260 /// Check if this signal is a multiplexer switch (marked with 'M')
261 #[inline]
262 #[must_use = "return value should be used"]
263 pub fn is_multiplexer_switch(&self) -> bool {
264 self.is_multiplexer_switch
265 }
266
267 /// Get the multiplexer switch value if this is a multiplexed signal (marked with 'm0', 'm1', etc.)
268 /// Returns None if this is a normal signal (not multiplexed)
269 #[inline]
270 #[must_use = "return value should be used"]
271 pub fn multiplexer_switch_value(&self) -> Option<u64> {
272 self.multiplexer_switch_value
273 }
274
275 /// Returns the signal comment from CM_ SG_ entry, if present.
276 #[inline]
277 #[must_use = "return value should be used"]
278 pub fn comment(&self) -> Option<&str> {
279 self.comment.as_ref().map(|c| c.as_ref())
280 }
281
282 /// Sets the signal comment (from CM_ SG_ entry).
283 /// Used internally during parsing when CM_ entries are processed after signals.
284 #[inline]
285 pub(crate) fn set_comment(&mut self, comment: crate::compat::Comment) {
286 self.comment = Some(comment);
287 }
288}
289
290impl PartialEq for Signal {
291 fn eq(&self, other: &Self) -> bool {
292 self.name == other.name
293 && self.start_bit == other.start_bit
294 && self.length == other.length
295 && self.byte_order == other.byte_order
296 && self.unsigned == other.unsigned
297 && canonical_f64_bits(self.factor) == canonical_f64_bits(other.factor)
298 && canonical_f64_bits(self.offset) == canonical_f64_bits(other.offset)
299 && canonical_f64_bits(self.min) == canonical_f64_bits(other.min)
300 && canonical_f64_bits(self.max) == canonical_f64_bits(other.max)
301 && self.unit == other.unit
302 && self.receivers == other.receivers
303 && self.is_multiplexer_switch == other.is_multiplexer_switch
304 && self.multiplexer_switch_value == other.multiplexer_switch_value
305 && self.comment == other.comment
306 }
307}
308
309// Custom Eq implementation that handles f64 (treats NaN as equal to NaN, and -0.0 == 0.0)
310impl Eq for Signal {}
311
312// Custom Hash implementation that handles f64 (treats NaN consistently)
313impl Hash for Signal {
314 fn hash<H: Hasher>(&self, state: &mut H) {
315 self.name.hash(state);
316 self.start_bit.hash(state);
317 self.length.hash(state);
318 self.byte_order.hash(state);
319 self.unsigned.hash(state);
320 // Handle f64: convert to bits for hashing (NaN will have consistent representation)
321 canonical_f64_bits(self.factor).hash(state);
322 canonical_f64_bits(self.offset).hash(state);
323 canonical_f64_bits(self.min).hash(state);
324 canonical_f64_bits(self.max).hash(state);
325 self.unit.hash(state);
326 self.receivers.hash(state);
327 self.is_multiplexer_switch.hash(state);
328 self.multiplexer_switch_value.hash(state);
329 self.comment.hash(state);
330 }
331}
332
333#[inline]
334fn canonical_f64_bits(v: f64) -> u64 {
335 // Ensure Hash/Eq are consistent and satisfy the contracts:
336
337 // - Treat -0.0 and 0.0 as equal (and hash identically)
338
339 // - Treat NaN values as equal (and hash identically)
340
341 if v == 0.0 {
342 0.0f64.to_bits()
343 } else if v.is_nan() {
344 f64::NAN.to_bits()
345 } else {
346 v.to_bits()
347 }
348}