Skip to main content

Payload Structures

This section provides detailed byte-by-byte documentation for the most commonly used MSP command payloads.

Data Type Conventions​

TypeRust TypeSizeDescription
U8u81 byteUnsigned 8-bit integer
U16u162 bytesUnsigned 16-bit, little-endian
U32u324 bytesUnsigned 32-bit, little-endian
I8i81 byteSigned 8-bit integer
I16i162 bytesSigned 16-bit, little-endian
I32i324 bytesSigned 32-bit, little-endian

Byte Order​

All multi-byte values in MSP are transmitted in little-endian format:

// Encoding a u16 value
let value: u16 = 0x1234;
let bytes = value.to_le_bytes(); // [0x34, 0x12]

// Decoding a u16 from bytes
let decoded = u16::from_le_bytes([bytes[0], bytes[1]]); // 0x1234

Common Patterns​

Reading Fixed-Size Payloads​

/// Generic payload reader trait
pub trait FromMspPayload: Sized {
fn from_payload(data: &[u8]) -> Option<Self>;
}

/// Example: Reading a 6-byte attitude payload
#[derive(Debug, Clone, Copy)]
pub struct Attitude {
pub roll: i16,
pub pitch: i16,
pub yaw: i16,
}

impl FromMspPayload for Attitude {
fn from_payload(data: &[u8]) -> Option<Self> {
if data.len() < 6 {
return None;
}
Some(Attitude {
roll: i16::from_le_bytes([data[0], data[1]]),
pitch: i16::from_le_bytes([data[2], data[3]]),
yaw: i16::from_le_bytes([data[4], data[5]]),
})
}
}

Writing Payloads​

/// Generic payload writer trait
pub trait ToMspPayload {
fn to_payload(&self) -> Vec<u8>;
}

/// Example: Writing motor values
#[derive(Debug)]
pub struct MotorOutput {
pub motors: [u16; 8],
}

impl ToMspPayload for MotorOutput {
fn to_payload(&self) -> Vec<u8> {
let mut payload = Vec::with_capacity(16);
for motor in &self.motors {
payload.extend_from_slice(&motor.to_le_bytes());
}
payload
}
}

Variable-Length Payloads​

Some payloads have variable length (e.g., RC channels, mode ranges):

/// Parse variable-length array of u16 values
pub fn parse_u16_array(data: &[u8]) -> Vec<u16> {
data.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect()
}

/// Parse array of fixed-size structs
pub fn parse_struct_array<T, F>(data: &[u8], struct_size: usize, parser: F) -> Vec<T>
where
F: Fn(&[u8]) -> Option<T>,
{
data.chunks(struct_size)
.filter_map(|chunk| parser(chunk))
.collect()
}

Scaling and Units​

Many values use scaled integers instead of floating-point:

MeasurementRaw TypeScaleUnit
Attitude anglesI16÷10degrees
VoltageU16÷100volts
CurrentI16÷100amps
AltitudeI32÷100meters
GPS coordinatesI32÷10^7degrees
GyroscopeI16÷4.096°/s
RSSIU16÷1023×100percent
/// Convert raw attitude to degrees
pub fn raw_to_degrees(raw: i16) -> f32 {
raw as f32 / 10.0
}

/// Convert raw voltage to volts
pub fn raw_to_volts(raw: u16) -> f32 {
raw as f32 / 100.0
}

/// Convert raw GPS coordinate to degrees
pub fn raw_to_gps_degrees(raw: i32) -> f64 {
raw as f64 / 10_000_000.0
}

Payload Index​

CommandNameSizeDescription
101MSP_STATUS13+ bytesFC status and sensor flags
108MSP_ATTITUDE6 bytesRoll, pitch, yaw angles
102MSP_RAW_IMU18 bytesAccelerometer, gyro, magnetometer
105MSP_RCVariableRC channel values
104MSP_MOTOR16 bytesMotor outputs
112MSP_PIDVariablePID coefficients
110MSP_ANALOG9 bytesBattery and RSSI

Common Payload Structure Module​

//! MSP payload structures and parsing utilities

mod status;
mod attitude;
mod raw_imu;
mod rc_channels;
mod motors;
mod pid;
mod analog;

pub use status::*;
pub use attitude::*;
pub use raw_imu::*;
pub use rc_channels::*;
pub use motors::*;
pub use pid::*;
pub use analog::*;

/// Trait for types that can be parsed from MSP payloads
pub trait MspPayload: Sized {
/// MSP command code for this payload type
const COMMAND: u8;

/// Minimum required payload size
const MIN_SIZE: usize;

/// Parse from raw bytes
fn parse(data: &[u8]) -> Option<Self>;
}

/// Generic MSP response wrapper
#[derive(Debug)]
pub struct MspResponse<T: MspPayload> {
pub command: u8,
pub payload: T,
}

impl<T: MspPayload> MspResponse<T> {
pub fn parse(command: u8, data: &[u8]) -> Option<Self> {
if command != T::COMMAND {
return None;
}
if data.len() < T::MIN_SIZE {
return None;
}
let payload = T::parse(data)?;
Some(MspResponse { command, payload })
}
}

Error Handling​

/// MSP payload parsing errors
#[derive(Debug, Clone, Copy)]
pub enum PayloadError {
/// Payload too short
TooShort { expected: usize, got: usize },
/// Invalid field value
InvalidValue { field: &'static str },
/// Checksum mismatch
ChecksumError,
}

impl std::fmt::Display for PayloadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PayloadError::TooShort { expected, got } => {
write!(f, "Payload too short: expected {} bytes, got {}", expected, got)
}
PayloadError::InvalidValue { field } => {
write!(f, "Invalid value in field: {}", field)
}
PayloadError::ChecksumError => {
write!(f, "Checksum verification failed")
}
}
}
}

impl std::error::Error for PayloadError {}