Crate uf_crsf

Crate uf_crsf 

Source
Expand description

ยงuf-crsf

CI codecov crates.io docs.rs

A no_std Rust library for parsing the TBS Crossfire protocol, designed for embedded environments without an allocator.

This library provides a two-layer API:

  • A low-level layer for raw packet parsing from a byte stream.
  • A higher-level layer that converts raw packets into idiomatic Rust structs.

ยงFeatures

  • no_std and allocator-free for embedded systems.
  • Two-layer API for flexible parsing.
  • Supports a wide range of CRSF packets.
  • IO and MCU agnostic.
  • Minimal dependencies.

ยงImplementation status

Legend:

  • ๐ŸŸข - Implemented
  • ๐Ÿ”ด - Not Implemented
Packet NamePacket AddressStatus
Broadcast Frames
GPS0x02๐ŸŸข
GPS Time0x03๐ŸŸข
GPS Extended0x06๐ŸŸข
Variometer Sensor0x07๐ŸŸข
Battery Sensor0x08๐ŸŸข
Barometric Altitude & Vertical Speed0x09๐ŸŸข
Airspeed0x0A๐ŸŸข
Heartbeat0x0B๐ŸŸข
RPM0x0C๐ŸŸข
TEMP0x0D๐ŸŸข
Voltages0x0E๐ŸŸข
Discontinued0x0F๐ŸŸข
VTX Telemetry0x10๐ŸŸข
Link Statistics0x14๐ŸŸข
RC Channels Packed Payload0x16๐ŸŸข
Subset RC Channels Packed0x17๐Ÿ”ด
RC Channels Packed 11-bits0x18๐Ÿ”ด
Link Statistics RX0x1C๐ŸŸข
Link Statistics TX0x1D๐ŸŸข
Attitude0x1E๐ŸŸข
MAVLink FC0x1F๐ŸŸข
Flight Mode0x21๐ŸŸข
ESP_NOW Messages0x22๐ŸŸข
Extended Frames
Parameter Ping Devices0x28๐ŸŸข
Parameter Device Information0x29๐ŸŸข
Parameter Settings (Entry)0x2B๐Ÿ”ด
Parameter Settings (Read)0x2C๐Ÿ”ด
Parameter Value (Write)0x2D๐Ÿ”ด
Direct Commands0x32๐ŸŸข
Logging0x34๐ŸŸข
Remote Related Frames0x3A๐ŸŸข
Game0x3C๐ŸŸข
KISSFC Reserved0x78 - 0x79๐Ÿ”ด
MSP Request0x7A๐Ÿ”ด
MSP Response0x7B๐Ÿ”ด
ArduPilot Legacy Reserved0x7F๐Ÿ”ด
ArduPilot Reserved Passthrough Frame0x80๐ŸŸข
mLRS Reserved0x81, 0x82๐Ÿ”ด
CRSF MAVLink Envelope0xAA๐ŸŸข
CRSF MAVLink System Status Sensor0xAC๐ŸŸข

ยงNote

Library is under active development and testing, API might change at any time.

ยงInstallation

Add uf-crsf to your Cargo.toml:

[dependencies]
uf-crsf = "*" # replace * by the latest version of the crate.

Or use the command line:

cargo add uf-crsf

ยงUsage

Here is a basic example of how to parse a CRSF packet from a byte array:

use uf_crsf::CrsfParser;

fn main() {
    let mut parser = CrsfParser::new();

    // A sample CRSF packet payload for RC channels
    let buf: [u8; 26] = [
        0xC8, // Address
        0x18, // Length
        0x16, // Type (RC Channels)
        0x03, 0x1F, 0x58, 0xC0, 0x07, 0x16, 0xB0, 0x80, 0x05, 0x2C, 0x60, 0x01, 0x0B, 0xF8, 0xC0,
        0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 252,  // Packet
        0x42, // Crc
    ];

    for item in parser.iter_packets(&buf) {
        match item {
            Ok(p) => println!("{:?}", p),
            Err(e) => eprintln!("Error parsing packet: {:?}", e),
        }
    }
}

Here is a basic example of how to parse and print telemetry CRSF packets from an ELRS TX16S radio controller, though it should work with any other EdgeTX device. Simply configure telemetry mirroring to a USB serial port and connect the controller to your PC.

use std::env;
use std::io::ErrorKind;
use std::process::exit;
use std::time::Duration;
use uf_crsf::CrsfParser;

fn main() {
    let ports = match serialport::available_ports() {
        Ok(ports) => ports,
        Err(e) => {
            eprintln!("Failed to enumerate serial ports: {}", e);
            exit(1);
        }
    };

    if ports.is_empty() {
        eprintln!("No serial ports found.");
        eprintln!("Please specify a serial port path as an argument.");
        exit(1);
    }

    let path = env::args().nth(1).unwrap_or_else(|| {
        const DEFAULT_PORT: &str = "/dev/tty.usbmodem00000000001B1";
        if ports.iter().any(|p| p.port_name == DEFAULT_PORT) {
            println!(
                "No serial port specified. Using default port: {}",
                DEFAULT_PORT
            );
            DEFAULT_PORT.to_string()
        } else {
            println!("No serial port specified. Available ports:");
            for p in &ports {
                println!("  {}", p.port_name);
            }
            println!("\nUsing first available port: {}", ports[0].port_name);
            ports[0].port_name.clone()
        }
    });

    let mut port = serialport::new(&path, 420_000)
        .timeout(Duration::from_millis(10))
        .open()
        .unwrap_or_else(|e| {
            eprintln!("Failed to open serial port '{}': {}", &path, e);
            exit(1);
        });

    let mut buf = [0; 1024];
    let mut parser = CrsfParser::new();
    println!("Reading from serial port '{}'...", path);
    loop {
        match port.read(buf.as_mut_slice()) {
            Ok(n) => {
                for packet in parser.iter_packets(&buf[..n]) {
                    println!("{:?}", packet);
                }
            }
            Err(ref e) if e.kind() == ErrorKind::TimedOut => {
                // This is expected when no data is coming in
            }
            Err(e) => {
                eprintln!("Error reading from serial port: {}", e);
                break;
            }
        }
    }
}

ยงLicense

This project is licensed under the Apache 2.0. See the LICENSE file for details.

ยงProtocol Specification

Re-exportsยง

pub use error::CrsfParsingError;
pub use error::CrsfStreamError;
pub use packets::write_packet_to_buffer;
pub use packets::Packet;
pub use packets::PacketAddress;
pub use packets::PacketType;
pub use parser::CrsfParser;
pub use parser::RawCrsfPacket;

Modulesยง

constants
error
packets
parser