Skip to content

Commit

Permalink
Merge branch 'master' into emit-extensions-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
pv42 authored Aug 22, 2024
2 parents cdb135e + 311cd65 commit 0fb0e2e
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 7 deletions.
3 changes: 2 additions & 1 deletion mavlink-bindgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ version = "0.13.2"
edition = "2021"
license = "MIT/Apache-2.0"
description = "Library used by rust-mavlink."
readme = "../README.md"
readme = "README.md"
repository = "https://github.com/mavlink/rust-mavlink"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down
98 changes: 98 additions & 0 deletions mavlink-bindgen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# mavlink-bindgen

[![Build status](https://github.com/mavlink/rust-mavlink/actions/workflows/test.yml/badge.svg)](https://github.com/mavlink/rust-mavlink/actions/workflows/test.yml)
[![Crate info](https://img.shields.io/crates/v/mavlink-bindgen.svg)](https://crates.io/crates/mavlink-bindgen)
[![Documentation](https://docs.rs/mavlink-bindgen/badge.svg)](https://docs.rs/mavlink-bindgen)

Library and CLI for generating code for the Rust implementation of the [MAVLink](https://mavlink.io/en) UAV messaging protocol.

`mavlink-bindgen` can be used to create MAVLink bindings for Rust. This is used from `build.rs` in the [mavlink](https://crates.io/crates/mavlink) crate to create bindings from the standard MAVLink dialects in <https://github.com/mavlink/mavlink>.

## Usage

`mavlink-bindgen` can be used as a code generator from `build.rs` as done is the `mavlink` crate for a custom MAVLink dialect or as a CLI tool to generate rust binding from XML dialect definitions. The generated code will depend on the [mavlink-core](https://crates.io/crates/mavlink-core) crate in both use cases. Each dialect generated will be locked behind a feature flag of the same name, that must be enabled when using the generated code.

### CLI

Build the binary using cargo with `cli` feature enabled:

```shell
cd mavlink-bindgen
cargo build --features cli
```

Alternatively you can build and install `mavlink-bindgen` to your locally installed crates:

```shell
cargo install mavlink-bindgen --features cli
```

To generate code using the resulting binary:

```shell
mavlink-bindgen --format-generated-code message_definitions mavlink_dialects
```

The full command line options are shown below.

```shell
Usage: mavlink-bindgen [OPTIONS] <DEFINITIONS_DIR> <DESTINATION_DIR>

Arguments:
<DEFINITIONS_DIR> Path to the directory containing the MAVLink dialect definitions
<DESTINATION_DIR> Path to the directory where the code is generated into, must already exist

Options:
--format-generated-code format code generated code
--emit-cargo-build-messages prints cargo build message indicating when the code has to be rebuild
-h, --help Print help
```

The output dir will contain a `mod.rs` file with each dialect in its own file locked behind a feature flag.

### Library as build dependency

Add to your Cargo.toml:

```toml
mavlink-bindgen = "0.13.1"
```

Add a `build/main.rs` or `build.rs` to your project if it does not already exist. Then add the following to the `main` function to generate the code:

```rs
let out_dir = env::var("OUT_DIR").unwrap();
let result = match mavlink_bindgen::generate(definitions_dir, out_dir) {
Ok(r) => r,
Err(e) => {
eprintln!("{e}");
return ExitCode::FAILURE;
}
};
```

If the generated code should be formated use

```rs
mavlink_bindgen::format_generated_code(&result);
```

To tell cargo when to regenerate code from the definitions use:

```rs
mavlink_bindgen::emit_cargo_build_messages(&result);
```

Finally include the generated code into the `lib.rs` or `main.rs` :

```rs
#![cfg_attr(not(feature = "std"), no_std)]
// include generate definitions
include!(concat!(env!("OUT_DIR"), "/mod.rs"));

pub use mavlink_core::*;
```

Since each dialect is locked behind a feature flag these need to be enabled for the dialects to become available when using the generated code.

This approach is used by the `mavlink` crate see its build script for an example.
5 changes: 5 additions & 0 deletions mavlink-bindgen/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ use clap::Parser;
use mavlink_bindgen::{emit_cargo_build_messages, format_generated_code, generate, BindGenError};

#[derive(Parser)]
/// Generate Rust bindings from MAVLink message dialect XML files.
struct Cli {
/// Path to the directory containing the MAVLink dialect definitions.
definitions_dir: PathBuf,
/// Path to the directory where the code is generated into, must already exist.
destination_dir: PathBuf,
/// format code generated code
#[arg(long)]
format_generated_code: bool,
/// prints cargo build messages indicating when the code has to be rebuild
#[arg(long)]
emit_cargo_build_messages: bool,
}
Expand Down
2 changes: 1 addition & 1 deletion mavlink-bindgen/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub enum BindGenError {
source: std::io::Error,
path: std::path::PathBuf,
},
/// Represents a failure to read the MAVLink definitions directory.
/// Represents a failure to read a MAVLink definition file.
#[error("Could not read definition file {path}: {source}")]
CouldNotReadDefinitionFile {
source: std::io::Error,
Expand Down
5 changes: 5 additions & 0 deletions mavlink-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ pub struct GeneratedBindings {
pub mod_rs: PathBuf,
}

/// Generate Rust MAVLink dialect binding for dialects present in `definitions_dir` into `destination_dir`.
///
/// If successful returns paths of generated bindings linked to their dialect definitions files.
pub fn generate<P1: AsRef<Path>, P2: AsRef<Path>>(
definitions_dir: P1,
destination_dir: P2,
Expand Down Expand Up @@ -99,6 +102,7 @@ fn _generate(
}
}

/// Formats generated code using `rustfmt`.
pub fn format_generated_code(result: &GeneratedBindings) {
if let Err(error) = Command::new("rustfmt")
.args(
Expand All @@ -114,6 +118,7 @@ pub fn format_generated_code(result: &GeneratedBindings) {
}
}

/// Prints definitions for cargo that describe which files the generated code depends on, indicating when it has to be regenerated.
pub fn emit_cargo_build_messages(result: &GeneratedBindings) {
for binding in &result.bindings {
// Re-run build if definition file changes
Expand Down
4 changes: 2 additions & 2 deletions mavlink-bindgen/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ impl MavEnum {
}

fn emit_defs(&self) -> Vec<TokenStream> {
let mut cnt = 0isize;
let mut cnt = 0u32;
self.entries
.iter()
.map(|enum_entry| {
Expand All @@ -330,7 +330,7 @@ impl MavEnum {
value = quote!(#cnt);
} else {
let tmp_value = enum_entry.value.unwrap();
cnt = cnt.max(tmp_value as isize);
cnt = cnt.max(tmp_value as u32);

Check warning on line 333 in mavlink-bindgen/src/parser.rs

View workflow job for this annotation

GitHub Actions / linting

casting to the same type is unnecessary (`u32` -> `u32`)

warning: casting to the same type is unnecessary (`u32` -> `u32`) --> mavlink-bindgen/src/parser.rs:333:35 | 333 | cnt = cnt.max(tmp_value as u32); | ^^^^^^^^^^^^^^^^ help: try: `tmp_value` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast = note: `#[warn(clippy::unnecessary_cast)]` on by default
let tmp = TokenStream::from_str(&tmp_value.to_string()).unwrap();
value = quote!(#tmp);
};
Expand Down
2 changes: 2 additions & 0 deletions mavlink-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ embedded-io-async = { version = "0.6.1", optional = true }
serde = { version = "1.0.115", optional = true, features = ["derive"] }
serde_arrays = { version = "0.1.0", optional = true }
serial = { version = "0.4", optional = true }
tokio = { version = "1.0", default-features = false, features = ["io-util"], optional = true }

[features]
"std" = ["byteorder/std"]
Expand All @@ -38,4 +39,5 @@ serial = { version = "0.4", optional = true }
"embedded" = ["dep:embedded-io", "dep:embedded-io-async"]
"embedded-hal-02" = ["dep:nb", "dep:embedded-hal-02"]
"serde" = ["dep:serde", "dep:serde_arrays"]
"tokio-1" = ["dep:tokio"]
default = ["std", "tcp", "udp", "direct-serial", "serde"]
121 changes: 120 additions & 1 deletion mavlink-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ pub async fn read_v1_msg_async<M: Message>(
}

const MAVLINK_IFLAG_SIGNED: u8 = 0x01;
const MAVLINK_SUPPORTED_IFLAGS: u8 = MAVLINK_IFLAG_SIGNED;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
// Follow protocol definition: `<https://mavlink.io/en/guide/serialization.html#mavlink2_packet_format>`
Expand All @@ -510,7 +511,7 @@ impl MAVLinkV2MessageRaw {
}

#[inline]
pub fn header(&mut self) -> &[u8] {
pub fn header(&self) -> &[u8] {
&self.0[1..=Self::HEADER_SIZE]
}

Expand Down Expand Up @@ -677,6 +678,12 @@ pub fn read_v2_raw_message<M: Message, R: Read>(
let header = &reader.peek_exact(MAVLinkV2MessageRaw::HEADER_SIZE)?
[..MAVLinkV2MessageRaw::HEADER_SIZE];
message.mut_header().copy_from_slice(header);

if message.incompatibility_flags() & !MAVLINK_SUPPORTED_IFLAGS > 0 {
// if there are incompatibility flags set that we do not know discard the message
continue;
}

let packet_length = message.raw_bytes().len() - 1;
let payload_and_checksum_and_sign =
&reader.peek_exact(packet_length)?[MAVLinkV2MessageRaw::HEADER_SIZE..packet_length];
Expand All @@ -691,6 +698,41 @@ pub fn read_v2_raw_message<M: Message, R: Read>(
}
}

/// Async read a raw buffer with the mavlink message
/// V2 maximum size is 280 bytes: `<https://mavlink.io/en/guide/serialization.html>`
#[cfg(feature = "tokio-1")]
pub async fn read_v2_raw_message_async<M: Message, R: tokio::io::AsyncReadExt + Unpin>(
reader: &mut R,
) -> Result<MAVLinkV2MessageRaw, error::MessageReadError> {
loop {
loop {
// search for the magic framing value indicating start of mavlink message
if reader.read_u8().await? == MAV_STX_V2 {
break;
}
}

let mut message = MAVLinkV2MessageRaw::new();

message.0[0] = MAV_STX_V2;
let header_len = reader.read_exact(message.mut_header()).await?;
assert_eq!(header_len, MAVLinkV2MessageRaw::HEADER_SIZE);

if message.incompatibility_flags() & !MAVLINK_SUPPORTED_IFLAGS > 0 {
// if there are incompatibility flags set that we do not know discard the message
continue;
}

reader
.read_exact(message.mut_payload_and_checksum_and_sign())
.await?;

if message.has_valid_crc::<M>() {
return Ok(message);
}
}
}

/// Async read a raw buffer with the mavlink message
/// V2 maximum size is 280 bytes: `<https://mavlink.io/en/guide/serialization.html>`
///
Expand Down Expand Up @@ -720,6 +762,12 @@ pub async fn read_v2_raw_message_async<M: Message>(
.read_exact(message.mut_header())
.await
.map_err(|_| error::MessageReadError::Io)?;

if message.incompatibility_flags() & !MAVLINK_SUPPORTED_IFLAGS > 0 {
// if there are incompatibility flags set that we do not know discard the message
continue;
}

reader
.read_exact(message.mut_payload_and_checksum_and_sign())
.await
Expand Down Expand Up @@ -753,6 +801,27 @@ pub fn read_v2_msg<M: Message, R: Read>(
.map_err(|err| err.into())
}

/// Async read a MAVLink v2 message from a Read stream.
#[cfg(feature = "tokio-1")]
pub async fn read_v2_msg_async<M: Message, R: tokio::io::AsyncReadExt + Unpin>(
read: &mut R,
) -> Result<(MavHeader, M), error::MessageReadError> {
let message = read_v2_raw_message_async::<M, _>(read).await?;

M::parse(MavlinkVersion::V2, message.message_id(), message.payload())
.map(|msg| {
(
MavHeader {
sequence: message.sequence(),
system_id: message.system_id(),
component_id: message.component_id(),
},
msg,
)
})
.map_err(|err| err.into())
}

/// Async read a MAVLink v2 message from a Read stream.
///
/// NOTE: it will be add ~80KB to firmware flash size because all *_DATA::deser methods will be add to firmware.
Expand Down Expand Up @@ -794,6 +863,20 @@ pub fn write_versioned_msg<M: Message, W: Write>(
}
}

/// Async write a message using the given mavlink version
#[cfg(feature = "tokio-1")]
pub async fn write_versioned_msg_async<M: Message, W: tokio::io::AsyncWriteExt + Unpin>(
w: &mut W,
version: MavlinkVersion,
header: MavHeader,
data: &M,
) -> Result<usize, error::MessageWriteError> {
match version {
MavlinkVersion::V2 => write_v2_msg_async(w, header, data).await,
MavlinkVersion::V1 => write_v1_msg_async(w, header, data).await,
}
}

/// Async write a message using the given mavlink version
///
/// NOTE: it will be add ~70KB to firmware flash size because all *_DATA::ser methods will be add to firmware.
Expand Down Expand Up @@ -828,6 +911,24 @@ pub fn write_v2_msg<M: Message, W: Write>(
Ok(len)
}

/// Async write a MAVLink v2 message to a Write stream.
#[cfg(feature = "tokio-1")]
pub async fn write_v2_msg_async<M: Message, W: tokio::io::AsyncWriteExt + Unpin>(
w: &mut W,
header: MavHeader,
data: &M,
) -> Result<usize, error::MessageWriteError> {
let mut message_raw = MAVLinkV2MessageRaw::new();
message_raw.serialize_message(header, data);

let payload_length: usize = message_raw.payload_length().into();
let len = 1 + MAVLinkV2MessageRaw::HEADER_SIZE + payload_length + 2;

w.write_all(&message_raw.0[..len]).await?;

Ok(len)
}

/// Async write a MAVLink v2 message to a Write stream.
///
/// NOTE: it will be add ~70KB to firmware flash size because all *_DATA::ser methods will be add to firmware.
Expand Down Expand Up @@ -868,6 +969,24 @@ pub fn write_v1_msg<M: Message, W: Write>(
Ok(len)
}

/// Async write a MAVLink v1 message to a Write stream.
#[cfg(feature = "tokio-1")]
pub async fn write_v1_msg_async<M: Message, W: tokio::io::AsyncWriteExt + Unpin>(
w: &mut W,
header: MavHeader,
data: &M,
) -> Result<usize, error::MessageWriteError> {
let mut message_raw = MAVLinkV1MessageRaw::new();
message_raw.serialize_message(header, data);

let payload_length: usize = message_raw.payload_length().into();
let len = 1 + MAVLinkV1MessageRaw::HEADER_SIZE + payload_length + 2;

w.write_all(&message_raw.0[..len]).await?;

Ok(len)
}

/// Write a MAVLink v1 message to a Write stream.
///
/// NOTE: it will be add ~70KB to firmware flash size because all *_DATA::ser methods will be add to firmware.
Expand Down
Loading

0 comments on commit 0fb0e2e

Please sign in to comment.