1use crate::{Dbc, Error, Message, Result, compat::Vec};
2
3const MAX_PAYLOAD_SIZE: usize = 64;
5
6impl Dbc {
7 #[inline]
69 pub fn encode(
70 &self,
71 id: u32,
72 signals: &[(&str, f64)],
73 is_extended: bool,
74 ) -> Result<Vec<u8, MAX_PAYLOAD_SIZE>> {
75 let id = if is_extended {
77 id | Message::EXTENDED_ID_FLAG
78 } else {
79 id
80 };
81
82 let message = self
84 .messages()
85 .find_by_id(id)
86 .ok_or(Error::Encoding(Error::MESSAGE_NOT_FOUND))?;
87
88 let dlc = message.dlc() as usize;
90 let mut payload: Vec<u8, MAX_PAYLOAD_SIZE> = Vec::new();
91 for _ in 0..dlc {
92 payload.push(0).map_err(|_| Error::Encoding(Error::MESSAGE_DLC_TOO_LARGE))?;
93 }
94
95 for &(signal_name, physical_value) in signals {
97 let signal = message
99 .signals()
100 .iter()
101 .find(|s| s.name() == signal_name)
102 .ok_or(Error::Encoding(Error::ENCODING_SIGNAL_NOT_FOUND))?;
103
104 signal.encode_to(physical_value, payload.as_mut_slice())?;
106 }
107
108 Ok(payload)
109 }
110
111 #[cfg(feature = "embedded-can")]
130 #[inline]
131 pub fn encode_for_id(
132 &self,
133 id: embedded_can::Id,
134 signals: &[(&str, f64)],
135 ) -> Result<Vec<u8, MAX_PAYLOAD_SIZE>> {
136 match id {
137 embedded_can::Id::Standard(std_id) => {
138 self.encode(std_id.as_raw() as u32, signals, false)
139 }
140 embedded_can::Id::Extended(ext_id) => self.encode(ext_id.as_raw(), signals, true),
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use crate::Dbc;
148
149 #[test]
150 fn test_encode_basic() {
151 let dbc = Dbc::parse(
152 r#"VERSION "1.0"
153
154BU_: ECM
155
156BO_ 256 Engine : 8 ECM
157 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
158"#,
159 )
160 .unwrap();
161
162 let payload = dbc.encode(256, &[("RPM", 2000.0)], false).unwrap();
164 assert_eq!(payload.len(), 8);
165 assert_eq!(payload[0], 0x40);
167 assert_eq!(payload[1], 0x1F);
168 }
169
170 #[test]
171 fn test_encode_multiple_signals() {
172 let dbc = Dbc::parse(
173 r#"VERSION "1.0"
174
175BU_: ECM
176
177BO_ 256 Engine : 8 ECM
178 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
179 SG_ Temp : 16|8@1- (1,-40) [-40|215] "°C" *
180"#,
181 )
182 .unwrap();
183
184 let payload = dbc.encode(256, &[("RPM", 2000.0), ("Temp", 50.0)], false).unwrap();
188 assert_eq!(payload.len(), 8);
189 assert_eq!(payload[0], 0x40); assert_eq!(payload[1], 0x1F); assert_eq!(payload[2], 0x5A); }
193
194 #[test]
195 fn test_encode_decode_roundtrip() {
196 let dbc = Dbc::parse(
197 r#"VERSION "1.0"
198
199BU_: ECM
200
201BO_ 256 Engine : 8 ECM
202 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
203 SG_ Temp : 16|8@1- (1,-40) [-40|215] "°C" *
204 SG_ Throttle : 24|8@1+ (1,0) [0|100] "%" *
205"#,
206 )
207 .unwrap();
208
209 let rpm = 3500.0;
211 let temp = 85.0;
212 let throttle = 42.0;
213
214 let payload = dbc
216 .encode(
217 256,
218 &[("RPM", rpm), ("Temp", temp), ("Throttle", throttle)],
219 false,
220 )
221 .unwrap();
222
223 let decoded = dbc.decode(256, &payload, false).unwrap();
225
226 let find_value = |name: &str| decoded.iter().find(|s| s.name == name).map(|s| s.value);
228 assert!((find_value("RPM").unwrap() - rpm).abs() < 0.5); assert!((find_value("Temp").unwrap() - temp).abs() < 0.5);
230 assert!((find_value("Throttle").unwrap() - throttle).abs() < 0.5);
231 }
232
233 #[test]
234 fn test_encode_message_not_found() {
235 let dbc = Dbc::parse(
236 r#"VERSION "1.0"
237
238BU_: ECM
239
240BO_ 256 Engine : 8 ECM
241 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
242"#,
243 )
244 .unwrap();
245
246 let result = dbc.encode(512, &[("RPM", 2000.0)], false);
247 assert!(result.is_err());
248 }
249
250 #[test]
251 fn test_encode_signal_not_found() {
252 let dbc = Dbc::parse(
253 r#"VERSION "1.0"
254
255BU_: ECM
256
257BO_ 256 Engine : 8 ECM
258 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
259"#,
260 )
261 .unwrap();
262
263 let result = dbc.encode(256, &[("NonExistent", 100.0)], false);
264 assert!(result.is_err());
265 }
266
267 #[test]
268 fn test_encode_value_out_of_range() {
269 let dbc = Dbc::parse(
270 r#"VERSION "1.0"
271
272BU_: ECM
273
274BO_ 256 Engine : 8 ECM
275 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
276"#,
277 )
278 .unwrap();
279
280 let result = dbc.encode(256, &[("RPM", 9000.0)], false);
282 assert!(result.is_err());
283
284 let result = dbc.encode(256, &[("RPM", -100.0)], false);
286 assert!(result.is_err());
287 }
288
289 #[test]
290 fn test_encode_signed_signal() {
291 let dbc = Dbc::parse(
292 r#"VERSION "1.0"
293
294BU_: ECM
295
296BO_ 256 Engine : 8 ECM
297 SG_ Torque : 0|16@1- (0.01,0) [-327.68|327.67] "Nm" *
298"#,
299 )
300 .unwrap();
301
302 let payload = dbc.encode(256, &[("Torque", -10.0)], false).unwrap();
304 assert_eq!(payload[0], 0x18); assert_eq!(payload[1], 0xFC); let decoded = dbc.decode(256, &payload, false).unwrap();
309 assert!((decoded[0].value - (-10.0)).abs() < 0.01);
310 }
311
312 #[test]
313 fn test_encode_big_endian() {
314 let dbc = Dbc::parse(
315 r#"VERSION "1.0"
316
317BU_: ECM
318
319BO_ 256 Engine : 8 ECM
320 SG_ Pressure : 7|16@0+ (0.01,0) [0|655.35] "kPa" *
321"#,
322 )
323 .unwrap();
324
325 let payload = dbc.encode(256, &[("Pressure", 10.0)], false).unwrap();
327 assert_eq!(payload[0], 0x03); assert_eq!(payload[1], 0xE8); let decoded = dbc.decode(256, &payload, false).unwrap();
332 assert!((decoded[0].value - 10.0).abs() < 0.01);
333 }
334
335 #[test]
336 fn test_encode_extended_can_id() {
337 let dbc = Dbc::parse(
339 r#"VERSION "1.0"
340
341BU_: ECM
342
343BO_ 2147484672 ExtendedMsg : 8 ECM
344 SG_ Speed : 0|16@1+ (0.1,0) [0|6553.5] "km/h" *
345"#,
346 )
347 .unwrap();
348
349 let payload = dbc.encode(0x400, &[("Speed", 100.0)], true).unwrap();
351 assert_eq!(payload[0], 0xE8); assert_eq!(payload[1], 0x03);
353
354 let decoded = dbc.decode(0x400, &payload, true).unwrap();
356 assert!((decoded[0].value - 100.0).abs() < 0.1);
357 }
358
359 #[test]
360 fn test_encode_multiplexed_signal() {
361 let dbc = Dbc::parse(
362 r#"VERSION "1.0"
363
364BU_: ECM
365
366BO_ 300 Sensors : 8 ECM
367 SG_ SensorID M : 0|8@1+ (1,0) [0|3] ""
368 SG_ Temperature m0 : 8|16@1- (0.1,-40) [-40|125] "°C" *
369 SG_ Pressure m1 : 8|16@1+ (0.01,0) [0|655.35] "kPa" *
370"#,
371 )
372 .unwrap();
373
374 let payload = dbc.encode(300, &[("SensorID", 0.0), ("Temperature", 25.0)], false).unwrap();
376
377 assert_eq!(payload[0], 0x00);
379
380 assert_eq!(payload[1], 0x8A);
383 assert_eq!(payload[2], 0x02);
384
385 let decoded = dbc.decode(300, &payload, false).unwrap();
387 let find_value = |name: &str| decoded.iter().find(|s| s.name == name).map(|s| s.value);
388 assert_eq!(find_value("SensorID"), Some(0.0));
389 assert!((find_value("Temperature").unwrap() - 25.0).abs() < 0.1);
390 assert!(find_value("Pressure").is_none());
392 }
393
394 #[test]
395 fn test_encode_preserves_unset_bits() {
396 let dbc = Dbc::parse(
398 r#"VERSION "1.0"
399
400BU_: ECM
401
402BO_ 256 Engine : 8 ECM
403 SG_ SignalA : 0|8@1+ (1,0) [0|255] ""
404 SG_ SignalB : 8|8@1+ (1,0) [0|255] ""
405"#,
406 )
407 .unwrap();
408
409 let payload = dbc.encode(256, &[("SignalA", 100.0)], false).unwrap();
411 assert_eq!(payload[0], 100);
412 assert_eq!(payload[1], 0); let payload = dbc.encode(256, &[("SignalB", 200.0)], false).unwrap();
416 assert_eq!(payload[0], 0); assert_eq!(payload[1], 200);
418
419 let payload = dbc.encode(256, &[("SignalA", 100.0), ("SignalB", 200.0)], false).unwrap();
421 assert_eq!(payload[0], 100);
422 assert_eq!(payload[1], 200);
423 }
424
425 #[cfg(feature = "embedded-can")]
426 mod embedded_can_tests {
427 use super::*;
428 use embedded_can::{ExtendedId, Id, StandardId};
429
430 #[test]
431 fn test_encode_for_id_standard() {
432 let dbc = Dbc::parse(
433 r#"VERSION "1.0"
434
435BU_: ECM
436
437BO_ 256 Engine : 8 ECM
438 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
439"#,
440 )
441 .unwrap();
442
443 let std_id = Id::Standard(StandardId::new(256).unwrap());
444 let payload = dbc.encode_for_id(std_id, &[("RPM", 2000.0)]).unwrap();
445 assert_eq!(payload[0], 0x40);
446 assert_eq!(payload[1], 0x1F);
447 }
448
449 #[test]
450 fn test_encode_for_id_extended() {
451 let dbc = Dbc::parse(
453 r#"VERSION "1.0"
454
455BU_: ECM
456
457BO_ 2147484672 ExtendedMsg : 8 ECM
458 SG_ Speed : 0|16@1+ (0.1,0) [0|6553.5] "km/h" *
459"#,
460 )
461 .unwrap();
462
463 let ext_id = Id::Extended(ExtendedId::new(0x400).unwrap());
464 let payload = dbc.encode_for_id(ext_id, &[("Speed", 100.0)]).unwrap();
465 assert_eq!(payload[0], 0xE8);
466 assert_eq!(payload[1], 0x03);
467 }
468 }
469}