-
Notifications
You must be signed in to change notification settings - Fork 2
Firmware
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.
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:
$Mplus 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:
$Xplus a direction byte, a flag, a 16-bit little-endian command, a 16-bit little-endian size, the payload, and acrc8_dvb_s2checksum 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).
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/statusreports whether a link is configured. -
GET /api/msp/detectreturns the firmware (Betaflight, INAV, Cleanflight, or Unknown from theFC_VARIANTcode), the version, the MSP API version, and the board identity (identifier, target name, and board name fromMSP_BOARD_INFO). -
GET /api/msp/telemetryreturns 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-bootloadersendsMSP_REBOOTwith the bootloader selector so the board drops into DFU mode for flashing.
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.
| 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, 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.
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.
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.
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, sobetaflightBuildHexposts 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.gzfirmware index, reduced to the latest stable.apjper board for each flyable vehicle type. -
PX4: recent GitHub releases, each with its per-board
.px4images.
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.
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.