BulletGCSS Communication Protocol

April 13, 2026 · View on GitHub

This document describes the full communication protocol between the ESP32 modem (aircraft side) and the Web UI (ground side).


Overview

Communication is bidirectional:

  • Uplink (telemetry): ESP32 publishes telemetry on bulletgcss/telem/<callsign>; the UI subscribes.
  • Downlink (commands): UI publishes commands on bulletgcss/cmd/<callsign>; the ESP32 subscribes.
Flight Controller  ──MSPv2 (UART)──►  ESP32 Modem  ──MQTT (uplink)──►  MQTT Broker  ──MQTT──►  Web UI
                                       ESP32 Modem  ◄─MQTT (downlink)─  MQTT Broker  ◄─MQTT──  Web UI

Transport Layer

ParameterESP32 → BrokerBrowser → Broker
ProtocolMQTT 3.1.1MQTT 3.1.1 over WebSocket
Default port1883 (plaintext)8084 (WSS/TLS)
Default brokerbroker.emqx.iobroker.emqx.io
QoS0 (fire and forget)0 (subscribe)
Client IDESP32_<MAC> (chip MAC address)web_<random>

QoS note: All messages use QoS 0 (fire-and-forget) by design. Cellular coverage is inherently intermittent — aircraft routinely fly beyond antenna range — and operators already expect gaps in telemetry. A dropped packet means a one-second stale display at most; the 10-second force-refresh cycle (see Standard Telemetry Message below) re-syncs all fields automatically when connectivity returns. The UI's stale-data indicator signals loss of connection; this is normal operating behaviour, not an error condition.

Topic format:

  • Uplink (telemetry): bulletgcss/telem/<callsign> — firmware publishes, UI subscribes.
  • Downlink (commands): bulletgcss/cmd/<callsign> — UI publishes, firmware subscribes.

The callsign is read from the flight controller at startup via MSP and used as the topic suffix. Both topics are configured independently in Config.h (firmware) and in the browser settings (UI).


Message Format

All messages are plain ASCII text. Each message is a comma-separated list of key:value pairs, always ending with a trailing comma:

key1:value1,key2:value2,key3:value3,

