Skip to content

Firmware

Judah Paul edited this page Jul 16, 2026 · 7 revisions

The Firmware tab (/firmware) flashes flight controllers across all four stacks. It detects a connected Betaflight or INAV board over the MultiWii Serial Protocol (MSP), reads its firmware and telemetry, browses the Betaflight, INAV, ArduPilot, and PX4 catalogs, and flashes each: Betaflight and INAV over USB DFU, ArduPilot and PX4 over the autopilot's serial bootloader.

MSP protocol

src/lib/msp.ts frames and parses MSP, the request/response protocol Betaflight and INAV speak over serial. It supports both wire versions:

  • MSP v1: $M plus a direction byte, an 8-bit size, an 8-bit command, the payload, and an XOR checksum over the size, command, and payload.
  • MSP v2: $X plus a direction byte, a flag, a 16-bit little-endian command, a 16-bit little-endian size, the payload, and a crc8_dvb_s2 checksum over the flag through the last payload byte.

MspParser.feed() takes arbitrary byte chunks, reassembles frames that span chunk boundaries, resynchronizes past noise or a bad checksum, and surfaces both response (>) and error (!) frames. Decoders cover the identity and telemetry commands: FC_VARIANT, FC_VERSION, API_VERSION, ATTITUDE, RAW_GPS, ANALOG, ALTITUDE, and STATUS (whose flight-mode flags carry the arm bit).

Flight controller link

src/lib/server/msp.ts opens the board's serial link and runs MSP as a request/response client. MSP has no sequence numbers, so requests run one at a time through a queue, each resolving on the matching response or an error frame, and an idle timer closes the link so a plugged-and-forgotten board does not hold the port. The link is opt-in through MSP_SERIAL_PATH (with MSP_BAUD, default 115200), or over TCP through MSP_TCP_HOST and MSP_TCP_PORT for a SITL or a serial-over-TCP bridge, so it never collides with the MAVLink autopilot serial; with neither set, the FC panel shows as not configured. See Configuration for the variables.

  • GET /api/msp/status reports whether a link is configured.
  • GET /api/msp/detect returns the firmware (Betaflight, INAV, Cleanflight, or Unknown from the FC_VARIANT code), the version, the MSP API version, and the board identity (identifier, target name, and board name from MSP_BOARD_INFO).
  • GET /api/msp/telemetry returns attitude, GPS, analog battery, altitude, and arm status; a sensor the board lacks (GPS on a bench board) reads back null instead of failing the whole poll.
  • POST /api/msp/reboot-bootloader sends MSP_REBOOT with the bootloader selector so the board drops into DFU mode for flashing.

Dashboard on an MSP board

When the station connects to a Betaflight or INAV board with no MAVLink autopilot, the layout poll detects the board and drives the dashboard from MSP telemetry: autopilot model, board type, attitude, heading, GPS position and satellites, altitude, battery, and arm state feed the same stores the MAVLink path uses. A board with no battery sensor reports 0 V, which shows as no reading rather than an empty-battery 0%.

The parameters page notes that Betaflight and INAV expose their settings over MSP and the Configurator, not the MAVLink parameter protocol. The flight controls are firmware-agnostic: the same takeoff, mission-run, and stop buttons run a MAVLink autopilot's own mission, run INAV's own onboard mission over MSP, and fly the plan by companion guidance on Betaflight (see below). The takeoff control offers takeoff whenever the vehicle is disarmed or in standby and landing only once it is armed.

The gamepad and the sensor-calibration page are agnostic the same way. Gamepad sticks stream as MANUAL_CONTROL on MAVLink and as MSP_SET_RAW_RC on an MSP board, so a no-GPS Betaflight or INAV craft flies by stick with no position estimate needed; src/lib/manual-transport.ts picks the transport, and on MAVLink the stick mode engages automatically once the vehicle arms. A station-only MSP craft has no transmitter, so the station is its receiver: src/lib/server/msp-manual.ts sets the board's receiver_type to MSP so the sticks reach the flight controller, resolves the aux channel that arms from the mode ranges, and holds that channel low then high to arm once the throttle is down. The gamepad control confirms the props are clear before arming an MSP board. The calibration page runs MSP_ACC_CALIBRATION (accelerometer, held level) and MSP_MAG_CALIBRATION (compass, rotated through all axes) on an MSP board and the autopilot's own calibration commands on MAVLink. Email alerts (failsafe, emergency, a crash heuristic that fires on an in-air disarm, battery, GPS, and link events) carry the coordinates and a map link, and the send retries with backoff so a transient uplink drop does not lose the alert.

