19 releases
0.3.4 | Aug 9, 2024 |
---|---|
0.3.3 | Mar 25, 2024 |
0.2.2 | Jan 26, 2024 |
0.1.3 | Jan 10, 2024 |
0.1.2 | Dec 30, 2023 |
#49 in Robotics
583 downloads per month
Used in 2 crates
81KB
714 lines
MAVSpec
A code-generator for MAVLink.
MAVLink is a lightweight open protocol for communicating between drones, onboard components and ground control stations.
It is used by such autopilots like PX4 or ArduPilot. MAVLink has simple and
compact serialization model. The basic abstraction is message
which can be sent through a link (UDP, TCP, UNIX
socket, UART, whatever) and deserialized into a struct with fields of primitive types or arrays of primitive types.
Such fields can be additionally restricted by enum
variants, annotated with metadata like units of measurements,
default or invalid values. There are several MAVLink dialects. Official dialect definitions are
XML files that can be found in the MAVlink
repository. Based on message
abstractions,
MAVLink defines so-called microservices
that specify how clients should respond on
a particular message under certain conditions or how they should initiate a particular action.
This library is a building block for other MAVLink-related tools (telemetry collectors, IO, etc.). It is only responsible for code generation. Other Mavka projects are focused on their own areas:
- MAVInspect is responsible for parsing mavlink message XML definitions.
MAVSpec
is using this library to discover and parse MAVLink dialects. - Mavio, a minimalistic library for transport-agnostic MAVLink communication
written in Rust. It supports
no-std
(andno-alloc
) targets and focuses on stateless parts of MAVLink protocol. - Maviola is a MAVLink communication library based on
Mavio
that provides a high-level interface for MAVLink messaging and takes care about stateful features of the protocol: sequencing, message time-stamping, automatic heartbeats, simplifies message signing, and so on.
This project respects semantic versioning
.
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 --featurs generators
The generators
feature enables all code-generators.
Usage
The following explains how to use library API, for command-line tool usage check CLI section.
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 MAVLink entities:
[package.metadata.mavspec]
microservices = ["HEARTBEAT", "MISSION"]
messages = ["PROTOCOL_VERSION", "MAV_INSPECT_V1", "PING"]
enums = ["STORAGE_STATUS", "GIMBAL_*"]
commands = ["MAV_CMD_DO_CHANGE_SPEED", "MAV_CMD_DO_SET_ROI*"]
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).
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
usesUpperCamelCase
. - 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 nameIMPORTANCE_LEVEL
and flag name isIMPORTANCE_LEVEL_THE_MATTER_OF_LIFE_AND_DEATH
, then flag name will beTheMatterOfLifeAndDeath
. - 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 nameVERY_IMPORTANT_FLAGS
and flag name isVERY_IMPORTANT_FLAGS_THE_MATTER_OF_LIFE_AND_DEATH_FLAG
, then flag name will beTHE_MATTER_OF_LIFE_AND_DEATH_FLAG
. - In the case of collision with rust keywords, we add underscore suffix. For example,
type
field ofHEARTBEAT
message will be encoded astype_
. - 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).
Fingerprints
MAVInspect may skip code re-generation if dialects haven't changed. It uses 64-bit CRC fingerprint to monitor
changes. Set fingerprints
feature flag to enable this behavior.
This feature is useful for reducing build time during development and CI runs. Make sure that your releases are clean and do not depend on fingerprints.
Unstable Features
Unstable features are enabled by unstable
feature flag. Such features are experimental and can be changed or
excluded in future releases.
CLI
Install mavspec
command-line tool.
cargo install mavspec --features cli
Check installation:
mavspec -V
If you are working from the MAVSpec
repository, then you always can run CLI-tool using cargo:
cargo run --bin mavspec --features cli --
Parse XML definitions from ./message_definitions/standard
and generate dialects in
tmp/mavlink
directory:
mavspec --src message_definitions/standard --out tmp/mavlink rust
Print mavspec
help for Rust code generator:
mavspec rust -h
Examples
examples/rust
β an example library with autogenerated code.cargo run --package mavspec_examples_rust --bin mavspec_examples_rust
Roadmap
API is considered relatively stable but certain advanced features are yet to be developed. However, most of these features are nice to have, rather than something necessary to consider this library complete.
Milestone v1
contains features considered necessary to
reach stable version 1.0.0
. Most of these features are related to Rust code generator.
Other code generators (will form a basis for other Mavka projects):
telemetry
milestone is focused on code generators required for storing MAVLink data in time-series databases like InfluxDB or Timescale.gRPC
milestone is reserved for Protobuf and gRPC bindings.
Propositions and pull-requests are welcomed.
Other MAVLink Tools
First of all, there is an official MAVLink client for Rust worth mentioning:
rust-mavlink
. One of the reasons behind writing this library was my desire
to decouple parser and code generator into the separate projects.
I was personally inspired by gomavlib
library for MAVLink (Go). I like the
way it is written, and its source code helped me in several cases when official MAVLink documentation wasn't clear
enough.
If you want to autogenerate language bindings and prefer Python, you might be interested in the official
mavgen
code-generation tool. If you are
looking for a router for MAVLink messages, then we suggest mavp2p
. If
you want a solution that supports MAVLink microservices, then it worth
checking MAVSDK
that uses gRPC API.
MAVLink is almost 15 years old, but the ecosystem around this protocol is still dynamic and developing. Some projects are stable and robust, while others are nice and feature-rich but incomplete.
License
Here we simply comply with the suggested dual licensing according to Rust API Guidelines (C-PERMISSIVE).
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://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.
Dependencies
~0β1.5MB
~25K SLoC