Skip to main content

dbc_rs/signal/
encode.rs

1use super::Signal;
2use crate::{Error, Result};
3
4/// Round to nearest integer (half away from zero).
5/// Equivalent to libm::round but without the dependency.
6#[inline]
7fn round(x: f64) -> f64 {
8    // Cast to i64 truncates towards zero, so we add/subtract 0.5 first
9    if x >= 0.0 {
10        (x + 0.5) as i64 as f64
11    } else {
12        (x - 0.5) as i64 as f64
13    }
14}
15
16impl Signal {
17    /// Encode a physical value to raw bits for this signal.
18    ///
19    /// This is the inverse of `decode_raw()`. It converts a physical value
20    /// (after factor and offset have been applied) back to the raw integer
21    /// value that can be inserted into a CAN message payload.
22    ///
23    /// # Arguments
24    ///
25    /// * `physical_value` - The physical value to encode (e.g., 2000.0 for RPM)
26    ///
27    /// # Returns
28    ///
29    /// * `Ok(raw_bits)` - The raw bits ready to be inserted into the payload
30    /// * `Err(Error)` - If the value is outside the signal's min/max range
31    ///
32    /// # Formula
33    ///
34    /// ```text
35    /// raw_value = (physical_value - offset) / factor
36    /// ```
37    #[inline]
38    pub fn encode_raw(&self, physical_value: f64) -> Result<u64> {
39        // Reject NaN / infinity: these compare false against both bounds and would
40        // otherwise poison rounding and the downstream cast.
41        if !physical_value.is_finite() {
42            return Err(Error::Encoding(Error::ENCODING_VALUE_OUT_OF_RANGE));
43        }
44
45        // Validate value is within min/max range
46        if physical_value < self.min || physical_value > self.max {
47            return Err(Error::Encoding(Error::ENCODING_VALUE_OUT_OF_RANGE));
48        }
49
50        // Reverse the decode formula: raw = (physical - offset) / factor
51        // Handle factor == 0 to avoid division by zero (shouldn't happen in valid DBC)
52        let raw_float = if self.factor != 0.0 {
53            (physical_value - self.offset) / self.factor
54        } else {
55            // If factor is 0, physical == offset always, so raw can be 0
56            0.0
57        };
58
59        // Round to nearest integer
60        let raw_signed = round(raw_float) as i64;
61
62        // Handle signed vs unsigned encoding
63        let raw_bits = if self.unsigned {
64            // Unsigned: ensure non-negative and fits in bit length
65            if raw_signed < 0 {
66                return Err(Error::Encoding(Error::ENCODING_VALUE_OVERFLOW));
67            }
68            let raw_unsigned = raw_signed as u64;
69            let max_value = if self.length >= 64 {
70                u64::MAX
71            } else {
72                (1u64 << self.length) - 1
73            };
74            if raw_unsigned > max_value {
75                return Err(Error::Encoding(Error::ENCODING_VALUE_OVERFLOW));
76            }
77            raw_unsigned
78        } else {
79            // Signed: use two's complement encoding
80            // Check if value fits in signed range for this bit length.
81            // For a full-width 64-bit signal `1i64 << 63` is i64::MIN, and negating
82            // it would overflow, so use the natural i64 bounds directly.
83            let (min_signed, max_signed) = if self.length >= 64 {
84                (i64::MIN, i64::MAX)
85            } else {
86                let half_range = 1i64 << (self.length - 1);
87                (-half_range, half_range - 1)
88            };
89
90            if raw_signed < min_signed || raw_signed > max_signed {
91                return Err(Error::Encoding(Error::ENCODING_VALUE_OVERFLOW));
92            }
93
94            // Convert to two's complement representation
95            // For positive values, just cast. For negative, mask to bit length.
96            if raw_signed >= 0 {
97                raw_signed as u64
98            } else {
99                // Two's complement: mask to signal bit length
100                let mask = if self.length >= 64 {
101                    u64::MAX
102                } else {
103                    (1u64 << self.length) - 1
104                };
105                (raw_signed as u64) & mask
106            }
107        };
108
109        Ok(raw_bits)
110    }
111
112    /// Encode a physical value and insert it into a payload buffer.
113    ///
114    /// This is a convenience method that combines `encode_raw()` with
115    /// `ByteOrder::insert_bits()` to directly write the encoded value
116    /// into a CAN message payload.
117    ///
118    /// # Arguments
119    ///
120    /// * `physical_value` - The physical value to encode
121    /// * `payload` - The mutable payload buffer to write into
122    ///
123    /// # Returns
124    ///
125    /// * `Ok(())` - Value was successfully encoded and written
126    /// * `Err(Error)` - If encoding failed or signal extends beyond payload
127    #[inline]
128    pub fn encode_to(&self, physical_value: f64, payload: &mut [u8]) -> Result<()> {
129        let start_bit = self.start_bit as usize;
130        let length = self.length as usize;
131
132        // Byte-order-aware span check (see `decode_raw`): big-endian signals must be
133        // bounded by their true physical span, not the little-endian forward span.
134        if self.required_len() > payload.len() {
135            return Err(Error::Encoding(Error::SIGNAL_EXTENDS_BEYOND_DATA));
136        }
137
138        let raw_bits = self.encode_raw(physical_value)?;
139        self.byte_order.insert_bits(payload, start_bit, length, raw_bits);
140        Ok(())
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::Signal;
147    use crate::Parser;
148
149    #[test]
150    fn test_encode_raw_unsigned() {
151        // Signal: 16-bit unsigned, factor=0.25, offset=0
152        // Physical 2000.0 -> raw = 2000 / 0.25 = 8000
153        let signal = Signal::parse(
154            &mut Parser::new(b"SG_ RPM : 0|16@1+ (0.25,0) [0|8000] \"rpm\"").unwrap(),
155        )
156        .unwrap();
157
158        let raw = signal.encode_raw(2000.0).unwrap();
159        assert_eq!(raw, 8000);
160    }
161
162    #[test]
163    fn test_encode_raw_with_offset() {
164        // Signal: 8-bit signed, factor=1, offset=-40
165        // Physical 50.0 -> raw = (50 - (-40)) / 1 = 90
166        let signal =
167            Signal::parse(&mut Parser::new(b"SG_ Temp : 16|8@1- (1,-40) [-40|87] \"\"").unwrap())
168                .unwrap();
169
170        let raw = signal.encode_raw(50.0).unwrap();
171        assert_eq!(raw, 90);
172    }
173
174    #[test]
175    fn test_encode_raw_signed_negative() {
176        // Signal: 16-bit signed, factor=0.01, offset=0
177        // Physical -10.0 -> raw = -10 / 0.01 = -1000
178        let signal = Signal::parse(
179            &mut Parser::new(b"SG_ Torque : 0|16@1- (0.01,0) [-327.68|327.67] \"Nm\"").unwrap(),
180        )
181        .unwrap();
182
183        let raw = signal.encode_raw(-10.0).unwrap();
184        // -1000 in 16-bit two's complement = 0xFC18
185        assert_eq!(raw, 0xFC18);
186    }
187
188    #[test]
189    fn test_encode_raw_out_of_range() {
190        let signal = Signal::parse(
191            &mut Parser::new(b"SG_ RPM : 0|16@1+ (0.25,0) [0|8000] \"rpm\"").unwrap(),
192        )
193        .unwrap();
194
195        // Value above max
196        let result = signal.encode_raw(9000.0);
197        assert!(result.is_err());
198
199        // Value below min
200        let result = signal.encode_raw(-100.0);
201        assert!(result.is_err());
202    }
203
204    #[test]
205    fn test_encode_decode_roundtrip() {
206        // Test that encode(decode(x)) == x for the raw value
207        let signal = Signal::parse(
208            &mut Parser::new(b"SG_ Speed : 0|16@1+ (0.1,0) [0|6553.5] \"km/h\"").unwrap(),
209        )
210        .unwrap();
211
212        // Encode physical value 100.0
213        let raw = signal.encode_raw(100.0).unwrap();
214        assert_eq!(raw, 1000); // 100 / 0.1 = 1000
215
216        // Decode the raw value back
217        let data = [0xE8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; // 1000 in LE
218        let (decoded_raw, decoded_physical) = signal.decode_raw(&data).unwrap();
219        assert_eq!(decoded_raw, 1000);
220        assert!((decoded_physical - 100.0).abs() < 0.001);
221    }
222
223    #[test]
224    fn test_encode_to_little_endian() {
225        let signal = Signal::parse(
226            &mut Parser::new(b"SG_ Speed : 0|16@1+ (0.1,0) [0|6553.5] \"km/h\"").unwrap(),
227        )
228        .unwrap();
229
230        let mut payload = [0x00; 8];
231        signal.encode_to(100.0, &mut payload).unwrap();
232
233        // 100.0 / 0.1 = 1000 = 0x03E8, little-endian: [0xE8, 0x03]
234        assert_eq!(payload[0], 0xE8);
235        assert_eq!(payload[1], 0x03);
236    }
237
238    #[test]
239    fn test_encode_to_big_endian() {
240        let signal = Signal::parse(
241            &mut Parser::new(b"SG_ Pressure : 7|16@0+ (0.01,0) [0|655.35] \"kPa\"").unwrap(),
242        )
243        .unwrap();
244
245        let mut payload = [0x00; 8];
246        signal.encode_to(10.0, &mut payload).unwrap();
247
248        // 10.0 / 0.01 = 1000 = 0x03E8, big-endian at bit 7
249        assert_eq!(payload[0], 0x03);
250        assert_eq!(payload[1], 0xE8);
251    }
252
253    #[test]
254    fn test_encode_to_at_offset() {
255        let signal =
256            Signal::parse(&mut Parser::new(b"SG_ Throttle : 24|8@1+ (1,0) [0|100] \"%\"").unwrap())
257                .unwrap();
258
259        let mut payload = [0x00; 8];
260        signal.encode_to(75.0, &mut payload).unwrap();
261
262        // 75 should be at byte 3 (bit 24)
263        assert_eq!(payload[3], 75);
264    }
265
266    #[test]
267    fn test_encode_to_preserves_other_bits() {
268        let signal =
269            Signal::parse(&mut Parser::new(b"SG_ Gear : 8|8@1+ (1,0) [0|5] \"\"").unwrap())
270                .unwrap();
271
272        let mut payload = [0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
273        signal.encode_to(3.0, &mut payload).unwrap();
274
275        // Byte 0 and 2+ should be preserved
276        assert_eq!(payload[0], 0xFF);
277        assert_eq!(payload[1], 3);
278        assert_eq!(payload[2], 0xFF);
279    }
280}