Autopilot capability by firmware

Capability ArduPilot / PX4 (MAVLink) INAV (MSP) Betaflight (MSP)
Telemetry, HUD, compass, live feed yes yes yes
Autonomous waypoint missions onboard onboard, uploaded over MSP none in firmware
Fly a plan from the station autopilot mission onboard mission over MSP companion guidance
Manual gamepad flight MANUAL_CONTROL MSP_SET_RAW_RC MSP_SET_RAW_RC
Position-hold without GPS with optical flow or VIO with optical flow with optical flow
Sensor calibration MAVLink MSP_ACC/MSP_MAG MSP_ACC/MSP_MAG
Vehicle parameters page MAVLink params Configurator/CLI Configurator/CLI
Email alerts with coordinates yes yes yes

Both an INAV mission and Betaflight companion guidance need a position fix: INAV navigates onboard and the station steers Betaflight, and neither can fly to coordinates without one, so each refuses up front when no fix is present and the gamepad is the manual path.

INAV missions

INAV, unlike Betaflight, flies autonomous waypoint missions, and the mission uploads over MSP rather than MAVLink. uploadMissionMsp in src/lib/server/msp.ts converts a plan into INAV waypoints and sends each as an MSP_SET_WP (209) frame the board acknowledges before the next: a packed 21-byte struct of waypoint number, action, latitude and longitude (degrees times 1e7), altitude (cm), three parameters, and a flag that is 0xA5 on the final waypoint. inavActionForType in src/lib/msp.ts maps each stored command to an INAV action (NAV_WAYPOINT/NAV_SPLINE_WAYPOINT to WAYPOINT, NAV_RETURN_TO_LAUNCH to RTH, NAV_LAND to LAND, loiters to POSHOLD); takeoff, servo, and condition commands have no INAV waypoint and are skipped. MSP_WP_GETINFO (20) reports the board's waypoint capacity so an oversized mission is refused, and a best-effort MSP_WP_MISSION_SAVE (19) persists the uploaded mission to onboard storage.

The mission planner's Save & Load Mission routes to POST /api/msp/load_mission when an INAV board is connected and to the MAVLink upload otherwise. INAV holds the uploaded mission in RAM and flies it once the vehicle is armed and in NAV WP mode.

Launching an INAV mission from the station

INAV has no MSP command to set a flight mode; a mode turns on only while its aux channel sits inside a configured range. So the Start and Takeoff controls engage a mission the way a transmitter switch would. src/lib/inav-mission.ts reads the board's mode ranges (MSP_MODE_RANGES with the box tables MSP_BOXNAMES and MSP_BOXIDS, requested over MSP v2 since the box-name table exceeds the MSP v1 size byte), finds the aux channels for ARM and NAV WP, and plans an RC frame that holds both inside their windows with the sticks centered and throttle low; when a board has no aux assignment for those modes the station assigns spare channels for the flight over MSP_SET_MODE_RANGE, which INAV applies to its running config and reverts on the next power cycle. src/lib/server/inav-mission.ts confirms a GPS fix, writes any assignments, and streams the frame with MSP_SET_RAW_RC (200) at 10 Hz. INAV arms, auto-takes-off, flies the waypoints, and runs the end action itself; a deadman heartbeat stops the stream if the station goes quiet, handing the craft to INAV's own return-to-home failsafe. Takeoff uploads a single waypoint overhead at the requested altitude and engages it, the MSP counterpart of a MAVLink NAV_TAKEOFF. The routes are POST /api/msp/inav_mission_start, inav_takeoff, inav_stop, inav_heartbeat, and GET /api/msp/inav_status.

The station drives the mode channels over MSP RC override, so the flight controller must accept it: set the receiver to MSP or enable the MSP RC OVERRIDE mode so the MSP_SET_RAW_RC channels reach the flight modes.

Companion guidance (MSP boards)

