dbc_data/lib.rs
1//! A derive-macro which produces code to access signals within CAN
2//! messages, as described by a `.dbc` file. The generated code has
3//! very few dependencies: just core primitives and `[u8]` slices, and
4//! is `#[no_std]` compatible.
5//!
6//! # Changelog
7//! [CHANGELOG.md]
8//!
9//! # Example
10//! Given a `.dbc` file containing:
11//!
12//! ```text
13//! BO_ 1023 SomeMessage: 4 Ecu1
14//! SG_ Unsigned16 : 16|16@0+ (1,0) [0|0] "" Vector__XXX
15//! SG_ Unsigned8 : 8|8@1+ (1,0) [0|0] "" Vector__XXX
16//! SG_ Signed8 : 0|8@1- (1,0) [0|0] "" Vector__XXX
17//! ```
18//! The following code can be written to access the fields of the
19//! message:
20//!
21//! ```
22//! pub use dbc_data::*;
23//!
24//! #[derive(DbcData, Default)]
25//! #[dbc_file = "tests/example.dbc"]
26//! struct TestData {
27//! some_message: SomeMessage,
28//! }
29//!
30//! fn test() {
31//! let mut t = TestData::default();
32//!
33//! assert_eq!(SomeMessage::ID, 1023);
34//! assert_eq!(SomeMessage::DLC, 4);
35//! assert!(t.some_message.decode(&[0x12, 0x34, 0x56, 0x78]));
36//! assert_eq!(t.some_message.Signed8, 0x12);
37//! assert_eq!(t.some_message.Unsigned8, 0x34);
38//! assert_eq!(t.some_message.Unsigned16, 0x5678); // big-endian
39//! }
40//! ```
41//! See the test cases in this crate for examples of usage.
42//!
43//! # Code Generation
44//! This crate is aimed at embedded systems where typically some
45//! subset of the messages and signals defined in the `.dbc` file are
46//! of interest, and the rest can be ignored for a minimal footpint.
47//! If you need to decode the entire DBC into rich (possibly `std`-dependent)
48//! types to run on a host system, there are other crates for that
49//! such as `dbc_codegen`.
50//!
51//! ## Messages
52//! As `.dbc` files typically contain multiple messages, each of these
53//! can be brought into scope by referencing their name as a type
54//! (e.g. `SomeMessage` as shown above) and this determines what code
55//! is generated. Messages not referenced will not generate any code.
56//!
57//! When a range of message IDs contain the same signals, such as a
58//! series of readings which do not fit into a single message, then
59//! declaring an array will allow that type to be used for all of them.
60//!
61//! # Signals
62//! For cases where only certain signals within a message are needed, the
63//! `#[dbc_signals]` attribute lets you specify which ones are used.
64//!
65//! ## Types
66//! Single-bit signals generate `bool` types, and signals with a scale factor
67//! generate `f32` types. All other signals generate signed or unsigned
68//! native types which are large enough to fit the contained values, e.g.
69//! 13-bit signals will be stored in a `u16` and 17-bit signals will be
70//! stored in a `u32`.
71//!
72//! # Functionality
73//! * Decode signals from PDU into native types
74//! * const definitions for `ID: u32`, `DLC: u8`, `EXTENDED: bool`,
75//! and `CYCLE_TIME: usize` when present
76//! * Encode signal into PDU (except unaligned BE)
77//!
78//! # TODO
79//! * Encode unaligned BE signals
80//! * Generate dispatcher for decoding based on ID (including ranges)
81//! * Enforce that arrays of messages contain the same signals
82//! * Support multiplexed signals
83//! * (Maybe) scope generated types to a module
84//!
85//! # License
86//! [LICENSE-MIT]
87//!
88
89extern crate proc_macro;
90use can_dbc::{
91 AttributeValuedForObjectType, ByteOrder, MessageId, Signal, ValueType, DBC,
92};
93use proc_macro2::TokenStream;
94use quote::{quote, TokenStreamExt};
95use std::{collections::BTreeMap, fs::read};
96use syn::{
97 parse_macro_input, spanned::Spanned, Attribute, Data, DeriveInput, Expr,
98 Field, Fields, Ident, Lit, Meta, Result, Type,
99};
100
101struct DeriveData<'a> {
102 /// Name of the struct we are deriving for
103 #[allow(dead_code)]
104 name: &'a Ident,
105 /// The parsed DBC file
106 dbc: can_dbc::DBC,
107 /// All of the messages to derive
108 messages: BTreeMap<String, MessageInfo<'a>>,
109}
110
111struct MessageInfo<'a> {
112 id: u32,
113 extended: bool,
114 index: usize,
115 ident: &'a Ident,
116 attrs: &'a Vec<Attribute>,
117 cycle_time: Option<usize>,
118}
119
120/// Filter signals based on #[dbc_signals] list
121struct SignalFilter {
122 names: Vec<String>,
123}
124
125impl SignalFilter {
126 /// Create a signal filter from a message's attribute
127 fn new(message: &MessageInfo) -> Self {
128 let mut names: Vec<String> = vec![];
129 if let Some(attrs) = parse_attr(message.attrs, "dbc_signals") {
130 let list = attrs.split(",");
131 for name in list {
132 let name = name.trim();
133 names.push(name.to_string());
134 }
135 }
136 Self { names }
137 }
138
139 /// Return whether a signal should be used, i.e. whether it is
140 /// in the filter list or the list is empty
141 fn use_signal(&self, name: impl Into<String>) -> bool {
142 if self.names.is_empty() {
143 return true;
144 }
145 let name = name.into();
146 self.names.contains(&name)
147 }
148}
149
150/// Information about signal within message
151struct SignalInfo<'a> {
152 signal: &'a Signal,
153 ident: Ident,
154 ntype: Ident,
155 utype: Ident,
156 start: usize,
157 width: usize,
158 nwidth: usize,
159 scale: f32,
160 signed: bool,
161}
162
163impl<'a> SignalInfo<'a> {
164 fn new(signal: &'a Signal, message: &MessageInfo) -> Self {
165 // TODO: sanitize and/or change name format
166 let name = signal.name();
167 let signed = matches!(signal.value_type(), ValueType::Signed);
168 let width = *signal.signal_size() as usize;
169 let scale = *signal.factor() as f32;
170
171 // get storage width of signal data
172 let nwidth = match width {
173 1 => 1,
174 2..=8 => 8,
175 9..=16 => 16,
176 17..=32 => 32,
177 _ => 64,
178 };
179
180 let utype = if width == 1 {
181 "bool"
182 } else {
183 &format!("{}{}", if signed { "i" } else { "u" }, nwidth)
184 };
185
186 // get native type for signal
187 let ntype = if scale == 1.0 { utype } else { "f32" };
188
189 Self {
190 signal,
191 ident: Ident::new(name, message.ident.span()),
192 ntype: Ident::new(ntype, message.ident.span()),
193 utype: Ident::new(utype, message.ident.span()),
194 start: *signal.start_bit() as usize,
195 scale,
196 signed,
197 width,
198 nwidth,
199 }
200 }
201
202 /// Generate the code for extracting signal bits
203 fn extract_bits(&self) -> TokenStream {
204 let low = self.start / 8;
205 let left = self.start % 8;
206 let high = (self.start + self.width - 1) / 8;
207 let right = (self.start + self.width) % 8;
208 let utype = &self.utype;
209 let le = self.signal.byte_order() == &ByteOrder::LittleEndian;
210
211 let mut ts = TokenStream::new();
212 if self.width == self.nwidth && left == 0 {
213 // aligned
214 let ext = if le {
215 Ident::new("from_le_bytes", utype.span())
216 } else {
217 Ident::new("from_be_bytes", utype.span())
218 };
219 let tokens = match self.width {
220 8 => quote! {
221 #utype::#ext([pdu[#low]])
222 },
223 16 => quote! {
224 #utype::#ext([pdu[#low],
225 pdu[#low + 1]])
226 },
227 32 => quote! {
228 #utype::#ext([pdu[#low + 0],
229 pdu[#low + 1],
230 pdu[#low + 2],
231 pdu[#low + 3]])
232 },
233 // NOTE: this compiles to very small code and does not
234 // involve actually fetching 8 separate bytes; e.g. on
235 // armv7 an `ldrd` to get both 32-bit values followed by
236 // two `rev` instructions to reverse the bytes.
237 64 => quote! {
238 #utype::#ext([pdu[#low + 0],
239 pdu[#low + 1],
240 pdu[#low + 2],
241 pdu[#low + 3],
242 pdu[#low + 4],
243 pdu[#low + 5],
244 pdu[#low + 6],
245 pdu[#low + 7],
246 ])
247 },
248 _ => unimplemented!(),
249 };
250 ts.append_all(tokens);
251 } else {
252 if le {
253 let count = high - low;
254 for o in 0..=count {
255 let byte = low + o;
256 if o == 0 {
257 // first byte
258 ts.append_all(quote! {
259 let v = pdu[#byte] as #utype;
260 });
261 if left != 0 {
262 if count == 0 {
263 ts.append_all(quote! {
264 let v = (v >> #left) & ((1 << #left) - 1);
265 });
266 } else {
267 ts.append_all(quote! {
268 let v = v >> #left;
269 });
270 }
271 }
272 } else {
273 let shift = (o * 8) - left;
274 if o == count && right != 0 {
275 ts.append_all(quote! {
276 let v = v | (((pdu[#byte]
277 & ((1 << #right) - 1))
278 as #utype) << #shift);
279 });
280 } else {
281 ts.append_all(quote! {
282 let v = v | ((pdu[#byte] as #utype) << #shift);
283 });
284 }
285 }
286 }
287 } else {
288 // big-endian
289 let mut rem = self.width;
290 let mut byte = low;
291 while rem > 0 {
292 if byte == low {
293 // first byte
294 ts.append_all(quote! {
295 let v = pdu[#byte] as #utype;
296 });
297 if rem < 8 {
298 // single byte
299 let mask = rem - 1;
300 let shift = left + 1 - rem;
301 ts.append_all(quote! {
302 let mask: #utype = (1 << #mask)
303 | ((1 << #mask) - 1);
304 let v = (v >> #shift) & mask;
305 });
306 rem = 0;
307 } else {
308 // first of multiple bytes
309 let mask = left;
310 let shift = rem - left - 1;
311 if mask < 7 {
312 ts.append_all(quote! {
313 let mask: #utype = (1 << #mask)
314 | ((1 << #mask) - 1);
315 let v = (v & mask) << #shift;
316 });
317 } else {
318 ts.append_all(quote! {
319 let v = v << #shift;
320 });
321 }
322 rem -= left + 1;
323 }
324 byte += 1;
325 } else {
326 if rem < 8 {
327 // last byte: take top bits
328 let shift = 8 - rem;
329 ts.append_all(quote! {
330 let v = v |
331 ((pdu[#byte] as #utype) >> #shift);
332 });
333 rem = 0;
334 } else {
335 rem -= 8;
336 ts.append_all(quote! {
337 let v = v |
338 ((pdu[#byte] as #utype) << #rem);
339 });
340 byte += 1;
341 }
342 };
343 }
344 }
345 // perform sign-extension for values with fewer bits than
346 // the storage type
347 if self.signed && self.width < self.nwidth {
348 let mask = self.width - 1;
349 ts.append_all(quote! {
350 let mask: #utype = (1 << #mask);
351 let v = if (v & mask) != 0 {
352 let mask = mask | (mask - 1);
353 v | !mask
354 } else {
355 v
356 };
357 });
358 }
359 ts.append_all(quote! { v });
360 }
361 quote! { { #ts } }
362 }
363
364 fn gen_decoder(&self) -> TokenStream {
365 let name = &self.ident;
366 if self.width == 1 {
367 // boolean
368 let byte = self.start / 8;
369 let bit = self.start % 8;
370 quote! {
371 self.#name = (pdu[#byte] & (1 << #bit)) != 0;
372 }
373 } else {
374 let value = self.extract_bits();
375 let ntype = &self.ntype;
376 if !self.is_float() {
377 quote! {
378 self.#name = #value as #ntype;
379 }
380 } else {
381 let scale = self.scale;
382 let offset = *self.signal.offset() as f32;
383 quote! {
384 self.#name = ((#value as f32) * #scale) + #offset;
385 }
386 }
387 }
388 }
389
390 fn gen_encoder(&self) -> TokenStream {
391 let name = &self.ident;
392 let low = self.start / 8;
393 let mut byte = low;
394 let bit = self.start % 8;
395 if self.width == 1 {
396 // boolean
397 quote! {
398 let mask: u8 = (1 << #bit);
399 if self.#name {
400 pdu[#byte] |= mask;
401 } else {
402 pdu[#byte] &= !mask;
403 }
404 }
405 } else {
406 let utype = &self.utype;
407 let left = self.start % 8;
408 // let right = (self.start + self.width) % 8;
409 let le = self.signal.byte_order() == &ByteOrder::LittleEndian;
410
411 let mut ts = TokenStream::new();
412 if self.is_float() {
413 let scale = self.scale;
414 let offset = self.signal.offset as f32;
415 ts.append_all(quote! {
416 let v = ((self.#name - #offset) / #scale) as #utype;
417 });
418 } else {
419 ts.append_all(quote! {
420 let v = self.#name;
421 });
422 }
423 if le {
424 if self.width == self.nwidth && left == 0 {
425 // aligned little-endian
426 let mut bits = self.nwidth;
427 let mut shift = 0;
428 while bits >= 8 {
429 ts.append_all(quote! {
430 pdu[#byte] = ((v >> #shift) as u8) & 0xff;
431 });
432 bits -= 8;
433 byte += 1;
434 shift += 8;
435 }
436 } else {
437 // unaligned little-endian
438 let mut rem = self.width;
439 let mut lshift = left;
440 let mut rshift = 0;
441 while rem > 0 {
442 if rem < 8 {
443 let mask: u8 = (1 << rem) - 1;
444 let mask = mask << lshift;
445 ts.append_all(quote! {
446 pdu[#byte] = (pdu[#byte] & !#mask) |
447 ((((v >> #rshift) << (#lshift)) as u8) & #mask);
448 });
449 break;
450 }
451
452 if lshift != 0 {
453 let mask: u8 = (1 << (8 - left)) - 1;
454 let mask = mask << lshift;
455 ts.append_all(quote! {
456 pdu[#byte] = (pdu[#byte] & !#mask) |
457 ((((v >> #rshift) << (#lshift)) as u8) & #mask);
458 });
459 } else {
460 ts.append_all(quote! {
461 pdu[#byte] = ((v >> #rshift) & 0xff) as u8;
462 });
463 }
464
465 if byte == low {
466 rem -= 8 - left;
467 rshift += 8 - left;
468 } else {
469 rem -= 8;
470 rshift += 8;
471 }
472 byte += 1;
473 lshift = 0;
474 }
475 }
476 } else {
477 if self.width == self.nwidth && left == 7 {
478 // aligned big-endian
479 let mut bits = self.nwidth;
480 let mut shift = bits - 8;
481 let mut byte = (self.start - 7) / 8;
482 while bits >= 8 {
483 ts.append_all(quote! {
484 pdu[#byte] = ((v >> #shift) as u8) & 0xff;
485 });
486 bits -= 8;
487 byte += 1;
488 if shift >= 8 {
489 shift -= 8;
490 }
491 }
492 } else {
493 // unaligned big-endian
494 // todo!();
495 }
496 }
497 ts
498 }
499 }
500
501 fn is_float(&self) -> bool {
502 self.scale != 1.0
503 }
504}
505
506impl<'a> MessageInfo<'a> {
507 fn new(dbc: &DBC, field: &'a Field) -> Option<Self> {
508 let stype = match &field.ty {
509 Type::Path(v) => v,
510 Type::Array(a) => match *a.elem {
511 // TODO: validate that all signals match in ID range
512 Type::Path(ref v) => v,
513 _ => unimplemented!(),
514 },
515 _ => unimplemented!(),
516 };
517 let ident = &stype.path.segments[0].ident;
518 let name = ident.to_string();
519
520 for (index, message) in dbc.messages().iter().enumerate() {
521 if message.message_name() == &name {
522 let id = message.message_id();
523 let (id32, extended) = match *id {
524 MessageId::Standard(id) => (id as u32, false),
525 MessageId::Extended(id) => (id, true),
526 };
527 let mut cycle_time: Option<usize> = None;
528 for attr in dbc.attribute_values().iter() {
529 let value = attr.attribute_value();
530 use AttributeValuedForObjectType as AV;
531 match value {
532 AV::MessageDefinitionAttributeValue(aid, Some(av)) => {
533 if aid == id
534 && attr.attribute_name() == "GenMsgCycleTime"
535 {
536 cycle_time = Some(Self::attr_value(av));
537 }
538 }
539 _ => {}
540 }
541 }
542
543 return Some(Self {
544 id: id32,
545 extended,
546 index,
547 ident,
548 cycle_time,
549 attrs: &field.attrs,
550 });
551 }
552 }
553 None
554 }
555
556 // TODO: revisit this to handle type conversion better; we
557 // expect that the value fits in a usize for e.g. GenMsgCycleTime
558 fn attr_value(v: &can_dbc::AttributeValue) -> usize {
559 use can_dbc::AttributeValue as AV;
560 match v {
561 AV::AttributeValueU64(x) => *x as usize,
562 AV::AttributeValueI64(x) => *x as usize,
563 AV::AttributeValueF64(x) => *x as usize,
564 AV::AttributeValueCharString(_) => 0usize, // TODO: parse as int?
565 }
566 }
567}
568
569impl<'a> DeriveData<'a> {
570 fn from(input: &'a DeriveInput) -> Result<Self> {
571 // load the DBC file
572 let dbc_file = parse_attr(&input.attrs, "dbc_file")
573 .expect("No DBC file specified");
574 let contents = read(&dbc_file).expect("Could not read DBC");
575 let dbc = DBC::from_slice(&contents).expect("Could not parse DBC");
576
577 // gather all of the messages and associated attributes
578 let mut messages: BTreeMap<String, MessageInfo<'_>> =
579 Default::default();
580 match &input.data {
581 Data::Struct(data) => match &data.fields {
582 Fields::Named(fields) => {
583 for field in &fields.named {
584 if let Some(info) = MessageInfo::new(&dbc, field) {
585 messages.insert(info.ident.to_string(), info);
586 } else {
587 return Err(syn::Error::new(
588 field.span(),
589 "Unknown message",
590 ));
591 }
592 }
593 }
594 Fields::Unnamed(_) | Fields::Unit => unimplemented!(),
595 },
596 _ => unimplemented!(),
597 }
598
599 Ok(Self {
600 name: &input.ident,
601 dbc,
602 messages,
603 })
604 }
605
606 fn build(self) -> TokenStream {
607 let mut out = TokenStream::new();
608
609 for (name, message) in self.messages.iter() {
610 let m = self
611 .dbc
612 .messages()
613 .get(message.index)
614 .unwrap_or_else(|| panic!("Unknown message {name}"));
615
616 let filter = SignalFilter::new(message);
617
618 let mut signals: Vec<Ident> = vec![];
619 let mut types: Vec<Ident> = vec![];
620 let mut infos: Vec<SignalInfo> = vec![];
621 for s in m.signals().iter() {
622 if !filter.use_signal(s.name()) {
623 continue;
624 }
625
626 let signal = SignalInfo::new(s, message);
627 signals.push(signal.ident.clone());
628 types.push(signal.ntype.clone());
629 infos.push(signal);
630 }
631
632 let id = message.id;
633 let extended = message.extended;
634
635 let dlc = *m.message_size() as usize;
636 let dlc8 = dlc as u8;
637 let ident = message.ident;
638
639 // build signal decoders and encoders
640 let mut decoders = TokenStream::new();
641 let mut encoders = TokenStream::new();
642 for info in infos.iter() {
643 decoders.append_all(info.gen_decoder());
644 encoders.append_all(info.gen_encoder());
645 }
646 let cycle_time = if let Some(c) = message.cycle_time {
647 quote! {
648 const CYCLE_TIME: usize = #c;
649 }
650 } else {
651 quote! {}
652 };
653
654 out.append_all(quote! {
655 #[allow(dead_code)]
656 #[allow(non_snake_case)]
657 #[derive(Default)]
658 pub struct #ident {
659 #(
660 pub #signals: #types
661 ),*
662 }
663
664 impl #ident {
665 const ID: u32 = #id;
666 const DLC: u8 = #dlc8;
667 const EXTENDED: bool = #extended;
668 #cycle_time
669
670 pub fn decode(&mut self, pdu: &[u8])
671 -> bool {
672 if pdu.len() != #dlc {
673 return false
674 }
675 #decoders
676 true
677 }
678
679 pub fn encode(&mut self, pdu: &mut [u8])
680 -> bool {
681 if pdu.len() != #dlc {
682 return false
683 }
684 #encoders
685 true
686 }
687 }
688 });
689 }
690 out
691 }
692}
693
694#[proc_macro_derive(DbcData, attributes(dbc_file, dbc_signals))]
695pub fn dbc_data_derive(
696 input: proc_macro::TokenStream,
697) -> proc_macro::TokenStream {
698 derive_data(&parse_macro_input!(input as DeriveInput))
699 .unwrap_or_else(|err| err.to_compile_error())
700 .into()
701}
702
703fn derive_data(input: &DeriveInput) -> Result<TokenStream> {
704 Ok(DeriveData::from(input)?.build())
705}
706
707fn parse_attr(attrs: &[Attribute], name: &str) -> Option<String> {
708 let attr = attrs
709 .iter()
710 .filter(|a| {
711 a.path().segments.len() == 1 && a.path().segments[0].ident == name
712 })
713 .nth(0)?;
714
715 let expr = match &attr.meta {
716 Meta::NameValue(n) => Some(&n.value),
717 _ => None,
718 };
719
720 match &expr {
721 Some(Expr::Lit(e)) => match &e.lit {
722 Lit::Str(s) => Some(s.value()),
723 _ => None,
724 },
725 _ => None,
726 }
727}