Values are always integers. Floating-point quantities are transmitted as scaled integers (see the field reference below for each field's scale factor).

There is no message envelope, no length prefix, and no message-level checksum. Each MQTT publish is one complete message.


Message Types

There are six distinct message types, distinguished by their content.

1. Session Start Message

Sent once, as the very first message after the ESP32 connects to the broker.

id:0,

This signals to any subscriber that a new session has begun.


2. Standard Telemetry Message

Sent every 1000 ms (configurable via MESSAGE_SEND_INTERVAL in Config.h).

To reduce bandwidth, a field is only included in a message if its value has changed since the last message. However, to prevent stale data in the UI (e.g. after the UI reconnects mid-flight), all fields are forced into the message periodically, regardless of whether they changed.

Fields are divided into 10 groups (0–9). On each cycle, msgCounter % 10 determines which group is force-refreshed. This means every field is guaranteed to be re-sent at least once every 10 seconds.

GroupFields force-refreshed
0ran, pan, hea, ggc, nvs, whd
1asl, alt, gsp
2vsp, hdr, hds
3acv, bpv, bfp
4cud, cad, rsi
5gla, glo, gsc
6ghp, css, 3df
7hwh, arm, dls, mro, cmdrth, cmdalt, cmdcrs, cmdbep, cmdwp, cmdph, fmcrs, fmalt, fmwp, fmph
8wpc, cwn, wpv
9fs, trp, att

The following fields are only sent when changed and are never force-refreshed here (they appear in the Low Priority Message instead):

hla, hlo, hal, ftm

The lseq field (last accepted command sequence number) is also included in the standard telemetry message only when its value changes — i.e. immediately after a command is accepted by the firmware. This allows connected UIs to update their sequence counters in near-real-time without waiting for the next low priority message.


3. Low Priority Message

Sent every 60 seconds (LOW_PRIORITY_MESSAGE_INTERVAL in Config.h), always including all fields regardless of change.

Contains slow-changing or static data: protocol version, home coordinates, cell count, callsign, flight times, and message frequency.

pv:1,bcc:4,cs:MyCallsign,hla:123456789,hlo:-456789012,hal:80000,ont:3600,flt:1200,ftm:9,mfr:1000,

Sent by the UI on the downlink topic (bulletgcss/cmd/<callsign>).

Ping:

cmd:ping,cid:ABC123,seq:42,sig:base64base64...==,

Flight controller command (RC mode toggle):

cmd:rth,cid:ABC123,seq:43,state:1,sig:base64base64...==,
FieldDescription
cmdCommand type — see table below
cidCommand ID — 6-character random alphanumeric string, unique per command
seqMonotonically increasing sequence number (uint32, stored in localStorage). Used by the firmware to reject replayed commands.
state(RC mode commands only) 1 = activate the mode, 0 = deactivate. Not included in the signed payload.
heading(setheading only) Target heading in degrees (0–359). Not included in the signed payload.
wp(jumpwp only) 0-based waypoint index to jump to. The UI sends displayed_wp_number - 1. Not included in the signed payload.
alt(setalt only) Target altitude in centimetres relative to home. Not included in the signed payload.
sigEd25519 signature of the canonical payload string cmd:<cmd>,cid:<cid>,seq:<seq> — base64-encoded, 88 characters. Extra fields (state, heading, wp, alt) are never part of the signed string.

The firmware verifies the signature against the stored commandPublicKey (32 bytes, configured in Config.h). Commands with an invalid signature, a missing sig field, a sequence number ≤ the last accepted sequence number, or sent while no public key is configured are silently dropped — no ack is sent. The last accepted sequence number is persisted to NVS so replay protection survives a firmware reboot.

The UI only sends commands if a private key is present in localStorage. See the Security panel in the UI sidebar.

Supported cmd values:

cmdExtra fieldsAction
pingNo-op; used to verify the downlink channel is working
rthstateReturn to Home — activates/deactivates BOXNAVRTH via RC channel override
altholdstateAltitude Hold — activates/deactivates BOXNAVALTHOLD via RC channel override
cruisestateCruise Mode — activates/deactivates BOXNAVCRUISE via RC channel override
wpstateWP Mission Mode — activates/deactivates BOXNAVWP via RC channel override
beeperstateBeeper — activates/deactivates BOXBEEPERON via RC channel override
setheadingheadingSets the Cruise/Course Hold heading target (degrees 0–359). Only effective when Cruise mode is active. Firmware converts to centidegrees and sends MSP2_INAV_SET_CRUISE_HEADING (0x2223).
setaltaltSets the altitude hold target (centimetres relative to takeoff point). Only effective when Altitude Hold is active. Requires INAV 10.0.0 or newer. Firmware sends MSP2_INAV_SET_ALT_TARGET (0x2215) with a 5-byte payload: uint8_t datum=0 (takeoff-relative) + int32_t altCm.
jumpwpwpJumps to a waypoint during an active WP mission (0-based index). Only effective when WP Mission mode is active. Firmware sends MSP2_INAV_SET_WP_INDEX (0x2221).
setwpwpno, la, lo, al, ac, p1, p2, p3, fUploads one waypoint to the firmware staging buffer. The firmware accumulates all waypoints and only forwards them to the FC once the last waypoint (f:165) is received and the full mission passes validation. See Mission Upload below.
getmissionRequests the firmware to publish the full mission currently stored on the aircraft. The firmware responds with one dlwp: message per waypoint followed by an ACK. See Mission Download below.

Mission Upload (setwp):

Each waypoint is sent as a separate signed command. Extra fields (not included in the signed payload):

FieldDescriptionNotes
wpnoWaypoint number (1-based)1..maxWaypoints
`la$\text{Latitude}\text{Degrees} \times 10{,}000{,}000
lolo\text{Longitude}\text{Degrees} \times 10{,}000{,}000
$al`AltitudeCentimetres relative to home
acAction code1=Waypoint, 3=Loiter, 4=RTH, 8=Land
p1Parameter 1Speed cm/s (Waypoint); loiter seconds (Loiter)
p2Parameter 2Speed cm/s (Loiter); unused otherwise
p3Parameter 3Bitfield (bit 0 = AMSL alt; 0 = relative to home)
fFlag0 = normal; 165 (0xA5) = last waypoint

The firmware buffers all incoming waypoints in a heap-allocated staging array. When the last waypoint (f:165) is received, it validates the complete mission (contiguous WP numbers, no gaps, WP Mission mode not active) before forwarding to the FC via sequential MSP_SET_WP calls. ACK is deferred until after validation. NACK reason codes: badfields, overflow, gap, busy, oom.

Mission Download (getmission):

The ACK for this command is deferred. Before sending the ACK, the firmware publishes one dlwp: message per waypoint (see below), then sends the ACK. If no mission is loaded, a NACK with reason:nomission is sent instead.


5. Acknowledge Message (Firmware → UI)

Sent by the firmware on the uplink topic in response to a verified, accepted command. Identified by the cmd: prefix, same as command messages.

cmd:ack,cid:ABC123,lseq:42,
FieldDescription
cmdValue ack identifies this as an acknowledge
cidEchoes back the cid from the received command
lseqLast accepted sequence number (uint32). Allows all connected UIs to sync their sequence counters after a command is accepted.

The UI detects the cmd: prefix, routes the message to parseCommandMessage() (not the standard telemetry parser), matches the cid against its pending command list, and marks the command as received. If no ack arrives within 10 subsequent telemetry messages, the command is marked lost.

The lseq value in the ACK is also used by other connected UI instances to synchronise their sequence counters — see the lseq field in the Low Priority Message reference.


6. Waypoint Message

Sent every 30 cycles (every ~30 seconds) when the aircraft has a waypoint mission loaded (waypointCount > 0).

One MQTT message is published per waypoint, including waypoint 0 (the home point). Waypoint messages are identified by starting with wpno:.

wpno:1,la:123456789,lo:-456789012,al:5000,ac:1,p1:100,

Optional fields (p1, p2, p3, f) are omitted when their value is 0.


7. Mission Download Message

Published by the firmware on the uplink topic in response to a getmission command. One message is sent per waypoint (excluding waypoint 0, the home/RTH position). Identified by the dlwp: prefix.

dlwp:1,la:123456789,lo:-456789012,al:5000,ac:1,p1:100,p2:0,p3:0,f:0,
dlwp:2,la:123456800,lo:-456789100,al:6000,ac:1,p1:0,p2:0,p3:0,f:165,

All fields are always present (unlike the periodic waypoint message where zero-valued optional fields are omitted). After all waypoints are published, the firmware sends a standard ACK for the getmission command.

The UI accumulates dlwp: messages in a buffer (missionDownloadBuffer) cleared before sending the getmission command. On ACK, the buffer is read, converted to the planner's plannedMission format, and loaded into the Mission Planner.


Standard Telemetry Field Reference

Attitude

KeyDescriptionUnit / ScaleJS fieldValid Range
ranRoll angleDecidegrees (÷10 → degrees)data.rollAngle-1800 to 1800
panPitch angleDecidegrees (÷10 → degrees)data.pitchAngle-900 to 900
heaHeading (yaw)Degreesdata.heading0 to 359
ggcGPS ground courseDegreesdata.gpsGroundCourse0 to 359

Altitude & Speed

KeyDescriptionUnit / ScaleJS fieldValid Range
altRelative altitude (barometric/estimated)Centimetersdata.altitude-1000000 to 10000000
aslAltitude above sea level (GPS)Metersdata.altitudeSeaLevel-500 to 9000
gspGround speedcm/sdata.groundSpeed0 to 15000
vspVertical speedcm/sdata.verticalSpeed-60000 to 60000

alt range covers 10 km below home to 100 km relative altitude. asl range covers below-sea-level terrain to above the highest mountain. gsp ceiling of 15000 cm/s = 540 km/h, well above any fixed-wing UAV speed. vsp allows ±2160 km/h vertical.

GPS

KeyDescriptionUnit / ScaleJS fieldValid Range
`gla$\text{GPS} \text{latitude}\text{Degrees} \times 10{,}000{,}000$data.gpsLatitude`-900000000 to 900000000
`glo$\text{GPS} \text{longitude}\text{Degrees} \times 10{,}000{,}000$data.gpsLongitude`-1800000000 to 1800000000
gscGPS satellite countCountdata.gpsSatCount0 to 50
`ghp$\text{GPS} \text{HDOP}\text{HDOP} \times 100 (÷100 → \text{HDOP})$data.gpsHDOP`0 to 9999
3dfGPS 3D fix0 = no fix, 1 = 3D fixdata.gps3DFix0 or 1
KeyDescriptionUnit / ScaleJS fieldValid Range
hdrDirection to homeDegreesdata.homeDirection0 to 359
hdsDistance to homeMetersdata.homeDistance0 to 20000000
nvsNavigation state (INAV nav_state enum)Integerdata.navState0 to 30
cwnCurrent active waypoint numberCountdata.currentWaypointNumber0 to 255
wpcTotal waypoint count in missionCountdata.waypointCount0 to 256
wpvWaypoint mission valid flag0 / 1data.isWaypointMissionValid0 or 1

hds ceiling of 20000 km ≈ half the Earth's circumference. nvs ceiling of 30 is intentionally generous — INAV's nav_state enum currently tops out around 20.

Battery & Power

KeyDescriptionUnit / ScaleJS fieldValid Range
bpvTotal battery voltageCentivolts (÷100 → V)data.batteryVoltage0 to 6000
acvAverage cell voltageCentivolts (÷100 → V)data.battCellVoltage0 to 500
bfpBattery fuel (charge) percentPercentdata.fuelPercent0 to 100
cudCurrent drawCentiamps (÷100 → A)data.currentDraw0 to 50000
cadCapacity drawnmAhdata.capacityDraw0 to 100000
whdEnergy drawnmWhdata.mWhDraw0 to 1000000
trpThrottle percentPercentdata.throttlePercent0 to 100
attAuto-throttle active0 / 1data.isAutoThrottleActive0 or 1

bpv ceiling of 6000 cV = 60 V covers up to 14S LiPo configurations. acv ceiling of 500 cV = 5.0 V per cell covers HV LiPo (4.35 V) with margin. cud ceiling of 50000 cA = 500 A accommodates very high-current setups.

Status Flags

KeyDescriptionValuesJS fieldValid Range
armAircraft armed0 / 1data.uavIsArmed0 or 1
fsFailsafe active0 / 1data.isFailsafeActive0 or 1
hwhHardware healthy0 / 1data.isHardwareHealthy0 or 1
dlsDownlink status0 = not subscribed, 1 = subscribed okdata.downlinkStatus0 or 1
mroMSP RC Override mode active0 = not active, 1 = activedata.mspRcOverride0 or 1
cssCellular/WiFi signal strength03data.cellSignalStrength0 to 3
rsiRC link RSSIPercentdata.rssiPercent0 to 100

mro indicates whether the MSP RC OVERRIDE flight mode is active on the flight controller. This mode must be active for the firmware's channel override commands to take effect. Without it, commands sent via MSP_SET_RAW_RC will be ignored by INAV. The UI shows a dedicated status icon for this field.

Command Channel State

These fields report which RC channel overrides the firmware is currently holding active. A value of 1 means the firmware is actively commanding that mode via MSP_SET_RAW_RC; 0 means the channel is set to its safe-off value.

KeyDescriptionJS fieldValid Range
cmdrthRTH override activedata.cmdRth0 or 1
cmdaltAltitude Hold override activedata.cmdAltHold0 or 1
cmdcrsCruise override activedata.cmdCruise0 or 1
cmdbepBeeper override activedata.cmdBeeper0 or 1
cmdwpWP Mission override activedata.cmdWp0 or 1
cmdphPosition Hold override activedata.cmdPosHold0 or 1

These reflect what the firmware is doing, not whether INAV has activated the mode. A 1 here with mro:0 means the override is being sent but INAV is ignoring it (MSP RC Override mode not active on FC).

Flight Mode Active

These fields report whether each flight mode is actually active on the flight controller, regardless of source (radio switch, Bullet GCSS command, or any other means). Derived from MSP_ACTIVEBOXES.

KeyDescriptionJS fieldValid Range
fmcrsCruise / Course Hold mode activedata.fmCruise0 or 1
fmaltAltitude Hold mode activedata.fmAltHold0 or 1
fmwpWP Mission mode activedata.fmWp0 or 1
fmphPosition Hold mode activedata.fmPosHold0 or 1

The UI uses these fields to gate the setheading, setalt, and jumpwp commands respectively — a command that has no effect when the relevant mode is inactive is not shown as available. fmwp additionally gates the map waypoint-click jump feature: clicking a waypoint marker while WP Mission is active prompts the user to jump to that waypoint.

Flight Mode

KeyDescriptionJS fieldValid Range
ftmFlight mode ID (see table below)data.flightMode (resolved to name)1 to 11

Flight mode ID values:

IDNameDescription
1MANUALManual / passthrough
2RTHReturn to home
3A+PHAltitude hold + position hold
4POS HPosition hold only
53CRS3D cruise with altitude hold
6CRSCruise
7WPWaypoint mission active
8ALT HAltitude hold + angle
9ANGLEAngle (self-leveling)
10HORIZONHorizon mode
11ACROAcro / rate mode

Low Priority Message Field Reference

KeyDescriptionUnit / ScaleJS fieldValid Range
pvProtocol versionIntegerdata.protocolVersion1 to 999
bccBattery cell countCountdata.batteryCellCount1 to 12
csAircraft callsignString (alphanumeric, _, -)data.callsign1–16 chars; pattern ^[A-Za-z0-9_-]+$
`hla$\text{Home} \text{latitude}\text{Degrees} \times 10{,}000{,}000$data.homeLatitude`-900000000 to 900000000
`hlo$\text{Home} \text{longitude}\text{Degrees} \times 10{,}000{,}000$data.homeLongitude`-1800000000 to 1800000000
halHome altitude above sea levelCentimeters (÷100 → m)data.homeAltitudeSL-50000 to 900000
ontOn-time (time since power on)Secondsdata.powerTime0 to 172800
fltFlight time (time since arm)Secondsdata.flightTime0 to 86400
ftmFlight mode IDSee flight mode tabledata.flightMode1 to 11
mfrMessage frequency (send interval)MillisecondspageSettings.messageInterval100 to 10000
fcverFlight controller firmware versionString "M.m.p"data.fcVersion; also sets data.extCmdsSupportedPattern ^\d+\.\d+\.\d+$
pkCommand signing public key (Ed25519)Base64 (44 chars)data.firmwarePublicKey44-char base64 string
lseqLast accepted command sequence numberuint32— (used to sync localStorage commandSeq)0 to 4294967295

pv was introduced in protocol version 1. Firmware that predates this field sends no pv key; the UI treats a missing pv as version 1 (same as the current protocol). The version is an integer incremented only on breaking changes (removed or reinterpreted fields). Adding new optional fields is not a breaking change and does not require a version bump. hal range covers Dead Sea (-430 m = -43000 cm) to above Everest (8849 m = 884900 cm), rounded to safe integers. ont ceiling of 172800 s = 48 h. flt ceiling of 86400 s = 24 h. mfr clamped to 100–10000 ms to prevent the UI from interpreting implausibly fast or slow rates. fcver is read from the FC once at startup via MSP_FC_VERSION and re-read on reconnect. The UI derives two capability flags from this value:

  • data.extCmdsSupported = 1 when FC version ≥ 9.0.2 — enables setheading (MSP2_INAV_SET_CRUISE_HEADING) and jumpwp (MSP2_INAV_SET_WP_INDEX). The Set Course, Jump to WP rows and the map waypoint-click jump feature are hidden when this is 0.
  • data.altCmdSupported = 1 when FC version ≥ 10.0.0 — enables setalt (MSP2_INAV_SET_ALT_TARGET). The Set Altitude row is hidden when this is 0. pk is always sent, including when the key is all zeros (base64 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=), which means signing is not yet configured. The UI uses this field to verify that its stored public key matches the one flashed to the firmware. Public keys are safe to broadcast — they can only be used to verify signatures, not forge them. lseq is the last command sequence number accepted by the firmware (persisted to NVS). The UI only updates its local commandSeq from this value if the firmware's public key (pk) matches the locally stored key — preventing a UI monitoring a different aircraft from accidentally adopting an unrelated sequence number.

Waypoint Message Field Reference

Waypoint messages start with wpno: and are parsed separately from telemetry messages.

KeyDescriptionUnit / ScaleValid Range
wpnoWaypoint number (0 = home point)Count0 to 255
`la$\text{Waypoint} \text{latitude}\text{Degrees} \times 10{,}000{,}000-900000000 \text{to} 900000000
lolo\text{Waypoint} \text{longitude}\text{Degrees} \times 10{,}000{,}000-1800000000 \text{to} 1800000000
$al`Waypoint altitudeCentimeters0 to 60000
acWaypoint action (INAV action enum)Integer1 to 8
p1Parameter 1 (omitted if 0)Integer-32768 to 32767
p2Parameter 2 (omitted if 0)Integer-32768 to 32767
p3Parameter 3 (omitted if 0)Integer-32768 to 32767
fFlag (omitted if 0)Integer0 to 255

al ceiling of 60000 cm = 600 m is the typical INAV waypoint altitude limit. ac values: 1=WAYPOINT, 2=POSHOLD_UNLIM, 3=POSHOLD_TIME, 4=RTH, 5=SET_POI, 6=JUMP, 7=SET_HEAD, 8=LAND. p1/p2/p3 are signed 16-bit integers as defined by the MSPv2 waypoint payload.


Mission Download Message Field Reference

Mission download messages start with dlwp: and are parsed separately from telemetry messages.

KeyDescriptionUnit / ScaleNotes
dlwpWaypoint number (1-based)CountWP 0 (home) is never included
`la$\text{Latitude}\text{Degrees} \times 10{,}000{,}000\text{Always} \text{present}
lolo\text{Longitude}\text{Degrees} \times 10{,}000{,}000\text{Always} \text{present}
$al`AltitudeCentimetres relative to homeAlways present
acAction codeInteger1=Waypoint, 3=Loiter, 4=RTH, 8=Land
p1Parameter 1IntegerSpeed cm/s (Waypoint); loiter seconds (Loiter)
p2Parameter 2IntegerSpeed cm/s (Loiter); unused otherwise
p3Parameter 3BitfieldBit 0 = AMSL altitude flag
fFlagInteger0 = normal; 165 (0xA5) = last waypoint

Unlike wpno: messages, all fields are always present in dlwp: messages regardless of their value.


Validation Strategy

When implementing input validation in the UI parser (CommScripts.js), apply the following rules consistently:

  • Out-of-range numeric fields: silently discard the field (leave the previous value unchanged). Do not clamp — a clamped value could be misleading (e.g. showing 100 m altitude when the message contained 999999 m).
  • Boolean / flag fields (0 or 1): discard the message field if the parsed value is anything other than 0 or 1.
  • Enum fields (nvs, ac, ftm): discard values outside the documented range.
  • String fields (cs): reject the callsign if it does not match ^[A-Za-z0-9_-]+$ or exceeds 16 characters. Fall back to the previously displayed callsign.
  • GPS coordinates: both latitude and longitude must be within range simultaneously. If either is invalid, discard both (a position with only one valid coordinate is unusable and could plot to the wrong location).
  • parseInt / parseFloat failures: NaN results must be treated as invalid and discarded — never passed to the display logic.

Timing Summary

Message typeInterval
Standard telemetry1000 ms (default)
Low priority60 s
Waypoint missionEvery 30 telemetry cycles (~30 s)
MSP fetch cycle (FC task)160 ms per group; 6 groups → full refresh every ~960 ms
MSP_GET_RC + MSP_SET_RAW_RCEvery cycle (160 ms) — keeps RC override freshness within INAV's 200 ms window
Callsign + waypointsEvery 10 seconds (slow-poll)

MSP Layer (ESP32 ↔ Flight Controller)

The ESP32 communicates with the flight controller using MSPv2 (MultiWii Serial Protocol v2) over UART at 115200 baud (Serial2: RX=GPIO19, TX=GPIO18).

The FC task runs every 160 ms (TASK_MSP_READ_MS). Telemetry messages are divided into 6 round-robin groups; one group is fetched per cycle, giving a full refresh every ~960 ms. MSP_RC and MSP_SET_RAW_RC run every cycle to keep INAV's RC override freshness timer alive (INAV drops overridden channels if no MSP_SET_RAW_RC arrives within 200 ms).

MSP MessageContentWhen
MSP_RC (105)Current RC channel values (all channels)Every cycle (160 ms)
MSP_SET_RAW_RC (200)Override RC channels for active mode commandsEvery cycle (160 ms)
MSP_RAW_GPSGPS coordinates, speed, fix type, satellite count, HDOPGroup 0
MSP_COMP_GPSDistance and direction to homeGroup 0
MSP_ATTITUDERoll, pitch, yaw (heading)Group 1
MSP_ALTITUDEEstimated altitude, vertical speedGroup 1
MSP_SENSOR_STATUSHardware health flagGroup 2
MSP_ACTIVEBOXESActive flight mode bitmaskGroup 2
MSP_WP_GETINFOWaypoint count and mission validityGroup 3
MSP_NAV_STATUSNav state, active waypoint numberGroup 3
MSP2_INAV_MISC2On-time, flight time, throttle, auto-throttleGroup 4
MSP2_INAV_ANALOGBattery voltage, current, RSSI, fuel percentGroup 5
MSP_FC_VERSION (3)FC firmware version (major, minor, patch)Once at startup
MSP_BOXIDS (119)Flight mode permanent IDs → discovers MSP RC OVERRIDE box IDOnce at startup
MSP_MODE_RANGES (34)RC channel-to-mode mapping → discovers channel/PWM for each modeOnce at startup
MSP2_COMMON_SETTING (0x1003)Reads msp_override_channels bitmaskOnce at startup (also to confirm write)
MSP2_COMMON_SET_SETTING (0x1004)Writes msp_override_channels to enable needed channelsOnce at startup
MSP_NAMEAircraft callsignOnce at startup; re-polled every 10 s
MSP_WPIndividual waypoint dataEvery 10 s (slow-poll)
MSP2_INAV_SET_WP_INDEX (0x2221)Jump to waypoint N (U8, 0-based) during an active WP missionOn jumpwp command
MSP2_INAV_SET_ALT_TARGET (0x2215)Set altitude hold target (U8 datum=0 + I32 centimetres, takeoff-relative); INAV 10+On setalt command
MSP2_INAV_SET_CRUISE_HEADING (0x2223)Set Cruise/Course Hold heading target (I32, centidegrees)On setheading command

Startup sequence: On each boot (or FC reconnect), the firmware probes for the FC every 2 seconds using MSP_NAME. Once the FC responds, the startup sequence runs: MSP_FC_VERSIONMSP_BOXIDSMSP_MODE_RANGESMSP2_COMMON_SETTING (read) / MSP2_COMMON_SET_SETTING (write) / MSP2_COMMON_SETTING (confirm). After this, per-cycle polling begins. If MSP communication is lost for more than 1 second, all startup flags reset and the probe sequence restarts.

MSPv2 frame format: $X< header, 1-byte flags, 2-byte message ID, 2-byte payload length, payload, 1-byte CRC8-DVB-S2 checksum.