Skip to main content

System Info Commands (0-10)

These commands provide information about the flight controller, firmware version, and board identification.

MSP_API_VERSION (1)​

Query the MSP protocol and API version.

Request​

Empty payload (0 bytes).

Response​

OffsetTypeFieldDescription
0U8protocol_version0 = MSPv1 only, 2 = MSPv2 capable
1U8api_majorAPI major version
2U8api_minorAPI minor version

Rust Implementation​

/// MSP_API_VERSION response
#[derive(Debug, Clone, Copy)]
#[repr(C, packed)]
pub struct MspApiVersion {
pub protocol_version: u8,
pub api_major: u8,
pub api_minor: u8,
}

impl MspApiVersion {
pub const COMMAND: u8 = 1;

pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 3 {
return None;
}
Some(MspApiVersion {
protocol_version: data[0],
api_major: data[1],
api_minor: data[2],
})
}

pub fn supports_v2(&self) -> bool {
self.protocol_version >= 2
}
}

Example​

// Request frame
let request = [0x24, 0x4D, 0x3C, 0x00, 0x01, 0x01];

// Response payload (Betaflight 4.4, API 1.48, MSPv2 capable)
let payload = [0x02, 0x01, 0x30]; // protocol=2, major=1, minor=48

let version = MspApiVersion::from_bytes(&payload).unwrap();
println!("API: {}.{}", version.api_major, version.api_minor);
// Output: API: 1.48

MSP_FC_VARIANT (2)​

Query the flight controller firmware variant.

Request​

Empty payload (0 bytes).

Response​

OffsetTypeFieldDescription
0-3char[4]identifier4-character firmware identifier

Known Variants​

CodeFirmware
BTFLBetaflight
INAVINAV
CLFLCleanflight
BAFLBaseflight
MWIIMultiWii
RCFLRaceflight

Rust Implementation​

/// MSP_FC_VARIANT response
#[derive(Debug, Clone)]
pub struct MspFcVariant {
pub identifier: [u8; 4],
}

impl MspFcVariant {
pub const COMMAND: u8 = 2;

pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 4 {
return None;
}
let mut identifier = [0u8; 4];
identifier.copy_from_slice(&data[0..4]);
Some(MspFcVariant { identifier })
}

pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.identifier).unwrap_or("????")
}

pub fn is_betaflight(&self) -> bool {
&self.identifier == b"BTFL"
}
}

Example​

// Response payload
let payload = [0x42, 0x54, 0x46, 0x4C]; // "BTFL"

let variant = MspFcVariant::from_bytes(&payload).unwrap();
println!("Firmware: {}", variant.as_str());
// Output: Firmware: BTFL

MSP_FC_VERSION (3)​

Query the firmware version number.

Request​

Empty payload (0 bytes).

Response​

OffsetTypeFieldDescription
0U8majorMajor version
1U8minorMinor version
2U8patchPatch version

Rust Implementation​

/// MSP_FC_VERSION response
#[derive(Debug, Clone, Copy)]
#[repr(C, packed)]
pub struct MspFcVersion {
pub major: u8,
pub minor: u8,
pub patch: u8,
}

impl MspFcVersion {
pub const COMMAND: u8 = 3;

pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 3 {
return None;
}
Some(MspFcVersion {
major: data[0],
minor: data[1],
patch: data[2],
})
}

/// Check if version is at least the specified version
pub fn at_least(&self, major: u8, minor: u8, patch: u8) -> bool {
(self.major, self.minor, self.patch) >= (major, minor, patch)
}
}

impl core::fmt::Display for MspFcVersion {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}

Example​

// Response payload (Betaflight 4.4.2)
let payload = [0x04, 0x04, 0x02];

let version = MspFcVersion::from_bytes(&payload).unwrap();
println!("Version: {}", version);
// Output: Version: 4.4.2

if version.at_least(4, 3, 0) {
println!("GPS Rescue V2 available");
}

MSP_BOARD_INFO (4)​

Query board identification and hardware information.

Request​

Empty payload (0 bytes).

Response​

OffsetTypeFieldDescription
0-3char[4]board_identifierBoard ID (e.g., "SPR3")
4-5U16hardware_revisionHardware revision
6U8board_type0=FC, 1=VTX, etc.
7U8target_capabilitiesCapability flags
8U8target_name_lengthLength of target name
9+char[]target_nameTarget name string

Board Types​

ValueType
0Flight Controller
1Video Transmitter
2Radio Receiver

Rust Implementation​

/// MSP_BOARD_INFO response
#[derive(Debug, Clone)]
pub struct MspBoardInfo {
pub board_identifier: [u8; 4],
pub hardware_revision: u16,
pub board_type: u8,
pub target_capabilities: u8,
pub target_name: String,
}

impl MspBoardInfo {
pub const COMMAND: u8 = 4;

pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 7 {
return None;
}

let mut board_identifier = [0u8; 4];
board_identifier.copy_from_slice(&data[0..4]);

let hardware_revision = u16::from_le_bytes([data[4], data[5]]);
let board_type = data[6];

let (target_capabilities, target_name) = if data.len() > 8 {
let caps = data[7];
let name_len = data[8] as usize;
let name = if data.len() >= 9 + name_len {
String::from_utf8_lossy(&data[9..9 + name_len]).to_string()
} else {
String::new()
};
(caps, name)
} else {
(0, String::new())
};

Some(MspBoardInfo {
board_identifier,
hardware_revision,
board_type,
target_capabilities,
target_name,
})
}

pub fn board_id_str(&self) -> &str {
core::str::from_utf8(&self.board_identifier).unwrap_or("????")
}
}

Example​

