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
| Offset | Type | Field | Description |
|---|---|---|---|
| 0 | U8 | protocol_version | 0 = MSPv1 only, 2 = MSPv2 capable |
| 1 | U8 | api_major | API major version |
| 2 | U8 | api_minor | API 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
| Offset | Type | Field | Description |
|---|---|---|---|
| 0-3 | char[4] | identifier | 4-character firmware identifier |
Known Variants
| Code | Firmware |
|---|---|
BTFL | Betaflight |
INAV | INAV |
CLFL | Cleanflight |
BAFL | Baseflight |
MWII | MultiWii |
RCFL | Raceflight |
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
| Offset | Type | Field | Description |
|---|---|---|---|
| 0 | U8 | major | Major version |
| 1 | U8 | minor | Minor version |
| 2 | U8 | patch | Patch 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
| Offset | Type | Field | Description |
|---|---|---|---|
| 0-3 | char[4] | board_identifier | Board ID (e.g., "SPR3") |
| 4-5 | U16 | hardware_revision | Hardware revision |
| 6 | U8 | board_type | 0=FC, 1=VTX, etc. |
| 7 | U8 | target_capabilities | Capability flags |
| 8 | U8 | target_name_length | Length of target name |
| 9+ | char[] | target_name | Target name string |
Board Types
| Value | Type |
|---|---|
| 0 | Flight Controller |
| 1 | Video Transmitter |
| 2 | Radio 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
| Offset | Type | Field | Description |
|---|---|---|---|
| 0-10 | char[11] | build_date | "MMM DD YYYY" |
| 11-18 | char[8] | build_time | "HH:MM:SS" |
| 19-25 | char[7] | git_revision | Short 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
| Offset | Type | Field | Description |
|---|---|---|---|
| 0-N | char[] | name | Craft 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
| Offset | Type | Field | Description |
|---|---|---|---|
| 0-N | char[] | name | New 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(())
}