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