Skip to main content

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