Skip to main content

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}