MAVSpec

MAVSpec #

A code-generator for MAVLink written in Rust.

MAVSpec

repository crates.io API docs issues

Install #

Install as a Cargo dependency.

cargo add mavspec --features specs

The specs feature enables all core interfaces which are used by autogenerated code.

Since you probably want to generate code as a part of you build sequence, we suggest to also add MAVSpec as a build dependency.

cargo add --build mavspec --features generators

The generators feature enables all code-generators.

Usage #

Each generator comes with two components that can be enabled by feature flags: specification, the core interfaces required by autogenerated code, and the generator itself. For example, for Rust we provide rust as a specification and rust_gen as a generator.

Rust #

API documentation for Rust code-generation can be found here.

Add MAVSpec with rust feature to your dependencies.

cargo add mavspec --features rust

This feature enables interfaces upon which your generated code will depend. You can access these interfaces through use mavspec::rust::spec.

Optionally enable std (for Rust standard library) or alloc (for memory allocation support) features if your target supports them (if you are not developing for an embedded devices, then we suggest to always enable std).

Add MAVSpec with rust_gen as a build dependency:

cargo add --build mavspec --features rust_gen

If necessary, add optional section to your Cargo.toml to generate only specific messages:

[package.metadata.mavspec]
messages = ["HEARTBEAT", "PROTOCOL_VERSION", "MAV_INSPECT_V1", "COMMAND_INT", "COMMAND_LONG"]
all_enums = false
generate_tests = false

This will greatly reduce compile time and may slightly reduce memory footprint (if you are not going to expose autogenerated code as a part of your library API, then Rust compiler will probably optimize away all unused pieces).

The all_enum key controls which enums will be generated. By default, only MAVLink enums required for selected messages will be generated. Set all_enums = true to generate all enums. If messages key is not specified, then all_enums won’t have any effect.

If you want to generate tests for generated code, set generate_tests to true. This mode is disabled by default.

Update your build.rs:

use std::env::var;
use std::path::Path;

use mavspec::rust::BuildHelper;

fn main() {
    // Assume that your library and `message_definitions` are both in the root of your project.
    let sources = vec![
        "./message_definitions/standard",
        "./message_definitions/extra",
    ];
    // Output path
    let destination = Path::new(&var("OUT_DIR").unwrap()).join("mavlink");
    // Path to your `Cargo.toml` manifest
    let manifest_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml");

    // Parse XML definitions and generate Rust code
    BuildHelper::builder(&destination)
        .set_sources(&sources)
        .set_manifest_path(&manifest_path)
        .generate()
        .unwrap();
}

The OUT_DIR environment variable is provided by Rust build toolchain and points to output library for your crate. It is considered a bad practice to write outside this path in the build scripts.

Finally, import generated code in your lib.rs (or anywhere it seems appropriate):

mod mavlink {
    include!(concat!(env!("OUT_DIR"), "/mavlink/mod.rs"));
}
pub use mavlink::dialects;

Check examples/rust for a slightly more elaborated example which uses Cargo features as flags for MAVLink dialect selection.

Rust naming conventions #

In MAVSpec we are trying to keep balance between names as they appear in MAVLink XML definitions and Rust naming conventions. In most situation we favor the Rust way unless it introduces confusions. In case we failed, and you are confused, all entities are supplemented with descriptions where canonical MAVlink names are mentioned. Here is the list of the naming rules:

  • For structs and enums MAVSpec uses UpperCamelCase.
  • For message fields we use snake_case.
  • For enum entries (enum entries) we use UpperCamelCase with MAVLink enum name prefix stripped (whenever applicable). For example, if bitmask enum has name IMPORTANCE_LEVEL and flag name is IMPORTANCE_LEVEL_THE_MATTER_OF_LIFE_AND_DEATH, then flag name will be TheMatterOfLifeAndDeath.
  • For bitmask flags (enum entries for enums which are bitmasks) we use SCREAMING_SNAKE_CASE with MAVLink enum name prefix stripped (whenever applicable). For example, if bitmask enum has name VERY_IMPORTANT_FLAGS and flag name is VERY_IMPORTANT_FLAGS_THE_MATTER_OF_LIFE_AND_DEATH_FLAG, then flag name will be THE_MATTER_OF_LIFE_AND_DEATH_FLAG.
  • In the case of collision with rust keywords, we add underscore suffix. For example, type field of HEARTBEAT message will be encoded as type_.
  • In the rare cases when symbolic name starts with numeric character, it will be prefixed with _.

Check mavspec_examples_rust.rs which shows how the last two cases of inconvenient names are handled (this is not something of high aesthetic value but in our defence we must say that all approaches we’ve considered looked equally ugly).