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}