#mav-link #uav #drones

no-std mavio

Minimalistic MAVLink client that supports no-std and no-alloc targets

3 unstable releases

0.2.0-rc1 Jan 26, 2024
0.1.1 Jan 11, 2024
0.1.0 Dec 30, 2023

#8 in Robotics

Download history 53/week @ 2023-12-27 5/week @ 2024-01-03 34/week @ 2024-01-10 5/week @ 2024-01-17 32/week @ 2024-01-24 7/week @ 2024-01-31 2/week @ 2024-02-07 50/week @ 2024-02-14

91 downloads per month
Used in maviola

MIT/Apache

195KB
2K SLoC

Mavio

Minimalistic library for transport-agnostic MAVLink communication. It supports no-std (and no-alloc) targets.

πŸ‡ΊπŸ‡¦ repository crates.io docs.rs issues

More on MAVLink

MAVLink is a lightweight open protocol for communicating between drones, onboard components and ground control stations. It is used by such autopilots like PX4 or ArduPilot. MAVLink has simple and compact serialization model. The basic abstraction is message which can be sent through a link (UDP, TCP, UNIX socket, UART, whatever) and deserialized into a struct with fields of primitive types or arrays of primitive types. Such fields can be additionally restricted by enum variants, annotated with metadata like units of measurements, default or invalid values.

There are several MAVLink dialects. Official dialect definitions are XML files that can be found in the MAVlink repository. Based on message abstractions, MAVLink defines so-called microservices that specify how clients should respond on a particular message under certain conditions or how they should initiate a particular action.

Mavio is a building block for more sophisticated tools. It is entirely focused on one thing: to include absolute minimum of functionality required for correct communication with everything that speaks MAVLink protocol.

  • Supports both MAVLink 1 and MAVLink 2 protocol versions.
  • Provides intermediate MAVLink packets decoding as "frames" that contain only header, checksum and signature being deserialized. Which means that client don't have to decode the entire message for routing and verification.
  • Supports optional high-level message decoding by utilizing MAVLink abstractions generated by MAVSpec.
  • Includes standard MAVLink dialects enabled by Cargo features.
  • Implements message verification via checksum.
  • Includes tools for message signing.

In other words, Mavio implements all stateless features of MAVLink protocol. Which means that it does not provide support for message sequencing, automatic heartbeats, etc. The client is responsible for implementing these parts of the protocol by their own or use a dedicated library. We've decided to keep Mavio as simple and catchy as an 8-bit melody.

At the same time, Mavio is flexible and tries to dictate as few as possible. In particular:

  • It supports custom dialects or may work with no dialect at all (for intermediate decoding). The latter is useful if you want to simply route or sign messages.
  • Can read and write messages to anything that implements std::io::Read and std::io::Write traits.
  • Compatible with no_std targets. For such cases the library provides simplified versions of Read and Write traits.
  • Support asynchronous I/O via Tokio.
  • Allows filtering out unnecessary MAVLink entities (i.e. messages, enums, commands) to reduce compilation time.

This library is a part of Mavka toolchain. It is integrated with other projects such as:

  • MAVInspect that responsible for MAVLink XML definitions parsing.
  • MAVSpec that focused on code generation. Mavio uses this library to generate MAVLink dialects.
  • Maviola (WIP), is a MAVLink communication library based on Mavio that provides a high-level interface for MAVLink messaging and takes care about stateful features of the protocol: sequencing, message time-stamping, automatic heartbeats, simplifies message signing, and so on.

This project respects semantic versioning. As allowed by specification, breaking changes may be introduced in minor releases until version 1.0.0 is reached. However, we will keep unstable features under the unstable feature flag whenever possible.

Install

Install Mavio with cargo:

cargo add mavio

Usage

For details, please check API section and API documentation.

Connect as TCP client, receive 10 frames, and decode any received HEARTBEAT message:

use std::net::TcpStream;
use mavio::{Frame, Receiver};
use mavio::dialects::minimal as dialect;
use dialect::Message;

fn main() -> mavio::errors::Result<()> {
    let mut receiver = Receiver::new(TcpStream::connect("0.0.0.0:5600")?);

    for i in 0..10 {
        let frame = receiver.recv()?;

        if let Err(err) = frame.validate_checksum(dialect::spec()) {
            eprintln!("Invalid checksum: {:?}", err);
            continue;
        }

        if let Ok(Message::Heartbeat(msg)) = dialect::decode(frame.payload()) {
            println!(
                "HEARTBEAT #{}: mavlink_version={:#?}",
                frame.sequence(),
                msg.mavlink_version,
            );
        }
    }
}

