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