Skip to main content
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.
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.

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

All six share the same shape in both languages:
The kind-specific methods are the meat. A CameraDriver yields FrameBuffers. A GpsDriver yields GpsFixes. 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 XCandidates 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>).
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 for the worked example, including the FLIR Lepton USB UVC reference plugin.
  • Vendor binaries when your driver needs a closed-source .so to talk to the device.
  • Hardware testing for SITL, hardware-in-loop, and rig-on-bench testing patterns.