A slightly more elaborated use-case can be found in tcp_client.rs example.

Listen to TCP port as a server, send 10 HEARTBEAT messages to any connected client using MAVLink 2 protocol, then disconnect a client.

use std::net::TcpStream;
use mavio::{Frame, Sender};
use mavio::protocol::MavLinkVersion;
use mavio::dialects::minimal as dialect;
use dialect::Message;
use dialect::enums::{MavAutopilot, MavModeFlag, MavState, MavType};

fn main() -> mavio::errors::Result<()> {
    let mut sender = Sender::new(TcpStream::connect("0.0.0.0:5600")?);

    let mavlink_version = MavLinkVersion::V2;
    let system_id = 15;
    let component_id = 42;

    for sequence in 0..10 {
        let message = dialect::messages::Heartbeat {
            type_: MavType::FixedWing,
            autopilot: MavAutopilot::Generic,
            base_mode: MavModeFlag::TEST_ENABLED
                & MavModeFlag::CUSTOM_MODE_ENABLED,
            custom_mode: 0,
            system_status: MavState::Active,
            mavlink_version: 3,
        };
        println!("MESSAGE #{}: {:#?}", sequence, message);

        let frame = Frame::builder()
            .set_sequence(sequence)
            .set_system_id(system_id)
            .set_component_id(component_id)
            .build_for(&message, mavlink_version)?;

        sender.send(&frame)?;
        println!("FRAME #{} sent: {:#?}", sequence, frame);
    }
}

Check tcp_server.rs for a slightly more elaborated use-case.

API Notes

This section provides a general API overview. For further details, please check API documentation.

I/O

Mavio provides two basic I/O primitives: Sender and Receiver. These structs send and receive instances of Frame.

Sender and Receiver are generic over std::io::Write and std::io::Read accordingly. That means you can communicate MAVLink messages over various transports which implements these traits including UDP, TCP, Unix sockets, and files. It is also easy to implement custom transport.

For no-std targets Mavio provides custom implementations of Read and Write traits. You can implement them for hardware-specific communication (like serial ports).

For asynchronous I/O Mavio provides AsyncSender and AsyncReceiver which work in the same fashion as their synchronous counterparts, except using Tokio. To enable asynchronous support, add tokio feature flag.

Encoding/Decoding

Upon receiving, MAVLink Frames can be validated and decoded into MAVLink messages. Frames can be routed, signed, or forwarded to another system/component ID without decoding.

Note!

MAVLink checksum validation requires CRC_EXTRA byte which its turn depends on a dialect specification. That means, if you are performing dialect-agnostic routing from a noisy source or from devices which implement outdated message specifications, you may forward junk messages. In case of high-latency channels you might want to enforce compliance with a particular dialect to filter incompatible messages.

To decode a frame into a MAVLink message, you need to use a specific dialect. Standard MAVLink dialects are available under mavio::dialects and can be enabled by the corresponding feature flags:

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

Custom Dialects

Concrete implementations of dialects are generated by MAVSpec check its documentation for details. You may also found useful to review build.rs, if you want to generate your custom dialects.

Examples

Examples for synchronous I/O in ./examples/sync/examples:

  • tcp_server.rs is a simple TCP server that awaits for connections, sends and receives heartbeats:
    cargo run --package mavio_examples_sync --example tcp_server
    
  • tcp_client.rs is a TCP client which connects to server, sends and receives heartbeats:
    cargo run --package mavio_examples_sync --example tcp_client
    
  • tcp_ping_pong.rs server and clients which communicate with each other via TCP:
    cargo run --package mavio_examples_sync --example tcp_ping_pong
    

Examples for asynchronous I/O in ./examples/async/examples:

  • async_tcp_ping_pong.rs server and clients which communicate with each other via TCP:
    cargo run --package mavio_examples_async --example async_tcp_ping_pong
    

Examples for custom dialect generation with filtered MAVLink entities can be found in ./examples/custom/examples:

  • custom_dialects_usage.rs a basic usage of custom-generated dialect:
    cargo run --package mavio_examples_custom --example mavio_examples_custom_usage
    
  • custom_message.rs crating and using a custom message:
    cargo run --package mavio_examples_custom --example custom_message
    

License

Here we simply comply with the suggested dual licensing according to Rust API Guidelines (C-PERMISSIVE).

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~1.4–4MB
~74K SLoC