> ## 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.

# Payload actuator driver

> Implement a PayloadActuatorDriver to control a sprayer, dropper, claw, or sampler.

A `PayloadActuatorDriver` controls a non-imaging mission payload:
sprayer, dropper, claw, sampler, winch, or any other actuator the
operator triggers from a panel or a mission action.

## The interface

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from ados.sdk.drivers.payload_actuator import (
    PayloadActuatorDriver,
    PayloadCandidate,
    PayloadCapabilities,
    PayloadSession,
    PayloadCommand,
    PayloadState,
)

class MyPayloadDriver(PayloadActuatorDriver):
    async def discover(self) -> list[PayloadCandidate]:
        ...

    async def open(self, candidate, config) -> PayloadSession:
        ...

    async def close(self, session) -> None:
        ...

    def capabilities(self, session) -> PayloadCapabilities:
        ...

    async def actuate(self, session, command: PayloadCommand) -> None:
        ...

    def get_state(self, session) -> PayloadState:
        ...
```

<Note>
  A Rust trait of the same shape ships in the `ados-sdk` crate at
  `ados_sdk::drivers::PayloadActuatorDriver`. It mirrors the Python
  contract (`discover`, `open`, `close`, `capabilities`, `actuate`,
  `get_state`) with an associated `Session` type and returns
  `DriverResult`. `actuate` returns `DriverError::InvalidParam` when
  `command.action_id` is not in `PayloadCapabilities::actions`, so the
  host can reject the request before it reaches hardware.
  `PayloadCandidate`, `PayloadCapabilities`, `PayloadCommand`, and
  `PayloadState` carry the same fields (`args` and `metadata` are
  msgpack value maps on the Rust side). Pick the language that matches
  your plugin's `agent.runtime` (`python` or `rust`).
</Note>

## Capabilities

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
PayloadCapabilities(
    actions=("arm", "trigger", "stop", "set_flow_rate"),
    has_position_feedback=False,
    has_flow_feedback=True,
    metadata={"max_payload_kg": 2.5, "update_hz": 10.0},
)
```

`actions` is the tuple of `action_id` strings the driver accepts in
`PayloadCommand`. The GCS panel discovers these and renders one
button per action. `metadata` is the open-ended bag for
driver-specific knobs (max payload mass, refill volume, vendor
firmware version).

## Commands

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
PayloadCommand(
    action_id="trigger",
    args={"duration_s": 1.5, "intensity": 0.8},
)
```

`action_id` is mandatory. `args` is a free-form dict that the
driver validates. Unknown keys should raise `ValueError`.

## State

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
PayloadState(
    timestamp_ns=ns,
    last_action_id="trigger",
    busy=False,
    metadata={"flow_lpm": 0.0, "tank_pct": 65.0, "armed": True},
)
```

`busy=True` while an action is in flight; the host serialises
`actuate` calls per session to keep the contract simple. Drivers
that report sustained errors should propagate them via
`metadata["error"]` and raise `DriverError` on the next `actuate`
call so the GCS sees the failure on the user-action path.

## Manifest permissions

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
agent:
  permissions:
    - id: sensor.payload.register   # required, gates registration
    - id: hardware.uart             # if the driver speaks raw serial
    - id: hardware.gpio             # if the actuator is driven from a GPIO line
```

Registration gates on `sensor.payload.register`, which the operator
approves when they install the plugin. Once registered, actuation
flows through the driver's `actuate(...)` method, and the host
serialises those calls per session.

## See also

* [Driver layer](/developers/driver-layer) for the contract.
* [Permissions](/developers/permissions) for the capability model and
  risk bands.