Betaflight has no onboard waypoint navigation; its own GPS autonomy is GPS Rescue (return to home). To fly a plan on a Betaflight board the station runs the navigation loop itself and steers the board over MSP RC override, using the flight controller as the inner-loop stabilizer. The dashboard and mission-planner Start control routes here for a connected Betaflight board, to INAV's own onboard mission over MSP, and to the autopilot's own mission on MAVLink, so the buttons are the same across firmware. src/lib/msp-guidance.ts holds the pure control law: computeGuidance drives the GPS velocity vector toward the next waypoint and expresses the error as body-frame lean, mapped to roll, pitch, and yaw stick microseconds, while throttle stays centered so a Betaflight altitude hold governs height; buildRcFrame assembles the AETR channel frame with the arm aux channel.

src/lib/server/msp-guidance.ts runs the loop at 10 Hz next to the MSP link: each cycle it reads position with readNavState, computes the sticks, and sends them with MSP_SET_RAW_RC (200). Two conditions release the override, which hands the craft to Betaflight's own RX failsafe rather than flying it blind: a deadman heartbeat the browser must refresh each second, and a GPS fix lost for more than three seconds. The routes are POST /api/msp/guidance_start (waypoints and optional gains), guidance_stop, guidance_heartbeat, and GET /api/msp/guidance_status.

src/lib/guidance-session.ts holds the client lifecycle: the Start control gates it behind a bench-test acknowledgment, streams the heartbeat while running, and shares a running store. GuidancePanel.svelte, mounted in the layout, shows the active waypoint and range and carries a prominent stop from any page. Guidance takes over an already-airborne craft that is armed and in Angle plus Altitude Hold; the transmitter stays the kill switch, and the channel directions in the config are defaulted to the common Betaflight AETR setup and must be bench-tested per airframe.

Firmware catalogs

A detected board fills its own target: the tab matches the board's target name (falling back to its board name or identifier) against the INAV targets or the Betaflight cloud targets and preselects the match, with a notice to pick by hand when nothing matches.

src/lib/server/firmware.ts proxies the upstream catalogs, cached for ten minutes so browsing the tab does not hammer them:

  • Betaflight: targets and releases from build.betaflight.com. Betaflight builds firmware in the cloud, so betaflightBuildHex posts a build request for the chosen release and target, polls the build, and downloads the hex when it is ready (an identical build is cached upstream and returns in seconds).
  • INAV: releases from the GitHub releases API, each carrying the list of per-target hex assets (inav_<version>_<target>.hex). Picking a release and target resolves the direct asset URL, downloads it, and flashes it in place.
  • ArduPilot: the manifest.json.gz firmware index, reduced to the latest stable .apj per board for each flyable vehicle type.
  • PX4: recent GitHub releases, each with its per-board .px4 images.

DFU flashing (Betaflight, INAV)

STM32-based Betaflight and INAV boards flash over USB DFU. flashHex writes the Intel HEX to a temp file, lifts it to a raw image with objcopy -I ihex -O binary, and writes it with dfu-util -a 0 -d 0483:df11 -s 0x08000000:leave, the STM32 ROM-bootloader USB id and the chip's flash base, leaving DFU so the board boots the new firmware. Both tools ship in the production image (Dockerfile.prod). The board must be in DFU mode first, by the tab's reboot-to-bootloader action or its boot pad, and connected to the host running the server.

Serial-bootloader flashing (ArduPilot, PX4)

ArduPilot .apj and PX4 .px4 files are one container: a JSON document whose image field is the zlib-compressed, base64-encoded firmware and whose board_id names the board it was built for (src/lib/server/apj.ts). src/lib/server/px-uploader.ts speaks the PX4/ArduPilot serial bootloader protocol, the same one Mission Planner and QGroundControl use: it syncs, reads the board id and flash size, refuses a mismatched image, erases, programs in 252-byte chunks, verifies the whole-flash CRC32, and reboots. flashAutopilot asks a live MAVLink autopilot to reboot into its bootloader (PREFLIGHT_REBOOT_SHUTDOWN with the bootloader parameter), releases the MAVLink link for the upload, and reconnects afterward, so the flash runs over the same serial device the station normally talks MAVLink on.

The tab flashes from five sources: an INAV target, a Betaflight cloud build, an ArduPilot board, a PX4 board, or any Intel HEX file. Each confirms before writing and streams the tool output to a flash log.

Clone this wiki locally