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.  The `Default`, `Copy`, and `Clone` traits are derived
81//! by default.
82//!
83//! # Usage
84//! As DBC message names tend to follow different conventions from Rust
85//! code, it can be helpful to wrap them in `newtype` declarations.
86//! Additionally, it is often desirable to scope these identifiers away
87//! from application code by using a private module:
88//!
89//! ```ignore
90//! mod private {
91//!     use dbc_data::DbcData;
92//!     #[derive(DbcData)]
93//!     // (struct with DBC messages, e.g. some_Message_NAME)
94//! }
95//!
96//! pub type SomeMessageName = private::some_Message_NAME;
97//!
98//! ```
99//!
100//! The application uses this wrapped type without exposure to the
101//! DBC-centric naming.  The wrapped types can have their own `impl`
102//! block(s) to extend functionality, if desired.  Functions which
103//! perform operations on signals, define new constants, etc. can be
104//! added in such blocks.  The application can access signal fields
105//! directly from the underlying type and/or use the wrapped
106//! interfaces.
107//!
108//! # Functionality
109//! * Decode signals from PDU into native types
110//!     * const definitions for `ID: u32`, `DLC: u8`, `EXTENDED: bool`,
111//!       and `CYCLE_TIME: usize` when present
112//! * Encode signal into PDU (except unaligned BE)
113//!
114//! # TODO
115//! * Encode unaligned BE signals
116//! * Generate dispatcher for decoding based on ID (including ranges)
117//! * Enforce that arrays of messages contain the same signals
118//! * Support multiplexed signals
119//! * Emit `enum`s for value-tables, with optional type association
120//!
121//! # License
122//! [LICENSE-MIT]
123//!
124
125extern crate proc_macro;
126
127mod derive;
128mod message;
129mod signal;
130
131use derive::DeriveData;
132use message::MessageInfo;
133use proc_macro2::TokenStream;
134use syn::{Attribute, DeriveInput, Expr, Lit, Meta, Result, parse_macro_input};
135
136/// See the crate documentation for details.
137///
138/// The `#[dbc_file]` attribute specifies the name of the .dbc file
139/// to use, and is required.
140///
141/// Individual messages may specify a `#[dbc_signals]` attribute
142/// naming the individual signals of interest; otherwise, all
143/// signals within the message are generated.
144#[proc_macro_derive(DbcData, attributes(dbc_file, dbc_derive, dbc_signals))]
145pub fn dbc_data_derive(
146    input: proc_macro::TokenStream,
147) -> proc_macro::TokenStream {
148    derive_data(&parse_macro_input!(input as DeriveInput))
149        .unwrap_or_else(|err| err.to_compile_error())
150        .into()
151}
152
153fn derive_data(input: &DeriveInput) -> Result<TokenStream> {
154    Ok(DeriveData::from(input)?.build())
155}
156
157fn parse_attr(attrs: &[Attribute], name: &str) -> Option<String> {
158    let attr = attrs.iter().find(|a| {
159        a.path().segments.len() == 1 && a.path().segments[0].ident == name
160    })?;
161
162    let expr = match &attr.meta {
163        Meta::NameValue(n) => Some(&n.value),
164        _ => None,
165    };
166
167    match &expr {
168        Some(Expr::Lit(e)) => match &e.lit {
169            Lit::Str(s) => Some(s.value()),
170            _ => None,
171        },
172        _ => None,
173    }
174}