> ## Documentation Index
> Fetch the complete documentation index at: https://docs.altnautica.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MAVLink extensions

> Subscribe to MAVLink messages, send frames to the flight controller, and register as a MAVLink component.

The agent already speaks MAVLink to the flight controller and routes it
to the GCS. A plugin extends that in three ways through the `ctx.mavlink`
facade: subscribe to messages, send frames, and register itself as a
MAVLink component.

## Capabilities

| Capability                                                 | Lets the plugin...                                                                                       |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `mavlink.read`                                             | Subscribe to MAVLink messages from the link.                                                             |
| `mavlink.write`                                            | Send MAVLink frames to the flight controller.                                                            |
| `mavlink.component.{camera,gimbal,payload,peripheral,vio}` | Register as a MAVLink component of that kind.                                                            |
| `mavlink.tunnel`                                           | Exchange application payloads over MAVLink `TUNNEL` frames.                                              |
| `vehicle.command`                                          | Issue high-level vehicle commands (arm, takeoff, RTL, mode change) through the agent's command pipeline. |
| `estimator.pose.inject`                                    | Push pose samples directly into the autopilot state estimator.                                           |

`mavlink.tunnel`, `vehicle.command`, and `estimator.pose.inject` are all
high-risk permissions, alongside `mavlink.write`. The `vio` component kind
is high-risk too, since a visual-odometry source feeds the EKF.

`mavlink.write` is a high-risk permission. Operators see a warning badge
in the install dialog, because a plugin that can write raw MAVLink can
arm motors, change modes, and alter parameters in flight.

## Subscribing to messages

`ctx.mavlink.subscribe` registers a callback for a message name. The
callback fires for each matching frame and receives the message name, the
raw MAVLink frame bytes, and a timestamp. Decode the frame with your own
MAVLink library:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
async def on_start(self, ctx) -> None:
    async def on_battery(msg):
        # msg["msg_name"], msg["frame"] (raw bytes), msg["timestamp_ms"]
        self.decode_and_store(msg["frame"])

    await ctx.mavlink.subscribe("BATTERY_STATUS", on_battery)
```

Subscriptions are coalesced by the host: ten plugins subscribed to
`BATTERY_STATUS` cause one underlying stream from the flight controller.
Inbound frames are read once and fanned out to subscribers; there is no
per-plugin parser running on the link.

## Sending frames

`ctx.mavlink.send` writes a MAVLink frame to the flight controller. Build
the frame with a MAVLink encoder, then send the bytes. An optional
`component_id` tags the sender when the plugin owns a registered
component:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
frame = encode_command_long(
    target_system=1,
    target_component=1,
    command=2000,  # MAV_CMD_DO_DIGICAM_CONTROL
    params=[0, 0, 0, 0, 1, 0, 0],
)
await ctx.mavlink.send(frame)
```

The host serializes outbound writes per flight-controller link. Two
plugins sending at the same time queue on one single-producer queue;
neither blocks the other for more than a few milliseconds.

## Registering as a component

A plugin that should appear on the MAVLink bus as a camera, gimbal,
payload, peripheral, or visual-odometry source registers a component id.
The capability is `mavlink.component.<kind>`:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
async def on_start(self, ctx) -> None:
    # Claim component id 100 as a camera component.
    await ctx.mavlink.register_component(100, "camera")
```

Declare the component in the manifest so the host knows to claim the id:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
agent:
  permissions:
    - id: mavlink.component.camera
  mavlink_components:
    - component_id: 100
      component_kind: camera
      sub_id: 1
```

The kinds are `camera`, `gimbal`, `payload`, `peripheral`, and `vio`.
Each maps to the matching `mavlink.component.<kind>` capability. The
gimbal extension registers component id 154 as a Gimbal v2 manager; the
thermal-camera extension registers a camera component.

### Visual odometry

A visual-odometry plugin registers the `vio` component, then sends pose
estimates to the flight controller. The vision SDK wraps this: it
registers the visual-odometry component once, then builds a
`VISION_POSITION_ESTIMATE` from a pose (or an `ODOMETRY` message from a
pose plus body-frame twist) and sends it under the visual-odometry
component id.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
async def on_start(self, ctx) -> None:
    await self.vio.register_vio_component()  # claims the vio component

async def on_pose(self, pose) -> None:
    await self.vio.inject_pose(pose)  # sends VISION_POSITION_ESTIMATE
```

That path keeps the pose stream on the normal MAVLink link. The separate
`estimator.pose.inject` capability is for plugins that push pose samples
directly into the autopilot state estimator instead of going over
MAVLink. Both are high-risk: a bad pose stream feeds the EKF and can
produce unsafe position commands.

## Re-publishing decoded data

A common pattern is a plugin that decodes a message and re-publishes it
as a clean event other plugins consume, so the raw MAVLink stays in one
place:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
async def on_start(self, ctx) -> None:
    async def on_rangefinder(msg):
        distance_m = self.decode_distance(msg["frame"])
        await ctx.events.publish(
            "rangefinder.reading", {"distance_m": distance_m}
        )

    await ctx.mavlink.subscribe("DISTANCE_SENSOR", on_rangefinder)
```

Another plugin then subscribes to
`plugin.com.example.rangefinder.rangefinder.reading` without touching
MAVLink. Publishing and subscribing on the plugin's own topics use the
`event.publish` and `event.subscribe` capabilities.

## High-level vehicle commands

For the canonical command set (arm, takeoff, RTL, land, mode change),
`vehicle.command` routes through the agent's command pipeline rather than
raw MAVLink. It is a high-risk capability because it can arm or fly the
aircraft. Use it instead of `mavlink.write` when the action is one of the
canonical commands; reserve raw `mavlink.write` for messages the command
pipeline does not cover.

## See also

* [Permissions](/developers/permissions)
* [Event hooks and bus](/developers/event-hooks-and-bus)
* [Agent plugin deep dive](/developers/agent-plugin-deep-dive)
