Skip to main content
stub — This page will be curated before publishing. Content reflects the current Python SDK (cyberwave.driver).

Overview

Edge drivers are built by composing mixins on BaseDriver or BaseROS2Driver. Each mixin registers part of the MQTT catalog and supplies seams (methods you implement) for hardware-specific behavior. Import the public surface from cyberwave.driver:

Composition order (MRO)

Method resolution order matters. Put JointControllerMixin before ArmCapabilityMixin so joint targets from teleop and arm commands (ee_move, grip, …) both flow through JointControllerMixin._apply_joint_targets. The Piper driver uses this exact stack:
See Reference pattern: Agilex Piper for how __init__, define_interface, and tick() tie the mixins together at runtime.

Mixin reference

Control types (no mixin — used by mixins)

See Joint motion shaping for shaped vs passthrough motion.

Bidirectional joint/update

The cyberwave/joint/{uuid}/update topic carries both teleop commands and edge feedback. Split responsibility: Teleop listeners reject edge* messages so the driver never re-ingests its own feedback.

Reference pattern: ROS manipulator arm (Agilex Piper)

The Agilex Piper driver (PiperDriver) is the canonical integration: five base classes, a handful of seams, and one define_interface publisher — no hand-written MQTT joint bridge.

How the pieces connect

Mixins do not run in isolation. They wire together through shared state (_joint_states, _tele_inbox), super() calls, and the ROS tick() timer:

Class declaration and flags

JointControllerMixin must precede ArmCapabilityMixin so IK-produced joint targets and MQTT teleop both enter JointControllerMixin._apply_joint_targets.

__init__ — wire mixin state

Without these two calls the inbox, teleop counters, and _joint_states cache are never allocated.

define_interface — feedback publisher only

super().define_interface(iface) lets mixins register joint/update listener and twin/command listeners. Piper adds the outbound path:
At runtime wire_ros_publishers() subscribes on ROS, calls convert_joints_to_payload (uses joint_name_map()), throttles to rate_hz, and publishes with source_type=edge. Do not add a second joint/update listener — JointControllerMixin already owns teleop.

Seam map — what Piper implements per mixin

Command path end-to-end:
Feedback path (two consumers, one ROS topic):

Passthrough motion vs shaped

Piper returns None from joint_controller_motion() so targets pass straight through; velocity and effort limits come from ROS parameters (joint_velocities, joint_efforts, gripper_effort) inside _build_joint_command_msg(). Arms that need SDK-side smoothing return trapezoidal_motion instead — see Joint motion shaping.

Operation modes and edge health

  • NO_OP: Piper resets the teleop inbox but keeps ROS lifecycle Active so from_ros feedback continues (on_enter_no_op).
  • Teleop: on_enter_teleop_local / remote call vendor enable + CAN prime; commands flow only when hardware accepts them.
  • Liveness: _touch_edge_health() on joint and arm-status callbacks; edge_health publishes at 1 Hz — no extra payload in edge_health_extras.

What stays driver-specific (not mixin)

Piper still owns vendor glue that mixins do not cover: managed_launch (piper_ros), CAN setup, /enable_srv, arm_status subscription, and homing repeats on teardown (HOME_ON_TEARDOWN). Mixins handle the Cyberwave contract; the driver handles hardware bring-up.

Client APIs vs driver plumbing

End users call twin.joints.set() / twin.joints.get(); they do not implement from_ros.

BaseROS2Driver

Dual lifecycle, from_ros, wire_ros_publishers, manifest export.

Joint motion shaping

Trapezoidal vs passthrough joint_controller_motion().

Writing compatible drivers

Platform contract, MQTT catalog, env vars.

Agilex Piper driver

Production reference — mixin integration, passthrough motion, managed launch.