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