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//! An `enum` can also be used to derive the types for signals
42//! and messages.
43//!
44//! See the test cases in this crate for examples of usage.
45//!
46//! # Code Generation
47//! This crate is aimed at embedded systems where typically some
48//! subset of the messages and signals defined in the `.dbc` file are
49//! of interest, and the rest can be ignored for a minimal footprint.
50//! If you need to decode the entire DBC into rich (possibly
51//! `std`-dependent) types to run on a host system, there are other
52//! crates for that such as `dbc_codegen`.
53//!
54//! ## Messages
55//! As `.dbc` files typically contain multiple messages, each of these
56//! can be brought into scope by referencing their name as a type
57//! (e.g. `SomeMessage` as shown above) and this determines what code
58//! is generated.  Messages not referenced will not generate any code.
59//!
60//! When a range of message IDs contain the same signals, such as a
61//! series of readings which do not fit into a single message, then
62//! declaring an array will allow that type to be used for all of
63//! them.
64//!
65//! # Signals
66//! For cases where only certain signals within a message are needed,
67//! the `#[dbc_signals]` attribute lets you specify which ones are
68//! used.
69//!
70//! ## Types
71//! Single-bit signals generate `bool` types, and signals with a scale
72//! factor generate `f32` types.  All other signals generate signed or
73//! unsigned native types which are large enough to fit the contained
74//! values, e.g.  13-bit signals will be stored in a `u16` and 17-bit
75//! signals will be stored in a `u32`.
76//!
77//! ## Additional `#[derive(..._]`s
78//! To specify additional traits derived for the generated types, use
79//! the `#[dbc_derive(...)]` attribute with a comma-separated list of
80//! trait names.
81//!
82//! # Usage
83//! As DBC message names tend to follow different conventions from Rust
84//! code, it can be helpful to wrap them in `newtype` declarations.
85//! Additionally, it is often desirable to scope these identifiers away
86//! from application code by using a private module:
87//!
88//! ```ignore
89//! mod private {
90//!     use dbc_data::DbcData;
91//!     #[derive(DbcData)]
92//!     // (struct with DBC messages, e.g. some_Message_NAME)
93//! }
94//!
95//! pub type SomeMessageName = private::some_Message_NAME;
96//!
97//! ```
98//!
99//! The application uses this wrapped type without exposure to the
100//! DBC-centric naming.  The wrapped types can have their own `impl`
101//! block(s) to extend functionality, if desired.  Functions which
102//! perform operations on signals, define new constants, etc. can be
103//! added in such blocks.  The application can access signal fields
104//! directly from the underlying type and/or use the wrapped
105//! interfaces.
106//!
107//! # Functionality
108//! * Decode signals from PDU into native types
109//!     * const definitions for `ID: u32`, `DLC: u8`, `EXTENDED: bool`,
110//!       and `CYCLE_TIME: usize` when present
111//! * Encode signal into PDU (except unaligned BE)
112//!
113//! # TODO
114//! * Encode unaligned BE signals
115//! * Generate dispatcher for decoding based on ID (including ranges)
116//! * Enforce that arrays of messages contain the same signals
117//! * Support multiplexed signals
118//! * Emit `enum`s for value-tables, with optional type association
119//!
120//! # License
121//! [LICENSE-MIT]
122//!
123
124extern crate proc_macro;
125
126mod derive;
127mod message;
128mod signal;
129
130use derive::DeriveData;
131use message::MessageInfo;
132use proc_macro2::TokenStream;
133use syn::{Attribute, DeriveInput, Expr, Lit, Meta, Result, parse_macro_input};
134
135/// See the crate documentation for details.
136///
137/// The `#[dbc_file]` attribute specifies the name of the .dbc file
138/// to use, and is required.
139///
140/// Individual messages may specify a `#[dbc_signals]` attribute
141/// naming the individual signals of interest; otherwise, all
142/// signals within the message are generated.
143#[proc_macro_derive(DbcData, attributes(dbc_file, dbc_derive, dbc_signals))]
144pub fn dbc_data_derive(
145    input: proc_macro::TokenStream,
146) -> proc_macro::TokenStream {
147    derive_data(&parse_macro_input!(input as DeriveInput))
148        .unwrap_or_else(|err| err.to_compile_error())
149        .into()
150}
151
152fn derive_data(input: &DeriveInput) -> Result<TokenStream> {
153    Ok(DeriveData::from(input)?.build())
154}
155
156fn parse_attr(attrs: &[Attribute], name: &str) -> Option<String> {
157    let attr = attrs.iter().find(|a| {
158        a.path().segments.len() == 1 && a.path().segments[0].ident == name
159    })?;
160
161    let expr = match &attr.meta {
162        Meta::NameValue(n) => Some(&n.value),
163        _ => None,
164    };
165
166    match &expr {
167        Some(Expr::Lit(e)) => match &e.lit {
168            Lit::Str(s) => Some(s.value()),
169            _ => None,
170        },
171        _ => None,
172    }
173}