Skip to content

Autopilot Support

Judah Paul edited this page Jul 15, 2026 · 5 revisions

CanaryGC targets both ArduPilot and PX4. The two stacks encode flight modes differently on the wire, so src/lib/flight-modes.ts isolates the difference behind a strategy interface and the rest of the app works in terms of a small set of intent-level modes. Betaflight and INAV boards connect over MSP rather than MAVLink; their telemetry, control, and mission handling live in Firmware.

Intent modes

The UI and mission logic use six modes:

type FlightMode = 'GUIDED' | 'AUTO' | 'RTL' | 'LOITER' | 'LAND' | 'POSCTL';

POSCTL is the stick-flying position hold that gamepad control switches into (Position mode on PX4, Loiter on ArduPilot).

Strategy interface

interface AutopilotStrategy {
  setModeParams(mode: FlightMode): [number, number, number]; // DO_SET_MODE params
  decodeCustomMode(customMode: number): string;
  supportsLocalSetpoint: boolean;
}

setModeParams returns the MAV_CMD_DO_SET_MODE parameters [baseMode, customMode, customSubMode]. decodeCustomMode turns a telemetry custom_mode back into a readable label. supportsLocalSetpoint reports whether the stack accepts a local-frame position setpoint.

ArduPilot

ArduPilot Copter puts the mode number directly in customMode with customSubMode unused. decodeCustomMode maps the number to a Copter mode name (STABILIZE, LOITER, AUTO, RTL, LAND, and the rest). Local-frame setpoints are supported, so set_position_local is available.

PX4

PX4 packs the main mode into bits 16 to 23 and the auto sub-mode into bits 24 to 31 of customMode. The strategy maps the intent modes to AUTO main mode with the matching sub-mode (LOITER, MISSION, RTL, LAND) and decodes telemetry by shifting those fields back out. PX4 does not take the local-frame setpoint used by ArduPilot, so set_position_local warns and no-ops on PX4.

Selection and helpers

The autopilot is chosen from the vehicle model string in telemetry:

  • isPX4(model) is true when the model contains PX4.
  • strategyFor(model) returns the matching strategy.
  • decodeMode(customMode, model) decodes a telemetry mode for the current stack.
  • isArmed(baseMode) tests the MAV_MODE_FLAG_SAFETY_ARMED bit (128).
  • isAutoLabel(label) and isGuidedLabel(label) classify a decoded label as "mission running" or "hold" across both families, which the mission-progress and control gates rely on.
  • isAirVehicle(type), isSubmarine(type), and isGroundOrSurface(type) classify the vehicle from its MAV_TYPE name so the dashboard shows the right vertical control.

Vehicle-class controls

The dashboard go-to control adapts to the vehicle class:

  • Air vehicles get Go to Altitude with Altitude Up and Down, targeting the altitude the stack accepts: a local-NED height in GUIDED (set_position_local) on an ArduPilot copter, a global target altitude (set_altitude) on an ArduPlane, and a relative reposition (DO_REPOSITION) on PX4.
  • Submarines get Go to Depth with Ascend and Descend. Depth is an altitude below the surface, commanded in ArduSub's ALT_HOLD depth-hold mode as a SET_POSITION_TARGET_GLOBAL_INT with lat/lon masked and a negative altitude (set_depth), so the sub holds depth on its barometer without a horizontal position source.
  • Rovers and boats move only in the surface plane, so they show no vertical control.

Max Speed sends DO_CHANGE_SPEED: an airspeed setpoint for a fixed wing and a groundspeed setpoint for every other class.

Adding an autopilot

Implement AutopilotStrategy for the new stack, add a detection branch to isPX4 / strategyFor keyed on the model string, and the UI, mode buttons, and mission gates work unchanged.

Clone this wiki locally