├── mavlink
├── rustfmt.toml
├── tests
│ ├── log.tlog
│ ├── target_fields_tests.rs
│ ├── helper_tests.rs
│ ├── direct_serial_tests.rs
│ ├── parse_test.rs
│ ├── process_log_files.rs
│ ├── random_test.rs
│ ├── invalid_value_test.rs
│ ├── udp_loopback_tests.rs
│ ├── signing.rs
│ ├── udp_loopback_async_tests.rs
│ ├── mav_frame_tests.rs
│ ├── file_connection_tests.rs
│ ├── tcp_loopback_tests.rs
│ ├── tcp_loopback_async_tests.rs
│ ├── agnostic_decode_test.rs
│ └── v1_encode_decode_tests.rs
├── examples
│ ├── embedded
│ │ ├── .cargo
│ │ │ └── config.toml
│ │ ├── memory.x
│ │ ├── README.md
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ └── main.rs
│ ├── embedded-async-read
│ │ ├── .cargo
│ │ │ └── config.toml
│ │ ├── README.md
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ └── main.rs
│ └── mavlink-dump
│ │ ├── Cargo.toml
│ │ └── src
│ │ └── main.rs
├── build
│ └── main.rs
├── src
│ └── lib.rs
└── Cargo.toml
├── .gitmodules
├── mavlink-bindgen
├── tests
│ ├── definitions
│ │ ├── no_field_description.xml
│ │ ├── heartbeat.xml
│ │ ├── mav_bool.xml
│ │ ├── deprecated.xml
│ │ └── parameters.xml
│ ├── snapshots
│ │ ├── e2e_snapshots__heartbeat.xml@mod.rs.snap
│ │ ├── e2e_snapshots__parameters.xml@mod.rs.snap
│ │ ├── e2e_snapshots__mav_bool.xml@mod.rs.snap
│ │ ├── e2e_snapshots__deprecated.xml@mod.rs.snap
│ │ ├── e2e_snapshots__no_field_description.xml@mod.rs.snap
│ │ ├── e2e_snapshots__no_field_description.xml@no_field_description.rs.snap
│ │ └── e2e_snapshots__heartbeat.xml@heartbeat.rs.snap
│ └── e2e_snapshots.rs
├── src
│ ├── main.rs
│ ├── util.rs
│ ├── binder.rs
│ ├── cli.rs
│ └── error.rs
└── Cargo.toml
├── .github
├── dependabot.yml
└── workflows
│ ├── deploy.yml
│ └── test.yml
├── .gitignore
├── Cargo.toml
├── mavlink-core
├── src
│ ├── connection
│ │ ├── file
│ │ │ └── config.rs
│ │ ├── tcp
│ │ │ └── config.rs
│ │ ├── udp
│ │ │ └── config.rs
│ │ ├── direct_serial
│ │ │ └── config.rs
│ │ ├── file.rs
│ │ ├── tcp.rs
│ │ └── direct_serial.rs
│ ├── embedded.rs
│ ├── utils.rs
│ ├── error.rs
│ ├── async_connection
│ │ ├── file.rs
│ │ ├── mod.rs
│ │ ├── direct_serial.rs
│ │ └── tcp.rs
│ ├── connectable.rs
│ ├── types.rs
│ ├── bytes.rs
│ ├── signing.rs
│ └── bytes_mut.rs
└── Cargo.toml
├── LICENSE-MIT
└── README.md
/mavlink/rustfmt.toml:
--------------------------------------------------------------------------------
1 | edition = "2018"
--------------------------------------------------------------------------------
/mavlink/tests/log.tlog:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mavlink/rust-mavlink/HEAD/mavlink/tests/log.tlog
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "mavlink/mavlink"]
2 | path = mavlink/mavlink
3 | url = https://github.com/mavlink/mavlink
4 |
--------------------------------------------------------------------------------
/mavlink/examples/embedded/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [target.thumbv7em-none-eabihf]
2 | rustflags = [
3 | "-C", "link-arg=-Tlink.x",
4 | ]
5 |
6 | [build]
7 | target = "thumbv7em-none-eabihf"
8 |
--------------------------------------------------------------------------------
/mavlink/examples/embedded-async-read/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [target.thumbv7em-none-eabihf]
2 | rustflags = [
3 | "-C", "link-arg=-Tlink.x",
4 | ]
5 |
6 | [build]
7 | target = "thumbv7em-none-eabihf"
8 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/definitions/no_field_description.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Raw RC Data
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: cargo
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | ignore:
8 | - dependency-name: "embedded-hal"
9 | versions: ["^0.2"]
10 | - package-ecosystem: github-actions
11 | directory: "/"
12 | schedule:
13 | interval: daily
14 |
--------------------------------------------------------------------------------
/mavlink/examples/embedded/memory.x:
--------------------------------------------------------------------------------
1 | /* Linker script for the STM32F303RE */
2 | MEMORY
3 | {
4 | CCMRAM : ORIGIN = 0x10000000, LENGTH = 16K
5 | FLASH : ORIGIN = 0x08000000, LENGTH = 512K
6 | RAM : ORIGIN = 0x20000000, LENGTH = 64K
7 | }
8 |
9 | _stack_start = ORIGIN(CCMRAM) + LENGTH(CCMRAM);
10 | _stack_end = ORIGIN(CCMRAM);
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Editor
2 | *.swp
3 | *.swo
4 | Session.vim
5 | .cproject
6 | .idea
7 | *.iml
8 | .vscode
9 | .project
10 | .vim/
11 | .helix/
12 | .zed/
13 | .favorites.json
14 | .settings/
15 | .vs/
16 | .dir-locals.el
17 |
18 | ## Rust
19 | target
20 | Cargo.lock
21 |
22 |
23 | # Typescript message generation
24 | mavlink/bindings
25 |
--------------------------------------------------------------------------------
/mavlink/examples/embedded/README.md:
--------------------------------------------------------------------------------
1 | # rust-MAVLink Embedded example
2 | ### How to run:
3 | - Install cargo flash:
4 | - cargo install cargo-flash
5 | - Install target
6 | - rustup target add thumbv7em-none-eabihf
7 | - Check if we can build the project
8 | - cargo build
9 | - Connect your STM32f303Xe board
10 | - Flash it!
11 | - cargo flash --chip stm32f303RETx --release --log info
12 |
--------------------------------------------------------------------------------
/mavlink/examples/embedded-async-read/README.md:
--------------------------------------------------------------------------------
1 | # rust-MAVLink Embedded async example (with reading loop)
2 | ### How to run:
3 | - Install cargo flash:
4 | - cargo install cargo-flash
5 | - Install target
6 | - rustup target add thumbv7em-none-eabihf
7 | - Check if we can build the project
8 | - cargo build
9 | - Connect your STM32f446re board
10 | - Flash it!
11 | - cargo flash --chip stm32f446RETx --release --log info
12 |
--------------------------------------------------------------------------------
/mavlink/examples/mavlink-dump/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "mavlink-dump"
3 | authors = [
4 | "Patrick José Pereira ",
5 | ]
6 | license = "MIT/Apache-2.0"
7 | edition = "2018"
8 | version = "0.1.0"
9 |
10 | [profile.release]
11 | opt-level = 3
12 | lto = true # Performs "fat" LTO which attempts to perform optimizations across all crates within the dependency graph
13 |
14 | [dependencies.mavlink] # MAVLink library
15 | path = "../../"
16 |
--------------------------------------------------------------------------------
/mavlink-bindgen/src/main.rs:
--------------------------------------------------------------------------------
1 | #![recursion_limit = "256"]
2 |
3 | use std::process::ExitCode;
4 |
5 | #[cfg(feature = "cli")]
6 | mod cli;
7 |
8 | fn main() -> ExitCode {
9 | #[cfg(feature = "cli")]
10 | if let Err(e) = cli::main() {
11 | eprintln!("{e}");
12 | return ExitCode::FAILURE;
13 | }
14 |
15 | #[cfg(not(feature = "cli"))]
16 | panic!("Compiled without cli feature");
17 |
18 | #[cfg(feature = "cli")]
19 | ExitCode::SUCCESS
20 | }
21 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/snapshots/e2e_snapshots__heartbeat.xml@mod.rs.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: mavlink-bindgen/tests/e2e_snapshots.rs
3 | expression: contents
4 | ---
5 | #[allow(non_camel_case_types)]
6 | #[allow(clippy::derive_partial_eq_without_eq)]
7 | #[allow(clippy::field_reassign_with_default)]
8 | #[allow(non_snake_case)]
9 | #[allow(clippy::unnecessary_cast)]
10 | #[allow(clippy::bad_bit_mask)]
11 | #[allow(clippy::suspicious_else_formatting)]
12 | #[cfg(feature = "heartbeat")]
13 | pub mod heartbeat;
14 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/snapshots/e2e_snapshots__parameters.xml@mod.rs.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: mavlink-bindgen/tests/e2e_snapshots.rs
3 | expression: contents
4 | ---
5 | #[allow(non_camel_case_types)]
6 | #[allow(clippy::derive_partial_eq_without_eq)]
7 | #[allow(clippy::field_reassign_with_default)]
8 | #[allow(non_snake_case)]
9 | #[allow(clippy::unnecessary_cast)]
10 | #[allow(clippy::bad_bit_mask)]
11 | #[allow(clippy::suspicious_else_formatting)]
12 | #[cfg(feature = "parameters")]
13 | pub mod parameters;
14 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/snapshots/e2e_snapshots__mav_bool.xml@mod.rs.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: mavlink-bindgen/tests/e2e_snapshots.rs
3 | assertion_line: 26
4 | expression: contents
5 | ---
6 | #[allow(non_camel_case_types)]
7 | #[allow(clippy::derive_partial_eq_without_eq)]
8 | #[allow(clippy::field_reassign_with_default)]
9 | #[allow(non_snake_case)]
10 | #[allow(clippy::unnecessary_cast)]
11 | #[allow(clippy::bad_bit_mask)]
12 | #[allow(clippy::suspicious_else_formatting)]
13 | #[cfg(feature = "mav_bool")]
14 | pub mod mav_bool;
15 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/snapshots/e2e_snapshots__deprecated.xml@mod.rs.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: mavlink-bindgen/tests/e2e_snapshots.rs
3 | assertion_line: 26
4 | expression: contents
5 | ---
6 | #[allow(non_camel_case_types)]
7 | #[allow(clippy::derive_partial_eq_without_eq)]
8 | #[allow(clippy::field_reassign_with_default)]
9 | #[allow(non_snake_case)]
10 | #[allow(clippy::unnecessary_cast)]
11 | #[allow(clippy::bad_bit_mask)]
12 | #[allow(clippy::suspicious_else_formatting)]
13 | #[cfg(feature = "deprecated")]
14 | pub mod deprecated;
15 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/snapshots/e2e_snapshots__no_field_description.xml@mod.rs.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: mavlink-bindgen/tests/e2e_snapshots.rs
3 | assertion_line: 26
4 | expression: contents
5 | ---
6 | #[allow(non_camel_case_types)]
7 | #[allow(clippy::derive_partial_eq_without_eq)]
8 | #[allow(clippy::field_reassign_with_default)]
9 | #[allow(non_snake_case)]
10 | #[allow(clippy::unnecessary_cast)]
11 | #[allow(clippy::bad_bit_mask)]
12 | #[allow(clippy::suspicious_else_formatting)]
13 | #[cfg(feature = "no_field_description")]
14 | pub mod no_field_description;
15 |
--------------------------------------------------------------------------------
/mavlink-bindgen/src/util.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 |
3 | pub fn to_module_name>(file_name: P) -> String {
4 | file_name
5 | .into()
6 | .file_stem() // remove extension
7 | .unwrap()
8 | .to_string_lossy() // convert to string
9 | .to_lowercase() // all lowercase
10 | .replace(|c: char| !c.is_alphanumeric(), "_") // remove non alphanum
11 | }
12 |
13 | pub fn to_dialect_name>(file_name: P) -> String {
14 | file_name
15 | .into()
16 | .file_stem() // remove extension
17 | .unwrap()
18 | .to_string_lossy()
19 | .to_string()
20 | }
21 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/definitions/heartbeat.xml:
--------------------------------------------------------------------------------
1 |
2 | 3
3 | 130
4 |
5 |
6 | Custom mode
7 | Type
8 | Autopilot
9 | Base mode
10 | System status
11 | Mavlink version
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/mavlink/tests/target_fields_tests.rs:
--------------------------------------------------------------------------------
1 | mod test_shared;
2 |
3 | #[cfg(feature = "common")]
4 | mod target_fields_tests {
5 | use mavlink::{common::MavMessage, Message};
6 |
7 | #[test]
8 | fn test_target_ids_present() {
9 | let data = crate::test_shared::get_cmd_nav_takeoff_msg();
10 | let msg = MavMessage::COMMAND_INT(data);
11 |
12 | assert_eq!(msg.target_system_id(), Some(42));
13 | assert_eq!(msg.target_component_id(), Some(84));
14 | }
15 |
16 | #[test]
17 | fn test_target_ids_absent() {
18 | let data = crate::test_shared::get_heartbeat_msg();
19 | let msg = MavMessage::HEARTBEAT(data);
20 |
21 | assert_eq!(msg.target_system_id(), None);
22 | assert_eq!(msg.target_component_id(), None);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/mavlink/tests/helper_tests.rs:
--------------------------------------------------------------------------------
1 | #[cfg(all(feature = "std", feature = "common"))]
2 | mod helper_tests {
3 | use mavlink::{common::MavMessage, Message};
4 |
5 | #[test]
6 | fn test_get_default_message_from_id() {
7 | let message_name = "PING";
8 | let id: Option = MavMessage::message_id_from_name(message_name);
9 | let id = id.unwrap();
10 | assert!(id == 4, "Invalid id for message name: PING");
11 | let message = MavMessage::default_message_from_id(id);
12 | #[expect(deprecated)]
13 | if !matches!(message, Some(MavMessage::PING(_))) {
14 | unreachable!("Invalid message type.")
15 | }
16 | assert!(
17 | message.unwrap().message_name() == message_name,
18 | "Message name does not match"
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["mavlink", "mavlink-bindgen", "mavlink-core"]
3 | resolver = "1"
4 |
5 | [workspace.dependencies]
6 | crc-any = { version = "2.3.5", default-features = false }
7 | num-traits = { version = "0.2", default-features = false }
8 | num-derive = "0.4"
9 | bitflags = { version = "2.9.1", default-features = false }
10 | byteorder = { version = "1.3.4", default-features = false }
11 |
12 | [workspace.package]
13 | edition = "2021"
14 | rust-version = "1.80.0"
15 | keywords = ["mavlink", "parser", "protocol", "embedded"]
16 | categories = ["aerospace", "aerospace::protocols", "parsing", "embedded"]
17 |
18 | [workspace.lints.clippy]
19 | cargo = { level = "warn", priority = -1 }
20 | multiple_crate_versions = "allow"
21 | semicolon_if_nothing_returned = "warn"
22 | unnecessary_semicolon = "warn"
23 | uninlined_format_args = "warn"
24 | use_self = "warn"
25 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/definitions/mav_bool.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Enum used to indicate true or false (also: success or failure, enabled or disabled, active or inactive).
6 |
7 | False.
8 |
9 |
10 | True.
11 |
12 |
13 |
14 |
15 |
16 | A message with MAV_BOOL
17 | unsigned
18 | signed
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/mavlink-bindgen/src/binder.rs:
--------------------------------------------------------------------------------
1 | use quote::{format_ident, quote};
2 | use std::io::Write;
3 |
4 | pub fn generate(modules: Vec<&str>, out: &mut W) {
5 | let modules_tokens = modules.into_iter().map(|module| {
6 | let module_ident = format_ident!("{}", module);
7 |
8 | quote! {
9 | #[allow(non_camel_case_types)]
10 | #[allow(clippy::derive_partial_eq_without_eq)]
11 | #[allow(clippy::field_reassign_with_default)]
12 | #[allow(non_snake_case)]
13 | #[allow(clippy::unnecessary_cast)]
14 | #[allow(clippy::bad_bit_mask)]
15 | #[allow(clippy::suspicious_else_formatting)]
16 | #[cfg(feature = #module)]
17 | pub mod #module_ident;
18 | }
19 | });
20 |
21 | let tokens = quote! {
22 | #(#modules_tokens)*
23 | };
24 |
25 | writeln!(out, "{tokens}").unwrap();
26 | }
27 |
--------------------------------------------------------------------------------
/mavlink/examples/embedded/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "mavlink-embedded"
3 | edition = "2021"
4 | authors = [
5 | "Patrick José Pereira ",
6 | ]
7 | version = "0.1.0"
8 |
9 | [profile.release]
10 | opt-level = 'z' # Optimize for binary size, but also turn off loop vectorization.
11 | lto = true # Performs "fat" LTO which attempts to perform optimizations across all crates within the dependency graph
12 |
13 | [dependencies]
14 | cortex-m = "0.7" # Low level access to Cortex-M processors
15 | cortex-m-rt = "0.7" # Startup code and minimal runtime for Cortex-M microcontrollers
16 | panic-halt = "0.2" # Panic handler
17 | stm32f3xx-hal = { version = "0.9", features = ["stm32f303xe"] } # HAL for stm32f303xe
18 |
19 | [dependencies.mavlink] # MAVLink library (wait for 0.9.0 version)
20 | path = "../../"
21 | features = ["embedded-hal-02", "common"]
22 | default-features = false
23 |
24 | [workspace]
25 |
--------------------------------------------------------------------------------
/mavlink-core/src/connection/file/config.rs:
--------------------------------------------------------------------------------
1 | use core::fmt::Display;
2 | use std::path::PathBuf;
3 |
4 | /// MAVLink connection address for a file input
5 | ///
6 | /// # Example
7 | ///
8 | /// ```ignore
9 | /// use mavlink::{Connectable, FileConfig};
10 | /// use std::path::PathBuf;
11 | ///
12 | /// let config = FileConfig::new(PathBuf::from("/some/path"));
13 | /// config
14 | /// .connect::()
15 | /// .unwrap();
16 | /// ```
17 | #[derive(Debug, Clone)]
18 | pub struct FileConfig {
19 | pub(crate) address: PathBuf,
20 | }
21 |
22 | impl FileConfig {
23 | /// Creates a file input address from a file path string.
24 | pub fn new(address: PathBuf) -> Self {
25 | Self { address }
26 | }
27 | }
28 | impl Display for FileConfig {
29 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
30 | write!(f, "file:{}", self.address.display())
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/mavlink/tests/direct_serial_tests.rs:
--------------------------------------------------------------------------------
1 | #[cfg(all(feature = "std", feature = "direct-serial", feature = "common"))]
2 | mod test_direct_serial {
3 | use mavlink::common::MavMessage;
4 |
5 | #[test]
6 | pub fn test_incomplete_address() {
7 | let conn_result = mavlink::connect::("serial:");
8 | assert!(conn_result.is_err(), "Incomplete address should error");
9 | }
10 |
11 | #[test]
12 | pub fn test_bogus_baud() {
13 | let conn_result = mavlink::connect::("serial:port1:badbaud");
14 | assert!(conn_result.is_err(), "Invalid baud should error");
15 | }
16 |
17 | #[test]
18 | pub fn test_nonexistent_port() {
19 | let bogus_port_str = "serial:8d73ba8c-eb87-4105-8d0c-2931940e13be:57600";
20 | let conn_result = mavlink::connect::(bogus_port_str);
21 | assert!(conn_result.is_err(), "Invalid port should error");
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 3D Robotics
2 |
3 | Permission is hereby granted, free of charge, to any
4 | person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the
6 | Software without restriction, including without
7 | limitation the rights to use, copy, modify, merge,
8 | publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software
10 | is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice
14 | shall be included in all copies or substantial portions
15 | of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 | DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/mavlink-bindgen/src/cli.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 |
3 | use clap::Parser;
4 | use mavlink_bindgen::{
5 | emit_cargo_build_messages, format_generated_code, generate, BindGenError, XmlDefinitions,
6 | };
7 |
8 | #[derive(Parser)]
9 | /// Generate Rust bindings from MAVLink message dialect XML files.
10 | struct Cli {
11 | /// Path to the directory containing the MAVLink dialect definitions.
12 | definitions_dir: PathBuf,
13 | /// Path to the directory where the code is generated into, must already exist.
14 | destination_dir: PathBuf,
15 | /// format code generated code, requires rustfmt to be installed
16 | #[arg(long)]
17 | format_generated_code: bool,
18 | /// prints cargo build messages indicating when the code has to be rebuild
19 | #[arg(long)]
20 | emit_cargo_build_messages: bool,
21 | }
22 |
23 | pub fn main() -> Result<(), BindGenError> {
24 | let args = Cli::parse();
25 | let result = generate(
26 | XmlDefinitions::Directory(args.definitions_dir),
27 | args.destination_dir,
28 | )?;
29 |
30 | if args.format_generated_code {
31 | format_generated_code(&result);
32 | }
33 |
34 | if args.emit_cargo_build_messages {
35 | emit_cargo_build_messages(&result);
36 | }
37 |
38 | Ok(())
39 | }
40 |
--------------------------------------------------------------------------------
/mavlink-core/src/connection/tcp/config.rs:
--------------------------------------------------------------------------------
1 | use core::fmt::Display;
2 |
3 | /// Type of TCP connection
4 | #[derive(Debug, Clone, Copy)]
5 | pub enum TcpMode {
6 | /// Connection will open a TCP server that binds to the provided address
7 | TcpIn,
8 | /// Connection will connect to the provided TCP server address
9 | TcpOut,
10 | }
11 |
12 | /// MAVLink connection address for a TCP server or client
13 | ///
14 | /// # Example
15 | ///
16 | /// ```ignore
17 | /// use mavlink::{Connectable, TcpConfig, TcpMode};
18 | ///
19 | /// let config = TcpConfig::new("0.0.0.0:14551".to_owned(), false);
20 | /// config.connect::();
21 | /// ```
22 | #[derive(Debug, Clone)]
23 | pub struct TcpConfig {
24 | pub(crate) address: String,
25 | pub(crate) mode: TcpMode,
26 | }
27 |
28 | impl TcpConfig {
29 | /// Creates a TCP connection address.
30 | pub fn new(address: String, mode: TcpMode) -> Self {
31 | Self { address, mode }
32 | }
33 | }
34 | impl Display for TcpConfig {
35 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
36 | match self.mode {
37 | TcpMode::TcpIn => write!(f, "tcpin:{}", self.address),
38 | TcpMode::TcpOut => write!(f, "tcpout:{}", self.address),
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/mavlink-bindgen/src/error.rs:
--------------------------------------------------------------------------------
1 | use thiserror::Error;
2 |
3 | #[derive(Error, Debug)]
4 | pub enum BindGenError {
5 | /// Represents a failure to read the MAVLink definitions directory.
6 | #[error("Could not read definitions directory {}: {source}", path.display())]
7 | CouldNotReadDefinitionsDirectory {
8 | source: std::io::Error,
9 | path: std::path::PathBuf,
10 | },
11 | /// Represents a failure to read a MAVLink definition file.
12 | #[error("Could not read definition file {}: {source}", path.display())]
13 | CouldNotReadDefinitionFile {
14 | source: std::io::Error,
15 | path: std::path::PathBuf,
16 | },
17 | /// Represents a failure to read a directory entry in the MAVLink definitions directory.
18 | #[error("Could not read MAVLink definitions directory entry {}: {source}", path.display())]
19 | CouldNotReadDirectoryEntryInDefinitionsDirectory {
20 | source: std::io::Error,
21 | path: std::path::PathBuf,
22 | },
23 | /// Represents a failure to create a Rust file for the generated MAVLink bindings.
24 | #[error("Could not create Rust bindings file {}: {source}", dest_path.display())]
25 | CouldNotCreateRustBindingsFile {
26 | source: std::io::Error,
27 | dest_path: std::path::PathBuf,
28 | },
29 | }
30 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/e2e_snapshots.rs:
--------------------------------------------------------------------------------
1 | use std::fs;
2 | use std::path::PathBuf;
3 |
4 | use insta::{self, assert_snapshot, glob};
5 | use mavlink_bindgen::{format_generated_code, generate, XmlDefinitions};
6 | use tempfile::TempDir;
7 |
8 | fn definitions_dir() -> PathBuf {
9 | PathBuf::from(env!("CARGO_MANIFEST_DIR"))
10 | .join("tests")
11 | .join("definitions")
12 | }
13 |
14 | fn run_snapshot(def_file: &str) {
15 | let defs = definitions_dir();
16 | let tmp = TempDir::new().expect("tmp dir");
17 | let out_dir = tmp.path();
18 |
19 | let xml = defs.join(def_file);
20 | let result = generate(XmlDefinitions::Files(vec![xml]), out_dir).expect("generate ok");
21 |
22 | format_generated_code(&result);
23 |
24 | glob!(out_dir, "**/*.rs", |path| {
25 | let contents = fs::read_to_string(path).expect("read generated file");
26 | assert_snapshot!(def_file, contents);
27 | });
28 | }
29 |
30 | #[test]
31 | fn snapshot_heartbeat() {
32 | run_snapshot("heartbeat.xml");
33 | }
34 |
35 | #[test]
36 | fn snapshot_parameters() {
37 | run_snapshot("parameters.xml");
38 | }
39 |
40 | #[test]
41 | fn snapshot_deprecated() {
42 | run_snapshot("deprecated.xml");
43 | }
44 |
45 | #[test]
46 | fn snapshot_no_field_description() {
47 | run_snapshot("no_field_description.xml");
48 | }
49 |
50 | #[test]
51 | fn snapshot_mav_bool() {
52 | run_snapshot("mav_bool.xml");
53 | }
54 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 |
3 | on:
4 | push:
5 | tags:
6 | - '[0-9]+.[0-9]+.[0-9]+'
7 |
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v6
13 | with:
14 | fetch-depth: 0
15 | ref: ${{ github.event.repository.default_branch }}
16 | - uses: actions-rs/toolchain@v1.0.7
17 | with:
18 | toolchain: stable
19 | override: true
20 | - name: Build
21 | run: cargo build
22 | - name: Extract version from tag
23 | id: get_version
24 | run: echo "::set-output name=version::${GITHUB_REF/refs\/tags\//}"
25 | - name: Commit version changes
26 | run: |
27 | git config --global user.name 'github-actions[bot]'
28 | git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com'
29 | - name: Set and publish workspace crates
30 | env:
31 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO }}
32 | run: |
33 | cargo install cargo-workspaces
34 | cargo workspaces version custom ${{ steps.get_version.outputs.version }} \
35 | --exact --yes --no-git-tag --no-git-push \
36 | -m "Commit new release ${{ steps.get_version.outputs.version }}" --force "mavlink-*"
37 | cargo publish -p mavlink-core --no-verify
38 | cargo publish -p mavlink-bindgen --no-verify
39 | cargo publish -p mavlink --no-verify
40 | - name: Push commit
41 | run: |
42 | git push origin ${{ github.event.repository.default_branch }}
43 |
--------------------------------------------------------------------------------
/mavlink-core/src/connection/udp/config.rs:
--------------------------------------------------------------------------------
1 | use core::fmt::Display;
2 |
3 | /// Type of UDP connection
4 | ///
5 | /// # Example
6 | ///
7 | /// ```ignore
8 | /// use mavlink::{Connectable, UdpConfig, UdpMode};
9 | ///
10 | /// let config = mavlink::UdpConfig::new("0.0.0.0:14552".to_owned(), UdpMode::Udpin);
11 | /// config
12 | /// .connect::()
13 | /// .unwrap();
14 | /// ```
15 | #[derive(Debug, Clone, Copy)]
16 | pub enum UdpMode {
17 | /// Server connection waiting for a client connection
18 | Udpin,
19 | /// Client connection connecting to a server
20 | Udpout,
21 | /// Client connection that is allowed to send to broadcast addresses
22 | Udpcast,
23 | }
24 |
25 | /// MAVLink address for a UDP server client or broadcast connection
26 | #[derive(Debug, Clone)]
27 | pub struct UdpConfig {
28 | pub(crate) address: String,
29 | pub(crate) mode: UdpMode,
30 | }
31 |
32 | impl UdpConfig {
33 | /// Creates a UDP connection address.
34 | ///
35 | /// The type of connection depends on the [`UdpMode`]
36 | pub fn new(address: String, mode: UdpMode) -> Self {
37 | Self { address, mode }
38 | }
39 | }
40 |
41 | impl Display for UdpConfig {
42 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43 | let mode = match self.mode {
44 | UdpMode::Udpin => "udpin",
45 | UdpMode::Udpout => "udpout",
46 | UdpMode::Udpcast => "udpcast",
47 | };
48 | write!(f, "{mode}:{}", self.address)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/mavlink-core/src/connection/direct_serial/config.rs:
--------------------------------------------------------------------------------
1 | use core::fmt::Display;
2 |
3 | /// MAVLink address for a serial connection
4 | ///
5 | /// # Example
6 | ///
7 | /// ```ignore
8 | /// use mavlink::{Connectable, SerialConfig};
9 | ///
10 | /// let config = SerialConfig::new("/dev/ttyTHS1".to_owned(), 115200);
11 | /// config.connect::();
12 | /// ```
13 | #[derive(Debug, Clone)]
14 | pub struct SerialConfig {
15 | pub(crate) port_name: String,
16 | pub(crate) baud_rate: u32,
17 | read_buffer_capacity: usize,
18 | }
19 |
20 | impl SerialConfig {
21 | /// Creates a serial connection address with port name and baud rate.
22 | pub fn new(port_name: String, baud_rate: u32) -> Self {
23 | // Calculate a sane default buffer capacity based on the baud rate.
24 | let default_capacity = (baud_rate / 100).clamp(1024, 1024 * 8) as usize;
25 |
26 | Self {
27 | port_name,
28 | baud_rate,
29 | read_buffer_capacity: default_capacity,
30 | }
31 | }
32 |
33 | /// Updates the read buffer capacity.
34 | pub fn with_read_buffer_capacity(mut self, capacity: usize) -> Self {
35 | self.read_buffer_capacity = capacity;
36 | self
37 | }
38 |
39 | /// Returns the configured read buffer capacity.
40 | pub fn buffer_capacity(&self) -> usize {
41 | self.read_buffer_capacity
42 | }
43 | }
44 |
45 | impl Display for SerialConfig {
46 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 | write!(f, "serial:{}:{}", self.port_name, self.baud_rate)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/mavlink-bindgen/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "mavlink-bindgen"
3 | version = "0.16.2"
4 | edition.workspace = true
5 | license = "MIT/Apache-2.0"
6 | description = "Library used by rust-mavlink."
7 | readme = "README.md"
8 | repository = "https://github.com/mavlink/rust-mavlink"
9 | rust-version.workspace = true
10 | keywords = ['mavlink', 'cli', "generator", "parser", "xml"]
11 | categories.workspace = true
12 |
13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
14 |
15 | [dependencies]
16 | anstyle = { version = "1.0.11", optional = true }
17 | anstyle-parse = { version = "0.2.7", optional = true }
18 | anstyle-query = { version = "1.1.4", optional = true }
19 | arbitrary = { version = "1.4", optional = true, features = ["derive"] }
20 | clap = { version = "~4.5.48", optional = true, default-features =false, features = ["derive", "help", "usage", "error-context"] }
21 | clap_builder = { version = "4.3.24", optional = true}
22 | clap_lex = { version = "0.7.5", optional = true }
23 | crc-any = { workspace = true, default-features = false }
24 | proc-macro2 = "1.0.43"
25 | quick-xml = "0.38"
26 | quote = "1"
27 | rand = { version = "0.9", optional = true, default-features = false, features = ["std", "std_rng"] }
28 | regex = { version = "1.0" }
29 | serde = { version = "1.0.115", optional = true, features = ["derive"] }
30 | thiserror = { version = "2.0.12", default-features = false }
31 |
32 | [features]
33 | cli = ["dep:clap", "dep:clap_lex", "dep:clap_builder", "dep:anstyle", "dep:anstyle-query", "dep:anstyle-parse"]
34 | arbitrary = ["dep:arbitrary", "dep:rand"]
35 | emit-extensions = []
36 |
37 | [dev-dependencies]
38 | insta = { version = "1.39.0", features = ["glob"] }
39 | tempfile = "3.10.1"
40 |
41 | [lints]
42 | workspace = true
43 |
--------------------------------------------------------------------------------
/mavlink/examples/embedded-async-read/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "mavlink-embedded-async-read"
3 | edition = "2021"
4 | authors = ["Patrick José Pereira "]
5 | version = "0.1.0"
6 |
7 | [profile.release]
8 | opt-level = 'z' # Optimize for binary size, but also turn off loop vectorization.
9 | lto = true # Performs "fat" LTO which attempts to perform optimizations across all crates within the dependency graph
10 |
11 | [dependencies]
12 | cortex-m = { version = "0.7", features = [
13 | "inline-asm",
14 | "critical-section-single-core",
15 | ] } # Low level access to Cortex-M processors
16 | cortex-m-rt = "0.7" # Startup code and minimal runtime for Cortex-M microcontrollers
17 | rtt-target = "0.5"
18 | panic-rtt-target = "0.1" # Panic handler
19 | static_cell = "2.1"
20 |
21 | embassy-time = { version = "0.3", features = ["tick-hz-32_768"] }
22 | embassy-executor = { version = "0.5", features = [
23 | "arch-cortex-m",
24 | "executor-thread",
25 | "executor-interrupt",
26 | "integrated-timers",
27 | ] }
28 | embassy-stm32 = { version = "0.1", features = [
29 | "memory-x",
30 | "stm32f446re",
31 | "time-driver-any",
32 | ] }
33 |
34 | [dependencies.mavlink] # MAVLink library (wait for 0.9.0 version)
35 | path = "../../"
36 | features = ["common", "embedded"]
37 | default-features = false
38 |
39 | [patch.crates-io]
40 | embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "86c48dde4192cabcad22faa10cabb4dc5f035c0a" }
41 | embassy-time-queue-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "86c48dde4192cabcad22faa10cabb4dc5f035c0a" }
42 | embassy-stm32 = { git = "https://github.com/embassy-rs/embassy.git", rev = "86c48dde4192cabcad22faa10cabb4dc5f035c0a" }
43 |
44 | [workspace]
45 |
--------------------------------------------------------------------------------
/mavlink/tests/parse_test.rs:
--------------------------------------------------------------------------------
1 | #[cfg(any(feature = "std", feature = "tokio-1"))]
2 | mod parse_tests {
3 | use mavlink::ConnectionAddress;
4 |
5 | fn assert_parse(addr: &str) {
6 | assert_eq!(
7 | format!("{}", ConnectionAddress::parse_address(addr).unwrap()),
8 | addr
9 | );
10 | }
11 |
12 | #[cfg(feature = "tcp")]
13 | #[test]
14 | fn test_parse_tcp() {
15 | assert_parse("tcpin:example.com:99");
16 | assert_parse("tcpout:127.0.0.1:14549");
17 | }
18 |
19 | #[cfg(feature = "tcp")]
20 | #[test]
21 | fn test_parse_file() {
22 | assert_parse("file:/mnt/12_44-mav.bin");
23 | assert_parse("file:C:\\mav_logs\\test.bin");
24 | }
25 |
26 | #[cfg(feature = "udp")]
27 | #[test]
28 | fn test_parse_udp() {
29 | assert_parse("udpcast:[::1]:4567");
30 | assert_parse("udpin:[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443");
31 | assert_parse("udpout:1.1.1.1:1");
32 | }
33 |
34 | #[cfg(feature = "direct-serial")]
35 | #[test]
36 | fn test_parse_serial() {
37 | assert_parse("serial:/dev/ttyUSB0:9600");
38 | assert_parse("serial:COM0:115200");
39 | }
40 |
41 | #[test]
42 | fn test_parse_errors() {
43 | assert!(ConnectionAddress::parse_address("serial:/dev/ttyUSB0").is_err());
44 | assert!(ConnectionAddress::parse_address("updout:1.1.1.1:1").is_err());
45 | assert!(ConnectionAddress::parse_address("tcp:127.0.0.1:14540").is_err());
46 | assert!(ConnectionAddress::parse_address("tcpin127.0.0.1:14540").is_err());
47 | assert!(ConnectionAddress::parse_address(" udpout:1.1.1.1:1 ").is_err());
48 | assert!(ConnectionAddress::parse_address(":udpcast:[::1]:4567").is_err());
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rust-mavlink
2 |
3 | [](https://github.com/mavlink/rust-mavlink/actions/workflows/test.yml)
4 | [](https://crates.io/crates/mavlink)
5 | [](https://docs.rs/mavlink)
6 |
7 | Rust implementation of the [MAVLink](https://mavlink.io/en) UAV messaging protocol,
8 | with bindings for all message sets.
9 |
10 | Add to your Cargo.toml:
11 |
12 | ```
13 | mavlink = "0.16"
14 | ```
15 |
16 | Building this crate requires `git`.
17 |
18 | ## Examples
19 | See [examples/](mavlink/examples/mavlink-dump/src/main.rs) for different usage examples.
20 |
21 | ### mavlink-dump
22 | [examples/mavlink-dump](mavlink/examples/mavlink-dump/src/main.rs) contains an executable example that can be used to test message reception.
23 |
24 | It can be executed directly by running:
25 | ```
26 | cargo run --example mavlink-dump [options]
27 | ```
28 |
29 | It's also possible to install the working example via `cargo` command line:
30 | ```sh
31 | cargo install --path examples/mavlink-dump
32 | ```
33 |
34 | It can then be executed by running:
35 | ```
36 | mavlink-dump [options]
37 | ```
38 |
39 | Execution call example:
40 | ```sh
41 | mavlink-dump udpin:127.0.0.1:14540
42 | ```
43 |
44 | ### Community projects
45 | Check some projects built by the community:
46 | - [mavlink2rest](https://github.com/patrickelectric/mavlink2rest): A REST server that provides easy and friendly access to mavlink messages.
47 | - [mavlink-camera-manager](https://github.com/mavlink/mavlink-camera-manager): Extensible cross-platform camera server.
48 |
49 | ## License
50 |
51 | Licensed under either of
52 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
53 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
54 | at your option.
55 |
56 |
--------------------------------------------------------------------------------
/mavlink/tests/process_log_files.rs:
--------------------------------------------------------------------------------
1 | #[cfg(all(feature = "default", feature = "ardupilotmega"))]
2 | mod process_files {
3 | use mavlink::ardupilotmega::MavMessage;
4 | use mavlink::error::MessageReadError;
5 | use mavlink::MavConnection;
6 |
7 | #[test]
8 | pub fn get_file() {
9 | // Get path for download script
10 | let tlog = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
11 | .join("tests/log.tlog")
12 | .canonicalize()
13 | .unwrap();
14 |
15 | let tlog = tlog.to_str().unwrap();
16 |
17 | let filename = std::path::Path::new(tlog);
18 | let filename = filename.to_str().unwrap();
19 | dbg!(filename);
20 |
21 | println!("Processing file: {filename}");
22 | let connection_string = format!("file:{filename}");
23 |
24 | // Process file
25 | process_file(&connection_string);
26 | }
27 |
28 | pub fn process_file(connection_string: &str) {
29 | let vehicle = mavlink::connect::(connection_string);
30 | assert!(vehicle.is_ok(), "Incomplete address should error");
31 |
32 | let vehicle = vehicle.unwrap();
33 | let mut counter = 0;
34 | loop {
35 | match vehicle.recv() {
36 | Ok((_header, _msg)) => {
37 | counter += 1;
38 | }
39 | Err(MessageReadError::Io(e)) => {
40 | if e.kind() == std::io::ErrorKind::WouldBlock {
41 | continue;
42 | } else {
43 | println!("recv error: {e:?}");
44 | break;
45 | }
46 | }
47 | _ => {}
48 | }
49 | }
50 |
51 | println!("Number of parsed messages: {counter}");
52 | assert!(
53 | counter == 1426,
54 | "Unable to hit the necessary amount of matches"
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/mavlink-core/src/embedded.rs:
--------------------------------------------------------------------------------
1 | use crate::error::*;
2 |
3 | #[cfg(all(feature = "embedded", feature = "embedded-hal-02"))]
4 | const _: () = panic!("Only one of 'embedded' and 'embedded-hal-02' features can be enabled.");
5 |
6 | /// Replacement for std::io::Read + byteorder::ReadBytesExt in no_std envs
7 | pub trait Read {
8 | fn read(&mut self, buf: &mut [u8]) -> Result {
9 | self.read_exact(buf).map(|_| buf.len())
10 | }
11 |
12 | fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), MessageReadError>;
13 | }
14 |
15 | #[cfg(all(feature = "embedded", not(feature = "embedded-hal-02")))]
16 | impl Read for R {
17 | fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), MessageReadError> {
18 | embedded_io::Read::read_exact(self, buf).map_err(|_| MessageReadError::Io)
19 | }
20 | }
21 |
22 | #[cfg(all(feature = "embedded-hal-02", not(feature = "embedded")))]
23 | impl> Read for R {
24 | fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), MessageReadError> {
25 | for byte in buf {
26 | *byte = nb::block!(self.read()).map_err(|_| MessageReadError::Io)?;
27 | }
28 |
29 | Ok(())
30 | }
31 | }
32 |
33 | /// Replacement for std::io::Write + byteorder::WriteBytesExt in no_std envs
34 | pub trait Write {
35 | fn write_all(&mut self, buf: &[u8]) -> Result<(), MessageWriteError>;
36 | }
37 |
38 | #[cfg(all(feature = "embedded", not(feature = "embedded-hal-02")))]
39 | impl Write for W {
40 | fn write_all(&mut self, buf: &[u8]) -> Result<(), MessageWriteError> {
41 | embedded_io::Write::write_all(self, buf).map_err(|_| MessageWriteError::Io)
42 | }
43 | }
44 |
45 | #[cfg(all(feature = "embedded-hal-02", not(feature = "embedded")))]
46 | impl> Write for W {
47 | fn write_all(&mut self, buf: &[u8]) -> Result<(), MessageWriteError> {
48 | for byte in buf {
49 | nb::block!(self.write(*byte)).map_err(|_| MessageWriteError::Io)?;
50 | }
51 |
52 | Ok(())
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/definitions/deprecated.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Use MAV_FRAME_GLOBAL in COMMAND_INT (and elsewhere) as a synonymous replacement.
7 | Global (WGS84) coordinate frame (scaled) + altitude relative to mean sea level (MSL).
8 |
9 |
10 |
11 |
12 |
13 | Enumeration of possible mount operation modes. This message is used by obsolete/deprecated gimbal messages.
14 |
15 | Load and keep safe position (Roll,Pitch,Yaw) from permanent memory and stop stabilization
16 |
17 |
18 |
19 |
20 |
21 |
22 | To be removed / merged with TIMESYNC
23 | A ping message either requesting or responding to a ping. This allows to measure the system latencies, including serial port, radio modem and UDP connections. The ping microservice is documented at https://mavlink.io/en/services/ping.html
24 | Timestamp (UNIX Epoch time or time since system boot). The receiving end can infer timestamp format (since 1.1.1970 or since system boot) by checking for the magnitude of the number.
25 | PING sequence
26 | 0: request ping from all receiving systems. If greater than 0: message is a ping response and number is the system id of the requesting system
27 | 0: request ping from all receiving components. If greater than 0: message is a ping response and number is the component id of the requesting component.
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/mavlink/tests/random_test.rs:
--------------------------------------------------------------------------------
1 | #[cfg(all(feature = "ardupilotmega", feature = "arbitrary"))]
2 | mod random_tests {
3 | use mavlink::{ardupilotmega::MavMessage, Message};
4 | use rand::{rngs::StdRng, SeedableRng};
5 |
6 | #[test]
7 | fn test_random_message_generation() {
8 | let seed = 42;
9 | let mut rng: StdRng = SeedableRng::seed_from_u64(seed);
10 |
11 | let known_ardupilotmega_ids = &[
12 | 0, 1, 2, 4, 5, 6, 7, 8, 11, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
13 | 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 54, 55, 61, 62, 63,
14 | 64, 65, 66, 67, 69, 70, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 85, 86, 87, 89, 90, 91,
15 | 92, 93, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115,
16 | 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132,
17 | 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 146, 147, 148, 149, 150,
18 | 151, 152, 153, 154, 155, 156, 157, 158, 160, 161, 162, 163, 164, 165, 166, 167, 168,
19 | 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185,
20 | 186, 191, 192, 193, 194, 195, 200, 201, 214, 215, 216, 217, 218, 219, 225, 226, 230,
21 | 231, 232, 233, 234, 235, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252,
22 | 253, 254, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270,
23 | 271, 275, 276, 280, 281, 282, 283, 284, 285, 286, 287, 288, 290, 291, 299, 300, 301,
24 | 310, 311, 320, 321, 322, 323, 324, 330, 331, 332, 333, 334, 335, 336, 339, 340, 350,
25 | 360, 370, 373, 375, 380, 385, 386, 387, 388, 390, 395, 397, 400, 401, 410, 411, 412,
26 | 413, 9000, 9005, 10001, 10002, 10003, 11000, 11001, 11002, 11003, 11010, 11011, 11020,
27 | 11030, 11031, 11032, 11033, 11034, 11035, 11036, 11037, 11038, 11039, 12900, 12901,
28 | 12902, 12903, 12904, 12905, 12915, 12918, 12919, 12920, 42000, 42001, 50001, 50002,
29 | 50003, 50004, 50005,
30 | ];
31 |
32 | for id in known_ardupilotmega_ids {
33 | let msg = MavMessage::random_message_from_id(*id, &mut rng).unwrap();
34 | let id2 = msg.message_id();
35 | assert_eq!(*id, id2, "Generated message had wrong ID");
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/mavlink-core/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "mavlink-core"
3 | version = "0.16.2"
4 | authors = [
5 | "Todd Stellanova",
6 | "Michal Podhradsky",
7 | "Kevin Mehall",
8 | "Tim Ryan",
9 | "Patrick José Pereira",
10 | "Ibiyemi Abiodun",
11 | ]
12 | description = "Implements the MAVLink data interchange format for UAVs."
13 | readme = "../README.md"
14 | license = "MIT/Apache-2.0"
15 | repository = "https://github.com/mavlink/rust-mavlink"
16 | edition.workspace = true
17 | rust-version.workspace = true
18 | keywords.workspace = true
19 | categories.workspace = true
20 |
21 | [dependencies]
22 | arbitrary = { version = "1.4", optional = true, features = ["derive"] }
23 | async-trait = { version = "0.1.18", optional = true }
24 | byteorder = { workspace = true, default-features = false }
25 | crc-any = { workspace = true, default-features = false }
26 | embedded-hal-02 = { version = "0.2", optional = true, package = "embedded-hal" }
27 | embedded-io = { version = "0.6.1", optional = true }
28 | embedded-io-async = { version = "0.6.1", optional = true }
29 | futures = { version = "0.3", default-features = false, optional = true }
30 | nb = { version = "1.0", optional = true }
31 | rand = { version = "0.9", optional = true, default-features = false, features = ["std", "std_rng"] }
32 | serde = { version = "1.0.115", optional = true, features = ["derive"] }
33 | serde_arrays = { version = "0.2.0", optional = true }
34 | serialport = { version = "4.7.2", default-features = false, optional = true }
35 | sha2 = { version = "0.10", optional = true }
36 | tokio = { version = "1.0", default-features = false, features = ["io-util", "net", "fs"], optional = true }
37 | tokio-serial = { version = "5.4.4", default-features = false, optional = true }
38 |
39 | [features]
40 | default = ["std", "tcp", "udp", "direct-serial", "serde"]
41 |
42 | std = ["byteorder/std"]
43 | udp = []
44 | tcp = []
45 | direct-serial = ["serialport"]
46 | # NOTE: Only one of 'embedded' and 'embedded-hal-02' features can be enabled.
47 | # Use "embedded' feature to enable embedded-hal=1.0 (embedded-io and embedded-io-async is part of embedded-hal).
48 | # Use 'embedded-hal-0.2' feature to enable deprecated embedded-hal=0.2.3 (some hals is not supports embedded-hal=1.0 yet).
49 | embedded = ["dep:embedded-io", "dep:embedded-io-async"]
50 | embedded-hal-02 = ["dep:nb", "dep:embedded-hal-02"]
51 | serde = ["dep:serde", "dep:serde_arrays"]
52 | tokio-1 = ["dep:tokio", "dep:async-trait", "dep:tokio-serial", "dep:futures"]
53 | signing = ["dep:sha2"]
54 | arbitrary = ["dep:arbitrary", "dep:rand"]
55 |
56 | [dev-dependencies]
57 | tokio = { version = "1.0", default-features = false, features = ["io-util", "net", "fs", "macros", "rt"] }
58 |
59 | [lints]
60 | workspace = true
61 |
--------------------------------------------------------------------------------
/mavlink/tests/invalid_value_test.rs:
--------------------------------------------------------------------------------
1 | mod test_shared;
2 |
3 | #[cfg(feature = "common")]
4 | mod helper_tests {
5 | use mavlink::{
6 | calculate_crc,
7 | common::MavMessage,
8 | error::{MessageReadError, ParserError},
9 | peek_reader::PeekReader,
10 | MavlinkVersion, MessageData,
11 | };
12 |
13 | #[test]
14 | fn test_invalid_enum() {
15 | use crate::test_shared::HEARTBEAT_V2;
16 |
17 | let mut invalid_enum_buf = [0; HEARTBEAT_V2.len()];
18 | invalid_enum_buf.copy_from_slice(HEARTBEAT_V2);
19 | // set autopilot to an invalid MavAutopilot value
20 | invalid_enum_buf[1 + 9 + 5] = 255;
21 | // update crc
22 | let crc = calculate_crc(
23 | &invalid_enum_buf[1..HEARTBEAT_V2.len() - 2],
24 | mavlink::common::HEARTBEAT_DATA::EXTRA_CRC,
25 | );
26 | invalid_enum_buf[HEARTBEAT_V2.len() - 2..HEARTBEAT_V2.len()]
27 | .copy_from_slice(&crc.to_le_bytes());
28 |
29 | let result = mavlink::read_v2_msg::(&mut PeekReader::new(
30 | invalid_enum_buf.as_slice(),
31 | ));
32 | assert!(matches!(
33 | result,
34 | Err(MessageReadError::Parse(ParserError::InvalidEnum {
35 | enum_type: "MavAutopilot",
36 | value: 255
37 | }))
38 | ));
39 | }
40 |
41 | #[test]
42 | fn test_invalid_bitflag() {
43 | use mavlink::common::HIL_ACTUATOR_CONTROLS_DATA;
44 |
45 | let msg = HIL_ACTUATOR_CONTROLS_DATA::DEFAULT;
46 | let mut invalid_flag_buf = [0; 1 + 9 + HIL_ACTUATOR_CONTROLS_DATA::ENCODED_LEN + 2];
47 | let len = msg.ser(
48 | MavlinkVersion::V2,
49 | &mut invalid_flag_buf[10..10 + HIL_ACTUATOR_CONTROLS_DATA::ENCODED_LEN],
50 | );
51 | invalid_flag_buf[0] = mavlink::MAV_STX_V2;
52 | invalid_flag_buf[1] = len as u8;
53 | invalid_flag_buf[7] = HIL_ACTUATOR_CONTROLS_DATA::ID as u8;
54 | // set flags to an invalid HilActuatorControlsFlags value
55 | invalid_flag_buf[1 + 9 + 8..1 + 9 + 16].copy_from_slice(&u64::MAX.to_le_bytes());
56 | // update crc
57 | let crc = calculate_crc(
58 | &invalid_flag_buf[1..1 + 9 + len],
59 | HIL_ACTUATOR_CONTROLS_DATA::EXTRA_CRC,
60 | );
61 | invalid_flag_buf[1 + 9 + len..1 + 9 + len + 2].copy_from_slice(&crc.to_le_bytes());
62 |
63 | let result = mavlink::read_v2_msg::(&mut PeekReader::new(
64 | invalid_flag_buf.as_slice(),
65 | ));
66 | assert!(matches!(
67 | result,
68 | Err(MessageReadError::Parse(ParserError::InvalidFlag {
69 | flag_type: "HilActuatorControlsFlags",
70 | value: u64::MAX
71 | }))
72 | ));
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/mavlink-core/src/utils.rs:
--------------------------------------------------------------------------------
1 | //! Utilities for processing MAVLink messages
2 |
3 | /// Removes the trailing zeroes in the payload
4 | ///
5 | /// # Note:
6 | ///
7 | /// There must always be at least one remaining byte even if it is a
8 | /// zero byte.
9 | pub fn remove_trailing_zeroes(data: &[u8]) -> usize {
10 | let mut len = data.len();
11 |
12 | while len > 1 && data[len - 1] == 0 {
13 | len -= 1;
14 | }
15 |
16 | len
17 | }
18 |
19 | /// A trait very similar to [`Default`] but is only implemented for the equivalent Rust types to
20 | /// `MavType`s.
21 | ///
22 | /// This is only needed because rust doesn't currently implement `Default` for arrays
23 | /// of all sizes. In particular this trait is only ever used when the "serde" feature is enabled.
24 | /// For more information, check out [this issue](https://users.rust-lang.org/t/issue-for-derives-for-arrays-greater-than-size-32/59055/3).
25 | pub trait RustDefault: Copy {
26 | fn rust_default() -> Self;
27 | }
28 |
29 | impl RustDefault for [T; N] {
30 | #[inline(always)]
31 | fn rust_default() -> Self {
32 | let val: T = RustDefault::rust_default();
33 | [val; N]
34 | }
35 | }
36 |
37 | macro_rules! impl_rust_default {
38 | ($($t:ty => $val:expr),* $(,)?) => {
39 | $(impl RustDefault for $t {
40 | #[inline(always)]
41 | fn rust_default() -> Self { $val }
42 | })*
43 | };
44 | }
45 |
46 | impl_rust_default! {
47 | u8 => 0,
48 | i8 => 0,
49 | u16 => 0,
50 | i16 => 0,
51 | u32 => 0,
52 | i32 => 0,
53 | u64 => 0,
54 | i64 => 0,
55 | f32 => 0.0,
56 | f64 => 0.0,
57 | char => '\0',
58 | }
59 |
60 | #[cfg(test)]
61 | mod tests {
62 | use super::*;
63 |
64 | #[test]
65 | fn test_remove_trailing_zeroes_empty_slice() {
66 | remove_trailing_zeroes(&[]);
67 | }
68 | }
69 |
70 | #[cfg(feature = "serde")]
71 | pub mod nulstr {
72 | use serde::de::Deserializer;
73 | use serde::ser::Serializer;
74 | use serde::Deserialize;
75 | use std::str;
76 |
77 | pub fn serialize(value: &[u8; N], serializer: S) -> Result
78 | where
79 | S: Serializer,
80 | {
81 | let nul_pos = value.iter().position(|&b| b == 0).unwrap_or(N);
82 | let s = str::from_utf8(&value[..nul_pos]).map_err(serde::ser::Error::custom)?;
83 | serializer.serialize_str(s)
84 | }
85 |
86 | pub fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result<[u8; N], D::Error>
87 | where
88 | D: Deserializer<'de>,
89 | {
90 | let s: String = Deserialize::deserialize(deserializer)?;
91 | let mut buf = [0u8; N];
92 | let bytes = s.as_bytes();
93 | let len = bytes.len().min(N);
94 | buf[..len].copy_from_slice(&bytes[..len]);
95 | Ok(buf)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/mavlink/build/main.rs:
--------------------------------------------------------------------------------
1 | #![recursion_limit = "256"]
2 |
3 | use std::env;
4 | use std::fs::read_dir;
5 | use std::path::Path;
6 | use std::process::{Command, ExitCode};
7 |
8 | use mavlink_bindgen::XmlDefinitions;
9 |
10 | fn main() -> ExitCode {
11 | let src_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
12 |
13 | // Check if git is installed
14 | if Command::new("git").arg("--version").status().is_err() {
15 | eprintln!("error: Git is not installed or could not be found.");
16 | return ExitCode::FAILURE;
17 | }
18 |
19 | // Update and init submodule
20 | if let Err(error) = Command::new("git")
21 | .arg("submodule")
22 | .arg("update")
23 | .arg("--init")
24 | .current_dir(src_dir)
25 | .status()
26 | {
27 | eprintln!("Failed to update MAVLink definitions submodule: {error}");
28 | return ExitCode::FAILURE;
29 | }
30 |
31 | // find & apply patches to XML definitions to avoid crashes
32 | let patch_dir = src_dir.join("build/patches");
33 | let mavlink_dir = src_dir.join("mavlink");
34 |
35 | if let Ok(dir) = read_dir(patch_dir) {
36 | for entry in dir.flatten() {
37 | if let Err(error) = Command::new("git")
38 | .arg("apply")
39 | .arg(entry.path().as_os_str())
40 | .current_dir(&mavlink_dir)
41 | .status()
42 | {
43 | eprintln!("Failed to apply MAVLink definitions patches: {error}");
44 | return ExitCode::FAILURE;
45 | }
46 | }
47 | }
48 |
49 | let out_dir = env::var("OUT_DIR").unwrap();
50 |
51 | let source_definitions_dir = src_dir.join("mavlink/message_definitions/v1.0");
52 |
53 | let enabled_features: Vec = env::vars()
54 | .filter_map(|(key, _)| key.strip_prefix("CARGO_FEATURE_").map(str::to_lowercase))
55 | .collect();
56 |
57 | let mut definitions_to_bind = vec![];
58 |
59 | if let Ok(dir) = read_dir(&source_definitions_dir) {
60 | for entry in dir.flatten() {
61 | let filename = entry
62 | .path()
63 | .file_stem()
64 | .unwrap()
65 | .to_string_lossy()
66 | .to_lowercase();
67 |
68 | if enabled_features.contains(&filename) {
69 | definitions_to_bind.push(entry.path());
70 | }
71 | }
72 | }
73 |
74 | let xml_definitions = if definitions_to_bind.is_empty() {
75 | XmlDefinitions::Directory(source_definitions_dir)
76 | } else {
77 | XmlDefinitions::Files(definitions_to_bind)
78 | };
79 |
80 | let result = match mavlink_bindgen::generate(xml_definitions, out_dir) {
81 | Ok(r) => r,
82 | Err(e) => {
83 | eprintln!("{e}");
84 | return ExitCode::FAILURE;
85 | }
86 | };
87 |
88 | #[cfg(feature = "format-generated-code")]
89 | mavlink_bindgen::format_generated_code(&result);
90 |
91 | mavlink_bindgen::emit_cargo_build_messages(&result);
92 |
93 | ExitCode::SUCCESS
94 | }
95 |
--------------------------------------------------------------------------------
/mavlink/tests/udp_loopback_tests.rs:
--------------------------------------------------------------------------------
1 | mod test_shared;
2 |
3 | #[cfg(all(feature = "std", feature = "udp", feature = "common"))]
4 | mod test_udp_connections {
5 | use std::thread;
6 |
7 | use mavlink::{MavConnection, MessageData};
8 |
9 | /// Test whether we can send a message via UDP and receive it OK
10 | #[test]
11 | fn test_udp_loopback() {
12 | const RECEIVE_CHECK_COUNT: i32 = 3;
13 |
14 | let server = mavlink::connect("udpin:0.0.0.0:14551").expect("Couldn't create server");
15 |
16 | // have the client send one heartbeat per second
17 | thread::spawn({
18 | move || {
19 | let msg =
20 | mavlink::common::MavMessage::HEARTBEAT(crate::test_shared::get_heartbeat_msg());
21 | let client =
22 | mavlink::connect("udpout:127.0.0.1:14551").expect("Couldn't create client");
23 | loop {
24 | client.send_default(&msg).ok();
25 | }
26 | }
27 | });
28 |
29 | //TODO use std::sync::WaitTimeoutResult to timeout ourselves if recv fails?
30 | let mut recv_count = 0;
31 | for _i in 0..RECEIVE_CHECK_COUNT {
32 | match server.recv() {
33 | Ok((_header, msg)) => {
34 | if let mavlink::common::MavMessage::HEARTBEAT(_heartbeat_msg) = msg {
35 | recv_count += 1;
36 | } else {
37 | // one message parse failure fails the test
38 | break;
39 | }
40 | }
41 | Err(..) => {
42 | // one message read failure fails the test
43 | break;
44 | }
45 | }
46 | }
47 | assert_eq!(recv_count, RECEIVE_CHECK_COUNT);
48 | }
49 |
50 | /// Test whether we can send a message via UDP and receive it OK using recv_raw
51 | #[test]
52 | fn test_udp_loopback_recv_raw() {
53 | const RECEIVE_CHECK_COUNT: i32 = 3;
54 |
55 | let server = mavlink::connect::("udpin:0.0.0.0:14561")
56 | .expect("Couldn't create server");
57 |
58 | // have the client send one heartbeat per second
59 | thread::spawn({
60 | move || {
61 | let msg =
62 | mavlink::common::MavMessage::HEARTBEAT(crate::test_shared::get_heartbeat_msg());
63 | let client =
64 | mavlink::connect("udpout:127.0.0.1:14561").expect("Couldn't create client");
65 | loop {
66 | client.send_default(&msg).ok();
67 | }
68 | }
69 | });
70 |
71 | //TODO use std::sync::WaitTimeoutResult to timeout ourselves if recv fails?
72 | let mut recv_count = 0;
73 | for _i in 0..RECEIVE_CHECK_COUNT {
74 | match server.recv_raw() {
75 | Ok(message) => {
76 | if message.message_id() == mavlink::common::HEARTBEAT_DATA::ID {
77 | recv_count += 1;
78 | } else {
79 | // one message parse failure fails the test
80 | break;
81 | }
82 | }
83 | Err(..) => {
84 | // one message read failure fails the test
85 | break;
86 | }
87 | }
88 | }
89 | assert_eq!(recv_count, RECEIVE_CHECK_COUNT);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/mavlink/examples/embedded-async-read/src/main.rs:
--------------------------------------------------------------------------------
1 | //! Target board: stm32f446RETx (stm32nucleo)
2 | //! Manual: https://www.st.com/resource/en/reference_manual/dm00043574-stm32f303xb-c-d-e-stm32f303x6-8-stm32f328x8-stm32f358xc-stm32f398xe-advanced-arm-based-mcus-stmicroelectronics.pdf
3 | #![no_main]
4 | #![no_std]
5 |
6 | // Panic handler
7 | use panic_rtt_target as _;
8 |
9 | use embassy_executor::Spawner;
10 | use embassy_stm32::{bind_interrupts, mode::Async, peripherals::*, usart};
11 | use embassy_time::Timer;
12 | use mavlink;
13 | use mavlink::common::{MavMessage, HEARTBEAT_DATA};
14 | use mavlink::{read_v2_raw_message_async, MAVLinkV2MessageRaw, MavlinkVersion, MessageData};
15 | use rtt_target::{rprintln, rtt_init_print};
16 | use static_cell::ConstStaticCell;
17 |
18 | bind_interrupts!(struct Irqs {
19 | USART1 => usart::InterruptHandler;
20 | });
21 |
22 | #[embassy_executor::main]
23 | async fn main(spawner: Spawner) {
24 | rtt_init_print!();
25 |
26 | // Peripherals access
27 | let p = embassy_stm32::init(embassy_stm32::Config::default());
28 |
29 | // Create an interface USART2 with 115200 baudrate
30 | let mut config = usart::Config::default();
31 | config.baudrate = 115200;
32 | let serial = usart::Uart::new(
33 | p.USART1, p.PA10, p.PA9, Irqs, p.DMA2_CH7, p.DMA2_CH2, config,
34 | )
35 | .unwrap();
36 |
37 | // Break serial in TX and RX (not used)
38 | let (mut tx, rx) = serial.split();
39 |
40 | // Create our mavlink header and heartbeat message
41 | let header = mavlink::MavHeader {
42 | system_id: 1,
43 | component_id: 1,
44 | sequence: 42,
45 | };
46 | let heartbeat = mavlink::common::HEARTBEAT_DATA {
47 | custom_mode: 0,
48 | mavtype: mavlink::common::MavType::MAV_TYPE_SUBMARINE,
49 | autopilot: mavlink::common::MavAutopilot::MAV_AUTOPILOT_ARDUPILOTMEGA,
50 | base_mode: mavlink::common::MavModeFlag::empty(),
51 | system_status: mavlink::common::MavState::MAV_STATE_STANDBY,
52 | mavlink_version: 0x3,
53 | };
54 |
55 | // Spawn Rx loop
56 | spawner.spawn(rx_task(rx)).unwrap();
57 |
58 | // Main loop
59 | loop {
60 | // Write the raw heartbeat message to reduce firmware flash size (using Message::ser will be add ~70KB because
61 | // all *_DATA::ser methods will be add to firmware).
62 | let mut raw = MAVLinkV2MessageRaw::new();
63 | raw.serialize_message_data(header, &heartbeat);
64 | tx.write(raw.raw_bytes()).await.unwrap();
65 |
66 | // Delay for 1 second
67 | Timer::after_millis(1000).await;
68 | }
69 | }
70 |
71 | #[embassy_executor::task]
72 | pub async fn rx_task(rx: usart::UartRx<'static, Async>) {
73 | // Make ring-buffered RX (over DMA)
74 | static BUF_MEMORY: ConstStaticCell<[u8; 1024]> = ConstStaticCell::new([0; 1024]);
75 | let mut rx_buffered = rx.into_ring_buffered(BUF_MEMORY.take());
76 |
77 | loop {
78 | // Read raw message to reduce firmware flash size (using read_v2_msg_async will be add ~80KB because
79 | // all *_DATA::deser methods will be add to firmware).
80 | let raw = read_v2_raw_message_async::(&mut rx_buffered)
81 | .await
82 | .unwrap();
83 | rprintln!("Read raw message: msg_id={}", raw.message_id());
84 |
85 | if raw.message_id() == HEARTBEAT_DATA::ID {
86 | let heartbeat = HEARTBEAT_DATA::deser(MavlinkVersion::V2, raw.payload()).unwrap();
87 | rprintln!("heartbeat: {:?}", heartbeat);
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/mavlink/tests/signing.rs:
--------------------------------------------------------------------------------
1 | mod test_shared;
2 |
3 | #[cfg(feature = "signing")]
4 | mod signing {
5 | use mavlink::{
6 | common::HEARTBEAT_DATA, peek_reader::PeekReader, read_v2_raw_message, MAVLinkV2MessageRaw,
7 | MavHeader, SigningConfig, SigningData, MAV_STX_V2,
8 | };
9 |
10 | use crate::test_shared::SECRET_KEY;
11 |
12 | const HEARTBEAT_SIGNED: &[u8] = &[
13 | MAV_STX_V2,
14 | 0x09,
15 | 0x01, // MAVLINK_IFLAG_SIGNED
16 | 0x00,
17 | crate::test_shared::COMMON_MSG_HEADER.sequence,
18 | crate::test_shared::COMMON_MSG_HEADER.system_id,
19 | crate::test_shared::COMMON_MSG_HEADER.component_id,
20 | 0x00, // msg ID
21 | 0x00,
22 | 0x00,
23 | 0x05, // payload
24 | 0x00,
25 | 0x00,
26 | 0x00,
27 | 0x02,
28 | 0x03,
29 | 0x59,
30 | 0x03,
31 | 0x03,
32 | 0xc9, // checksum
33 | 0x8b,
34 | 0x00, // link_id
35 | 0xff, // use max timestamp to ensure test will never fail against current time
36 | 0xff,
37 | 0xff,
38 | 0xff,
39 | 0xff,
40 | 0xff,
41 | 0x27, // signature
42 | 0x18,
43 | 0xb1,
44 | 0x68,
45 | 0xcc,
46 | 0xf5,
47 | ];
48 |
49 | #[test]
50 | pub fn test_verify() {
51 | let signing_cfg = SigningConfig::new(SECRET_KEY, 0, true, false);
52 | let signing_data = SigningData::from_config(signing_cfg);
53 | let mut r = PeekReader::new(HEARTBEAT_SIGNED);
54 | let msg = read_v2_raw_message::(&mut r).unwrap();
55 | assert!(
56 | signing_data.verify_signature(&msg),
57 | "Message verification failed"
58 | );
59 | }
60 |
61 | #[test]
62 | pub fn test_invalid_ts() {
63 | let signing_cfg = SigningConfig::new(SECRET_KEY, 0, true, false);
64 | let signing_data = SigningData::from_config(signing_cfg);
65 | let mut r = PeekReader::new(HEARTBEAT_SIGNED);
66 | let mut msg = read_v2_raw_message::(&mut r).unwrap();
67 | msg.signature_timestamp_bytes_mut()
68 | .copy_from_slice(&[0, 0, 0, 0, 0, 0]); // set timestamp to min causing the timestamp test to fail
69 | assert!(
70 | !signing_data.verify_signature(&msg),
71 | "Invalid message verified"
72 | );
73 | }
74 |
75 | #[test]
76 | pub fn test_sign_verify() {
77 | use mavlink::common::MavMessage;
78 | let heartbeat_message = MavMessage::HEARTBEAT(HEARTBEAT_DATA::default());
79 | let mut message = MAVLinkV2MessageRaw::new();
80 | let header = MavHeader {
81 | system_id: 4,
82 | component_id: 3,
83 | sequence: 42,
84 | };
85 | message.serialize_message_for_signing(header, &heartbeat_message);
86 |
87 | let signing_cfg = SigningConfig::new(SECRET_KEY, 0, true, false);
88 | let signing_data = SigningData::from_config(signing_cfg);
89 | signing_data.sign_message(&mut message);
90 | assert!(
91 | signing_data.verify_signature(&message),
92 | "Message verification failed"
93 | );
94 | // the same message must not be allowed to be verified again
95 | assert!(
96 | !signing_data.verify_signature(&message),
97 | "Invalid message verified"
98 | );
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/mavlink/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(not(feature = "std"), no_std)]
2 | #![cfg_attr(docsrs, feature(doc_cfg))]
3 |
4 | //! Rust implementation of the MAVLink UAV messaging protocol, with bindings for all dialects.
5 | //! This crate provides message set code generation, packet building, parsing and connection handling for blocking and asynchronous I/O.
6 | //!
7 | //! # Feature flags
8 | //! The `mavlink` crate uses a number of [feature flags] to reduce the amount of compiled code by making certain functions and MAVLink message sets (dialects) optional.
9 | //! These feature flags are available to control the provided functionalities:
10 | //!
11 | //! - `std`: Enables the usage of `std` in `mavlink`, enabled by default, this can be disabled for embedded applications.
12 | //! - `direct-serial`: Enable serial MAVLink connections, enabled by default.
13 | //! - `udp`: Enables UDP based MAVLink connections, enabled by default.
14 | //! - `tcp`: Enables TCP based MAVLink connections, enabled by default.
15 | //! - `signing`: Enable support for [MAVLink 2 message signing]
16 | //! - `embedded`: Enables embedded support using the [embedded-io] crate, incompatible with `embedded-hal-02` and `tokio-1`.
17 | //! - `embedded-hal-02`: Enables embedded support using version 0.2 of the [embedded-hal] crate, incompatible with `embedded`.
18 | //! - `tokio-1`: Enable support for asynchronous I/O using [tokio], incompatible with `embedded`.
19 | //! - `serde`: Enables [serde] support in generated message sets, enabled by default.
20 | //! - `format-generated-code`: Generated MAVLink message set code will be formatted, requires `rustfmt` to be installed, enabled by default.
21 | //! - `emit-extensions`: Generated MAVLink message set code will include [MAVLink 2 message extensions].
22 | //! - `arbitrary`: Enable support for the [arbitrary] crate.
23 | //! - `ts`: Enable support for [ts-rs] typescript generation.
24 | //!
25 | //! Either `std`, `embedded` or `embedded-hal-02` must be enabled.
26 | //!
27 | //! Each MAVlink message set (dialect) can be enabled using its feature flag. The following message set feature flags are available:
28 | //! - `ardupilotmega`, enabled by default
29 | //! - `common`, enabled by default
30 | //! - `all`, this includes all other sets in the same message set
31 | //! - `asluav`
32 | //! - `avssuas`
33 | //! - `cubepilot`
34 | //! - `csairlink`
35 | //! - `development`
36 | //! - `icarous`
37 | //! - `loweheiser`
38 | //! - `matrixpilot`
39 | //! - `minimal`
40 | //! - `paparazzi`
41 | //! - `python_array_test`
42 | //! - `slugs`
43 | //! - `standard`
44 | //! - `storm32`
45 | //! - `test`
46 | //! - `ualberta`
47 | //! - `uavionix`
48 | //!
49 | //! The `all-dialects` feature enables all message sets except `all`.
50 | //!
51 | //! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section
52 | //! [MAVLink 2 message signing]: https://mavlink.io/en/guide/message_signing.html
53 | //! [MAVLink 2 message extensions]: https://mavlink.io/en/guide/define_xml_element.html#message_extensions
54 | //! [embedded-io]: https://crates.io/crates/embedded-io
55 | //! [embedded-hal]: https://crates.io/crates/embedded-hal
56 | //! [tokio]: https://crates.io/crates/tokio
57 | //! [serde]: https://crates.io/crates/serde
58 | //! [arbitrary]: https://crates.io/crates/arbitrary
59 | //! [ts-rs]: https://crates.io/crates/ts-rs
60 |
61 | // include generate definitions
62 | include!(concat!(env!("OUT_DIR"), "/mod.rs"));
63 |
64 | pub use mavlink_core::*;
65 |
66 | #[cfg(feature = "emit-extensions")]
67 | #[allow(unused_imports)]
68 | pub(crate) use mavlink_core::utils::RustDefault;
69 |
70 | #[cfg(feature = "serde")]
71 | #[allow(unused_imports)]
72 | pub(crate) use mavlink_core::utils::nulstr;
73 |
--------------------------------------------------------------------------------
/mavlink/examples/mavlink-dump/src/main.rs:
--------------------------------------------------------------------------------
1 | use mavlink::error::MessageReadError;
2 | use mavlink::MavConnection;
3 | use std::{env, sync::Arc, thread, time::Duration};
4 |
5 | fn main() {
6 | let args: Vec<_> = env::args().collect();
7 |
8 | if args.len() < 2 {
9 | println!(
10 | "Usage: mavlink-dump (tcpout|tcpin|udpout|udpin|udpbcast|serial|file):(ip|dev|path):(port|baud)"
11 | );
12 | return;
13 | }
14 |
15 | // It's possible to change the mavlink dialect to be used in the connect call
16 | let mut mavconn = mavlink::connect::(&args[1]).unwrap();
17 |
18 | // here as an example we force the protocol version to mavlink V1:
19 | // the default for this library is mavlink V2
20 | mavconn.set_protocol_version(mavlink::MavlinkVersion::V1);
21 |
22 | let vehicle = Arc::new(mavconn);
23 | vehicle
24 | .send(&mavlink::MavHeader::default(), &request_parameters())
25 | .unwrap();
26 | vehicle
27 | .send(&mavlink::MavHeader::default(), &request_stream())
28 | .unwrap();
29 |
30 | thread::spawn({
31 | let vehicle = vehicle.clone();
32 | move || loop {
33 | let res = vehicle.send_default(&heartbeat_message());
34 | if res.is_ok() {
35 | thread::sleep(Duration::from_secs(1));
36 | } else {
37 | println!("send failed: {res:?}");
38 | }
39 | }
40 | });
41 |
42 | loop {
43 | match vehicle.recv() {
44 | Ok((_header, msg)) => {
45 | println!("received: {msg:?}");
46 | }
47 | Err(MessageReadError::Io(e)) => {
48 | if e.kind() == std::io::ErrorKind::WouldBlock {
49 | //no messages currently available to receive -- wait a while
50 | thread::sleep(Duration::from_secs(1));
51 | continue;
52 | } else {
53 | println!("recv error: {e:?}");
54 | break;
55 | }
56 | }
57 | // messages that didn't get through due to parser errors are ignored
58 | _ => {}
59 | }
60 | }
61 | }
62 |
63 | /// Create a heartbeat message using 'ardupilotmega' dialect
64 | pub fn heartbeat_message() -> mavlink::ardupilotmega::MavMessage {
65 | mavlink::ardupilotmega::MavMessage::HEARTBEAT(mavlink::ardupilotmega::HEARTBEAT_DATA {
66 | custom_mode: 0,
67 | mavtype: mavlink::ardupilotmega::MavType::MAV_TYPE_QUADROTOR,
68 | autopilot: mavlink::ardupilotmega::MavAutopilot::MAV_AUTOPILOT_ARDUPILOTMEGA,
69 | base_mode: mavlink::ardupilotmega::MavModeFlag::empty(),
70 | system_status: mavlink::ardupilotmega::MavState::MAV_STATE_STANDBY,
71 | mavlink_version: 0x3,
72 | })
73 | }
74 |
75 | /// Create a message requesting the parameters list
76 | pub fn request_parameters() -> mavlink::ardupilotmega::MavMessage {
77 | mavlink::ardupilotmega::MavMessage::PARAM_REQUEST_LIST(
78 | mavlink::ardupilotmega::PARAM_REQUEST_LIST_DATA {
79 | target_system: 0,
80 | target_component: 0,
81 | },
82 | )
83 | }
84 |
85 | /// Create a message enabling data streaming
86 | pub fn request_stream() -> mavlink::ardupilotmega::MavMessage {
87 | #[expect(deprecated)]
88 | mavlink::ardupilotmega::MavMessage::REQUEST_DATA_STREAM(
89 | mavlink::ardupilotmega::REQUEST_DATA_STREAM_DATA {
90 | target_system: 0,
91 | target_component: 0,
92 | req_stream_id: 0,
93 | req_message_rate: 10,
94 | start_stop: 1,
95 | },
96 | )
97 | }
98 |
--------------------------------------------------------------------------------
/mavlink/tests/udp_loopback_async_tests.rs:
--------------------------------------------------------------------------------
1 | mod test_shared;
2 |
3 | #[cfg(all(feature = "tokio-1", feature = "udp", feature = "common"))]
4 | mod test_udp_connections {
5 | use mavlink::MessageData;
6 |
7 | /// Test whether we can send a message via UDP and receive it OK using async_connect
8 | #[tokio::test]
9 | async fn test_udp_loopback() {
10 | const RECEIVE_CHECK_COUNT: i32 = 3;
11 |
12 | let server = mavlink::connect_async("udpin:0.0.0.0:14552")
13 | .await
14 | .expect("Couldn't create server");
15 |
16 | // have the client send one heartbeat per second
17 | tokio::spawn({
18 | async move {
19 | let msg =
20 | mavlink::common::MavMessage::HEARTBEAT(crate::test_shared::get_heartbeat_msg());
21 | let client = mavlink::connect_async("udpout:127.0.0.1:14552")
22 | .await
23 | .expect("Couldn't create client");
24 | loop {
25 | client.send_default(&msg).await.ok();
26 | }
27 | }
28 | });
29 |
30 | //TODO use std::sync::WaitTimeoutResult to timeout ourselves if recv fails?
31 | let mut recv_count = 0;
32 | for _i in 0..RECEIVE_CHECK_COUNT {
33 | match server.recv().await {
34 | Ok((_header, msg)) => {
35 | if let mavlink::common::MavMessage::HEARTBEAT(_heartbeat_msg) = msg {
36 | recv_count += 1;
37 | } else {
38 | // one message parse failure fails the test
39 | break;
40 | }
41 | }
42 | Err(..) => {
43 | // one message read failure fails the test
44 | break;
45 | }
46 | }
47 | }
48 | assert_eq!(recv_count, RECEIVE_CHECK_COUNT);
49 | }
50 |
51 | /// Test whether we can send a message via UDP and receive it OK using async_connect recv_raw
52 | #[tokio::test]
53 | async fn test_udp_loopback_recv_raw() {
54 | const RECEIVE_CHECK_COUNT: i32 = 3;
55 |
56 | let server = mavlink::connect_async::("udpin:0.0.0.0:14562")
57 | .await
58 | .expect("Couldn't create server");
59 |
60 | // have the client send one heartbeat per second
61 | tokio::spawn({
62 | async move {
63 | let msg =
64 | mavlink::common::MavMessage::HEARTBEAT(crate::test_shared::get_heartbeat_msg());
65 | let client = mavlink::connect_async("udpout:127.0.0.1:14562")
66 | .await
67 | .expect("Couldn't create client");
68 | loop {
69 | client.send_default(&msg).await.ok();
70 | }
71 | }
72 | });
73 |
74 | //TODO use std::sync::WaitTimeoutResult to timeout ourselves if recv fails?
75 | let mut recv_count = 0;
76 | for _i in 0..RECEIVE_CHECK_COUNT {
77 | match server.recv_raw().await {
78 | Ok(message) => {
79 | if message.message_id() == mavlink::common::HEARTBEAT_DATA::ID {
80 | recv_count += 1;
81 | } else {
82 | // one message parse failure fails the test
83 | break;
84 | }
85 | }
86 | Err(..) => {
87 | // one message read failure fails the test
88 | break;
89 | }
90 | }
91 | }
92 | assert_eq!(recv_count, RECEIVE_CHECK_COUNT);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/mavlink/examples/embedded/src/main.rs:
--------------------------------------------------------------------------------
1 | //! Target board: stm32f303RETx (stm32nucleo)
2 | //! Manual: https://www.st.com/resource/en/reference_manual/dm00043574-stm32f303xb-c-d-e-stm32f303x6-8-stm32f328x8-stm32f358xc-stm32f398xe-advanced-arm-based-mcus-stmicroelectronics.pdf
3 | #![no_main]
4 | #![no_std]
5 |
6 | // Panic handler
7 | use panic_halt as _;
8 |
9 | use cortex_m_rt::entry;
10 | use hal::pac;
11 | use hal::prelude::*;
12 | use mavlink;
13 | use stm32f3xx_hal as hal;
14 |
15 | #[entry]
16 | fn main() -> ! {
17 | // Peripherals access
18 | let dp = pac::Peripherals::take().unwrap();
19 | let cp = cortex_m::Peripherals::take().unwrap();
20 |
21 | // 9: RCC: Reset and clock control (RCC)
22 | let mut rcc = dp.RCC.constrain();
23 |
24 | // Configure GPIOA using AHB
25 | // 9.4.6: AHB peripheral clock enable register (RCC_AHBENR)
26 | // Bit 17 IOPAEN: I/O port A clock enable
27 | let mut gpioa = dp.GPIOA.split(&mut rcc.ahb);
28 |
29 | // stm32nucleo has a LED on pin PA5
30 | let mut led = gpioa
31 | .pa5
32 | .into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper);
33 |
34 | // Constrains the FLASH peripheral to play nicely with the other abstractions
35 | let mut flash = dp.FLASH.constrain();
36 |
37 | // Freezes the clock configuration
38 | // This function internally calculates the specific
39 | // divisors for the different clock peripheries
40 | // 4.5.1 Flash access control register (FLASH_ACR)
41 | let clocks = rcc.cfgr.freeze(&mut flash.acr);
42 |
43 | // USART2 uses Pins A9 and A10
44 | // We don't need the datasheet to check which alternative function to use
45 | // https://docs.rs/stm32f3xx-hal/0.6.1/stm32f3xx_hal/gpio/gpioa/struct.PA2.html#impl-TxPin%3CUSART2%3E
46 | // The documentation provide the necessary information about each possible hardware configuration
47 | let pin_tx = gpioa.pa2.into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl);
48 | let pin_rx = gpioa.pa3.into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl);
49 |
50 | // Create an interface USART2 with 115200 baudrate
51 | let serial = hal::serial::Serial::new(
52 | dp.USART2,
53 | (pin_tx, pin_rx),
54 | 115_200.Bd(),
55 | clocks,
56 | &mut rcc.apb1,
57 | );
58 |
59 | // Break serial in TX and RX (not used)
60 | let (mut tx, _) = serial.split();
61 |
62 | // Create our mavlink header and heartbeat message
63 | let header = mavlink_header();
64 | let heartbeat = mavlink_heartbeat_message();
65 |
66 | // Create a delay object based on SysTick
67 | let mut delay = hal::delay::Delay::new(cp.SYST, clocks);
68 |
69 | // Main loop
70 | loop {
71 | // Write the mavlink message via serial
72 | mavlink::write_versioned_msg(&mut tx, mavlink::MavlinkVersion::V2, header, &heartbeat)
73 | .unwrap();
74 |
75 | // Toggle the LED
76 | led.toggle().unwrap();
77 |
78 | // Delay for 1 second
79 | delay.delay_ms(1_000u32);
80 | }
81 | }
82 |
83 | fn mavlink_header() -> mavlink::MavHeader {
84 | mavlink::MavHeader {
85 | system_id: 1,
86 | component_id: 1,
87 | sequence: 42,
88 | }
89 | }
90 |
91 | pub fn mavlink_heartbeat_message() -> mavlink::common::MavMessage {
92 | mavlink::common::MavMessage::HEARTBEAT(mavlink::common::HEARTBEAT_DATA {
93 | custom_mode: 0,
94 | mavtype: mavlink::common::MavType::MAV_TYPE_SUBMARINE,
95 | autopilot: mavlink::common::MavAutopilot::MAV_AUTOPILOT_ARDUPILOTMEGA,
96 | base_mode: mavlink::common::MavModeFlag::empty(),
97 | system_status: mavlink::common::MavState::MAV_STATE_STANDBY,
98 | mavlink_version: 0x3,
99 | })
100 | }
101 |
--------------------------------------------------------------------------------
/mavlink/Cargo.toml:
--------------------------------------------------------------------------------
1 |
2 | [package]
3 | name = "mavlink"
4 | version = "0.16.2"
5 | authors = [
6 | "Todd Stellanova",
7 | "Michal Podhradsky",
8 | "Kevin Mehall",
9 | "Tim Ryan",
10 | "Patrick José Pereira",
11 | "Ibiyemi Abiodun",
12 | ]
13 | build = "build/main.rs"
14 | description = "Implements the MAVLink data interchange format for UAVs."
15 | readme = "../README.md"
16 | license = "MIT/Apache-2.0"
17 | repository = "https://github.com/mavlink/rust-mavlink"
18 | edition.workspace = true
19 | rust-version.workspace = true
20 | keywords.workspace = true
21 | categories.workspace = true
22 |
23 | [build-dependencies]
24 | mavlink-bindgen = { version = "=0.16.2", path = "../mavlink-bindgen", default-features = false }
25 |
26 | [[example]]
27 | name = "mavlink-dump"
28 | path = "examples/mavlink-dump/src/main.rs"
29 | required-features = ["ardupilotmega"]
30 |
31 | [dependencies]
32 | mavlink-core = { version="=0.16.2", path = "../mavlink-core", default-features = false }
33 | num-traits = { workspace = true, default-features = false }
34 | num-derive = { workspace = true }
35 | bitflags = { workspace = true }
36 | serde = { version = "1.0.115", optional = true, features = ["derive"] }
37 | serde_arrays = { version = "0.2.0", optional = true }
38 | arbitrary = { version = "1.4", optional = true, features = ["derive"] }
39 | rand = { version = "0.9", optional = true, default-features = false, features = ["std", "std_rng"] }
40 | ts-rs = { version = "11.0.1", optional = true }
41 |
42 | [features]
43 | default = ["std", "tcp", "udp", "direct-serial", "serde", "ardupilotmega", "common", "format-generated-code"]
44 |
45 | all = []
46 | ardupilotmega = []
47 | asluav = []
48 | avssuas = []
49 | development = []
50 | matrixpilot = []
51 | minimal = []
52 | paparazzi = []
53 | python_array_test = []
54 | standard = []
55 | test = []
56 | ualberta = []
57 | uavionix = []
58 | icarous = []
59 | common = []
60 | cubepilot = []
61 | csairlink = []
62 | loweheiser = []
63 | storm32 = []
64 |
65 | all-dialects = [
66 | "ardupilotmega",
67 | "asluav",
68 | "avssuas",
69 | "development",
70 | "matrixpilot",
71 | "minimal",
72 | "paparazzi",
73 | "python_array_test",
74 | "standard",
75 | "test",
76 | "ualberta",
77 | "uavionix",
78 | "icarous",
79 | "common",
80 | "cubepilot",
81 | "storm32",
82 | "csairlink",
83 | "loweheiser"
84 | ]
85 |
86 | format-generated-code = []
87 | emit-extensions = ["mavlink-bindgen/emit-extensions"]
88 | std = ["mavlink-core/std"]
89 | udp = ["mavlink-core/udp"]
90 | tcp = ["mavlink-core/tcp"]
91 | signing = ["mavlink-core/signing"]
92 | direct-serial = ["mavlink-core/direct-serial"]
93 | # NOTE: Only one of 'embedded' and 'embedded-hal-02' features can be enabled.
94 | # Use "embedded' feature to enable embedded-hal=1.0 (embedded-io and embedded-io-async is part of embedded-hal).
95 | # Use 'embedded-hal-0.2' feature to enable deprecated embedded-hal=0.2.3 (some hals is not supports embedded-hal=1.0 yet).
96 | embedded = ["mavlink-core/embedded"]
97 | embedded-hal-02 = ["mavlink-core/embedded-hal-02"]
98 | serde = ["bitflags/serde", "dep:serde", "dep:serde_arrays", "mavlink-core/serde"]
99 | tokio-1 = ["mavlink-core/tokio-1"]
100 | arbitrary = ["dep:arbitrary", "dep:rand", "mavlink-bindgen/arbitrary", "mavlink-core/arbitrary", "bitflags/arbitrary"]
101 | # Used for typescript generation
102 | ts = ["dep:ts-rs"]
103 |
104 | # build with all features on docs.rs so that users viewing documentation
105 | # can see everything
106 | [package.metadata.docs.rs]
107 | features = [
108 | "default",
109 | "arbitrary",
110 | "all-dialects",
111 | "emit-extensions",
112 | "format-generated-code",
113 | "tokio-1",
114 | "signing"
115 | ]
116 |
117 | [dev-dependencies]
118 | tokio = { version = "1.0", default-features = false, features = ["macros", "rt", "time" ] }
119 | serde_json = { version = "1.0", features = ["preserve_order"] }
120 | serde_test = "1.0"
121 |
122 | [lints]
123 | workspace = true
124 |
--------------------------------------------------------------------------------
/mavlink/tests/mav_frame_tests.rs:
--------------------------------------------------------------------------------
1 | pub mod test_shared;
2 |
3 | mod mav_frame_tests {
4 | use mavlink::MavFrame;
5 |
6 | // NOTE: No STX, length, or flag fields in the header
7 | pub const HEARTBEAT_V2: &[u8] = &[
8 | // Currently [`MavFrame::deser`] and [`MavFrame::ser`] does not account for the first four fields.
9 | // 0xfd, // STX V2
10 | // 0x09, // len
11 | // 0x00, // incompat_flags
12 | // 0x00, // compat_flags
13 | crate::test_shared::COMMON_MSG_HEADER.sequence,
14 | crate::test_shared::COMMON_MSG_HEADER.system_id,
15 | crate::test_shared::COMMON_MSG_HEADER.component_id,
16 | 0x00, // msg ID
17 | 0x00,
18 | 0x00,
19 | 0x05, // payload
20 | 0x00,
21 | 0x00,
22 | 0x00,
23 | 0x02,
24 | 0x03,
25 | 0x59,
26 | 0x03,
27 | 0x03,
28 | 0x10, // checksum
29 | 0xf0,
30 | ];
31 |
32 | #[cfg(feature = "common")]
33 | #[test]
34 | pub fn test_deser_ser() {
35 | use mavlink::{common::MavMessage, MavlinkVersion};
36 | let frame = MavFrame::::deser(MavlinkVersion::V2, HEARTBEAT_V2)
37 | .expect("failed to parse message");
38 |
39 | assert_eq!(frame.header, crate::test_shared::COMMON_MSG_HEADER);
40 | let heartbeat_msg = crate::test_shared::get_heartbeat_msg();
41 |
42 | let mut buffer = [0u8; HEARTBEAT_V2.len()];
43 | frame.ser(&mut buffer);
44 | assert_eq!(buffer[..buffer.len() - 2], HEARTBEAT_V2[..buffer.len() - 2]);
45 |
46 | let msg = match frame.msg {
47 | MavMessage::HEARTBEAT(msg) => msg,
48 | _ => panic!("Decoded wrong message type"),
49 | };
50 | assert_eq!(msg.custom_mode, heartbeat_msg.custom_mode);
51 | assert_eq!(msg.mavtype, heartbeat_msg.mavtype);
52 | assert_eq!(msg.autopilot, heartbeat_msg.autopilot);
53 | assert_eq!(msg.base_mode, heartbeat_msg.base_mode);
54 | assert_eq!(msg.system_status, heartbeat_msg.system_status);
55 | assert_eq!(msg.mavlink_version, heartbeat_msg.mavlink_version);
56 | }
57 |
58 | #[cfg(feature = "ardupilotmega")]
59 | #[test]
60 | pub fn test_deser_ser_message() {
61 | let buf: &mut [u8; 255] = &mut [0; 255];
62 |
63 | let mavlink_message = mavlink_message();
64 | let mavlink_frame = new(mavlink_message);
65 |
66 | let _len = mavlink_frame.ser(buf);
67 |
68 | let parsed_mavlink_frame =
69 | MavFrame::::deser(mavlink::MavlinkVersion::V2, buf)
70 | .unwrap();
71 |
72 | assert_eq!(
73 | format!("{mavlink_frame:?}"),
74 | format!("{parsed_mavlink_frame:?}")
75 | );
76 | }
77 |
78 | #[cfg(feature = "ardupilotmega")]
79 | fn mavlink_message() -> mavlink::ardupilotmega::MavMessage {
80 | mavlink::ardupilotmega::MavMessage::LINK_NODE_STATUS(
81 | mavlink::ardupilotmega::LINK_NODE_STATUS_DATA {
82 | timestamp: 92197916,
83 | tx_rate: 0x11223344,
84 | rx_rate: 0x55667788,
85 | messages_sent: 0x99001122,
86 | messages_received: 0x33445566,
87 | messages_lost: 0x77889900,
88 | rx_parse_err: 0x1122,
89 | tx_overflows: 0x3355,
90 | rx_overflows: 0x5566,
91 | tx_buf: 0xff,
92 | rx_buf: 0x11,
93 | },
94 | )
95 | }
96 |
97 | #[cfg(feature = "ardupilotmega")]
98 | fn new(
99 | msg: mavlink::ardupilotmega::MavMessage,
100 | ) -> MavFrame {
101 | use mavlink::MavHeader;
102 | MavFrame {
103 | header: MavHeader {
104 | system_id: 1,
105 | component_id: 2,
106 | sequence: 84,
107 | },
108 | msg,
109 | protocol_version: mavlink::MavlinkVersion::V2,
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/mavlink/tests/file_connection_tests.rs:
--------------------------------------------------------------------------------
1 | mod test_shared;
2 |
3 | #[cfg(feature = "common")]
4 | mod test_file_connections {
5 | use mavlink::ardupilotmega::MavMessage;
6 | use mavlink::MavConnection;
7 |
8 | /// Test whether we can send a message via TCP and receive it OK using async_connect.
9 | /// This also test signing as a property of a MavConnection if the signing feature is enabled.
10 | #[cfg(feature = "tokio-1")]
11 | #[tokio::test]
12 | pub async fn test_file_async_read_raw() {
13 | let tlog = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
14 | .join("tests/log.tlog")
15 | .canonicalize()
16 | .unwrap();
17 |
18 | let tlog = tlog.to_str().unwrap();
19 |
20 | let filename = std::path::Path::new(tlog);
21 | let filename = filename.to_str().unwrap();
22 | dbg!(filename);
23 |
24 | println!("Processing file: {filename}");
25 | let connection_string = format!("file:{filename}");
26 |
27 | println!("connection_string - {connection_string}");
28 |
29 | let vehicle = mavlink::connect_async::(&connection_string)
30 | .await
31 | .expect("Couldn't read from file");
32 |
33 | let mut counter = 0;
34 | loop {
35 | match vehicle.recv_raw().await {
36 | Ok(raw_msg) => {
37 | println!(
38 | "raw_msg.component_id() {} | sequence number {} | message_id {:?}",
39 | raw_msg.component_id(),
40 | raw_msg.sequence(),
41 | raw_msg.message_id()
42 | );
43 |
44 | counter += 1;
45 | }
46 | Err(mavlink::error::MessageReadError::Io(e)) => {
47 | if e.kind() == tokio::io::ErrorKind::UnexpectedEof {
48 | break;
49 | }
50 | }
51 | _ => {
52 | break;
53 | }
54 | }
55 | }
56 |
57 | println!("Number of parsed messages: {counter}");
58 | assert!(
59 | counter == 1426,
60 | "Unable to hit the necessary amount of matches"
61 | );
62 | }
63 |
64 | #[test]
65 | pub fn test_file_read_raw() {
66 | let tlog = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
67 | .join("tests/log.tlog")
68 | .canonicalize()
69 | .unwrap();
70 |
71 | let tlog = tlog.to_str().unwrap();
72 |
73 | let filename = std::path::Path::new(tlog);
74 | let filename = filename.to_str().unwrap();
75 | dbg!(filename);
76 |
77 | println!("Processing file: {filename}");
78 | let connection_string = format!("file:{filename}");
79 |
80 | println!("connection_string - {connection_string}");
81 |
82 | let vehicle =
83 | mavlink::connect::(&connection_string).expect("Couldn't read from file");
84 |
85 | let mut counter = 0;
86 | loop {
87 | match vehicle.recv_raw() {
88 | Ok(raw_msg) => {
89 | println!(
90 | "raw_msg.component_id() {} | sequence number {} | message_id {:?}",
91 | raw_msg.component_id(),
92 | raw_msg.sequence(),
93 | raw_msg.message_id()
94 | );
95 |
96 | counter += 1;
97 | }
98 | Err(mavlink::error::MessageReadError::Io(e)) => {
99 | if e.kind() == tokio::io::ErrorKind::UnexpectedEof {
100 | break;
101 | }
102 | }
103 | _ => {
104 | break;
105 | }
106 | }
107 | }
108 |
109 | println!("Number of parsed messages: {counter}");
110 | assert!(
111 | counter == 1426,
112 | "Unable to hit the necessary amount of matches"
113 | );
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/mavlink-core/src/error.rs:
--------------------------------------------------------------------------------
1 | use crate::bytes;
2 | use core::fmt::{Display, Formatter};
3 | #[cfg(feature = "std")]
4 | use std::error::Error;
5 |
6 | /// Error while parsing a MAVLink message
7 | #[derive(Debug)]
8 | pub enum ParserError {
9 | /// Bit flag for this type is invalid
10 | InvalidFlag { flag_type: &'static str, value: u64 },
11 | /// Enum value for this enum type does not exist
12 | InvalidEnum { enum_type: &'static str, value: u64 },
13 | /// Message ID does not exist in this message set
14 | UnknownMessage { id: u32 },
15 | /// Errors that occurred in the bytes module.
16 | BytesError(bytes::Error),
17 | }
18 |
19 | impl From for ParserError {
20 | fn from(error: bytes::Error) -> Self {
21 | Self::BytesError(error)
22 | }
23 | }
24 |
25 | impl Display for ParserError {
26 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
27 | match self {
28 | Self::InvalidFlag { flag_type, value } => write!(
29 | f,
30 | "Invalid flag value for flag type {flag_type:?}, got {value:?}"
31 | ),
32 | Self::InvalidEnum { enum_type, value } => write!(
33 | f,
34 | "Invalid enum value for enum type {enum_type:?}, got {value:?}"
35 | ),
36 | Self::UnknownMessage { id } => write!(f, "Unknown message with ID {id:?}"),
37 | Self::BytesError(error) => write!(f, "{error}"),
38 | }
39 | }
40 | }
41 |
42 | #[cfg(feature = "std")]
43 | impl Error for ParserError {}
44 |
45 | /// Error while reading and parsing a MAVLink message
46 | #[derive(Debug)]
47 | pub enum MessageReadError {
48 | /// IO Error while reading
49 | #[cfg(feature = "std")]
50 | Io(std::io::Error),
51 | /// IO Error while reading
52 | #[cfg(any(feature = "embedded", feature = "embedded-hal-02"))]
53 | Io,
54 | /// Error while parsing
55 | Parse(ParserError),
56 | }
57 |
58 | impl MessageReadError {
59 | pub fn eof() -> Self {
60 | #[cfg(feature = "std")]
61 | return Self::Io(std::io::ErrorKind::UnexpectedEof.into());
62 | #[cfg(any(feature = "embedded", feature = "embedded-hal-02"))]
63 | return Self::Io;
64 | }
65 | }
66 |
67 | impl Display for MessageReadError {
68 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
69 | match self {
70 | #[cfg(feature = "std")]
71 | Self::Io(e) => write!(f, "Failed to read message: {e:#?}"),
72 | #[cfg(any(feature = "embedded", feature = "embedded-hal-02"))]
73 | Self::Io => write!(f, "Failed to read message"),
74 | Self::Parse(e) => write!(f, "Failed to read message: {e:#?}"),
75 | }
76 | }
77 | }
78 |
79 | #[cfg(feature = "std")]
80 | impl Error for MessageReadError {}
81 |
82 | #[cfg(feature = "std")]
83 | impl From for MessageReadError {
84 | fn from(e: std::io::Error) -> Self {
85 | Self::Io(e)
86 | }
87 | }
88 |
89 | impl From for MessageReadError {
90 | fn from(e: ParserError) -> Self {
91 | Self::Parse(e)
92 | }
93 | }
94 |
95 | /// Error while writing a MAVLink message
96 | #[derive(Debug)]
97 | pub enum MessageWriteError {
98 | /// IO Error while writing
99 | #[cfg(feature = "std")]
100 | Io(std::io::Error),
101 | /// IO Error while writing
102 | #[cfg(any(feature = "embedded", feature = "embedded-hal-02"))]
103 | Io,
104 | /// Message does not support MAVLink 1
105 | MAVLink2Only,
106 | }
107 |
108 | impl Display for MessageWriteError {
109 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
110 | match self {
111 | #[cfg(feature = "std")]
112 | Self::Io(e) => write!(f, "Failed to write message: {e:#?}"),
113 | #[cfg(any(feature = "embedded", feature = "embedded-hal-02"))]
114 | Self::Io => write!(f, "Failed to write message"),
115 | Self::MAVLink2Only => write!(f, "Message is not supported in MAVLink 1"),
116 | }
117 | }
118 | }
119 |
120 | #[cfg(feature = "std")]
121 | impl Error for MessageWriteError {}
122 |
123 | #[cfg(feature = "std")]
124 | impl From for MessageWriteError {
125 | fn from(e: std::io::Error) -> Self {
126 | Self::Io(e)
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test all targets
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | formatting:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v6
10 | - uses: dtolnay/rust-toolchain@stable
11 | with:
12 | components: rustfmt
13 | - name: Run rustfmt
14 | run: cargo fmt --all -- --check
15 |
16 | linting:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v6
20 | - uses: dtolnay/rust-toolchain@nightly
21 | with:
22 | components: clippy
23 | - uses: actions-rs-plus/clippy-check@v2
24 | with:
25 | args: --all --all-targets --features format-generated-code --features signing --features tokio-1
26 |
27 | internal-tests:
28 | runs-on: ubuntu-latest
29 | strategy:
30 | fail-fast: false
31 | matrix:
32 | dialect: ["ardupilotmega", "asluav", "matrixpilot", "minimal", "paparazzi", "python_array_test", "standard", "test", "ualberta", "uavionix", "icarous", "common", "storm32", "csairlink", "loweheiser"]
33 | signing: ["", "--features signing"]
34 | steps:
35 | - uses: actions/checkout@v6
36 | - uses: dtolnay/rust-toolchain@stable
37 | with:
38 | components: rustfmt
39 | - name: Run internal tests
40 | run: cargo test --verbose --features arbitrary,${{ matrix.dialect }} ${{ matrix.signing }} -- --nocapture
41 |
42 | mavlink-dump:
43 | runs-on: ubuntu-latest
44 | steps:
45 | - uses: actions/checkout@v6
46 | - uses: dtolnay/rust-toolchain@stable
47 | - name: Build mavlink-dump
48 | run: cargo build --verbose --example mavlink-dump
49 |
50 | msrv:
51 | runs-on: ubuntu-latest
52 | strategy:
53 | matrix:
54 | features: ["", "--features serde,tokio-1", "--features signing"]
55 | steps:
56 | - uses: actions/checkout@v6
57 | - uses: taiki-e/install-action@cargo-hack
58 | - run: cargo hack --no-dev-deps --rust-version check --package mavlink ${{ matrix.features }}
59 |
60 | build:
61 | needs: [formatting, linting, internal-tests, mavlink-dump, msrv]
62 | runs-on: ${{ matrix.os }}
63 | strategy:
64 | fail-fast: false
65 | matrix:
66 | include:
67 | - os: macos-latest
68 | TARGET: x86_64-apple-darwin
69 | FEATURES: --features ardupilotmega
70 |
71 | - os: ubuntu-latest
72 | TARGET: arm-unknown-linux-musleabihf
73 | FLAGS: --features ardupilotmega
74 |
75 | - os: ubuntu-latest
76 | TARGET: armv7-unknown-linux-musleabihf
77 | FLAGS: --features ardupilotmega
78 |
79 | - os: ubuntu-latest
80 | TARGET: x86_64-unknown-linux-musl
81 | FLAGS: --features ardupilotmega
82 |
83 | - os: ubuntu-latest
84 | TARGET: x86_64-unknown-linux-musl
85 | FLAGS: --features ardupilotmega,emit-extensions
86 |
87 | - os: ubuntu-latest
88 | TARGET: thumbv7m-none-eabi
89 | FLAGS: --no-default-features --features embedded
90 |
91 | - os: windows-latest
92 | TARGET: x86_64-pc-windows-msvc
93 | FLAGS: --features ardupilotmega
94 |
95 | steps:
96 | - name: Building ${{ matrix.TARGET }}
97 | run: echo "${{ matrix.TARGET }}"
98 | - uses: actions/checkout@v6
99 | - uses: dtolnay/rust-toolchain@stable
100 | with:
101 | target: ${{ matrix.TARGET }}
102 | - uses: actions-rs/cargo@v1
103 | with:
104 | use-cross: true
105 | command: build
106 | args: --verbose --release --package=mavlink --target=${{ matrix.TARGET }} ${{ matrix.FLAGS }}
107 |
108 | test-embedded-size:
109 | needs: build
110 | runs-on: ubuntu-latest
111 | steps:
112 | - uses: actions/checkout@v6
113 | - uses: dtolnay/rust-toolchain@nightly
114 | with:
115 | target: thumbv7em-none-eabihf
116 | - name: Build
117 | working-directory: ./mavlink/examples/embedded
118 | run: cargo +nightly build --out-dir $PWD/../../.. --release -Z unstable-options
119 |
120 | docs:
121 | needs: internal-tests
122 | runs-on: ubuntu-latest
123 | steps:
124 | - uses: actions/checkout@v6
125 | - uses: dtolnay/rust-toolchain@nightly
126 | with:
127 | components: rustfmt
128 | - name: Build docs
129 | run: RUSTDOCFLAGS=--cfg=docsrs cargo doc --no-deps --features "default all-dialects emit-extensions format-generated-code tokio-1 signing"
130 | - name: Deploy
131 | uses: peaceiris/actions-gh-pages@v3
132 | if: ${{ github.ref == 'refs/heads/master' }}
133 | with:
134 | github_token: ${{ secrets.GITHUB_TOKEN }}
135 | publish_dir: ./target/doc
136 |
--------------------------------------------------------------------------------
/mavlink-core/src/async_connection/file.rs:
--------------------------------------------------------------------------------
1 | //! Async File MAVLINK connection
2 | use core::ops::DerefMut;
3 | use std::io;
4 | use std::path::PathBuf;
5 |
6 | use super::{AsyncConnectable, AsyncMavConnection};
7 | use crate::connection::file::config::FileConfig;
8 | use crate::error::{MessageReadError, MessageWriteError};
9 | use crate::{
10 | async_peek_reader::AsyncPeekReader, MAVLinkMessageRaw, MavHeader, MavlinkVersion, Message,
11 | ReadVersion,
12 | };
13 |
14 | use async_trait::async_trait;
15 | use futures::lock::Mutex;
16 | use tokio::fs::File;
17 |
18 | #[cfg(not(feature = "signing"))]
19 | use crate::{read_versioned_msg_async, read_versioned_raw_message_async};
20 |
21 | #[cfg(feature = "signing")]
22 | use crate::{
23 | read_versioned_msg_async_signed, read_versioned_raw_message_async_signed, SigningConfig,
24 | SigningData,
25 | };
26 |
27 | pub async fn open(file_path: &PathBuf) -> io::Result {
28 | let file = File::open(file_path).await?;
29 | Ok(AsyncFileConnection {
30 | file: Mutex::new(AsyncPeekReader::new(file)),
31 | protocol_version: MavlinkVersion::V2,
32 | recv_any_version: false,
33 | #[cfg(feature = "signing")]
34 | signing_data: None,
35 | })
36 | }
37 |
38 | pub struct AsyncFileConnection {
39 | file: Mutex>,
40 | protocol_version: MavlinkVersion,
41 | recv_any_version: bool,
42 | #[cfg(feature = "signing")]
43 | signing_data: Option,
44 | }
45 |
46 | #[async_trait::async_trait]
47 | impl AsyncMavConnection for AsyncFileConnection {
48 | async fn recv_raw(&self) -> Result {
49 | let mut file = self.file.lock().await;
50 | let version = ReadVersion::from_async_conn_cfg::<_, M>(self);
51 | loop {
52 | #[cfg(not(feature = "signing"))]
53 | let result = read_versioned_raw_message_async::(file.deref_mut(), version).await;
54 | #[cfg(feature = "signing")]
55 | let result = read_versioned_raw_message_async_signed::(
56 | file.deref_mut(),
57 | version,
58 | self.signing_data.as_ref(),
59 | )
60 | .await;
61 | match result {
62 | ok @ Ok(..) => {
63 | return ok;
64 | }
65 | Err(MessageReadError::Io(e)) => {
66 | if e.kind() == io::ErrorKind::UnexpectedEof {
67 | return Err(MessageReadError::Io(e));
68 | }
69 | }
70 | _ => {}
71 | }
72 | }
73 | }
74 |
75 | async fn recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError> {
76 | let mut file = self.file.lock().await;
77 | let version = ReadVersion::from_async_conn_cfg::<_, M>(self);
78 | loop {
79 | #[cfg(not(feature = "signing"))]
80 | let result = read_versioned_msg_async(file.deref_mut(), version).await;
81 | #[cfg(feature = "signing")]
82 | let result = read_versioned_msg_async_signed(
83 | file.deref_mut(),
84 | version,
85 | self.signing_data.as_ref(),
86 | )
87 | .await;
88 | match result {
89 | ok @ Ok(..) => {
90 | return ok;
91 | }
92 | Err(MessageReadError::Io(e)) => {
93 | if e.kind() == io::ErrorKind::UnexpectedEof {
94 | return Err(MessageReadError::Io(e));
95 | }
96 | }
97 | _ => {}
98 | }
99 | }
100 | }
101 |
102 | async fn send(&self, _header: &MavHeader, _data: &M) -> Result {
103 | Ok(0)
104 | }
105 |
106 | fn set_protocol_version(&mut self, version: MavlinkVersion) {
107 | self.protocol_version = version;
108 | }
109 |
110 | fn protocol_version(&self) -> MavlinkVersion {
111 | self.protocol_version
112 | }
113 |
114 | fn set_allow_recv_any_version(&mut self, allow: bool) {
115 | self.recv_any_version = allow;
116 | }
117 |
118 | fn allow_recv_any_version(&self) -> bool {
119 | self.recv_any_version
120 | }
121 |
122 | #[cfg(feature = "signing")]
123 | fn setup_signing(&mut self, signing_data: Option) {
124 | self.signing_data = signing_data.map(SigningData::from_config);
125 | }
126 | }
127 |
128 | #[async_trait]
129 | impl AsyncConnectable for FileConfig {
130 | async fn connect_async(&self) -> io::Result + Sync + Send>>
131 | where
132 | M: Message + Sync + Send,
133 | {
134 | Ok(Box::new(open(&self.address).await?))
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/mavlink-core/src/connection/file.rs:
--------------------------------------------------------------------------------
1 | //! File MAVLINK connection
2 |
3 | use crate::connection::{Connection, MavConnection};
4 | use crate::error::{MessageReadError, MessageWriteError};
5 | use crate::peek_reader::PeekReader;
6 | use crate::{Connectable, MAVLinkMessageRaw};
7 | use crate::{MavHeader, MavlinkVersion, Message, ReadVersion};
8 | use core::ops::DerefMut;
9 | use std::fs::File;
10 | use std::io;
11 | use std::path::PathBuf;
12 | use std::sync::Mutex;
13 |
14 | #[cfg(not(feature = "signing"))]
15 | use crate::{read_versioned_msg, read_versioned_raw_message};
16 | #[cfg(feature = "signing")]
17 | use crate::{
18 | read_versioned_msg_signed, read_versioned_raw_message_signed, SigningConfig, SigningData,
19 | };
20 |
21 | pub mod config;
22 |
23 | use config::FileConfig;
24 |
25 | pub fn open(file_path: &PathBuf) -> io::Result {
26 | let file = File::open(file_path)?;
27 |
28 | Ok(FileConnection {
29 | file: Mutex::new(PeekReader::new(file)),
30 | protocol_version: MavlinkVersion::V2,
31 | #[cfg(feature = "signing")]
32 | signing_data: None,
33 | recv_any_version: false,
34 | })
35 | }
36 |
37 | pub struct FileConnection {
38 | file: Mutex>,
39 | protocol_version: MavlinkVersion,
40 | recv_any_version: bool,
41 | #[cfg(feature = "signing")]
42 | signing_data: Option,
43 | }
44 |
45 | impl MavConnection for FileConnection {
46 | fn recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError> {
47 | let mut file = self.file.lock().unwrap();
48 | let version = ReadVersion::from_conn_cfg::<_, M>(self);
49 |
50 | loop {
51 | #[cfg(not(feature = "signing"))]
52 | let result = read_versioned_msg(file.deref_mut(), version);
53 | #[cfg(feature = "signing")]
54 | let result =
55 | read_versioned_msg_signed(file.deref_mut(), version, self.signing_data.as_ref());
56 | match result {
57 | ok @ Ok(..) => {
58 | return ok;
59 | }
60 | Err(MessageReadError::Io(e)) => {
61 | if e.kind() == io::ErrorKind::UnexpectedEof {
62 | return Err(MessageReadError::Io(e));
63 | }
64 | }
65 | _ => {}
66 | }
67 | }
68 | }
69 |
70 | fn recv_raw(&self) -> Result {
71 | let mut file = self.file.lock().unwrap();
72 | let version = ReadVersion::from_conn_cfg::<_, M>(self);
73 |
74 | loop {
75 | #[cfg(not(feature = "signing"))]
76 | let result = read_versioned_raw_message::(file.deref_mut(), version);
77 | #[cfg(feature = "signing")]
78 | let result = read_versioned_raw_message_signed::(
79 | file.deref_mut(),
80 | version,
81 | self.signing_data.as_ref(),
82 | );
83 | match result {
84 | ok @ Ok(..) => {
85 | return ok;
86 | }
87 | Err(MessageReadError::Io(e)) => {
88 | if e.kind() == io::ErrorKind::UnexpectedEof {
89 | return Err(MessageReadError::Io(e));
90 | }
91 | }
92 | _ => {}
93 | }
94 | }
95 | }
96 |
97 | fn try_recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError> {
98 | let mut file = self.file.lock().unwrap();
99 | let version = ReadVersion::from_conn_cfg::<_, M>(self);
100 |
101 | #[cfg(not(feature = "signing"))]
102 | let result = read_versioned_msg(file.deref_mut(), version);
103 | #[cfg(feature = "signing")]
104 | let result =
105 | read_versioned_msg_signed(file.deref_mut(), version, self.signing_data.as_ref());
106 |
107 | result
108 | }
109 |
110 | fn send(&self, _header: &MavHeader, _data: &M) -> Result {
111 | Ok(0)
112 | }
113 |
114 | fn set_protocol_version(&mut self, version: MavlinkVersion) {
115 | self.protocol_version = version;
116 | }
117 |
118 | fn protocol_version(&self) -> MavlinkVersion {
119 | self.protocol_version
120 | }
121 |
122 | fn set_allow_recv_any_version(&mut self, allow: bool) {
123 | self.recv_any_version = allow;
124 | }
125 |
126 | fn allow_recv_any_version(&self) -> bool {
127 | self.recv_any_version
128 | }
129 |
130 | #[cfg(feature = "signing")]
131 | fn setup_signing(&mut self, signing_data: Option) {
132 | self.signing_data = signing_data.map(SigningData::from_config);
133 | }
134 | }
135 |
136 | impl Connectable for FileConfig {
137 | fn connect(&self) -> io::Result> {
138 | Ok(open(&self.address)?.into())
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/mavlink-core/src/connectable.rs:
--------------------------------------------------------------------------------
1 | use core::fmt::Display;
2 | use std::io;
3 | use std::path::PathBuf;
4 |
5 | #[cfg(feature = "direct-serial")]
6 | use crate::connection::direct_serial::config::SerialConfig;
7 | use crate::connection::file::config::FileConfig;
8 | #[cfg(feature = "tcp")]
9 | use crate::connection::tcp::config::{TcpConfig, TcpMode};
10 | #[cfg(feature = "udp")]
11 | use crate::connection::udp::config::{UdpConfig, UdpMode};
12 |
13 | /// A parsed MAVLink connection address
14 | pub enum ConnectionAddress {
15 | /// TCP client or server address
16 | #[cfg(feature = "tcp")]
17 | Tcp(TcpConfig),
18 | /// UDP client, server or broadcast address
19 | #[cfg(feature = "udp")]
20 | Udp(UdpConfig),
21 | /// Serial port address
22 | #[cfg(feature = "direct-serial")]
23 | Serial(SerialConfig),
24 | /// File input address
25 | File(FileConfig),
26 | }
27 |
28 | #[cfg(feature = "tcp")]
29 | impl From for ConnectionAddress {
30 | fn from(value: TcpConfig) -> Self {
31 | Self::Tcp(value)
32 | }
33 | }
34 |
35 | #[cfg(feature = "udp")]
36 | impl From for ConnectionAddress {
37 | fn from(value: UdpConfig) -> Self {
38 | Self::Udp(value)
39 | }
40 | }
41 |
42 | #[cfg(feature = "direct-serial")]
43 | impl From for ConnectionAddress {
44 | fn from(value: SerialConfig) -> Self {
45 | Self::Serial(value)
46 | }
47 | }
48 |
49 | impl From for ConnectionAddress {
50 | fn from(value: FileConfig) -> Self {
51 | Self::File(value)
52 | }
53 | }
54 |
55 | impl Display for ConnectionAddress {
56 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
57 | match self {
58 | #[cfg(feature = "tcp")]
59 | Self::Tcp(connectable) => write!(f, "{connectable}"),
60 | #[cfg(feature = "udp")]
61 | Self::Udp(connectable) => write!(f, "{connectable}"),
62 | #[cfg(feature = "direct-serial")]
63 | Self::Serial(connectable) => write!(f, "{connectable}"),
64 | Self::File(connectable) => write!(f, "{connectable}"),
65 | }
66 | }
67 | }
68 |
69 | impl ConnectionAddress {
70 | /// Parse a MAVLink address string.
71 | ///
72 | /// The address must be in one of the following formats:
73 | ///
74 | /// * `tcpin::` to create a TCP server, listening for an incoming connection
75 | /// * `tcpout::` to create a TCP client
76 | /// * `udpin::` to create a UDP server, listening for incoming packets
77 | /// * `udpout::` to create a UDP client
78 | /// * `udpbcast::` to create a UDP broadcast
79 | /// * `serial::` to create a serial connection
80 | /// * `file:` to extract file data, writing to such a connection does nothing
81 | ///
82 | /// # Errors
83 | ///
84 | /// - [`AddrNotAvailable`] if the address string could not be parsed as a valid MAVLink address
85 | ///
86 | /// [`AddrNotAvailable`]: io::ErrorKind::AddrNotAvailable
87 | pub fn parse_address(address: &str) -> Result {
88 | let (protocol, address) = address.split_once(':').ok_or(io::Error::new(
89 | io::ErrorKind::AddrNotAvailable,
90 | "Protocol unsupported",
91 | ))?;
92 | let conn = match protocol {
93 | #[cfg(feature = "direct-serial")]
94 | "serial" => {
95 | let (port_name, baud) = address.split_once(':').ok_or(io::Error::new(
96 | io::ErrorKind::AddrNotAvailable,
97 | "Incomplete port settings",
98 | ))?;
99 | Self::Serial(SerialConfig::new(
100 | port_name.to_string(),
101 | baud.parse().map_err(|_| {
102 | io::Error::new(io::ErrorKind::AddrNotAvailable, "Invalid baud rate")
103 | })?,
104 | ))
105 | }
106 | #[cfg(feature = "tcp")]
107 | "tcpin" | "tcpout" => {
108 | let mode = if protocol == "tcpout" {
109 | TcpMode::TcpOut
110 | } else {
111 | TcpMode::TcpIn
112 | };
113 | Self::Tcp(TcpConfig::new(address.to_string(), mode))
114 | }
115 | #[cfg(feature = "udp")]
116 | "udpin" | "udpout" | "udpcast" => Self::Udp(UdpConfig::new(
117 | address.to_string(),
118 | match protocol {
119 | "udpin" => UdpMode::Udpin,
120 | "udpout" => UdpMode::Udpout,
121 | "udpcast" => UdpMode::Udpcast,
122 | _ => unreachable!(),
123 | },
124 | )),
125 | "file" => Self::File(FileConfig::new(PathBuf::from(address))),
126 | _ => {
127 | return Err(io::Error::new(
128 | io::ErrorKind::AddrNotAvailable,
129 | "Protocol unsupported",
130 | ))
131 | }
132 | };
133 | Ok(conn)
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/mavlink/tests/tcp_loopback_tests.rs:
--------------------------------------------------------------------------------
1 | mod test_shared;
2 |
3 | #[cfg(all(feature = "std", feature = "tcp", feature = "common"))]
4 | mod test_tcp_connections {
5 | use std::thread;
6 |
7 | #[cfg(feature = "signing")]
8 | use crate::test_shared;
9 | #[cfg(feature = "signing")]
10 | use mavlink::SigningConfig;
11 | use mavlink::{MavConnection, MessageData};
12 |
13 | /// Test whether we can send a message via TCP and receive it OK. This also test signing as a property of a MavConnection if the signing feature is enabled.
14 | #[test]
15 | fn test_tcp_loopback() {
16 | const RECEIVE_CHECK_COUNT: i32 = 5;
17 |
18 | #[cfg(feature = "signing")]
19 | let singing_cfg_server = SigningConfig::new(test_shared::SECRET_KEY, 0, true, false);
20 | #[cfg(feature = "signing")]
21 | let singing_cfg_client = singing_cfg_server.clone();
22 |
23 | let server_thread = thread::spawn(move || {
24 | //TODO consider using get_available_port to use a random port
25 | #[allow(unused_mut)]
26 | let mut server =
27 | mavlink::connect("tcpin:0.0.0.0:14550").expect("Couldn't create server");
28 |
29 | #[cfg(feature = "signing")]
30 | server.setup_signing(Some(singing_cfg_server));
31 |
32 | let mut recv_count = 0;
33 | for _i in 0..RECEIVE_CHECK_COUNT {
34 | match server.recv() {
35 | Ok((_header, msg)) => {
36 | if let mavlink::common::MavMessage::HEARTBEAT(_heartbeat_msg) = msg {
37 | recv_count += 1;
38 | } else {
39 | // one message parse failure fails the test
40 | break;
41 | }
42 | }
43 | Err(..) => {
44 | // one message read failure fails the test
45 | break;
46 | }
47 | }
48 | }
49 | assert_eq!(recv_count, RECEIVE_CHECK_COUNT);
50 | });
51 |
52 | // Give some time for the server to connect
53 | thread::sleep(std::time::Duration::from_millis(100));
54 |
55 | // have the client send a few hearbeats
56 | thread::spawn(move || {
57 | let msg =
58 | mavlink::common::MavMessage::HEARTBEAT(crate::test_shared::get_heartbeat_msg());
59 | #[allow(unused_mut)]
60 | let mut client =
61 | mavlink::connect("tcpout:127.0.0.1:14550").expect("Couldn't create client");
62 |
63 | #[cfg(feature = "signing")]
64 | client.setup_signing(Some(singing_cfg_client));
65 |
66 | for _i in 0..RECEIVE_CHECK_COUNT {
67 | client.send_default(&msg).ok();
68 | }
69 | });
70 |
71 | server_thread.join().unwrap();
72 | }
73 |
74 | /// Test whether we can send a message via TCP and receive it OK as a raw message. This also test signing as a property of a MavConnection if the signing feature is enabled.
75 | #[test]
76 | fn test_tcp_loopback_recv_raw() {
77 | const RECEIVE_CHECK_COUNT: i32 = 5;
78 |
79 | #[cfg(feature = "signing")]
80 | let singing_cfg_server = SigningConfig::new(test_shared::SECRET_KEY, 0, true, false);
81 | #[cfg(feature = "signing")]
82 | let singing_cfg_client = singing_cfg_server.clone();
83 |
84 | let server_thread = thread::spawn(move || {
85 | //TODO consider using get_available_port to use a random port
86 | #[allow(unused_mut)]
87 | let mut server = mavlink::connect::("tcpin:0.0.0.0:14560")
88 | .expect("Couldn't create server");
89 |
90 | #[cfg(feature = "signing")]
91 | server.setup_signing(Some(singing_cfg_server));
92 |
93 | let mut recv_count = 0;
94 | for _i in 0..RECEIVE_CHECK_COUNT {
95 | match server.recv_raw() {
96 | Ok(message) => {
97 | if message.message_id() == mavlink::common::HEARTBEAT_DATA::ID {
98 | recv_count += 1;
99 | } else {
100 | // one message parse failure fails the test
101 | break;
102 | }
103 | }
104 | Err(..) => {
105 | // one message read failure fails the test
106 | break;
107 | }
108 | }
109 | }
110 | assert_eq!(recv_count, RECEIVE_CHECK_COUNT);
111 | });
112 |
113 | // Give some time for the server to connect
114 | thread::sleep(std::time::Duration::from_millis(100));
115 |
116 | // have the client send a few hearbeats
117 | thread::spawn(move || {
118 | let msg =
119 | mavlink::common::MavMessage::HEARTBEAT(crate::test_shared::get_heartbeat_msg());
120 | #[allow(unused_mut)]
121 | let mut client =
122 | mavlink::connect("tcpout:127.0.0.1:14560").expect("Couldn't create client");
123 |
124 | #[cfg(feature = "signing")]
125 | client.setup_signing(Some(singing_cfg_client));
126 |
127 | for _i in 0..RECEIVE_CHECK_COUNT {
128 | client.send_default(&msg).ok();
129 | }
130 | });
131 |
132 | server_thread.join().unwrap();
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/mavlink-core/src/types.rs:
--------------------------------------------------------------------------------
1 | use core::ops::{Deref, DerefMut};
2 |
3 | #[cfg(feature = "serde")]
4 | use crate::utils::nulstr;
5 | #[cfg(feature = "serde")]
6 | use serde::{Deserialize, Serialize};
7 |
8 | #[cfg(feature = "arbitrary")]
9 | use arbitrary::{Arbitrary, Unstructured};
10 |
11 | /// Abstraction around a byte array that represents a string.
12 | ///
13 | /// MAVLink encodes strings as C char arrays and the handling is field dependent.
14 | /// This abstration allows to choose if one wants to handle the field as
15 | /// a raw byte array or if one wants the convenience of a str that stops at the first null byte.
16 | ///
17 | /// # Example
18 | /// ```
19 | /// use mavlink_core::types::CharArray;
20 | ///
21 | /// let data = [0x48, 0x45, 0x4c, 0x4c, 0x4f, 0x00, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x00, 0x00, 0x00];
22 | /// let ca = CharArray::new(data);
23 | /// assert_eq!(ca.to_str().unwrap(), "HELLO");
24 | /// // or using the from str method
25 | /// let ca: CharArray<10> = "HELLO!".into();
26 | /// assert_eq!(ca.to_str().unwrap(), "HELLO!");
27 | ///
28 | /// ```
29 | #[cfg_attr(feature = "serde", derive(Serialize))]
30 | #[cfg_attr(feature = "serde", serde(transparent))]
31 | #[derive(Debug, PartialEq, Clone, Copy)]
32 | pub struct CharArray {
33 | #[cfg_attr(
34 | feature = "serde",
35 | serde(serialize_with = "nulstr::serialize::<_, N>",)
36 | )]
37 | data: [u8; N],
38 |
39 | #[cfg_attr(feature = "serde", serde(skip))]
40 | str_len: usize,
41 | }
42 |
43 | impl CharArray {
44 | pub const fn new(data: [u8; N]) -> Self {
45 | // Note: The generated code uses this in const contexts, so this is a const fn
46 | // and so we can't use iterators or other fancy stuff unfortunately.
47 | let mut first_null = N;
48 | let mut i = 0;
49 | loop {
50 | if i >= N {
51 | break;
52 | }
53 | if data[i] == 0 {
54 | first_null = i;
55 | break;
56 | }
57 | i += 1;
58 | }
59 | Self {
60 | data,
61 | str_len: first_null,
62 | }
63 | }
64 |
65 | /// Get the string representation of the char array.
66 | /// Returns the string stopping at the first null byte and if the string is not valid utf8
67 | /// the returned string will be empty.
68 | pub fn to_str(&self) -> Result<&str, core::str::Utf8Error> {
69 | core::str::from_utf8(&self.data[..self.str_len])
70 | }
71 | }
72 |
73 | impl Deref for CharArray {
74 | type Target = [u8; N];
75 |
76 | fn deref(&self) -> &Self::Target {
77 | &self.data
78 | }
79 | }
80 |
81 | impl DerefMut for CharArray {
82 | fn deref_mut(&mut self) -> &mut Self::Target {
83 | &mut self.data
84 | }
85 | }
86 |
87 | impl<'a, const N: usize> IntoIterator for &'a CharArray {
88 | type Item = &'a u8;
89 | type IntoIter = core::slice::Iter<'a, u8>;
90 |
91 | fn into_iter(self) -> Self::IntoIter {
92 | self.data.iter()
93 | }
94 | }
95 |
96 | impl From<[u8; N]> for CharArray {
97 | fn from(data: [u8; N]) -> Self {
98 | Self::new(data)
99 | }
100 | }
101 |
102 | impl From> for [u8; N] {
103 | fn from(value: CharArray) -> Self {
104 | value.data
105 | }
106 | }
107 |
108 | impl From<&str> for CharArray {
109 | fn from(s: &str) -> Self {
110 | let mut data = [0u8; N];
111 | let bytes = s.as_bytes();
112 | let len = bytes.len().min(N);
113 | data[..len].copy_from_slice(&bytes[..len]);
114 | Self::new(data)
115 | }
116 | }
117 |
118 | impl crate::utils::RustDefault for CharArray {
119 | #[inline(always)]
120 | fn rust_default() -> Self {
121 | Self::new([0u8; N])
122 | }
123 | }
124 |
125 | #[cfg(feature = "serde")]
126 | impl<'de, const N: usize> Deserialize<'de> for CharArray {
127 | fn deserialize(deserializer: D) -> Result
128 | where
129 | D: serde::Deserializer<'de>,
130 | {
131 | let data: [u8; N] = nulstr::deserialize(deserializer)?;
132 | Ok(Self::new(data))
133 | }
134 | }
135 |
136 | #[cfg(feature = "arbitrary")]
137 | impl<'a, const N: usize> Arbitrary<'a> for CharArray {
138 | fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result {
139 | let mut data = [0u8; N];
140 |
141 | for b in &mut data {
142 | // Take a char from the printable ASCII range.
143 | *b = u.int_in_range(32..=126)?;
144 | }
145 |
146 | Ok(Self::new(data))
147 | }
148 | }
149 |
150 | #[cfg(test)]
151 | mod tests {
152 | use super::CharArray;
153 |
154 | #[test]
155 | fn char_array_to_str_handles_no_nulls() {
156 | let data = *b"HELLOWORLD";
157 | let ca = CharArray::new(data);
158 | assert_eq!(ca.len(), 10);
159 | assert_eq!(ca.to_str().unwrap(), "HELLOWORLD");
160 | }
161 |
162 | #[test]
163 | fn char_array_to_str_trims_after_first_null() {
164 | let mut data = [0u8; 10];
165 | data[..3].copy_from_slice(b"abc");
166 | // data[3..] are zeros
167 | let ca = CharArray::new(data);
168 | assert_eq!(ca.len(), 10);
169 | assert_eq!(ca.to_str().unwrap(), "abc");
170 | }
171 |
172 | #[test]
173 | fn char_array_from_str_into_str() {
174 | let ca: CharArray<10> = "HELLOWORLD".into();
175 | assert_eq!(ca.len(), 10);
176 | assert_eq!(ca.to_str().unwrap(), "HELLOWORLD");
177 |
178 | let ca: CharArray<10> = "abc".into();
179 | assert_eq!(ca.len(), 10);
180 | assert_eq!(ca.to_str().unwrap(), "abc");
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/mavlink/tests/tcp_loopback_async_tests.rs:
--------------------------------------------------------------------------------
1 | mod test_shared;
2 |
3 | #[cfg(all(feature = "tokio-1", feature = "tcp", feature = "common"))]
4 | mod test_tcp_connections {
5 | #[cfg(feature = "signing")]
6 | use crate::test_shared;
7 | use mavlink::MessageData;
8 | #[cfg(feature = "signing")]
9 | use mavlink::SigningConfig;
10 |
11 | /// Test whether we can send a message via TCP and receive it OK using async_connect.
12 | /// This also test signing as a property of a MavConnection if the signing feature is enabled.
13 | #[tokio::test]
14 | async fn test_tcp_loopback() {
15 | const RECEIVE_CHECK_COUNT: i32 = 5;
16 |
17 | #[cfg(feature = "signing")]
18 | let singing_cfg_server = SigningConfig::new(test_shared::SECRET_KEY, 0, true, false);
19 | #[cfg(feature = "signing")]
20 | let singing_cfg_client = singing_cfg_server.clone();
21 |
22 | let server_thread = tokio::spawn(async move {
23 | //TODO consider using get_available_port to use a random port
24 | #[allow(unused_mut)]
25 | let mut server = mavlink::connect_async("tcpin:0.0.0.0:14551")
26 | .await
27 | .expect("Couldn't create server");
28 |
29 | #[cfg(feature = "signing")]
30 | server.setup_signing(Some(singing_cfg_server));
31 |
32 | let mut recv_count = 0;
33 | for _i in 0..RECEIVE_CHECK_COUNT {
34 | match server.recv().await {
35 | Ok((_header, msg)) => {
36 | if let mavlink::common::MavMessage::HEARTBEAT(_heartbeat_msg) = msg {
37 | recv_count += 1;
38 | } else {
39 | // one message parse failure fails the test
40 | break;
41 | }
42 | }
43 | Err(..) => {
44 | // one message read failure fails the test
45 | break;
46 | }
47 | }
48 | }
49 | assert_eq!(recv_count, RECEIVE_CHECK_COUNT);
50 | });
51 |
52 | // Give some time for the server to connect
53 | tokio::time::sleep(std::time::Duration::from_millis(100)).await;
54 |
55 | // have the client send a few hearbeats
56 | tokio::spawn(async move {
57 | let msg =
58 | mavlink::common::MavMessage::HEARTBEAT(crate::test_shared::get_heartbeat_msg());
59 | #[allow(unused_mut)]
60 | let mut client = mavlink::connect_async("tcpout:127.0.0.1:14551")
61 | .await
62 | .expect("Couldn't create client");
63 |
64 | #[cfg(feature = "signing")]
65 | client.setup_signing(Some(singing_cfg_client));
66 |
67 | for _i in 0..RECEIVE_CHECK_COUNT {
68 | client.send_default(&msg).await.ok();
69 | }
70 | });
71 |
72 | server_thread.await.unwrap();
73 | }
74 |
75 | /// Test whether we can send a message via TCP and receive it OK using async_connect recv_raw.
76 | /// This also test signing as a property of a MavConnection if the signing feature is enabled.
77 | #[tokio::test]
78 | async fn test_tcp_loopback_recv_raw() {
79 | const RECEIVE_CHECK_COUNT: i32 = 5;
80 |
81 | #[cfg(feature = "signing")]
82 | let singing_cfg_server = SigningConfig::new(test_shared::SECRET_KEY, 0, true, false);
83 | #[cfg(feature = "signing")]
84 | let singing_cfg_client = singing_cfg_server.clone();
85 |
86 | let server_thread = tokio::spawn(async move {
87 | //TODO consider using get_available_port to use a random port
88 | #[allow(unused_mut)]
89 | let mut server =
90 | mavlink::connect_async::("tcpin:0.0.0.0:14561")
91 | .await
92 | .expect("Couldn't create server");
93 |
94 | #[cfg(feature = "signing")]
95 | server.setup_signing(Some(singing_cfg_server));
96 |
97 | let mut recv_count = 0;
98 | for _i in 0..RECEIVE_CHECK_COUNT {
99 | match server.recv_raw().await {
100 | Ok(message) => {
101 | if message.message_id() == mavlink::common::HEARTBEAT_DATA::ID {
102 | recv_count += 1;
103 | } else {
104 | // one message parse failure fails the test
105 | break;
106 | }
107 | }
108 | Err(..) => {
109 | // one message read failure fails the test
110 | break;
111 | }
112 | }
113 | }
114 | assert_eq!(recv_count, RECEIVE_CHECK_COUNT);
115 | });
116 |
117 | // Give some time for the server to connect
118 | tokio::time::sleep(std::time::Duration::from_millis(100)).await;
119 |
120 | // have the client send a few hearbeats
121 | tokio::spawn(async move {
122 | let msg =
123 | mavlink::common::MavMessage::HEARTBEAT(crate::test_shared::get_heartbeat_msg());
124 | #[allow(unused_mut)]
125 | let mut client = mavlink::connect_async("tcpout:127.0.0.1:14561")
126 | .await
127 | .expect("Couldn't create client");
128 |
129 | #[cfg(feature = "signing")]
130 | client.setup_signing(Some(singing_cfg_client));
131 |
132 | for _i in 0..RECEIVE_CHECK_COUNT {
133 | client.send_default(&msg).await.ok();
134 | }
135 | });
136 |
137 | server_thread.await.unwrap();
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/mavlink-core/src/async_connection/mod.rs:
--------------------------------------------------------------------------------
1 | use async_trait::async_trait;
2 | use std::io;
3 |
4 | use crate::{
5 | connectable::ConnectionAddress, MAVLinkMessageRaw, MavFrame, MavHeader, MavlinkVersion, Message,
6 | };
7 | #[cfg(feature = "tcp")]
8 | mod tcp;
9 |
10 | #[cfg(feature = "udp")]
11 | mod udp;
12 |
13 | #[cfg(feature = "direct-serial")]
14 | mod direct_serial;
15 |
16 | mod file;
17 |
18 | #[cfg(feature = "signing")]
19 | use crate::SigningConfig;
20 |
21 | /// An async MAVLink connection
22 | #[async_trait::async_trait]
23 | pub trait AsyncMavConnection {
24 | /// Receive a mavlink message.
25 | ///
26 | /// Yield until a valid frame is received, ignoring invalid messages.
27 | async fn recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError>;
28 |
29 | /// Receive a raw, unparsed mavlink message.
30 | ///
31 | /// Yield until a valid frame is received, ignoring invalid messages.
32 | async fn recv_raw(&self) -> Result;
33 |
34 | /// Send a mavlink message
35 | async fn send(
36 | &self,
37 | header: &MavHeader,
38 | data: &M,
39 | ) -> Result;
40 |
41 | /// Sets the MAVLink version to use for receiving (when `allow_recv_any_version()` is `false`) and sending messages.
42 | fn set_protocol_version(&mut self, version: MavlinkVersion);
43 | /// Gets the currently used MAVLink version
44 | fn protocol_version(&self) -> MavlinkVersion;
45 |
46 | /// Set wether MAVLink messages of either version may be received.
47 | ///
48 | /// If set to false only messages of the version configured with `set_protocol_version()` are received.
49 | fn set_allow_recv_any_version(&mut self, allow: bool);
50 | /// Wether messages of any MAVLink version may be received
51 | fn allow_recv_any_version(&self) -> bool;
52 |
53 | /// Write whole frame
54 | async fn send_frame(
55 | &self,
56 | frame: &MavFrame,
57 | ) -> Result {
58 | self.send(&frame.header, &frame.msg).await
59 | }
60 |
61 | /// Read whole frame
62 | async fn recv_frame(&self) -> Result, crate::error::MessageReadError> {
63 | let (header, msg) = self.recv().await?;
64 | let protocol_version = self.protocol_version();
65 | Ok(MavFrame {
66 | header,
67 | msg,
68 | protocol_version,
69 | })
70 | }
71 |
72 | /// Send a message with default header
73 | async fn send_default(&self, data: &M) -> Result {
74 | let header = MavHeader::default();
75 | self.send(&header, data).await
76 | }
77 |
78 | /// Setup secret key used for message signing, or disable message signing
79 | #[cfg(feature = "signing")]
80 | fn setup_signing(&mut self, signing_data: Option);
81 | }
82 |
83 | /// Connect asynchronously to a MAVLink node by address string.
84 | ///
85 | /// The address must be in one of the following formats:
86 | ///
87 | /// * `tcpin::` to create a TCP server, listening for an incoming connection
88 | /// * `tcpout::` to create a TCP client
89 | /// * `udpin::` to create a UDP server, listening for incoming packets
90 | /// * `udpout::` to create a UDP client
91 | /// * `udpbcast::` to create a UDP broadcast
92 | /// * `serial::` to create a serial connection
93 | /// * `file:` to extract file data, writing to such a connection does nothing
94 | ///
95 | /// The type of the connection is determined at runtime based on the address type, so the
96 | /// connection is returned as a trait object.
97 | ///
98 | /// # Errors
99 | ///
100 | /// - [`AddrNotAvailable`] if the address string could not be parsed as a valid MAVLink address
101 | /// - When the connection could not be established a corresponding [`io::Error`] is returned
102 | ///
103 | /// [`AddrNotAvailable`]: io::ErrorKind::AddrNotAvailable
104 | pub async fn connect_async(
105 | address: &str,
106 | ) -> io::Result + Sync + Send>> {
107 | ConnectionAddress::parse_address(address)?
108 | .connect_async::()
109 | .await
110 | }
111 |
112 | /// Returns the socket address for the given address.
113 | #[cfg(any(feature = "tcp", feature = "udp"))]
114 | pub(crate) fn get_socket_addr(
115 | address: T,
116 | ) -> Result {
117 | let addr = match address.to_socket_addrs()?.next() {
118 | Some(addr) => addr,
119 | None => {
120 | return Err(io::Error::other("Host address lookup failed"));
121 | }
122 | };
123 | Ok(addr)
124 | }
125 |
126 | /// A MAVLink connection address that can be connected to, establishing an [`AsyncMavConnection`]
127 | ///
128 | /// This is the `async` version of `Connectable`.
129 | #[async_trait]
130 | pub trait AsyncConnectable {
131 | /// Attempt to establish an asynchronous MAVLink connection
132 | async fn connect_async(&self) -> io::Result + Sync + Send>>
133 | where
134 | M: Message + Sync + Send;
135 | }
136 |
137 | #[async_trait]
138 | impl AsyncConnectable for ConnectionAddress {
139 | async fn connect_async(&self) -> io::Result + Sync + Send>>
140 | where
141 | M: Message + Sync + Send,
142 | {
143 | match self {
144 | #[cfg(feature = "tcp")]
145 | Self::Tcp(connectable) => connectable.connect_async::().await,
146 | #[cfg(feature = "udp")]
147 | Self::Udp(connectable) => connectable.connect_async::().await,
148 | #[cfg(feature = "direct-serial")]
149 | Self::Serial(connectable) => connectable.connect_async::().await,
150 | Self::File(connectable) => connectable.connect_async::().await,
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/mavlink-core/src/async_connection/direct_serial.rs:
--------------------------------------------------------------------------------
1 | //! Async Serial MAVLink connection
2 |
3 | use core::ops::DerefMut;
4 | use core::sync::atomic::{self, AtomicU8};
5 | use std::io;
6 |
7 | use async_trait::async_trait;
8 | use futures::lock::Mutex;
9 | use tokio::io::BufReader;
10 | use tokio_serial::{SerialPort, SerialPortBuilderExt, SerialStream};
11 |
12 | use super::AsyncConnectable;
13 | use crate::connection::direct_serial::config::SerialConfig;
14 | use crate::MAVLinkMessageRaw;
15 | use crate::{async_peek_reader::AsyncPeekReader, MavHeader, MavlinkVersion, Message, ReadVersion};
16 |
17 | #[cfg(not(feature = "signing"))]
18 | use crate::{
19 | read_versioned_msg_async, read_versioned_raw_message_async, write_versioned_msg_async,
20 | };
21 | #[cfg(feature = "signing")]
22 | use crate::{
23 | read_versioned_msg_async_signed, read_versioned_raw_message_async_signed,
24 | write_versioned_msg_async_signed, SigningConfig, SigningData,
25 | };
26 |
27 | use super::AsyncMavConnection;
28 |
29 | pub struct AsyncSerialConnection {
30 | port: Mutex>>,
31 | sequence: AtomicU8,
32 | protocol_version: MavlinkVersion,
33 | recv_any_version: bool,
34 | #[cfg(feature = "signing")]
35 | signing_data: Option,
36 | }
37 |
38 | #[async_trait::async_trait]
39 | impl AsyncMavConnection for AsyncSerialConnection {
40 | async fn recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError> {
41 | let mut port = self.port.lock().await;
42 | let version = ReadVersion::from_async_conn_cfg::<_, M>(self);
43 | #[cfg(not(feature = "signing"))]
44 | let result = read_versioned_msg_async(port.deref_mut(), version).await;
45 | #[cfg(feature = "signing")]
46 | let result =
47 | read_versioned_msg_async_signed(port.deref_mut(), version, self.signing_data.as_ref())
48 | .await;
49 | result
50 | }
51 |
52 | async fn recv_raw(&self) -> Result {
53 | let mut port = self.port.lock().await;
54 | let version = ReadVersion::from_async_conn_cfg::<_, M>(self);
55 | #[cfg(not(feature = "signing"))]
56 | let result = read_versioned_raw_message_async::(port.deref_mut(), version).await;
57 | #[cfg(feature = "signing")]
58 | let result = read_versioned_raw_message_async_signed::(
59 | port.deref_mut(),
60 | version,
61 | self.signing_data.as_ref(),
62 | )
63 | .await;
64 | result
65 | }
66 |
67 | async fn send(
68 | &self,
69 | header: &MavHeader,
70 | data: &M,
71 | ) -> Result {
72 | let mut port = self.port.lock().await;
73 |
74 | let sequence = self.sequence.fetch_add(
75 | 1,
76 | // Safety:
77 | //
78 | // We are using `Ordering::Relaxed` here because:
79 | // - We only need a unique sequence number per message
80 | // - `Mutex` on `self.port` already makes sure the rest of the code is synchronized
81 | // - No other thread reads or writes `self.sequence` without going through this `Mutex`
82 | //
83 | // Warning:
84 | //
85 | // If we later change this code to access `self.sequence` without locking `self.port` with the `Mutex`,
86 | // then we should upgrade this ordering to `Ordering::SeqCst`.
87 | atomic::Ordering::Relaxed,
88 | );
89 |
90 | let header = MavHeader {
91 | sequence,
92 | system_id: header.system_id,
93 | component_id: header.component_id,
94 | };
95 |
96 | #[cfg(not(feature = "signing"))]
97 | let result =
98 | write_versioned_msg_async(port.reader_mut(), self.protocol_version, header, data).await;
99 | #[cfg(feature = "signing")]
100 | let result = write_versioned_msg_async_signed(
101 | port.reader_mut(),
102 | self.protocol_version,
103 | header,
104 | data,
105 | self.signing_data.as_ref(),
106 | )
107 | .await;
108 | result
109 | }
110 |
111 | fn set_protocol_version(&mut self, version: MavlinkVersion) {
112 | self.protocol_version = version;
113 | }
114 |
115 | fn protocol_version(&self) -> MavlinkVersion {
116 | self.protocol_version
117 | }
118 |
119 | fn set_allow_recv_any_version(&mut self, allow: bool) {
120 | self.recv_any_version = allow;
121 | }
122 |
123 | fn allow_recv_any_version(&self) -> bool {
124 | self.recv_any_version
125 | }
126 |
127 | #[cfg(feature = "signing")]
128 | fn setup_signing(&mut self, signing_data: Option) {
129 | self.signing_data = signing_data.map(SigningData::from_config);
130 | }
131 | }
132 |
133 | #[async_trait]
134 | impl AsyncConnectable for SerialConfig {
135 | async fn connect_async(&self) -> io::Result + Sync + Send>>
136 | where
137 | M: Message + Sync + Send,
138 | {
139 | let mut port = tokio_serial::new(&self.port_name, self.baud_rate).open_native_async()?;
140 | port.set_data_bits(tokio_serial::DataBits::Eight)?;
141 | port.set_parity(tokio_serial::Parity::None)?;
142 | port.set_stop_bits(tokio_serial::StopBits::One)?;
143 | port.set_flow_control(tokio_serial::FlowControl::None)?;
144 |
145 | let read_buffer_capacity = self.buffer_capacity();
146 | let buf_reader = BufReader::with_capacity(read_buffer_capacity, port);
147 |
148 | Ok(Box::new(AsyncSerialConnection {
149 | port: Mutex::new(AsyncPeekReader::new(buf_reader)),
150 | sequence: AtomicU8::new(0),
151 | protocol_version: MavlinkVersion::V2,
152 | recv_any_version: false,
153 | #[cfg(feature = "signing")]
154 | signing_data: None,
155 | }))
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/mavlink-core/src/bytes.rs:
--------------------------------------------------------------------------------
1 | pub struct Bytes<'a> {
2 | data: &'a [u8],
3 | pos: usize,
4 | }
5 |
6 | /// Simple error types for the bytes module.
7 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
8 | pub enum Error {
9 | /// Attempted to to read more bytes than available.
10 | NotEnoughBuffer { requested: usize, available: usize },
11 | }
12 |
13 | impl Error {
14 | #[inline]
15 | fn not_enough_buffer(requested: usize, bytes: &Bytes) -> Self {
16 | Self::NotEnoughBuffer {
17 | requested,
18 | available: bytes.remaining(),
19 | }
20 | }
21 | }
22 |
23 | impl core::fmt::Display for Error {
24 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
25 | match self {
26 | Self::NotEnoughBuffer {
27 | requested,
28 | available,
29 | } => write!(
30 | f,
31 | "Attempted to read {requested} bytes but only {available} available.",
32 | ),
33 | }
34 | }
35 | }
36 |
37 | impl<'a> Bytes<'a> {
38 | pub fn new(data: &'a [u8]) -> Self {
39 | Self { data, pos: 0 }
40 | }
41 |
42 | #[inline]
43 | fn remaining(&self) -> usize {
44 | self.data.len() - self.pos
45 | }
46 |
47 | #[inline]
48 | pub fn remaining_bytes(&self) -> &'a [u8] {
49 | &self.data[self.pos..]
50 | }
51 |
52 | /// # Errors
53 | ///
54 | /// Will return an error if not at least `count` bytes remain in the buffer
55 | #[inline]
56 | pub fn get_bytes(&mut self, count: usize) -> Result<&[u8], Error> {
57 | let bytes = &self
58 | .data
59 | .get(self.pos..(self.pos + count))
60 | .ok_or_else(|| Error::not_enough_buffer(count, self))?;
61 | self.pos += count;
62 | Ok(bytes)
63 | }
64 |
65 | /// # Errors
66 | ///
67 | /// Will return an error if not at least `SIZE` bytes remain in the buffer
68 | #[inline]
69 | pub fn get_array(&mut self) -> Result<[u8; SIZE], Error> {
70 | let bytes = self.get_bytes(SIZE)?;
71 | let mut arr = [0u8; SIZE];
72 |
73 | arr.copy_from_slice(bytes);
74 |
75 | debug_assert_eq!(arr.as_slice(), bytes);
76 |
77 | Ok(arr)
78 | }
79 |
80 | /// # Errors
81 | ///
82 | /// Will return an error if nothing is remaining in the buffer
83 | #[inline]
84 | pub fn get_u8(&mut self) -> Result {
85 | let val = *self
86 | .data
87 | .get(self.pos)
88 | .ok_or_else(|| Error::not_enough_buffer(1, self))?;
89 | self.pos += 1;
90 | Ok(val)
91 | }
92 |
93 | /// # Errors
94 | ///
95 | /// Will return an error if nothing is remaining in the buffer
96 | #[inline]
97 | pub fn get_i8(&mut self) -> Result {
98 | let val = *self
99 | .data
100 | .get(self.pos)
101 | .ok_or_else(|| Error::not_enough_buffer(1, self))? as i8;
102 | self.pos += 1;
103 | Ok(val)
104 | }
105 |
106 | /// # Errors
107 | ///
108 | /// Will return an error if less then the 2 required bytes for a `u16` remain
109 | #[inline]
110 | pub fn get_u16_le(&mut self) -> Result {
111 | Ok(u16::from_le_bytes(self.get_array()?))
112 | }
113 |
114 | /// # Errors
115 | ///
116 | /// Will return an error if less then the 2 required bytes for a `i16` remain
117 | #[inline]
118 | pub fn get_i16_le(&mut self) -> Result {
119 | Ok(i16::from_le_bytes(self.get_array()?))
120 | }
121 |
122 | /// # Errors
123 | ///
124 | /// Will return an error if not at least 3 bytes remain
125 | #[inline]
126 | pub fn get_u24_le(&mut self) -> Result {
127 | const SIZE: usize = 3;
128 |
129 | let mut val = [0u8; SIZE + 1];
130 | val[..3].copy_from_slice(
131 | self.data
132 | .get(self.pos..self.pos + SIZE)
133 | .ok_or_else(|| Error::not_enough_buffer(SIZE, self))?,
134 | );
135 | self.pos += SIZE;
136 |
137 | debug_assert_eq!(val[3], 0);
138 | Ok(u32::from_le_bytes(val))
139 | }
140 |
141 | /// # Errors
142 | ///
143 | /// Will return an error if not at least 3 bytes remain
144 | #[inline]
145 | pub fn get_i24_le(&mut self) -> Result {
146 | const SIZE: usize = 3;
147 |
148 | let mut val = [0u8; SIZE + 1];
149 | val[..3].copy_from_slice(
150 | self.data
151 | .get(self.pos..self.pos + SIZE)
152 | .ok_or_else(|| Error::not_enough_buffer(SIZE, self))?,
153 | );
154 | self.pos += SIZE;
155 |
156 | debug_assert_eq!(val[3], 0);
157 | Ok(i32::from_le_bytes(val))
158 | }
159 |
160 | /// # Errors
161 | ///
162 | /// Will return an error if less then the 4 required bytes for a `u32` remain
163 | #[inline]
164 | pub fn get_u32_le(&mut self) -> Result {
165 | Ok(u32::from_le_bytes(self.get_array()?))
166 | }
167 |
168 | /// # Errors
169 | ///
170 | /// Will return an error if less then the 4 required bytes for a `i32` remain
171 | #[inline]
172 | pub fn get_i32_le(&mut self) -> Result {
173 | Ok(i32::from_le_bytes(self.get_array()?))
174 | }
175 |
176 | /// # Errors
177 | ///
178 | /// Will return an error if less then the 8 required bytes for a `u64` remain
179 | #[inline]
180 | pub fn get_u64_le(&mut self) -> Result {
181 | Ok(u64::from_le_bytes(self.get_array()?))
182 | }
183 |
184 | /// # Errors
185 | ///
186 | /// Will return an error if less then the 8 required bytes for a `i64` remain
187 | #[inline]
188 | pub fn get_i64_le(&mut self) -> Result {
189 | Ok(i64::from_le_bytes(self.get_array()?))
190 | }
191 |
192 | /// # Errors
193 | ///
194 | /// Will return an error if less then the 4 required bytes for a `f32` remain
195 | #[inline]
196 | pub fn get_f32_le(&mut self) -> Result {
197 | Ok(f32::from_le_bytes(self.get_array()?))
198 | }
199 |
200 | /// # Errors
201 | ///
202 | /// Will return an error if less then the 8 required bytes for a `f64` remain
203 | #[inline]
204 | pub fn get_f64_le(&mut self) -> Result {
205 | Ok(f64::from_le_bytes(self.get_array()?))
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/definitions/parameters.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Specifies the datatype of a MAVLink parameter.
5 |
6 | 8-bit unsigned integer
7 |
8 |
9 | 8-bit signed integer
10 |
11 |
12 | 16-bit unsigned integer
13 |
14 |
15 | 16-bit signed integer
16 |
17 |
18 | 32-bit unsigned integer
19 |
20 |
21 | 32-bit signed integer
22 |
23 |
24 | 64-bit unsigned integer
25 |
26 |
27 | 64-bit signed integer
28 |
29 |
30 | 32-bit floating-point
31 |
32 |
33 | 64-bit floating-point
34 |
35 |
36 |
37 |
38 |
39 | Request to read the onboard parameter with the param_id string id. Onboard
40 | parameters are stored as key[const char*] -> value[float]. This allows to send a
41 | parameter to any other component (such as the GCS) without the need of previous
42 | knowledge of possible parameter names. Thus the same GCS can store different
43 | parameters
44 | for different autopilots. See also https://mavlink.io/en/services/parameter.html for
45 | a
46 | full documentation of QGroundControl and IMU code.
47 | System ID
48 | Component ID
49 | Onboard parameter id, terminated by NULL if the
50 | length is less than 16 human-readable chars and WITHOUT null termination (NULL) byte
51 | if
52 | the length is exactly 16 chars - applications have to provide 16+1 bytes storage if
53 | the
54 | ID is stored as string
55 | Parameter index. Send -1 to use
56 | the
57 | param ID field as identifier (else the param id will be ignored)
58 |
59 |
60 | Request all parameters of this component. After this request, all
61 | parameters
62 | are emitted. The parameter microservice is documented at
63 | https://mavlink.io/en/services/parameter.html
64 | System ID
65 | Component ID
66 |
67 |
68 | Emit the value of a onboard parameter. The inclusion of param_count and
69 | param_index in the message allows the recipient to keep track of received parameters
70 | and
71 | allows him to re-request missing parameters after a loss or timeout. The parameter
72 | microservice is documented at https://mavlink.io/en/services/parameter.html
73 | Onboard parameter id, terminated by NULL if the
74 | length is less than 16 human-readable chars and WITHOUT null termination (NULL) byte
75 | if
76 | the length is exactly 16 chars - applications have to provide 16+1 bytes storage if
77 | the
78 | ID is stored as string
79 | Onboard parameter value
80 | Onboard parameter type.
81 | Total number of onboard parameters
82 | Index of this onboard parameter
83 |
84 |
85 | Set a parameter value (write new value to permanent storage).
86 | The receiving component should acknowledge the new parameter value by broadcasting a
87 | PARAM_VALUE message (broadcasting ensures that multiple GCS all have an up-to-date
88 | list
89 | of all parameters). If the sending GCS did not receive a PARAM_VALUE within its
90 | timeout
91 | time, it should re-send the PARAM_SET message. The parameter microservice is
92 | documented
93 | at https://mavlink.io/en/services/parameter.html.
94 |
95 | System ID
96 | Component ID
97 | Onboard parameter id, terminated by NULL if the
98 | length is less than 16 human-readable chars and WITHOUT null termination (NULL) byte
99 | if
100 | the length is exactly 16 chars - applications have to provide 16+1 bytes storage if
101 | the
102 | ID is stored as string
103 | Onboard parameter value
104 | Onboard parameter type.
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/mavlink/tests/agnostic_decode_test.rs:
--------------------------------------------------------------------------------
1 | pub mod test_shared;
2 |
3 | use mavlink::MAV_STX;
4 | use mavlink::MAV_STX_V2;
5 |
6 | // 100 randomly generted bytes with 10 extra MAV_STX/MAV_STX_V2 each inserted
7 | const GARBAGE: [u8; 120] = [
8 | 0xfe, 0x43, 0x2d, MAV_STX, MAV_STX, 0x26, 0x1e, 0x33, 0x85, 0x38, 0x1d, 0x20, 0x20, 0x90, 0xd9,
9 | 0x24, 0xb6, 0xd7, 0xb1, 0x22, 0x3b, 0xaf, 0x7c, 0x2f, MAV_STX, 0x9d, 0x1a, 0x13, 0x16, 0x2b,
10 | 0xf8, 0x6f, 0xf4, 0xdc, 0x66, 0xff, 0x2d, MAV_STX_V2, 0xe2, 0x2c, 0xb1, MAV_STX_V2, 0x4e, 0xc9,
11 | 0xc6, 0xcb, 0x3e, 0x3e, 0xf4, MAV_STX_V2, MAV_STX_V2, 0x49, 0xbc, 0x11, 0xb7, 0xd4, 0x5e,
12 | MAV_STX, 0x46, 0x6a, 0xd3, 0xb9, MAV_STX, 0xe3, 0x81, 0x1d, MAV_STX_V2, 0x80, 0x47, 0xfc, 0xff,
13 | 0x0c, 0xaa, 0xf3, MAV_STX, MAV_STX_V2, 0x87, 0x2f, 0x9a, 0x15, MAV_STX_V2, MAV_STX, 0x06, 0xc9,
14 | 0xe1, 0xc0, 0x98, 0xf5, 0x71, 0x78, 0x1c, 0x4a, 0xe3, 0xf1, MAV_STX_V2, 0x5f, 0xdb, 0x0e, 0x3f,
15 | MAV_STX, 0x2e, MAV_STX_V2, 0x08, 0x39, 0x6e, 0x15, 0x3c, 0x55, 0xcb, 0x78, 0xe0, MAV_STX, 0x5a,
16 | 0xb3, 0x1b, 0xf9, MAV_STX, 0xe0, 0xa0, MAV_STX_V2,
17 | ];
18 |
19 | #[cfg(feature = "common")]
20 | mod test_agnostic_encode_decode {
21 | use crate::GARBAGE;
22 | use mavlink_core::peek_reader::PeekReader;
23 | use std::io::Write;
24 |
25 | #[test]
26 | pub fn test_read_heartbeats() {
27 | let mut buf = vec![];
28 | _ = buf.write(crate::test_shared::HEARTBEAT_V1);
29 | _ = buf.write(crate::test_shared::HEARTBEAT_V2);
30 | let mut r = PeekReader::new(buf.as_slice());
31 | // read 2 messages
32 | for _ in 0..2 {
33 | let (header, msg) = mavlink::read_any_msg(&mut r).expect("Failed to parse message");
34 |
35 | assert_eq!(header, crate::test_shared::COMMON_MSG_HEADER);
36 | let heartbeat_msg = crate::test_shared::get_heartbeat_msg();
37 |
38 | if let mavlink::common::MavMessage::HEARTBEAT(msg) = msg {
39 | assert_eq!(msg.custom_mode, heartbeat_msg.custom_mode);
40 | assert_eq!(msg.mavtype, heartbeat_msg.mavtype);
41 | assert_eq!(msg.autopilot, heartbeat_msg.autopilot);
42 | assert_eq!(msg.base_mode, heartbeat_msg.base_mode);
43 | assert_eq!(msg.system_status, heartbeat_msg.system_status);
44 | assert_eq!(msg.mavlink_version, heartbeat_msg.mavlink_version);
45 | } else {
46 | panic!("Decoded wrong message type")
47 | }
48 | }
49 | }
50 |
51 | #[test]
52 | pub fn test_read_inbetween_garbage() {
53 | // write some garbage bytes as well as 2 heartbeats and attempt to read them
54 |
55 | let mut buf = vec![];
56 | _ = buf.write(&GARBAGE);
57 | _ = buf.write(crate::test_shared::HEARTBEAT_V1);
58 | _ = buf.write(&GARBAGE);
59 | // only part of message
60 | _ = buf.write(&crate::test_shared::HEARTBEAT_V1[..5]);
61 | _ = buf.write(crate::test_shared::HEARTBEAT_V2);
62 | _ = buf.write(&GARBAGE);
63 | // only part of message
64 | _ = buf.write(&crate::test_shared::HEARTBEAT_V1[5..]);
65 | // add some zeros to prevent invalid package sizes from causing a read error
66 | _ = buf.write(&[0; 100]);
67 |
68 | let mut r = PeekReader::new(buf.as_slice());
69 | _ = mavlink::read_any_msg::(&mut r).unwrap();
70 | _ = mavlink::read_any_msg::(&mut r).unwrap();
71 | assert!(
72 | mavlink::read_any_msg::(&mut r).is_err(),
73 | "Parsed message from garbage data"
74 | );
75 | }
76 | }
77 |
78 | #[cfg(all(feature = "std", feature = "tokio-1", feature = "common"))]
79 | mod test_agnostic_encode_decode_async {
80 | use crate::GARBAGE;
81 | use mavlink_core::async_peek_reader::AsyncPeekReader;
82 | use std::io::Write;
83 |
84 | #[tokio::test]
85 | pub async fn test_read_heartbeats() {
86 | let mut buf = vec![];
87 | _ = buf.write(crate::test_shared::HEARTBEAT_V1);
88 | _ = buf.write(crate::test_shared::HEARTBEAT_V2);
89 | let mut r = AsyncPeekReader::new(buf.as_slice());
90 | // read 2 messages
91 | for _ in 0..2 {
92 | let (header, msg) = mavlink::read_any_msg_async(&mut r)
93 | .await
94 | .expect("Failed to parse message");
95 |
96 | assert_eq!(header, crate::test_shared::COMMON_MSG_HEADER);
97 | let heartbeat_msg = crate::test_shared::get_heartbeat_msg();
98 |
99 | if let mavlink::common::MavMessage::HEARTBEAT(msg) = msg {
100 | assert_eq!(msg.custom_mode, heartbeat_msg.custom_mode);
101 | assert_eq!(msg.mavtype, heartbeat_msg.mavtype);
102 | assert_eq!(msg.autopilot, heartbeat_msg.autopilot);
103 | assert_eq!(msg.base_mode, heartbeat_msg.base_mode);
104 | assert_eq!(msg.system_status, heartbeat_msg.system_status);
105 | assert_eq!(msg.mavlink_version, heartbeat_msg.mavlink_version);
106 | } else {
107 | panic!("Decoded wrong message type")
108 | }
109 | }
110 | }
111 |
112 | #[tokio::test]
113 | pub async fn test_read_inbetween_garbage() {
114 | // write some garbage bytes as well as 2 heartbeats and attempt to read them
115 |
116 | let mut buf = vec![];
117 | _ = buf.write(&GARBAGE);
118 | _ = buf.write(crate::test_shared::HEARTBEAT_V1);
119 | _ = buf.write(&GARBAGE);
120 | // only part of message
121 | _ = buf.write(&crate::test_shared::HEARTBEAT_V1[..5]);
122 | _ = buf.write(crate::test_shared::HEARTBEAT_V2);
123 | _ = buf.write(&GARBAGE);
124 | // only part of message
125 | _ = buf.write(&crate::test_shared::HEARTBEAT_V1[5..]);
126 | // add some zeros to prevent invalid package sizes from causing a read error
127 | _ = buf.write(&[0; 100]);
128 |
129 | let mut r = AsyncPeekReader::new(buf.as_slice());
130 | _ = mavlink::read_any_msg_async::(&mut r)
131 | .await
132 | .unwrap();
133 | _ = mavlink::read_any_msg_async::(&mut r)
134 | .await
135 | .unwrap();
136 | assert!(
137 | mavlink::read_any_msg_async::(&mut r)
138 | .await
139 | .is_err(),
140 | "Parsed message from garbage data"
141 | );
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/mavlink-core/src/async_connection/tcp.rs:
--------------------------------------------------------------------------------
1 | //! Async TCP MAVLink connection
2 |
3 | use std::io;
4 |
5 | use super::{get_socket_addr, AsyncConnectable, AsyncMavConnection};
6 | use crate::async_peek_reader::AsyncPeekReader;
7 | use crate::connection::tcp::config::{TcpConfig, TcpMode};
8 | use crate::{MAVLinkMessageRaw, MavHeader, MavlinkVersion, Message, ReadVersion};
9 |
10 | use async_trait::async_trait;
11 | use core::ops::DerefMut;
12 | use futures::lock::Mutex;
13 | use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
14 | use tokio::net::{TcpListener, TcpStream};
15 |
16 | #[cfg(not(feature = "signing"))]
17 | use crate::{
18 | read_versioned_msg_async, read_versioned_raw_message_async, write_versioned_msg_async,
19 | };
20 | #[cfg(feature = "signing")]
21 | use crate::{
22 | read_versioned_msg_async_signed, read_versioned_raw_message_async_signed,
23 | write_versioned_msg_async_signed, SigningConfig, SigningData,
24 | };
25 |
26 | pub async fn tcpout(address: T) -> io::Result {
27 | let addr = get_socket_addr(address)?;
28 |
29 | let socket = TcpStream::connect(addr).await?;
30 |
31 | let (reader, writer) = socket.into_split();
32 |
33 | Ok(AsyncTcpConnection {
34 | reader: Mutex::new(AsyncPeekReader::new(reader)),
35 | writer: Mutex::new(TcpWrite {
36 | socket: writer,
37 | sequence: 0,
38 | }),
39 | protocol_version: MavlinkVersion::V2,
40 | recv_any_version: false,
41 | #[cfg(feature = "signing")]
42 | signing_data: None,
43 | })
44 | }
45 |
46 | pub async fn tcpin(address: T) -> io::Result {
47 | let addr = get_socket_addr(address)?;
48 | let listener = TcpListener::bind(addr).await?;
49 |
50 | //For now we only accept one incoming stream: this yields until we get one
51 | match listener.accept().await {
52 | Ok((socket, _)) => {
53 | let (reader, writer) = socket.into_split();
54 | return Ok(AsyncTcpConnection {
55 | reader: Mutex::new(AsyncPeekReader::new(reader)),
56 | writer: Mutex::new(TcpWrite {
57 | socket: writer,
58 | sequence: 0,
59 | }),
60 | protocol_version: MavlinkVersion::V2,
61 | recv_any_version: false,
62 | #[cfg(feature = "signing")]
63 | signing_data: None,
64 | });
65 | }
66 | Err(e) => {
67 | //TODO don't println in lib
68 | println!("listener err: {e}");
69 | }
70 | }
71 | Err(io::Error::new(
72 | io::ErrorKind::NotConnected,
73 | "No incoming connections!",
74 | ))
75 | }
76 |
77 | pub struct AsyncTcpConnection {
78 | reader: Mutex>,
79 | writer: Mutex,
80 | protocol_version: MavlinkVersion,
81 | recv_any_version: bool,
82 | #[cfg(feature = "signing")]
83 | signing_data: Option,
84 | }
85 |
86 | struct TcpWrite {
87 | socket: OwnedWriteHalf,
88 | sequence: u8,
89 | }
90 |
91 | #[async_trait::async_trait]
92 | impl AsyncMavConnection for AsyncTcpConnection {
93 | async fn recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError> {
94 | let mut reader = self.reader.lock().await;
95 | let version = ReadVersion::from_async_conn_cfg::<_, M>(self);
96 | #[cfg(not(feature = "signing"))]
97 | let result = read_versioned_msg_async(reader.deref_mut(), version).await;
98 | #[cfg(feature = "signing")]
99 | let result = read_versioned_msg_async_signed(
100 | reader.deref_mut(),
101 | version,
102 | self.signing_data.as_ref(),
103 | )
104 | .await;
105 | result
106 | }
107 |
108 | async fn recv_raw(&self) -> Result {
109 | let mut reader = self.reader.lock().await;
110 | let version = ReadVersion::from_async_conn_cfg::<_, M>(self);
111 | #[cfg(not(feature = "signing"))]
112 | let result = read_versioned_raw_message_async::(reader.deref_mut(), version).await;
113 | #[cfg(feature = "signing")]
114 | let result = read_versioned_raw_message_async_signed::(
115 | reader.deref_mut(),
116 | version,
117 | self.signing_data.as_ref(),
118 | )
119 | .await;
120 | result
121 | }
122 |
123 | async fn send(
124 | &self,
125 | header: &MavHeader,
126 | data: &M,
127 | ) -> Result {
128 | let mut lock = self.writer.lock().await;
129 |
130 | let header = MavHeader {
131 | sequence: lock.sequence,
132 | system_id: header.system_id,
133 | component_id: header.component_id,
134 | };
135 |
136 | lock.sequence = lock.sequence.wrapping_add(1);
137 | #[cfg(not(feature = "signing"))]
138 | let result =
139 | write_versioned_msg_async(&mut lock.socket, self.protocol_version, header, data).await;
140 | #[cfg(feature = "signing")]
141 | let result = write_versioned_msg_async_signed(
142 | &mut lock.socket,
143 | self.protocol_version,
144 | header,
145 | data,
146 | self.signing_data.as_ref(),
147 | )
148 | .await;
149 | result
150 | }
151 |
152 | fn set_protocol_version(&mut self, version: MavlinkVersion) {
153 | self.protocol_version = version;
154 | }
155 |
156 | fn protocol_version(&self) -> MavlinkVersion {
157 | self.protocol_version
158 | }
159 |
160 | fn set_allow_recv_any_version(&mut self, allow: bool) {
161 | self.recv_any_version = allow;
162 | }
163 |
164 | fn allow_recv_any_version(&self) -> bool {
165 | self.recv_any_version
166 | }
167 |
168 | #[cfg(feature = "signing")]
169 | fn setup_signing(&mut self, signing_data: Option) {
170 | self.signing_data = signing_data.map(SigningData::from_config);
171 | }
172 | }
173 |
174 | #[async_trait]
175 | impl AsyncConnectable for TcpConfig {
176 | async fn connect_async(&self) -> io::Result + Sync + Send>>
177 | where
178 | M: Message + Sync + Send,
179 | {
180 | let conn = match self.mode {
181 | TcpMode::TcpIn => tcpin(&self.address).await,
182 | TcpMode::TcpOut => tcpout(&self.address).await,
183 | };
184 |
185 | Ok(Box::new(conn?))
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/mavlink-core/src/signing.rs:
--------------------------------------------------------------------------------
1 | use crate::MAVLinkV2MessageRaw;
2 |
3 | use std::time::SystemTime;
4 | use std::{collections::HashMap, sync::Mutex};
5 |
6 | use crate::MAVLINK_IFLAG_SIGNED;
7 |
8 | /// Configuration used for MAVLink 2 messages signing as defined in .
9 | ///
10 | /// To use a [`SigningConfig`] for sending and reciving messages create a [`SigningData`] object using `SigningData::from_config`.
11 | ///
12 | /// # Examples
13 | /// Creating `SigningData`:
14 | /// ```
15 | /// # use mavlink_core::{SigningData, SigningConfig};
16 | /// let config = SigningConfig::new([0u8; 32], 0, true, false);
17 | /// let sign_data = SigningData::from_config(config);
18 | /// ```
19 | ///
20 | #[derive(Debug, Clone)]
21 | pub struct SigningConfig {
22 | secret_key: [u8; 32],
23 | link_id: u8,
24 | pub(crate) sign_outgoing: bool,
25 | pub(crate) allow_unsigned: bool,
26 | }
27 |
28 | // mutable state of signing per connection
29 | pub(crate) struct SigningState {
30 | timestamp: u64,
31 | stream_timestamps: HashMap<(u8, u8, u8), u64>,
32 | }
33 |
34 | /// MAVLink 2 message signing data
35 | ///
36 | /// Contains a [`SigningConfig`] as well as a mutable state that is reused for all messages in a connection.
37 | pub struct SigningData {
38 | pub(crate) config: SigningConfig,
39 | pub(crate) state: Mutex,
40 | }
41 |
42 | impl SigningConfig {
43 | /// Creates a new signing configuration.
44 | ///
45 | /// If `sign_outgoing` is set messages send using this configuration will be signed.
46 | /// If `allow_unsigned` is set, when receiving messages, all unsigned messages are accepted, this may also includes MAVLink 1 messages.
47 | pub fn new(
48 | secret_key: [u8; 32],
49 | link_id: u8,
50 | sign_outgoing: bool,
51 | allow_unsigned: bool,
52 | ) -> Self {
53 | Self {
54 | secret_key,
55 | link_id,
56 | sign_outgoing,
57 | allow_unsigned,
58 | }
59 | }
60 | }
61 |
62 | impl SigningData {
63 | /// Initializes signing data from a given [`SigningConfig`]
64 | pub fn from_config(config: SigningConfig) -> Self {
65 | Self {
66 | config,
67 | state: Mutex::new(SigningState {
68 | timestamp: 0,
69 | stream_timestamps: HashMap::new(),
70 | }),
71 | }
72 | }
73 |
74 | /// Verify the signature of a MAVLink 2 message.
75 | ///
76 | /// This respects the `allow_unsigned` parameter in [`SigningConfig`].
77 | pub fn verify_signature(&self, message: &MAVLinkV2MessageRaw) -> bool {
78 | if message.incompatibility_flags() & MAVLINK_IFLAG_SIGNED > 0 {
79 | // The code that holds the mutex lock is not expected to panic, therefore the expect is justified.
80 | // The only issue that might cause a panic, presuming the opertions on the message buffer are sound,
81 | // is the `SystemTime::now()` call in `get_current_timestamp()`.
82 | let mut state = self
83 | .state
84 | .lock()
85 | .expect("Code holding MutexGuard should not panic.");
86 | state.timestamp = u64::max(state.timestamp, Self::get_current_timestamp());
87 | let timestamp = message.signature_timestamp();
88 | let src_system = message.system_id();
89 | let src_component = message.component_id();
90 | let stream_key = (message.signature_link_id(), src_system, src_component);
91 | match state.stream_timestamps.get(&stream_key) {
92 | Some(stream_timestamp) => {
93 | if timestamp <= *stream_timestamp {
94 | // reject old timestamp
95 | return false;
96 | }
97 | }
98 | None => {
99 | if timestamp + 60 * 1000 * 100 < state.timestamp {
100 | // bad new stream, more then a minute older the the last one
101 | return false;
102 | }
103 | }
104 | }
105 |
106 | let mut signature_buffer = [0u8; 6];
107 | message.calculate_signature(&self.config.secret_key, &mut signature_buffer);
108 | let result = signature_buffer == message.signature_value();
109 | if result {
110 | // if signature is valid update timestamps
111 | state.stream_timestamps.insert(stream_key, timestamp);
112 | state.timestamp = u64::max(state.timestamp, timestamp);
113 | }
114 | result
115 | } else {
116 | self.config.allow_unsigned
117 | }
118 | }
119 |
120 | /// Sign a MAVLink 2 message if its incompatibility flag is set accordingly.
121 | pub fn sign_message(&self, message: &mut MAVLinkV2MessageRaw) {
122 | if message.incompatibility_flags() & MAVLINK_IFLAG_SIGNED > 0 {
123 | // The code that holds the mutex lock is not expected to panic, therefore the expect is justified.
124 | // The only issue that might cause a panic, presuming the opertions on the message buffer are sound,
125 | // is the `SystemTime::now()` call in `get_current_timestamp()`.
126 | let mut state = self
127 | .state
128 | .lock()
129 | .expect("Code holding MutexGuard should not panic.");
130 | state.timestamp = u64::max(state.timestamp, Self::get_current_timestamp());
131 | let ts_bytes = u64::to_le_bytes(state.timestamp);
132 | message
133 | .signature_timestamp_bytes_mut()
134 | .copy_from_slice(&ts_bytes[0..6]);
135 | *message.signature_link_id_mut() = self.config.link_id;
136 |
137 | let mut signature_buffer = [0u8; 6];
138 | message.calculate_signature(&self.config.secret_key, &mut signature_buffer);
139 |
140 | message
141 | .signature_value_mut()
142 | .copy_from_slice(&signature_buffer);
143 | state.timestamp += 1;
144 | }
145 | }
146 |
147 | fn get_current_timestamp() -> u64 {
148 | // fallback to 0 if the system time appears to be before epoch
149 | let now = SystemTime::now()
150 | .duration_since(SystemTime::UNIX_EPOCH)
151 | .map(|n| n.as_micros())
152 | .unwrap_or(0);
153 | // use 1st January 2015 GMT as offset, fallback to 0 if before that date, the used 48 bit of this will overflow in 2104
154 | (now.saturating_sub(1420070400u128 * 1000000u128) / 10u128) as u64
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/mavlink-core/src/connection/tcp.rs:
--------------------------------------------------------------------------------
1 | //! TCP MAVLink connection
2 |
3 | use crate::connection::get_socket_addr;
4 | use crate::connection::{Connection, MavConnection};
5 | use crate::peek_reader::PeekReader;
6 | #[cfg(not(feature = "signing"))]
7 | use crate::read_versioned_raw_message;
8 | #[cfg(feature = "signing")]
9 | use crate::read_versioned_raw_message_signed;
10 | use crate::Connectable;
11 | use crate::MAVLinkMessageRaw;
12 | use crate::{MavHeader, MavlinkVersion, Message, ReadVersion};
13 | use core::ops::DerefMut;
14 | use std::io;
15 | use std::net::ToSocketAddrs;
16 | use std::net::{TcpListener, TcpStream};
17 | use std::sync::Mutex;
18 | use std::time::Duration;
19 |
20 | #[cfg(not(feature = "signing"))]
21 | use crate::{read_versioned_msg, write_versioned_msg};
22 |
23 | #[cfg(feature = "signing")]
24 | use crate::{read_versioned_msg_signed, write_versioned_msg_signed, SigningConfig, SigningData};
25 |
26 | pub mod config;
27 |
28 | use config::{TcpConfig, TcpMode};
29 |
30 | pub fn tcpout(address: T) -> io::Result {
31 | let addr = get_socket_addr(&address)?;
32 |
33 | let socket = TcpStream::connect(addr)?;
34 | socket.set_read_timeout(Some(Duration::from_millis(100)))?;
35 |
36 | Ok(TcpConnection {
37 | reader: Mutex::new(PeekReader::new(socket.try_clone()?)),
38 | writer: Mutex::new(TcpWrite {
39 | socket,
40 | sequence: 0,
41 | }),
42 | protocol_version: MavlinkVersion::V2,
43 | recv_any_version: false,
44 | #[cfg(feature = "signing")]
45 | signing_data: None,
46 | })
47 | }
48 |
49 | pub fn tcpin(address: T) -> io::Result {
50 | let addr = get_socket_addr(&address)?;
51 | let listener = TcpListener::bind(addr)?;
52 |
53 | //For now we only accept one incoming stream: this blocks until we get one
54 | for incoming in listener.incoming() {
55 | match incoming {
56 | Ok(socket) => {
57 | return Ok(TcpConnection {
58 | reader: Mutex::new(PeekReader::new(socket.try_clone()?)),
59 | writer: Mutex::new(TcpWrite {
60 | socket,
61 | sequence: 0,
62 | }),
63 | protocol_version: MavlinkVersion::V2,
64 | recv_any_version: false,
65 | #[cfg(feature = "signing")]
66 | signing_data: None,
67 | })
68 | }
69 | Err(e) => {
70 | //TODO don't println in lib
71 | println!("listener err: {e}");
72 | }
73 | }
74 | }
75 | Err(io::Error::new(
76 | io::ErrorKind::NotConnected,
77 | "No incoming connections!",
78 | ))
79 | }
80 |
81 | pub struct TcpConnection {
82 | reader: Mutex>,
83 | writer: Mutex,
84 | protocol_version: MavlinkVersion,
85 | recv_any_version: bool,
86 | #[cfg(feature = "signing")]
87 | signing_data: Option,
88 | }
89 |
90 | struct TcpWrite {
91 | socket: TcpStream,
92 | sequence: u8,
93 | }
94 |
95 | impl MavConnection for TcpConnection {
96 | fn recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError> {
97 | let mut reader = self.reader.lock().unwrap();
98 | let version = ReadVersion::from_conn_cfg::<_, M>(self);
99 | #[cfg(not(feature = "signing"))]
100 | let result = read_versioned_msg(reader.deref_mut(), version);
101 | #[cfg(feature = "signing")]
102 | let result =
103 | read_versioned_msg_signed(reader.deref_mut(), version, self.signing_data.as_ref());
104 | result
105 | }
106 |
107 | fn recv_raw(&self) -> Result {
108 | let mut reader = self.reader.lock().unwrap();
109 | let version = ReadVersion::from_conn_cfg::<_, M>(self);
110 | #[cfg(not(feature = "signing"))]
111 | let result = read_versioned_raw_message::(reader.deref_mut(), version);
112 | #[cfg(feature = "signing")]
113 | let result = read_versioned_raw_message_signed::(
114 | reader.deref_mut(),
115 | version,
116 | self.signing_data.as_ref(),
117 | );
118 | result
119 | }
120 |
121 | fn try_recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError> {
122 | let mut reader = self.reader.lock().unwrap();
123 | reader.reader_mut().set_nonblocking(true)?;
124 |
125 | let version = ReadVersion::from_conn_cfg::<_, M>(self);
126 |
127 | #[cfg(not(feature = "signing"))]
128 | let result = read_versioned_msg(reader.deref_mut(), version);
129 |
130 | #[cfg(feature = "signing")]
131 | let result =
132 | read_versioned_msg_signed(reader.deref_mut(), version, self.signing_data.as_ref());
133 |
134 | reader.reader_mut().set_nonblocking(false)?;
135 |
136 | result
137 | }
138 |
139 | fn send(&self, header: &MavHeader, data: &M) -> Result {
140 | let mut lock = self.writer.lock().unwrap();
141 |
142 | let header = MavHeader {
143 | sequence: lock.sequence,
144 | system_id: header.system_id,
145 | component_id: header.component_id,
146 | };
147 |
148 | lock.sequence = lock.sequence.wrapping_add(1);
149 | #[cfg(not(feature = "signing"))]
150 | let result = write_versioned_msg(&mut lock.socket, self.protocol_version, header, data);
151 | #[cfg(feature = "signing")]
152 | let result = write_versioned_msg_signed(
153 | &mut lock.socket,
154 | self.protocol_version,
155 | header,
156 | data,
157 | self.signing_data.as_ref(),
158 | );
159 | result
160 | }
161 |
162 | fn set_protocol_version(&mut self, version: MavlinkVersion) {
163 | self.protocol_version = version;
164 | }
165 |
166 | fn protocol_version(&self) -> MavlinkVersion {
167 | self.protocol_version
168 | }
169 |
170 | fn set_allow_recv_any_version(&mut self, allow: bool) {
171 | self.recv_any_version = allow;
172 | }
173 |
174 | fn allow_recv_any_version(&self) -> bool {
175 | self.recv_any_version
176 | }
177 |
178 | #[cfg(feature = "signing")]
179 | fn setup_signing(&mut self, signing_data: Option) {
180 | self.signing_data = signing_data.map(SigningData::from_config);
181 | }
182 | }
183 |
184 | impl Connectable for TcpConfig {
185 | fn connect(&self) -> io::Result> {
186 | let conn = match self.mode {
187 | TcpMode::TcpIn => tcpin(&self.address),
188 | TcpMode::TcpOut => tcpout(&self.address),
189 | };
190 |
191 | Ok(conn?.into())
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/snapshots/e2e_snapshots__no_field_description.xml@no_field_description.rs.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: mavlink-bindgen/tests/e2e_snapshots.rs
3 | assertion_line: 26
4 | expression: contents
5 | ---
6 | #![doc = "MAVLink no_field_description dialect."]
7 | #![doc = ""]
8 | #![doc = "This file was automatically generated, do not edit."]
9 | #![allow(deprecated)]
10 | #![allow(clippy::match_single_binding)]
11 | #[cfg(feature = "arbitrary")]
12 | use arbitrary::Arbitrary;
13 | #[allow(unused_imports)]
14 | use bitflags::{bitflags, Flags};
15 | #[allow(unused_imports)]
16 | use mavlink_core::{
17 | bytes::Bytes, bytes_mut::BytesMut, types::CharArray, MavlinkVersion, Message, MessageData,
18 | };
19 | #[allow(unused_imports)]
20 | use num_derive::{FromPrimitive, ToPrimitive};
21 | #[allow(unused_imports)]
22 | use num_traits::{FromPrimitive, ToPrimitive};
23 | #[cfg(feature = "serde")]
24 | use serde::{Deserialize, Serialize};
25 | #[cfg(feature = "ts")]
26 | use ts_rs::TS;
27 | #[doc = "Raw RC Data."]
28 | #[doc = ""]
29 | #[doc = "ID: 50001"]
30 | #[derive(Debug, Clone, PartialEq)]
31 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32 | #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
33 | #[cfg_attr(feature = "ts", derive(TS))]
34 | #[cfg_attr(feature = "ts", ts(export))]
35 | pub struct CUBEPILOT_RAW_RC_DATA {
36 | #[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
37 | #[cfg_attr(feature = "ts", ts(type = "Array"))]
38 | pub rc_raw: [u8; 32],
39 | }
40 | impl CUBEPILOT_RAW_RC_DATA {
41 | pub const ENCODED_LEN: usize = 32usize;
42 | pub const DEFAULT: Self = Self {
43 | rc_raw: [0_u8; 32usize],
44 | };
45 | #[cfg(feature = "arbitrary")]
46 | pub fn random(rng: &mut R) -> Self {
47 | use arbitrary::{Arbitrary, Unstructured};
48 | let mut buf = [0u8; 1024];
49 | rng.fill_bytes(&mut buf);
50 | let mut unstructured = Unstructured::new(&buf);
51 | Self::arbitrary(&mut unstructured).unwrap_or_default()
52 | }
53 | }
54 | impl Default for CUBEPILOT_RAW_RC_DATA {
55 | fn default() -> Self {
56 | Self::DEFAULT.clone()
57 | }
58 | }
59 | impl MessageData for CUBEPILOT_RAW_RC_DATA {
60 | type Message = MavMessage;
61 | const ID: u32 = 50001u32;
62 | const NAME: &'static str = "CUBEPILOT_RAW_RC";
63 | const EXTRA_CRC: u8 = 246u8;
64 | const ENCODED_LEN: usize = 32usize;
65 | fn deser(
66 | _version: MavlinkVersion,
67 | __input: &[u8],
68 | ) -> Result {
69 | let avail_len = __input.len();
70 | let mut payload_buf = [0; Self::ENCODED_LEN];
71 | let mut buf = if avail_len < Self::ENCODED_LEN {
72 | payload_buf[0..avail_len].copy_from_slice(__input);
73 | Bytes::new(&payload_buf)
74 | } else {
75 | Bytes::new(__input)
76 | };
77 | let mut __struct = Self::default();
78 | for v in &mut __struct.rc_raw {
79 | let val = buf.get_u8()?;
80 | *v = val;
81 | }
82 | Ok(__struct)
83 | }
84 | fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
85 | let mut __tmp = BytesMut::new(bytes);
86 | #[allow(clippy::absurd_extreme_comparisons)]
87 | #[allow(unused_comparisons)]
88 | if __tmp.remaining() < Self::ENCODED_LEN {
89 | panic!(
90 | "buffer is too small (need {} bytes, but got {})",
91 | Self::ENCODED_LEN,
92 | __tmp.remaining(),
93 | )
94 | }
95 | for val in &self.rc_raw {
96 | __tmp.put_u8(*val);
97 | }
98 | if matches!(version, MavlinkVersion::V2) {
99 | let len = __tmp.len();
100 | ::mavlink_core::utils::remove_trailing_zeroes(&bytes[..len])
101 | } else {
102 | __tmp.len()
103 | }
104 | }
105 | }
106 | #[derive(Clone, PartialEq, Debug)]
107 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
108 | #[cfg_attr(feature = "serde", serde(tag = "type"))]
109 | #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
110 | #[cfg_attr(feature = "ts", derive(TS))]
111 | #[cfg_attr(feature = "ts", ts(export))]
112 | #[repr(u32)]
113 | pub enum MavMessage {
114 | #[doc = "Raw RC Data."]
115 | #[doc = ""]
116 | #[doc = "ID: 50001"]
117 | CUBEPILOT_RAW_RC(CUBEPILOT_RAW_RC_DATA),
118 | }
119 | impl MavMessage {
120 | pub const fn all_ids() -> &'static [u32] {
121 | &[50001u32]
122 | }
123 | pub const fn all_messages() -> &'static [(&'static str, u32)] {
124 | &[(CUBEPILOT_RAW_RC_DATA::NAME, CUBEPILOT_RAW_RC_DATA::ID)]
125 | }
126 | }
127 | impl Message for MavMessage {
128 | fn parse(
129 | version: MavlinkVersion,
130 | id: u32,
131 | payload: &[u8],
132 | ) -> Result {
133 | match id {
134 | CUBEPILOT_RAW_RC_DATA::ID => {
135 | CUBEPILOT_RAW_RC_DATA::deser(version, payload).map(Self::CUBEPILOT_RAW_RC)
136 | }
137 | _ => Err(::mavlink_core::error::ParserError::UnknownMessage { id }),
138 | }
139 | }
140 | fn message_name(&self) -> &'static str {
141 | match self {
142 | Self::CUBEPILOT_RAW_RC(..) => CUBEPILOT_RAW_RC_DATA::NAME,
143 | }
144 | }
145 | fn message_id(&self) -> u32 {
146 | match self {
147 | Self::CUBEPILOT_RAW_RC(..) => CUBEPILOT_RAW_RC_DATA::ID,
148 | }
149 | }
150 | fn message_id_from_name(name: &str) -> Option {
151 | match name {
152 | CUBEPILOT_RAW_RC_DATA::NAME => Some(CUBEPILOT_RAW_RC_DATA::ID),
153 | _ => None,
154 | }
155 | }
156 | fn default_message_from_id(id: u32) -> Option {
157 | match id {
158 | CUBEPILOT_RAW_RC_DATA::ID => {
159 | Some(Self::CUBEPILOT_RAW_RC(CUBEPILOT_RAW_RC_DATA::default()))
160 | }
161 | _ => None,
162 | }
163 | }
164 | #[cfg(feature = "arbitrary")]
165 | fn random_message_from_id(id: u32, rng: &mut R) -> Option {
166 | match id {
167 | CUBEPILOT_RAW_RC_DATA::ID => {
168 | Some(Self::CUBEPILOT_RAW_RC(CUBEPILOT_RAW_RC_DATA::random(rng)))
169 | }
170 | _ => None,
171 | }
172 | }
173 | fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
174 | match self {
175 | Self::CUBEPILOT_RAW_RC(body) => body.ser(version, bytes),
176 | }
177 | }
178 | fn extra_crc(id: u32) -> u8 {
179 | match id {
180 | CUBEPILOT_RAW_RC_DATA::ID => CUBEPILOT_RAW_RC_DATA::EXTRA_CRC,
181 | _ => 0,
182 | }
183 | }
184 | fn target_system_id(&self) -> Option {
185 | match self {
186 | _ => None,
187 | }
188 | }
189 | fn target_component_id(&self) -> Option {
190 | match self {
191 | _ => None,
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/mavlink/tests/v1_encode_decode_tests.rs:
--------------------------------------------------------------------------------
1 | pub mod test_shared;
2 |
3 | #[cfg(feature = "common")]
4 | mod test_v1_encode_decode {
5 | use crate::test_shared::HEARTBEAT_V1;
6 | use mavlink_core::peek_reader::PeekReader;
7 |
8 | #[test]
9 | pub fn test_read_heartbeat() {
10 | let mut r = PeekReader::new(HEARTBEAT_V1);
11 | let (header, msg) = mavlink::read_v1_msg(&mut r).expect("Failed to parse message");
12 | //println!("{:?}, {:?}", header, msg);
13 |
14 | assert_eq!(header, crate::test_shared::COMMON_MSG_HEADER);
15 | let heartbeat_msg = crate::test_shared::get_heartbeat_msg();
16 |
17 | if let mavlink::common::MavMessage::HEARTBEAT(msg) = msg {
18 | assert_eq!(msg.custom_mode, heartbeat_msg.custom_mode);
19 | assert_eq!(msg.mavtype, heartbeat_msg.mavtype);
20 | assert_eq!(msg.autopilot, heartbeat_msg.autopilot);
21 | assert_eq!(msg.base_mode, heartbeat_msg.base_mode);
22 | assert_eq!(msg.system_status, heartbeat_msg.system_status);
23 | assert_eq!(msg.mavlink_version, heartbeat_msg.mavlink_version);
24 | } else {
25 | panic!("Decoded wrong message type")
26 | }
27 | }
28 |
29 | #[test]
30 | pub fn test_write_heartbeat() {
31 | let mut b = [0u8; 280];
32 | let mut v: &mut [u8] = &mut b;
33 | let heartbeat_msg = crate::test_shared::get_heartbeat_msg();
34 | mavlink::write_v1_msg(
35 | &mut v,
36 | crate::test_shared::COMMON_MSG_HEADER,
37 | &mavlink::common::MavMessage::HEARTBEAT(heartbeat_msg),
38 | )
39 | .expect("Failed to write message");
40 |
41 | assert_eq!(&b[..HEARTBEAT_V1.len()], HEARTBEAT_V1);
42 | }
43 |
44 | #[test]
45 | #[cfg(not(feature = "emit-extensions"))]
46 | pub fn test_echo_servo_output_raw() {
47 | use mavlink::Message;
48 |
49 | let mut b = [0u8; 280];
50 | let mut v: &mut [u8] = &mut b;
51 | let send_msg = crate::test_shared::get_servo_output_raw_v1();
52 |
53 | mavlink::write_v2_msg(
54 | &mut v,
55 | crate::test_shared::COMMON_MSG_HEADER,
56 | &mavlink::common::MavMessage::SERVO_OUTPUT_RAW(send_msg),
57 | )
58 | .expect("Failed to write message");
59 |
60 | let mut c = PeekReader::new(b.as_slice());
61 | let (_header, recv_msg): (mavlink::MavHeader, mavlink::common::MavMessage) =
62 | mavlink::read_v2_msg(&mut c).expect("Failed to read");
63 |
64 | assert_eq!(
65 | mavlink::common::MavMessage::extra_crc(recv_msg.message_id()),
66 | 222_u8
67 | );
68 |
69 | if let mavlink::common::MavMessage::SERVO_OUTPUT_RAW(recv_msg) = recv_msg {
70 | assert_eq!(recv_msg.port, 123_u8);
71 | assert_eq!(recv_msg.servo4_raw, 1400_u16);
72 | } else {
73 | panic!("Decoded wrong message type")
74 | }
75 | }
76 |
77 | #[test]
78 | pub fn test_serialize_to_raw() {
79 | let heartbeat_msg = crate::test_shared::get_heartbeat_msg();
80 | let mut raw_msg = mavlink::MAVLinkV1MessageRaw::new();
81 |
82 | raw_msg.serialize_message_data(crate::test_shared::COMMON_MSG_HEADER, &heartbeat_msg);
83 |
84 | assert_eq!(raw_msg.raw_bytes(), HEARTBEAT_V1);
85 | assert!(raw_msg.has_valid_crc::());
86 | }
87 |
88 | #[test]
89 | #[cfg(feature = "std")]
90 | pub fn test_read_error() {
91 | use std::io::ErrorKind;
92 |
93 | use mavlink_core::error::MessageReadError;
94 |
95 | let mut reader = PeekReader::new(crate::test_shared::BlockyReader::new(HEARTBEAT_V1));
96 |
97 | loop {
98 | match mavlink::read_v1_msg::(&mut reader) {
99 | Ok((header, _)) => {
100 | assert_eq!(header, crate::test_shared::COMMON_MSG_HEADER);
101 | break;
102 | }
103 | Err(MessageReadError::Io(err)) if err.kind() == ErrorKind::WouldBlock => {}
104 | Err(err) => panic!("{err}"),
105 | }
106 | }
107 | }
108 |
109 | #[test]
110 | #[cfg(feature = "emit-extensions")]
111 | pub fn test_extensions_v1() {
112 | use mavlink::common::COMMAND_ACK_DATA;
113 | // test if "Extension fields are not sent when a message is encoded using the MAVLink 1 protocol" holds
114 | let ack_command = COMMAND_ACK_DATA {
115 | command: mavlink::common::MavCmd::MAV_CMD_NAV_WAYPOINT,
116 | result: mavlink::common::MavResult::MAV_RESULT_TEMPORARILY_REJECTED,
117 | progress: 2,
118 | result_param2: 3,
119 | target_system: 4,
120 | target_component: 5,
121 | };
122 | let ack_msg_data = mavlink::common::MavMessage::COMMAND_ACK(ack_command);
123 | let mut buf = vec![];
124 | mavlink::write_v1_msg(
125 | &mut buf,
126 | crate::test_shared::COMMON_MSG_HEADER,
127 | &ack_msg_data,
128 | )
129 | .unwrap();
130 | // check expected len of serialized buffer
131 | // expected is 1 byte STX, 5 byte header, 3 bytes for message content and 2 byte crc
132 | assert_eq!(buf.len(), 1 + 5 + 3 + 2);
133 |
134 | let mut reader = PeekReader::new(&*buf);
135 | let (_, read_msg) =
136 | mavlink::read_v1_msg::(&mut reader).unwrap();
137 | if let mavlink::common::MavMessage::COMMAND_ACK(read_ack_command) = read_msg {
138 | // chech if the deserialized message has extension fields set to 0
139 | assert_eq!(
140 | read_ack_command.command,
141 | mavlink::common::MavCmd::MAV_CMD_NAV_WAYPOINT
142 | );
143 | assert_eq!(
144 | read_ack_command.result,
145 | mavlink::common::MavResult::MAV_RESULT_TEMPORARILY_REJECTED
146 | );
147 | assert_eq!(read_ack_command.progress, 0);
148 | assert_eq!(read_ack_command.result_param2, 0);
149 | assert_eq!(read_ack_command.target_system, 0);
150 | assert_eq!(read_ack_command.target_component, 0);
151 | } else {
152 | panic!("Read invalid message")
153 | }
154 | }
155 |
156 | #[test]
157 | pub fn test_overflowing_msg_id() {
158 | // test behaivior for message ids that are not valid for MAVLink 1
159 | let msg_data = mavlink::common::MavMessage::SETUP_SIGNING(
160 | mavlink::common::SETUP_SIGNING_DATA::default(),
161 | );
162 | let mut buf = vec![];
163 | assert!(
164 | matches!(
165 | mavlink::write_v1_msg(&mut buf, crate::test_shared::COMMON_MSG_HEADER, &msg_data,),
166 | Err(mavlink::error::MessageWriteError::MAVLink2Only)
167 | ),
168 | "Writing a message with id 256 should return an error for MAVLink 1"
169 | );
170 | assert!(buf.is_empty(), "No bytes should be written");
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/mavlink-core/src/bytes_mut.rs:
--------------------------------------------------------------------------------
1 | pub struct BytesMut<'a> {
2 | data: &'a mut [u8],
3 | len: usize,
4 | }
5 |
6 | impl<'a> BytesMut<'a> {
7 | pub fn new(data: &'a mut [u8]) -> Self {
8 | Self { data, len: 0 }
9 | }
10 |
11 | #[inline]
12 | pub fn len(&self) -> usize {
13 | self.len
14 | }
15 |
16 | #[inline]
17 | pub fn is_empty(&self) -> bool {
18 | self.len() == 0
19 | }
20 |
21 | #[inline]
22 | pub fn remaining(&self) -> usize {
23 | self.data.len() - self.len
24 | }
25 |
26 | #[inline]
27 | fn check_remaining(&self, count: usize) {
28 | assert!(
29 | self.remaining() >= count,
30 | "write buffer overflow; remaining {} bytes, try add {count} bytes",
31 | self.remaining(),
32 | );
33 | }
34 |
35 | /// # Panics
36 | ///
37 | /// Will panic if not enough space is remaining in the buffer to store the whole slice
38 | #[inline]
39 | pub fn put_slice(&mut self, src: &[u8]) {
40 | self.check_remaining(src.len());
41 |
42 | unsafe {
43 | core::ptr::copy_nonoverlapping(src.as_ptr(), &mut self.data[self.len], src.len());
44 | }
45 | self.len += src.len();
46 | }
47 |
48 | /// # Panics
49 | ///
50 | /// Will panic if no space is remaing in the buffer
51 | #[inline]
52 | pub fn put_u8(&mut self, val: u8) {
53 | self.check_remaining(1);
54 |
55 | self.data[self.len] = val;
56 | self.len += 1;
57 | }
58 |
59 | /// # Panics
60 | ///
61 | /// Will panic if no space is remaing in the buffer
62 | #[inline]
63 | pub fn put_i8(&mut self, val: i8) {
64 | self.check_remaining(1);
65 |
66 | self.data[self.len] = val as u8;
67 | self.len += 1;
68 | }
69 |
70 | /// # Panics
71 | ///
72 | /// Will panic if less space then the 2 bytes required by a `u16` remain in the buffer
73 | #[inline]
74 | pub fn put_u16_le(&mut self, val: u16) {
75 | const SIZE: usize = core::mem::size_of::();
76 | self.check_remaining(SIZE);
77 |
78 | let src = val.to_le_bytes();
79 | self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
80 | self.len += SIZE;
81 | }
82 |
83 | /// # Panics
84 | ///
85 | /// Will panic if less space then the 2 bytes required by a `i16` remain in the buffer
86 | #[inline]
87 | pub fn put_i16_le(&mut self, val: i16) {
88 | const SIZE: usize = core::mem::size_of::();
89 | self.check_remaining(SIZE);
90 |
91 | let src = val.to_le_bytes();
92 | self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
93 | self.len += SIZE;
94 | }
95 |
96 | /// # Panics
97 | ///
98 | /// Will panic if `val` is not a valid 24 bit unsigned integer or if not
99 | /// enough space is remaing in the buffer to store 3 bytes
100 | #[inline]
101 | pub fn put_u24_le(&mut self, val: u32) {
102 | const SIZE: usize = 3;
103 | const MAX: u32 = 2u32.pow(24) - 1;
104 |
105 | assert!(
106 | val <= MAX,
107 | "Attempted to put value that is too large for 24 bits, \
108 | attempted to push: {val}, max allowed: {MAX}",
109 | );
110 |
111 | let src = val.to_le_bytes();
112 | self.data[self.len..self.len + SIZE].copy_from_slice(&src[..3]);
113 | self.len += SIZE;
114 | }
115 |
116 | /// # Panics
117 | ///
118 | /// Will panic if `val` is not a valid 24 bit signed integer or if not
119 | /// enough space is remaing in the buffer to store 3 bytes
120 | #[inline]
121 | pub fn put_i24_le(&mut self, val: i32) {
122 | const SIZE: usize = 3;
123 | const MIN: i32 = 2i32.pow(23);
124 | const MAX: i32 = 2i32.pow(23) - 1;
125 |
126 | assert!(
127 | val <= MAX,
128 | "Attempted to put value that is too large for 24 bits, \
129 | attempted to push: {val}, max allowed: {MAX}",
130 | );
131 | assert!(
132 | val >= MIN,
133 | "Attempted to put value that is too negative for 24 bits, \
134 | attempted to push: {val}, min allowed: {MIN}",
135 | );
136 |
137 | let src = val.to_le_bytes();
138 | self.data[self.len..self.len + SIZE].copy_from_slice(&src[..3]);
139 | self.len += SIZE;
140 | }
141 |
142 | /// # Panics
143 | ///
144 | /// Will panic if less space then the 4 bytes required by a `u32` remain in the buffer
145 | #[inline]
146 | pub fn put_u32_le(&mut self, val: u32) {
147 | const SIZE: usize = core::mem::size_of::();
148 | self.check_remaining(SIZE);
149 |
150 | let src = val.to_le_bytes();
151 | self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
152 | self.len += SIZE;
153 | }
154 |
155 | /// # Panics
156 | ///
157 | /// Will panic if less space then the 4 bytes required by a `i32` remain in the buffer
158 | #[inline]
159 | pub fn put_i32_le(&mut self, val: i32) {
160 | const SIZE: usize = core::mem::size_of::();
161 | self.check_remaining(SIZE);
162 |
163 | let src = val.to_le_bytes();
164 | self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
165 | self.len += SIZE;
166 | }
167 |
168 | /// # Panics
169 | ///
170 | /// Will panic if less space then the 8 bytes required by a `u64` remain in the buffer
171 | #[inline]
172 | pub fn put_u64_le(&mut self, val: u64) {
173 | const SIZE: usize = core::mem::size_of::();
174 | self.check_remaining(SIZE);
175 |
176 | let src = val.to_le_bytes();
177 | self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
178 | self.len += SIZE;
179 | }
180 |
181 | /// # Panics
182 | ///
183 | /// Will panic if less space then the 8 bytes required by a `i64` remain in the buffer
184 | #[inline]
185 | pub fn put_i64_le(&mut self, val: i64) {
186 | const SIZE: usize = core::mem::size_of::();
187 | self.check_remaining(SIZE);
188 |
189 | let src = val.to_le_bytes();
190 | self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
191 | self.len += SIZE;
192 | }
193 |
194 | /// # Panics
195 | ///
196 | /// Will panic if less space then the 4 bytes required by a `f32` remain in the buffer
197 | #[inline]
198 | pub fn put_f32_le(&mut self, val: f32) {
199 | const SIZE: usize = core::mem::size_of::();
200 | self.check_remaining(SIZE);
201 |
202 | let src = val.to_le_bytes();
203 | self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
204 | self.len += SIZE;
205 | }
206 |
207 | /// # Panics
208 | ///
209 | /// Will panic if less space then the 8 bytes required by a `f64` remain in the buffer
210 | #[inline]
211 | pub fn put_f64_le(&mut self, val: f64) {
212 | const SIZE: usize = core::mem::size_of::();
213 | self.check_remaining(SIZE);
214 |
215 | let src = val.to_le_bytes();
216 | self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
217 | self.len += SIZE;
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/mavlink-core/src/connection/direct_serial.rs:
--------------------------------------------------------------------------------
1 | //! Serial MAVLINK connection
2 |
3 | use crate::connection::{Connection, MavConnection};
4 | use crate::error::{MessageReadError, MessageWriteError};
5 | use crate::peek_reader::PeekReader;
6 | use crate::Connectable;
7 | use crate::{MAVLinkMessageRaw, MavHeader, MavlinkVersion, Message, ReadVersion};
8 | use core::ops::DerefMut;
9 | use core::sync::atomic::{self, AtomicU8};
10 | use std::io::{self, BufReader};
11 | use std::sync::Mutex;
12 |
13 | use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits};
14 |
15 | #[cfg(not(feature = "signing"))]
16 | use crate::{read_versioned_msg, read_versioned_raw_message, write_versioned_msg};
17 | #[cfg(feature = "signing")]
18 | use crate::{
19 | read_versioned_msg_signed, read_versioned_raw_message_signed, write_versioned_msg_signed,
20 | SigningConfig, SigningData,
21 | };
22 |
23 | pub mod config;
24 |
25 | use config::SerialConfig;
26 |
27 | pub struct SerialConnection {
28 | // Separate ports for reading and writing as it's safe to use concurrently.
29 | // See the official ref: https://github.com/serialport/serialport-rs/blob/321f85e1886eaa1302aef8a600a631bc1c88703a/examples/duplex.rs
30 | read_port: Mutex>>>,
31 | write_port: Mutex>,
32 | sequence: AtomicU8,
33 | protocol_version: MavlinkVersion,
34 | recv_any_version: bool,
35 | #[cfg(feature = "signing")]
36 | signing_data: Option,
37 | }
38 |
39 | impl MavConnection for SerialConnection {
40 | fn recv(&self) -> Result<(MavHeader, M), MessageReadError> {
41 | let mut port = self.read_port.lock().unwrap();
42 | let version = ReadVersion::from_conn_cfg::<_, M>(self);
43 |
44 | loop {
45 | #[cfg(not(feature = "signing"))]
46 | let result = read_versioned_msg(port.deref_mut(), version);
47 | #[cfg(feature = "signing")]
48 | let result =
49 | read_versioned_msg_signed(port.deref_mut(), version, self.signing_data.as_ref());
50 | match result {
51 | ok @ Ok(..) => {
52 | return ok;
53 | }
54 | Err(MessageReadError::Io(e)) => {
55 | if e.kind() == io::ErrorKind::UnexpectedEof {
56 | return Err(MessageReadError::Io(e));
57 | }
58 | }
59 | _ => {}
60 | }
61 | }
62 | }
63 |
64 | fn recv_raw(&self) -> Result {
65 | let mut port = self.read_port.lock().unwrap();
66 | let version = ReadVersion::from_conn_cfg::<_, M>(self);
67 |
68 | loop {
69 | #[cfg(not(feature = "signing"))]
70 | let result = read_versioned_raw_message::(port.deref_mut(), version);
71 | #[cfg(feature = "signing")]
72 | let result = read_versioned_raw_message_signed::(
73 | port.deref_mut(),
74 | version,
75 | self.signing_data.as_ref(),
76 | );
77 | match result {
78 | ok @ Ok(..) => {
79 | return ok;
80 | }
81 | Err(MessageReadError::Io(e)) => {
82 | if e.kind() == io::ErrorKind::UnexpectedEof {
83 | return Err(MessageReadError::Io(e));
84 | }
85 | }
86 | _ => {}
87 | }
88 | }
89 | }
90 |
91 | fn try_recv(&self) -> Result<(MavHeader, M), MessageReadError> {
92 | let mut port = self.read_port.lock().unwrap();
93 | let version = ReadVersion::from_conn_cfg::<_, M>(self);
94 |
95 | #[cfg(not(feature = "signing"))]
96 | let result = read_versioned_msg(port.deref_mut(), version);
97 |
98 | #[cfg(feature = "signing")]
99 | let result =
100 | read_versioned_msg_signed(port.deref_mut(), version, self.signing_data.as_ref());
101 |
102 | result
103 | }
104 |
105 | fn send(&self, header: &MavHeader, data: &M) -> Result {
106 | let mut port = self.write_port.lock().unwrap();
107 |
108 | let sequence = self.sequence.fetch_add(
109 | 1,
110 | // Safety:
111 | //
112 | // We are using `Ordering::Relaxed` here because:
113 | // - We only need a unique sequence number per message
114 | // - `Mutex` on `self.write_port` already makes sure the rest of the code is synchronized
115 | // - No other thread reads or writes `self.sequence` without going through this `Mutex`
116 | //
117 | // Warning:
118 | //
119 | // If we later change this code to access `self.sequence` without locking `self.write_port` with the `Mutex`,
120 | // then we should upgrade this ordering to `Ordering::SeqCst`.
121 | atomic::Ordering::Relaxed,
122 | );
123 |
124 | let header = MavHeader {
125 | sequence,
126 | system_id: header.system_id,
127 | component_id: header.component_id,
128 | };
129 |
130 | #[cfg(not(feature = "signing"))]
131 | let result = write_versioned_msg(port.deref_mut(), self.protocol_version, header, data);
132 | #[cfg(feature = "signing")]
133 | let result = write_versioned_msg_signed(
134 | port.deref_mut(),
135 | self.protocol_version,
136 | header,
137 | data,
138 | self.signing_data.as_ref(),
139 | );
140 | result
141 | }
142 |
143 | fn set_protocol_version(&mut self, version: MavlinkVersion) {
144 | self.protocol_version = version;
145 | }
146 |
147 | fn protocol_version(&self) -> MavlinkVersion {
148 | self.protocol_version
149 | }
150 |
151 | fn set_allow_recv_any_version(&mut self, allow: bool) {
152 | self.recv_any_version = allow;
153 | }
154 |
155 | fn allow_recv_any_version(&self) -> bool {
156 | self.recv_any_version
157 | }
158 |
159 | #[cfg(feature = "signing")]
160 | fn setup_signing(&mut self, signing_data: Option) {
161 | self.signing_data = signing_data.map(SigningData::from_config);
162 | }
163 | }
164 |
165 | impl Connectable for SerialConfig {
166 | fn connect(&self) -> io::Result> {
167 | let read_port = serialport::new(&self.port_name, self.baud_rate)
168 | .data_bits(DataBits::Eight)
169 | .parity(Parity::None)
170 | .stop_bits(StopBits::One)
171 | .flow_control(FlowControl::None)
172 | .open()?;
173 |
174 | let write_port = read_port.try_clone()?;
175 |
176 | let read_buffer_capacity = self.buffer_capacity();
177 | let buf_reader = BufReader::with_capacity(read_buffer_capacity, read_port);
178 |
179 | Ok(SerialConnection {
180 | read_port: Mutex::new(PeekReader::new(buf_reader)),
181 | write_port: Mutex::new(write_port),
182 | sequence: AtomicU8::new(0),
183 | protocol_version: MavlinkVersion::V2,
184 | #[cfg(feature = "signing")]
185 | signing_data: None,
186 | recv_any_version: false,
187 | }
188 | .into())
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/mavlink-bindgen/tests/snapshots/e2e_snapshots__heartbeat.xml@heartbeat.rs.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: mavlink-bindgen/tests/e2e_snapshots.rs
3 | assertion_line: 26
4 | expression: contents
5 | ---
6 | #![doc = "MAVLink heartbeat dialect."]
7 | #![doc = ""]
8 | #![doc = "This file was automatically generated, do not edit."]
9 | #![allow(deprecated)]
10 | #![allow(clippy::match_single_binding)]
11 | #[cfg(feature = "arbitrary")]
12 | use arbitrary::Arbitrary;
13 | #[allow(unused_imports)]
14 | use bitflags::{bitflags, Flags};
15 | #[allow(unused_imports)]
16 | use mavlink_core::{
17 | bytes::Bytes, bytes_mut::BytesMut, types::CharArray, MavlinkVersion, Message, MessageData,
18 | };
19 | #[allow(unused_imports)]
20 | use num_derive::{FromPrimitive, ToPrimitive};
21 | #[allow(unused_imports)]
22 | use num_traits::{FromPrimitive, ToPrimitive};
23 | #[cfg(feature = "serde")]
24 | use serde::{Deserialize, Serialize};
25 | #[cfg(feature = "ts")]
26 | use ts_rs::TS;
27 | pub const MINOR_MAVLINK_VERSION: u8 = 3u8;
28 | pub const DIALECT_NUMBER: u8 = 130u8;
29 | #[doc = "ID: 0"]
30 | #[derive(Debug, Clone, PartialEq)]
31 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32 | #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
33 | #[cfg_attr(feature = "ts", derive(TS))]
34 | #[cfg_attr(feature = "ts", ts(export))]
35 | pub struct HEARTBEAT_DATA {
36 | #[doc = "Custom mode"]
37 | pub custom_mode: u32,
38 | #[doc = "Type"]
39 | pub mavtype: u8,
40 | #[doc = "Autopilot"]
41 | pub autopilot: u8,
42 | #[doc = "Base mode"]
43 | pub base_mode: u8,
44 | #[doc = "System status"]
45 | pub system_status: u8,
46 | #[doc = "Mavlink version"]
47 | pub mavlink_version: u8,
48 | }
49 | impl HEARTBEAT_DATA {
50 | pub const ENCODED_LEN: usize = 9usize;
51 | pub const DEFAULT: Self = Self {
52 | custom_mode: 0_u32,
53 | mavtype: 0_u8,
54 | autopilot: 0_u8,
55 | base_mode: 0_u8,
56 | system_status: 0_u8,
57 | mavlink_version: MINOR_MAVLINK_VERSION,
58 | };
59 | #[cfg(feature = "arbitrary")]
60 | pub fn random(rng: &mut R) -> Self {
61 | use arbitrary::{Arbitrary, Unstructured};
62 | let mut buf = [0u8; 1024];
63 | rng.fill_bytes(&mut buf);
64 | let mut unstructured = Unstructured::new(&buf);
65 | Self::arbitrary(&mut unstructured).unwrap_or_default()
66 | }
67 | }
68 | impl Default for HEARTBEAT_DATA {
69 | fn default() -> Self {
70 | Self::DEFAULT.clone()
71 | }
72 | }
73 | impl MessageData for HEARTBEAT_DATA {
74 | type Message = MavMessage;
75 | const ID: u32 = 0u32;
76 | const NAME: &'static str = "HEARTBEAT";
77 | const EXTRA_CRC: u8 = 50u8;
78 | const ENCODED_LEN: usize = 9usize;
79 | fn deser(
80 | _version: MavlinkVersion,
81 | __input: &[u8],
82 | ) -> Result {
83 | let avail_len = __input.len();
84 | let mut payload_buf = [0; Self::ENCODED_LEN];
85 | let mut buf = if avail_len < Self::ENCODED_LEN {
86 | payload_buf[0..avail_len].copy_from_slice(__input);
87 | Bytes::new(&payload_buf)
88 | } else {
89 | Bytes::new(__input)
90 | };
91 | let mut __struct = Self::default();
92 | __struct.custom_mode = buf.get_u32_le()?;
93 | __struct.mavtype = buf.get_u8()?;
94 | __struct.autopilot = buf.get_u8()?;
95 | __struct.base_mode = buf.get_u8()?;
96 | __struct.system_status = buf.get_u8()?;
97 | __struct.mavlink_version = buf.get_u8()?;
98 | Ok(__struct)
99 | }
100 | fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
101 | let mut __tmp = BytesMut::new(bytes);
102 | #[allow(clippy::absurd_extreme_comparisons)]
103 | #[allow(unused_comparisons)]
104 | if __tmp.remaining() < Self::ENCODED_LEN {
105 | panic!(
106 | "buffer is too small (need {} bytes, but got {})",
107 | Self::ENCODED_LEN,
108 | __tmp.remaining(),
109 | )
110 | }
111 | __tmp.put_u32_le(self.custom_mode);
112 | __tmp.put_u8(self.mavtype);
113 | __tmp.put_u8(self.autopilot);
114 | __tmp.put_u8(self.base_mode);
115 | __tmp.put_u8(self.system_status);
116 | __tmp.put_u8(self.mavlink_version);
117 | if matches!(version, MavlinkVersion::V2) {
118 | let len = __tmp.len();
119 | ::mavlink_core::utils::remove_trailing_zeroes(&bytes[..len])
120 | } else {
121 | __tmp.len()
122 | }
123 | }
124 | }
125 | #[derive(Clone, PartialEq, Debug)]
126 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
127 | #[cfg_attr(feature = "serde", serde(tag = "type"))]
128 | #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
129 | #[cfg_attr(feature = "ts", derive(TS))]
130 | #[cfg_attr(feature = "ts", ts(export))]
131 | #[repr(u32)]
132 | pub enum MavMessage {
133 | #[doc = "ID: 0"]
134 | HEARTBEAT(HEARTBEAT_DATA),
135 | }
136 | impl MavMessage {
137 | pub const fn all_ids() -> &'static [u32] {
138 | &[0u32]
139 | }
140 | pub const fn all_messages() -> &'static [(&'static str, u32)] {
141 | &[(HEARTBEAT_DATA::NAME, HEARTBEAT_DATA::ID)]
142 | }
143 | }
144 | impl Message for MavMessage {
145 | fn parse(
146 | version: MavlinkVersion,
147 | id: u32,
148 | payload: &[u8],
149 | ) -> Result {
150 | match id {
151 | HEARTBEAT_DATA::ID => HEARTBEAT_DATA::deser(version, payload).map(Self::HEARTBEAT),
152 | _ => Err(::mavlink_core::error::ParserError::UnknownMessage { id }),
153 | }
154 | }
155 | fn message_name(&self) -> &'static str {
156 | match self {
157 | Self::HEARTBEAT(..) => HEARTBEAT_DATA::NAME,
158 | }
159 | }
160 | fn message_id(&self) -> u32 {
161 | match self {
162 | Self::HEARTBEAT(..) => HEARTBEAT_DATA::ID,
163 | }
164 | }
165 | fn message_id_from_name(name: &str) -> Option {
166 | match name {
167 | HEARTBEAT_DATA::NAME => Some(HEARTBEAT_DATA::ID),
168 | _ => None,
169 | }
170 | }
171 | fn default_message_from_id(id: u32) -> Option {
172 | match id {
173 | HEARTBEAT_DATA::ID => Some(Self::HEARTBEAT(HEARTBEAT_DATA::default())),
174 | _ => None,
175 | }
176 | }
177 | #[cfg(feature = "arbitrary")]
178 | fn random_message_from_id(id: u32, rng: &mut R) -> Option {
179 | match id {
180 | HEARTBEAT_DATA::ID => Some(Self::HEARTBEAT(HEARTBEAT_DATA::random(rng))),
181 | _ => None,
182 | }
183 | }
184 | fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
185 | match self {
186 | Self::HEARTBEAT(body) => body.ser(version, bytes),
187 | }
188 | }
189 | fn extra_crc(id: u32) -> u8 {
190 | match id {
191 | HEARTBEAT_DATA::ID => HEARTBEAT_DATA::EXTRA_CRC,
192 | _ => 0,
193 | }
194 | }
195 | fn target_system_id(&self) -> Option {
196 | match self {
197 | _ => None,
198 | }
199 | }
200 | fn target_component_id(&self) -> Option {
201 | match self {
202 | _ => None,
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------