#dbc #can #automotive #ecu #socketcan

bin+lib dbcc

Compiles data base CAN (dbc) files into Rust code

17 releases (8 stable)

2.2.1 Nov 5, 2020
2.2.0 Oct 23, 2020
2.1.2 Sep 23, 2020
2.1.0 Jul 22, 2020
1.0.1 Apr 10, 2019

#3 in #can

43 downloads per month

MIT license

295KB
519 lines

dbcc Build Status

=============

dbcc can translate data base CAN files into Rust code. The generated code allows interacting with CAN signals in a type safe manner by e.g. matching against signal value enum types. Furthermore it provides a convenient way to use SocketCAN BCM Sockets, via tokio streams, to filter for a specified message by can identifier.

Features

  • Generate message, signal decoder code
  • Generate message id constants
  • Generate enums for matching against signal values
  • Generate tokio streams for CAN messages
  • Generate message, signal encoders

Option 1 - Run CLI

Install

cargo install dbcc

Generate code using the CLI.

dbcc --input dbcc --with-tokio -i examples/j1939.dbc > examples/gen/j1939.rs

For warnings during the generation run with:

RUST_LOG=info dbcc --with-tokio -i examples/j1939.dbc > examples/gen/j1939.rs

Option 2 - build.rs

Generate code at build time. Add the following to your build.rs. Adapt the dbc input path and target path according to your needs.

use dbcc::{DbccOpt, can_code_gen};
use can_dbc;

use std::fs::File;
use std::io::prelude::*;
use std::path::Path;

fn main() -> std::io::Result<()> {
    let dbcs = &[
        ("./dbcs/j1939.dbc", "./src/lib.rs"),
    ];
    generate_code_for_dbc(dbcs)?;
    Ok(())
}

fn generate_code_for_dbc<P: AsRef<Path>>(input_output: &[(P, P)]) -> std::io::Result<()> {

    for (input_path, output_path) in input_output {
        let mut f = File::open(input_path).expect("Failed to open input file");
        let mut buffer = Vec::new();
        f.read_to_end(&mut buffer).expect("Failed to read file");

        let opt = DbccOpt {
            with_tokio: true,
        };

        let dbc_content = can_dbc::DBC::from_slice(&buffer).expect("Failed to read DBC file");
        let code = can_code_gen(&opt, &dbc_content).expect("Failed to generate rust code");

        let mut f = File::create(output_path)?;
        f.write_all(&code.to_string().into_bytes())?;
    }

    Ok(())
}

Include

  • Move the generated rust file to your project's src/ folder.
  • Add the following dependency to your project's Cargo.toml
[dependencies]
byteorder = "1.2"

Use

/// If you are using Rust 2018 no `external crate byteorder;` is necessary
/// Generated module
mod j1939;

fn main() {
    // J1939 - Operators External Light Controls Message Id
    let can_message_id = 2365443326u32;
    // can frame data field (0-8 bytes)
    let can_frame_data: Vec<u8> = vec![0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];

    // CAN Message ID constant from generated code
    if can_message_id == j1939::MESSAGE_ID_OEL {
        // J1939 - Operators External Light Controls Message
        let oel = j1939::Oel::new(can_frame_data);

        // Signal indicate the selected position of the operator's hazard light switch.
        match oel.hazardlightswitch() {
            j1939::HazardLightSwitch2365443326::HazardLampsToBeFlashing => println!("Hazard Lamps To Be Flashing"),
            j1939::HazardLightSwitch2365443326::HazardLampsToBeOff => println!("Hazard Lamps To Be Off"),
            j1939::HazardLightSwitch2365443326::NotAvailable => println!("Not available"),
            j1939::HazardLightSwitch2365443326::Error => println!("Error"),
            j1939::HazardLightSwitch2365443326::XValue(_) => unreachable!(),
        }
    }
}

Including SocketCAN Streams

  • Make sure you pass the --with-tokio flag when invoking dbcc.
  • Move the generated rust file to your project's src/ folder.
  • Add the following dependencies to your project's Cargo.toml
[dependencies]
byteorder = "1.3"
futures = "0.3"
tokio = "0.3"
tokio-socketcan-bcm = "1.0"
mod j1939;

use futures::future::Future;
use futures::stream::Stream;
use std::io;
use std::time::Duration;
use tokio;

fn main() -> io::Result<()> {
    let ival = Duration::from_secs(0);

    let f = j1939::Oel::stream("vcan0", &ival, &ival)?
        .for_each(|oel| {
            // Signal indicates the selected position of the operator's hazard light switch.
            match oel.hazardlightswitch() {
                j1939::HazardLightSwitch2365443326::HazardLampsToBeFlashing => {
                    println!("Hazard Lamps To Be Flashing")
                }
                j1939::HazardLightSwitch2365443326::HazardLampsToBeOff => {
                    println!("Hazard Lamps To Be Off")
                }
                j1939::HazardLightSwitch2365443326::NotAvailable => println!("Not available"),
                j1939::HazardLightSwitch2365443326::Error => println!("Error"),
                j1939::HazardLightSwitch2365443326::XValue(_) => unreachable!(),
            }
            Ok(())
        });

    tokio::run(f.map_err(|_| ()));

    Ok(())
}

Naming

Recommendation: Value descriptions aka VAL_ ... should contain only alphanumeric characters or underscores and should start with an alphabetic character. E.g. VAL_ 100 "111 Wunderschön Inc" 255 should be VAL_ 100 " Wunderschoen Inc 111" 255

  • Enums: Generated names are prefixed with an X if the name does not start with an alphabetic character.
  • Enums: Characters that are not alphanumeric or _ are replaced with an X
  • Enums: An XValue(f64) variant is added to each enum since value descriptions often do not cover all possibilities.

Dependencies

~6.5MB
~138K SLoC