mavspec 0.6.0

A set of code generation utilities for MAVLink protocol.
Documentation
MAVSpec
=======

A code-generator for [MAVLink](https://mavlink.io/en/).

<span style="font-size:24px">[πŸ‡ΊπŸ‡¦](https://mavka.gitlab.io/home/a_note_on_the_war_in_ukraine/)</span>
[![
`repository`](https://img.shields.io/gitlab/pipeline-status/mavka/libs/mavspec.svg?branch=main&label=repository)](https://gitlab.com/mavka/libs/mavspec)
[![`crates.io`](https://img.shields.io/crates/v/mavspec.svg)](https://crates.io/crates/mavspec)
[![`docs.rs`](https://img.shields.io/docsrs/mavspec.svg?label=docs.rs)](https://docs.rs/mavspec/latest/mavspec/)
[![
`issues`](https://img.shields.io/gitlab/issues/open/mavka/libs/mavspec.svg)](https://gitlab.com/mavka/libs/mavspec/-/issues/)

MAVLink is a lightweight open protocol for communicating between drones, onboard components and ground control stations.
It is used by such autopilots like [PX4](https://px4.io) or [ArduPilot](https://ardupilot.org/#). 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](https://mavlink.io/en/guide/xml_schema.html) that can be found in the MAVlink
[repository](https://github.com/mavlink/mavlink/tree/master/message_definitions/v1.0). Based on `message` abstractions,
MAVLink defines so-called [`microservices`](https://mavlink.io/en/services/) 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](https://mavka.gitlab.io/home/) projects are focused on their own areas:

* [MAVInspect]https://crates.io/crates/mavinspect is responsible for parsing mavlink message XML definitions.
  `MAVSpec` is using this library to discover and parse MAVLink dialects.
* [Mavio]https://crates.io/crates/mavio, a minimalistic library for transport-agnostic MAVLink communication
  written in Rust. It supports `no-std` (and `no-alloc`) targets and focuses on **stateless** parts of MAVLink protocol.
* [Maviola]https://crates.io/crates/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`](https://semver.org).

Install
-------

Install as a Cargo dependency.

```shell
cargo add mavspec --features all
```

The `all` feature will generate all standard MAVLink dialects.

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

```shell
cargo add --build mavspec --featurs generators
```

The `generators` feature enables all code-generators.

Usage
-----

MAVSpec is designed to be both feature-rich and flexible. The following is just a short guide for the common use-cases.
If you want a deep understanding, check [API docs](https://docs.rs/mavspec/latest/mavspec/).

> The following explains how to use library API, for command-line tool usage check [CLI]#cli section.

### Standard MAVLink dialects

Standard MAVLink dialects are generated from the corresponding
[XML specifications](https://gitlab.com/mavka/spec/protocols/mavlink/message-definitions-v1.0) and are available in
`mavspec::rust::microservices` module. To use bundled dialects just install MAVSpec with the specified dialects
features:

```shell
cargo add mavspec --featurs all
```

Available dialects are:

* [`minimal`](https://mavlink.io/en/messages/minimal.html) β€” minimal dialect required to
  expose your presence to other MAVLink devices.
* [`standard`]https://mavlink.io/en/messages/standard.html β€” a superset of `minimal` dialect,
  that expected to be used by almost all flight stack.
* [`common`]https://mavlink.io/en/messages/common.html β€” minimum viable dialect with most of
  the features, a building block for other future-rich dialects.
* [`ardupilotmega`]https://mavlink.io/en/messages/common.html β€” feature-full dialect used by
  [ArduPilot]http://ardupilot.org. In most cases this dialect is the go-to choice if you want
  to recognize almost all MAVLink messages used by existing flight stacks.
* [`all`]https://mavlink.io/en/messages/all.html β€” meta-dialect which includes all other
  standard dialects including those which were created for testing purposes. It is guaranteed
  that namespaces of the dialects in `all` family do not collide.
* Other dialects from MAVLink
  XML [definitions]https://github.com/mavlink/mavlink/tree/master/message_definitions/v1.0:
  `asluav`, `avssuas`, `csairlink`, `cubepilot`, `development`, `icarous`, `matrixpilot`,
  `paparazzi`, `ualberta`, `uavionix`. These do not include `python_array_test` and `test`
  dialects which should be either generated manually or as a part of `all` meta-dialect.

#### Default dialect

When standard MAVLink dialects are used (at least `minimal` Cargo feature is enabled), this
library exposes `default_dialect` and `DefaultDialect` entities that allow to access the most feature-rich enabled
MAVLink dialect.

You might think about default dialect as a sequence of smaller and smaller dialects where `all` it the most rich dialect
and `minimal` provides a bare minimum of functionality:

[`all`](https://mavlink.io/en/messages/all.html) >
[`ardupilotmega`](https://mavlink.io/en/messages/common.html) >
[`common`](https://mavlink.io/en/messages/common.html) >
[`standard`]((https://mavlink.io/en/messages/standard.html)) >
[`minimal`]((https://mavlink.io/en/messages/minimal.html))

### MAVLink microservices

MAVSpec allows to generate additional structures tailored for MAVLink [microservices](https://mavlink.io/en/services/).
Each microservice is a subdialect with only those messages and enums which are necessary. To generate microservice
subdialects use `msrv-*` feature flags.

> ⚠️ We do not recommend to enable microservices for libraries that perform generic MAVLink
> operations as this may include compilation time.

MAVSpec also provides additional utils to work with MAVLink microservices. These tools can be
enabled by `msrv-utils-*` feature flags and available in `mavspec::rust::microservices` module.

> ⚠️ `msrv-utils-*` are considered unstable for now! Use `unstable` feature flag to enable them.

### Metadata

It is possible to bundle metadata generated by [MAVInspect](https://crates.io/crates/mavinspect) into `meta` module.
This can be useful for ground control stations that require to present the user with the descriptions of MAVLink
entities.

To enable metadata bundling use `metadata` feature flag.

> ⚠️ Metadata available only with `std` feature enabled. Otherwise, this will cause build to fail.

### Generate custom Rust bindings

API documentation for Rust code-generation can be found [here](https://docs.rs/mavspec/latest/mavspec/rust/gen).

Add MAVSpec with `rust` feature to your dependencies.

```shell
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:

```shell
cargo add --build mavspec --features rust_gen
```

If necessary, add optional section to your `Cargo.toml` to generate only specific MAVLink entities:

```toml
[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`:

```rust
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):

```rust
mod mavlink {
    include!(concat!(env!("OUT_DIR"), "/mavlink/mod.rs"));
}

pub use mavlink::dialects;
```

Check [`examples/rust`](examples/rust/README.md) 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`](examples/rust/src/bin/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.

```shell
cargo install mavspec --features cli
```

Check installation:

```shell
mavspec -V
```

If you are working from the `MAVSpec` repository, then you always can run CLI-tool using cargo:

```shell
cargo run --bin mavspec --features cli --
```

Parse XML definitions from [`./message_definitions/standard`](./message_definitions/standard) and generate dialects in
`tmp/mavlink` directory:

```shell
mavspec --src message_definitions/standard --out tmp/mavlink rust
```

Print `mavspec` help for Rust code generator:

```shell
mavspec rust -h
```

Examples
--------

- [`examples/build_custom_dialects`]examples/build_custom_dialects β€” an example library with autogenerated code.
  ```shell
  cargo run --package mavspec_examples_custom_dialects --bin custom_dialects_example
  ```

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`](https://gitlab.com/mavka/libs/mavspec/-/milestones/1) 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](https://mavka.gitlab.io/home/) projects):

- [`telemetry`]https://gitlab.com/mavka/libs/mavspec/-/milestones/2 milestone is focused on code generators required
  for storing MAVLink data in time-series databases like [InfluxDB]https://www.influxdata.com or
  [Timescale]https://www.timescale.com.
- [`gRPC`]https://gitlab.com/mavka/libs/mavspec/-/milestones/3 milestone is reserved for
  [Protobuf]https://protobuf.dev and [gRPC]https://grpc.io bindings.

[Propositions](https://gitlab.com/mavka/spec/libs/mavspec/-/issues) and
[pull-requests](https://gitlab.com/mavka/spec/libs/mavspec/-/merge_requests) are welcomed.

Other MAVLink Tools
-------------------

First of all, there is an official MAVLink client for Rust worth mentioning:
[`rust-mavlink`](https://github.com/mavlink/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`](https://github.com/bluenviron/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`](https://mavlink.io/en/getting_started/generate_libraries.html#mavgen) code-generation tool. If you are
looking for a router for MAVLink messages, then we suggest [`mavp2p`](https://github.com/bluenviron/mavp2p). If
you want a solution that supports MAVLink microservices, then it worth
checking [`MAVSDK`](https://github.com/mavlink/MAVSDK)
that uses [gRPC](https://grpc.io) 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]https://rust-lang.github.io/api-guidelines/about.html (C-PERMISSIVE).

Licensed under either of

* Apache License, Version 2.0
  ([LICENSE-APACHE]LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license
  ([LICENSE-MIT]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.