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