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}