dbc-codegen 0.4.0

CAN DBC code-gen for embedded Rust
Documentation
# CAN DBC code generator for Rust

[![GitHub repo](https://img.shields.io/badge/github-oxibus/dbc--codegen-8da0cb?logo=github)](https://github.com/oxibus/dbc-codegen)
[![crates.io version](https://img.shields.io/crates/v/dbc-codegen)](https://crates.io/crates/dbc-codegen)
[![crate usage](https://img.shields.io/crates/d/dbc-codegen)](https://crates.io/crates/dbc-codegen)
[![docs.rs status](https://img.shields.io/docsrs/dbc-codegen)](https://docs.rs/dbc-codegen)
[![crates.io license](https://img.shields.io/crates/l/dbc-codegen)](https://github.com/oxibus/dbc-codegen)
[![CI build status](https://github.com/oxibus/dbc-codegen/actions/workflows/ci.yml/badge.svg)](https://github.com/oxibus/dbc-codegen/actions)
[![Codecov](https://img.shields.io/codecov/c/github/oxibus/dbc-codegen)](https://app.codecov.io/gh/oxibus/dbc-codegen)

Generates Rust messages from a `dbc` file. DBC files are descriptions of CAN frames.
See [this post](https://www.kvaser.com/developer-blog/an-introduction-j1939-and-dbc-files/)
for an introduction.

## Installation

Install published version using [cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html):

```bash
cargo install dbc-codegen-cli
```
Install latest version from the git repository:

```bash
cargo install dbc-codegen-cli --git https://github.com/technocreatives/dbc-codegen --branch main
```

## Using dbc-codegen

Generate `messages.rs` from `example.dbc` using the CLI:

```bash
dbc-codegen testing/dbc-examples/example.dbc dir/where/messages_rs/file/is/written
```

Or put something like this into your `build.rs` file. Create a `Config` and pass it to `codegen` along with the contents of a DBC-file. See `Config` docs for a complete list of options.


```rust,no_run
use std::env::var;
use std::path::PathBuf;
use std::fs::read_to_string;

use dbc_codegen::{Config, FeatureConfig};

fn main() {
    let dbc_path = "../dbc-examples/example.dbc";
    let dbc_file = read_to_string(dbc_path).unwrap();
    println!("cargo:rerun-if-changed={dbc_path}");

    let path = PathBuf::from(var("OUT_DIR").unwrap()).join("messages.rs");

    Config::builder()
        .dbc_name("example.dbc")
        .dbc_content(&dbc_file)
        //.impl_arbitrary(FeatureConfig::Gated("arbitrary")) // optional
        //.impl_debug(FeatureConfig::Always)                 // optional
        .build()
        .write_to_file(path)
        .unwrap();
}
```

## Using generated Rust code

dbc-codegen generates a Rust file that is usually placed into the `OUT_DIR` directory.
Here is an example [`testing/can-messages/Cargo.toml`](testing/can-messages/Cargo.toml) which defines dependencies and features that are used in generated message file.

### Project setup

To use the code, add this code to your `lib.rs` (or `main.rs`):

```rust,ignore
// Import the generated code.
mod messages {
    include!(concat!(env!("OUT_DIR"), "/messages.rs"));
}
```

You will most likely want to interact with the generated `Messages` enum, and call `Messages::from_can_message(id, &payload)`.

Note: The generated code contains a lot of documentation.
Give it a try:

```bash
cargo doc --open
```

### Optional impls

The generator config has the following flags that control what code gets generated:

- `impl_debug`: enables `#[derive(Debug)]` for messages.
- `impl_arbitrary`: enables implementation of [`Arbitrary`]https://docs.rs/arbitrary/1.0.0/arbitrary/trait.Arbitrary.html trait.
  Also requires you to add `arbitrary` crate (version 1.x) as a dependency of the crate.
- `impl_error`: Implements `core::error::Error` for `CanError`. This makes it easy to use crates like `anyhow` for error handling.
- `check_ranges`: adds range checks in signal setters. (Enabled by default)

These implementations can be enabled, disabled, or placed behind feature guards, like so:

```rust,no_run
use dbc_codegen::{Config, FeatureConfig};

Config::builder()
    // this will generate Debug implementations
    .impl_debug(FeatureConfig::Always)

    // this will generate Error implementations behind a `#[cfg(feature = "std")]` guard
    .impl_error(FeatureConfig::Gated("std"))

    // this will disable range checks
    .check_ranges(FeatureConfig::Never);
```

### Attribute-driven constants

Many DBCs contain additional metadata in `BA_` attributes - for example, parameters describing an AUTOSAR E2E protection scheme for specific CAN signals. `attribute_structs` lets you expose this metadata as typed constants in the generated message types.

You can declare a struct in your own crate and map each field to a DBC attribute, derived signal, or a literal:

```rust,no_run
use dbc_codegen::{AttributeField, AttributeScope, AttributeStruct, Config, FieldSource};

// `data_protection::E2EDataIdInfo { data_id, start_byte, width_bit }` is a type defined in your crate.
let e2e = AttributeStruct {
    type_path: "data_protection::E2EDataIdInfo",
    const_name: "E2E",
    scope: AttributeScope::Signal,   // One const per matching signal
    require: "E2EDataId",            // Only signals carrying this attribute
    fields: &[
        AttributeField { name: "data_id",    source: FieldSource::Attr("E2EDataId") },
        AttributeField { name: "start_byte",  source: FieldSource::StartByte },
        AttributeField { name: "width_bit",   source: FieldSource::Attr("E2EDataLength") },
    ],
    for_node: None,
};

let dbc_file = "";
Config::builder()
    .dbc_name("example.dbc")
    .dbc_content(dbc_file)
    .attribute_structs(&[e2e])
    .build()
    .generate()
    .unwrap();
```

For every signal that carries an `E2EDataId` attribute, this generates:

```rust,ignore
impl SomeMessage {
    pub const SOME_SIGNAL_E2E: data_protection::E2EDataIdInfo =
        data_protection::E2EDataIdInfo { data_id: 373, start_byte: 0, width_bit: 48 };
}
```

DBCs can also attach attributes to relations between a node and a signal (`BA_DEF_REL_ BU_SG_REL_` / `BA_REL_`) or a node and a message (`BA_DEF_REL_ BU_BO_REL_` / `BA_REL_`), for example, a signal timeout. `AttributeScope::NodeSignal` emits one const per receiving node of a matching signal, and `AttributeScope::NodeMessage` emits one const per node related to a matching message. `FieldSource::NodeName` exposes the related node's name and is only valid with these two scopes:

```rust,no_run
use dbc_codegen::{AttributeField, AttributeScope, AttributeStruct, Config, FieldSource};

// `relation::SigTimeoutInfo { node, timeout_ms }` is a type defined in your crate.
let sig_timeout = AttributeStruct {
    type_path: "relation::SigTimeoutInfo",
    const_name: "SIG_TIMEOUT",
    scope: AttributeScope::NodeSignal,     // One const per receiving node of a matching signal
    require: "GenSigTimeoutTime",          // Only nodes carrying this relation attribute
    fields: &[
        AttributeField { name: "node",       source: FieldSource::NodeName },
        AttributeField { name: "timeout_ms", source: FieldSource::Attr("GenSigTimeoutTime") },
    ],
    for_node: None,
};

let dbc_file = "";
Config::builder()
    .dbc_name("example.dbc")
    .dbc_content(dbc_file)
    .attribute_structs(&[sig_timeout])
    .build()
    .generate()
    .unwrap();
```

For every signal and receiving node pair that carries a `GenSigTimeoutTime` relation attribute, this generates:

```rust,ignore
impl SomeMessage {
    pub const SOME_SIGNAL_ECU2_SIG_TIMEOUT: relation::SigTimeoutInfo =
        relation::SigTimeoutInfo { node: "ECU2", timeout_ms: 60 };
}
```

If you're generating code for a single node/ECU, you can set `for_node` to the node name to generates constants only for relation attributes that are relevant to the specific node. This also removes the node name from the constant names, making the generated code node/ECU name-agnostic.

```rust,no_run
use dbc_codegen::{AttributeField, AttributeScope, AttributeStruct, Config, FieldSource};

let sig_timeout_for_ecu2 = AttributeStruct {
    type_path: "relation::SigTimeoutInfo",
    const_name: "SIG_TIMEOUT",
    scope: AttributeScope::NodeSignal,
    require: "GenSigTimeoutTime",
    fields: &[
        AttributeField { name: "node",       source: FieldSource::NodeName },
        AttributeField { name: "timeout_ms", source: FieldSource::Attr("GenSigTimeoutTime") },
    ],
    for_node: Some("ECU2"),
};

let dbc_file = "";
Config::builder()
    .dbc_name("example.dbc")
    .dbc_content(dbc_file)
    .attribute_structs(&[sig_timeout_for_ecu2])
    .build()
    .generate()
    .unwrap();
```

```rust,ignore
impl SomeMessage {
    pub const SOME_SIGNAL_SIG_TIMEOUT: relation::SigTimeoutInfo =
        relation::SigTimeoutInfo { node: "ECU2", timeout_ms: 60 };
}
```

### `no_std`

The generated code is `no_std` compatible.

### Field/variant rename rules

If some field name starts with a non-alphabetic character or is a Rust keyword then it is prefixed with `x`.

For example:

```dbc
VAL_ 512 Five 0 "0Off" 1 "1On" 2 "2Oner" 3 "3Onest";
```

…is generated as:

```rust
pub enum BarFive {
    X0off,
    X1on,
    X2oner,
    X3onest,
    _Other(bool),
}
```

`Type` here:

```dbc
SG_ Type : 30|1@0+ (1,0) [0|1] "boolean" Dolor
```

…conflicts with the Rust keyword `type`. Therefore, we prefix it with `x`:

```rust,ignore
pub fn xtype(&self) -> BarType {
    match self.xtype_phys_val() {
        false => BarType::X0off,
        true => BarType::X1on,
        x => BarType::_Other(x),
    }
}
```

## Development
* This project is easier to develop with [just]https://just.systems/man/en/, a modern alternative to `make`.
* To get a list of available commands, run `just`.
* To run tests, use `just test`.
* This project uses [insta]https://insta.rs for snapshot testing. To update the snapshots run `just bless`

### lorri for Nix

If using Nix, dbc-codegen is integrated with [lorri](https://github.com/nix-community/lorri) for easy project dependency management. To enable, create a symlink in the top-level working directory:

```sh
ln -s envrc.lorri .envrc
```

## License

Licensed under either of

* Apache License, Version 2.0 ([LICENSE-APACHE]LICENSE-APACHE or <https://www.apache.org/licenses/LICENSE-2.0>)
* MIT license ([LICENSE-MIT]LICENSE-MIT or <https://opensource.org/licenses/MIT>)
  at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the
Apache-2.0 license, shall be dual-licensed as above, without any
additional terms or conditions.