// Response payload
let payload = [
0x53, 0x50, 0x52, 0x33, // "SPR3"
0x00, 0x00, // hardware revision = 0
0x00, // board type = FC
0x0F, // capabilities
0x0C, // name length = 12
// "SPRACINGF3EVO"
0x53, 0x50, 0x52, 0x41, 0x43, 0x49, 0x4E, 0x47, 0x46, 0x33, 0x45, 0x56
];

let info = MspBoardInfo::from_bytes(&payload).unwrap();
println!("Board: {} ({})", info.board_id_str(), info.target_name);
// Output: Board: SPR3 (SPRACINGF3EV)

MSP_BUILD_INFO (5)​

Query build information including date, time, and git hash.

Request​

Empty payload (0 bytes).

Response​

OffsetTypeFieldDescription
0-10char[11]build_date"MMM DD YYYY"
11-18char[8]build_time"HH:MM:SS"
19-25char[7]git_revisionShort git hash

Rust Implementation​

/// MSP_BUILD_INFO response
#[derive(Debug, Clone)]
pub struct MspBuildInfo {
pub build_date: [u8; 11],
pub build_time: [u8; 8],
pub git_revision: [u8; 7],
}

impl MspBuildInfo {
pub const COMMAND: u8 = 5;

pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 26 {
return None;
}

let mut build_date = [0u8; 11];
let mut build_time = [0u8; 8];
let mut git_revision = [0u8; 7];

build_date.copy_from_slice(&data[0..11]);
build_time.copy_from_slice(&data[11..19]);
git_revision.copy_from_slice(&data[19..26]);

Some(MspBuildInfo {
build_date,
build_time,
git_revision,
})
}

pub fn date_str(&self) -> &str {
core::str::from_utf8(&self.build_date).unwrap_or("")
}

pub fn time_str(&self) -> &str {
core::str::from_utf8(&self.build_time).unwrap_or("")
}

pub fn git_str(&self) -> &str {
core::str::from_utf8(&self.git_revision).unwrap_or("")
}
}

Example​

// Response payload
let payload = b"Jan 15 2024\x0012:34:56\x00abc1234";

let info = MspBuildInfo::from_bytes(payload).unwrap();
println!("Built: {} {} ({})", info.date_str(), info.time_str(), info.git_str());
// Output: Built: Jan 15 2024 12:34:56 (abc1234)

MSP_NAME (10)​

Query the craft/pilot name.

Request​

Empty payload (0 bytes).

Response​

OffsetTypeFieldDescription
0-Nchar[]nameCraft name (null-terminated or length-delimited)

Rust Implementation​

/// MSP_NAME response
pub struct MspName {
pub name: String,
}

impl MspName {
pub const COMMAND: u8 = 10;

pub fn from_bytes(data: &[u8]) -> Self {
let name = String::from_utf8_lossy(data)
.trim_end_matches('\0')
.to_string();
MspName { name }
}
}

MSP_SET_NAME (11)​

Set the craft/pilot name.

Request​

OffsetTypeFieldDescription
0-Nchar[]nameNew name (max 16 chars typical)

Response​

Empty (ACK only).

Rust Implementation​

/// Build MSP_SET_NAME request
pub fn build_set_name_request(name: &str) -> Vec<u8> {
const MSP_SET_NAME: u8 = 11;
const MAX_NAME_LEN: usize = 16;

let name_bytes: Vec<u8> = name.bytes().take(MAX_NAME_LEN).collect();
let size = name_bytes.len() as u8;

let mut frame = vec![b'$', b'M', b'<', size, MSP_SET_NAME];
frame.extend_from_slice(&name_bytes);

// Checksum
let mut checksum = size ^ MSP_SET_NAME;
for b in &name_bytes {
checksum ^= b;
}
frame.push(checksum);

frame
}

Example​

// Set craft name to "RACER01"
let request = build_set_name_request("RACER01");

// Request bytes: $M< 07 0B 52 41 43 45 52 30 31 [checksum]
println!("Request: {:02X?}", request);

Complete Connection Example​

use std::io::{Read, Write};

/// Query all system info in sequence
fn get_system_info<T: Read + Write>(port: &mut T) -> Result<(), &'static str> {
// Helper to send request and get payload
fn query(port: &mut impl Write + Read, cmd: u8) -> Result<Vec<u8>, &'static str> {
let checksum = 0 ^ cmd;
let request = [b'$', b'M', b'<', 0, cmd, checksum];
port.write_all(&request).map_err(|_| "Write failed")?;

let mut buf = [0u8; 256];
let n = port.read(&mut buf).map_err(|_| "Read failed")?;

if n < 6 || &buf[0..3] != b"$M>" {
return Err("Invalid response");
}

let size = buf[3] as usize;
Ok(buf[5..5 + size].to_vec())
}

// Query API version
let api = query(port, 1)?;
let api_version = MspApiVersion::from_bytes(&api).ok_or("Parse failed")?;
println!("API: {}.{} (MSPv{})",
api_version.api_major,
api_version.api_minor,
if api_version.supports_v2() { "2" } else { "1" }
);

// Query FC variant
let variant = query(port, 2)?;
let fc_variant = MspFcVariant::from_bytes(&variant).ok_or("Parse failed")?;
println!("Firmware: {}", fc_variant.as_str());

// Query FC version
let version = query(port, 3)?;
let fc_version = MspFcVersion::from_bytes(&version).ok_or("Parse failed")?;
println!("Version: {}", fc_version);

// Query board info
let board = query(port, 4)?;
let board_info = MspBoardInfo::from_bytes(&board).ok_or("Parse failed")?;
println!("Board: {} ({})", board_info.board_id_str(), board_info.target_name);

// Query craft name
let name = query(port, 10)?;
let craft_name = MspName::from_bytes(&name);
println!("Name: {}", craft_name.name);

Ok(())
}