Skip to main content
This page is for developers who want to read, audit or extend the extension. Operators don’t need it. The agent half is a compiled Rust binary. The plugin host execs it directly under systemd, passes the per-plugin socket on the command line, and passes the capability token and agent id through the unit environment. The binary reads frames from the agent’s shared vision frame bus; it never opens a camera. The GCS half is an iframe bundle that renders the Vision Nav tab and the Engage Skill.

Module map

Start-up

  1. Merge the static config with the live per-drone keys and start the config watcher. The watcher polls the store once a second; on a change it exits with status 75 and the unit’s restart policy brings the plugin back on the new config.
  2. Register MAVLink component 198 as kind vio. The registration needs the mavlink.component.vio permission, which the manifest requests.
  3. Subscribe to the flight controller streams the modes need. A refused subscription is recorded in degradedReasons.
  4. Start the ticks: TIMESYNC (1 Hz), the companion HEARTBEAT (1 Hz), the health publish (1 Hz), and MAV_CMD_SET_MESSAGE_INTERVAL requests for RAW_IMU at 100 Hz and DISTANCE_SENSOR at 20 Hz, repeated every 30 s so a flight controller reboot does not drop them.
  5. Run the estimator on every frame pair from the vision frame bus. While the plugin reads frames, the vision tap holds a hero lease on the video encoder, so a drone on the thumbnail profile still encodes at the hero frame rate. The lease has no expiry and ends when the engine detaches.

The estimator contract

Every estimator answers step(...) with one EstimatorOutput:
off returns an empty None sample and emits nothing. The two optical-flow estimators fill the flow fields.

The scale ladder

scale::ScaleLadder serves optical_flow_degraded. Each pick() returns the first healthy rung:
Distances are clamped to 0.3 to 50 m. The degraded estimator multiplies the raw quality by the rung’s factor and reports degraded on the static rung whatever the raw quality.

The pre-arm gate

pre_arm::PreArmGate::evaluate() is a pure function: no MAVLink, no filesystem, no async. It returns a report of checks, each ok, pending or blocking with a detail string, and an armable flag that is true only when every check is ok.
  • off: no checks.
  • optical_flow: companion_active, flow_quality (at or above the gate), rangefinder (a configured topology and a fresh reading inside the sensor’s min-max range).
  • optical_flow_degraded: companion_active, flow_quality, scale_source (rangefinder, baro, gps or vision; static blocks).
The gate is advisory. The report rides the heartbeat; nothing in the agent or Mission Control stops the flight controller from arming.

Companion state

The companion state drives the component-198 HEARTBEAT system_status: A converged estimator makes the companion active. A 2 s degraded or failed streak, or 2 s with no frames, makes it critical.

The navigation heartbeat

Once a second the plugin publishes a navigation block through ctx.telemetry.extend. Losing frames or getting them back publishes the block at once instead of waiting for the next tick. Keys are camelCase end to end; a snake_case key is dropped at the cloud relay.
The Vision Nav tab reads this block: the companion pill, the mode card, the Sensors card, the Estimator card, the telemetry charts, Flow health, the Pre-arm card and the fallback banner.

Adding an estimator

  1. Implement the Estimator trait in estimators.rs (or a new module).
  2. Add its id to available_estimators() so the heartbeat advertises it.
  3. Add the mode variant to Mode in config.rs, and to the mode enum in config-schema.json.
  4. Add a branch to PreArmGate::evaluate() with the checks the new mode needs.
  5. Add the mode to the GCS mode card.
  6. Write tests for its state machine and output shape.

Next steps

Vision plugins

The vision frame bus and detection APIs for plugin authors.

Overview

Back to the start of the Vision Navigation guide.
Last modified on October 11, 2026