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

# Driver layer

> Typed base classes hardware-driver plugins subclass.

The driver layer is a sub-pattern of agent plugins. Same manifest,
same lifecycle, same sandbox. What is new is a set of typed base
classes that the host knows how to register, discover, and route
data to and from. A driver is just a plugin that subclasses one of
these bases and declares the matching `sensor.*.register` capability
in its manifest.

<Note>
  The driver layer exists in **both** agent SDKs. Python drivers
  subclass the abstract base classes in `ados.sdk.drivers`. Rust
  drivers implement the matching trait in `ados-sdk::drivers`. The two
  are method-for-method equivalents: the same `discover` / `open` /
  `close` / `capabilities` preamble, the same candidate / capabilities
  / session shapes, the same kind-specific streaming methods. A driver
  plugin can be authored in either language. Set `agent.runtime` in the
  manifest to `python` or `rust` to pick.
</Note>

## Why a driver layer

Hardware-driver plugins are the highest-value class of extensions.
A vendor shipping a thermal camera, a LiDAR, a custom GPS, a
payload actuator, or a vendor-specific gimbal wants their hardware
to "just work" once installed. They do not want to fork the agent.
They want a stable interface that says "implement these methods
and we will route the rest."

## The six driver kinds

| Kind                                               | Base class              | Page                                                           |
| -------------------------------------------------- | ----------------------- | -------------------------------------------------------------- |
| Camera (visible, thermal, depth, multi-spectral)   | `CameraDriver`          | [Camera driver](/developers/camera-driver)                     |
| Gimbal (SBGC, MAVLink mount, vendor serial)        | `GimbalDriver`          | [Gimbal driver](/developers/gimbal-driver)                     |
| LiDAR (RPLidar, Livox, Velodyne, custom)           | `LidarDriver`           | [LiDAR driver](/developers/lidar-driver)                       |
| GPS (u-blox, NMEA, RTK, vendor-custom)             | `GpsDriver`             | [GPS driver](/developers/gps-driver)                           |
| ESC telemetry (DShot, KISS, BLHeli32)              | `EscDriver`             | [ESC driver](/developers/esc-driver)                           |
| Payload actuator (sprayer, dropper, claw, sampler) | `PayloadActuatorDriver` | [Payload actuator driver](/developers/payload-actuator-driver) |

All six share the same shape in both languages:

<CodeGroup>
  ```python ados.sdk.drivers (Python) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  from abc import ABC, abstractmethod

  class XDriver(ABC):
      async def discover(self) -> list[XCandidate]: ...
      async def open(self, candidate: XCandidate, config: dict) -> XSession: ...
      async def close(self, session: XSession) -> None: ...
      def capabilities(self, session: XSession) -> XCapabilities: ...
      # plus kind-specific I/O methods
  ```

  ```rust ados-sdk::drivers (Rust) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  #[async_trait]
  pub trait XDriver: Send + Sync {
      type Session: Send + Sync;
      async fn discover(&self) -> DriverResult<Vec<XCandidate>>;
      async fn open(&self, candidate: &XCandidate, config: &BTreeMap<String, Value>)
          -> DriverResult<Self::Session>;
      async fn close(&self, session: Self::Session) -> DriverResult<()>;
      fn capabilities(&self, session: &Self::Session) -> XCapabilities;
      // plus kind-specific I/O methods
  }
  ```
</CodeGroup>

The kind-specific methods are the meat. A `CameraDriver` yields
`FrameBuffer`s. A `GpsDriver` yields `GpsFix`es. A `GimbalDriver`
takes attitude commands. The shared four-method preamble lets the
agent's peripheral manager handle every kind uniformly, whichever
language the driver is written in. In Python the kind-specific stream
methods return an `AsyncIterator`. In Rust they return a `SampleStream`
(a boxed `Stream`), and the opaque `*Session` base class becomes a
`Session` associated type the host hands back as a token.

## Lifecycle

1. The plugin's `on_start` hook constructs the driver and registers
   it with the peripheral manager. In Python that is
   `peripheral_manager.register_camera_driver(driver)`; in Rust it is
   `ctx.peripheral_manager.register_camera_driver(driver_ref)`. The
   manifest must declare the matching `sensor.camera.register`
   capability or registration is rejected.
2. The peripheral manager calls `discover()` on every registered
   driver at boot and on USB / serial hotplug events. Each driver
   reports a list of `XCandidate`s for devices it claims it can
   open.
3. Multiple drivers may claim the same device. The peripheral
   manager arbitrates among the candidates and opens one of them.
4. The winning driver gets `open(candidate, config)` called against
   it. `config` is the operator-edited config validated against
   `config-schema.json`.
5. The driver yields data through its kind-specific stream method
   (`frame_iterator`, `fix_iterator`, `state_iterator`, etc.).
6. On unload or hotplug-disappear, `close(session)` runs.

## Registration capabilities

A driver registers under a `sensor.*.register` capability that the
manifest must declare. Camera, depth, lidar, imu, and payload drivers
each gate on their own capability (`sensor.camera.register`,
`sensor.depth.register`, `sensor.lidar.register`,
`sensor.imu.register`, `sensor.payload.register`). Gimbal, GPS, and
ESC drivers do not have dedicated capabilities in the current catalog;
they register under `sensor.payload.register`. The per-kind pages note
which capability each one uses.

## Errors

Python drivers raise from `ados.sdk.drivers.errors`. Rust drivers
return the `DriverError` enum from `ados-sdk::drivers` (every driver
method returns `DriverResult<T>`).

<CodeGroup>
  ```python ados.sdk.drivers.errors (Python) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  from ados.sdk.drivers.errors import (
      DriverError,
      DriverDeviceNotFound,
      DriverPermissionDenied,
  )
  ```

  ```rust ados-sdk::drivers (Rust) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  use ados_sdk::drivers::{DriverError, DriverResult};

  // DriverError variants:
  //   DriverError::DeviceNotFound(String)
  //   DriverError::PermissionDenied(String)
  //   DriverError::InvalidParam(String)
  //   DriverError::NotSupported(String)
  //   DriverError::Other(String)
  ```
</CodeGroup>

In Python, `DriverError` is the base. `DriverDeviceNotFound` says "the
candidate disappeared between discover and open." `DriverPermissionDenied`
says "the device exists but the agent process cannot open it"
(missing udev rule, locked USB, etc.). The Rust enum mirrors these:
`DeviceNotFound` and `PermissionDenied` carry the same meaning,
`InvalidParam` is returned for an unknown `set_param` name or an
unknown `actuate` action, and `NotSupported` is returned for an
optional capability a device does not have (a rate command on a
position-only gimbal, RTCM injection on a non-RTK GPS). `Other` carries
any predictable, recoverable condition that does not fit a named kind.

The host catches these, logs them, and surfaces them in the GCS
plugin event stream. A Python driver that raises bare `Exception`
(or a Rust driver that panics) is treated as crashed and the plugin is
moved to the circuit-breaker state per the lifecycle rules.

## Where to go next

* [Camera driver](/developers/camera-driver) for the worked
  example, including the FLIR Lepton USB UVC reference plugin.
* [Vendor binaries](/developers/vendor-binaries) when your driver
  needs a closed-source `.so` to talk to the device.
* [Hardware testing](/developers/hardware-testing) for SITL,
  hardware-in-loop, and rig-on-bench testing patterns.
