Skip to content

Implementing a Driver

An arm driver normally implements traits in this order: lifecycle, physical description, motion spaces, realtime control, and finally the unified Arm surface.

1. Implement Robot

use robot_behavior::{ArmState, Robot, RobotResult};

struct MyArm {
    q: [f64; 6],
}

impl Robot for MyArm {
    type State = ArmState<6>;
    const CONTROL_PERIOD: f64 = 0.001;

    fn version() -> String {
        "my-arm 0.1".to_string()
    }

    fn read_state(&mut self) -> RobotResult<Self::State> {
        Ok(ArmState::default())
    }
}

init, shutdown, enable, disable and stop have defaults. Real drivers should override them according to the hardware semantics.

2. Declare Limits

use robot_behavior::{EndPoint, Joints, to_radians_array};

impl Joints<6> for MyArm {
    const JOINT_MIN: [f64; 6] = to_radians_array([-170.0; 6]);
    const JOINT_MAX: [f64; 6] = to_radians_array([170.0; 6]);
    const JOINT_VEL_BOUND: [f64; 6] = [2.0; 6];
}

impl EndPoint for MyArm {
    const CARTESIAN_VEL_BOUND: f64 = 1.0;
    const ROTATION_VEL_BOUND: f64 = 3.14;
}

Joints<N> requires only JOINT_MIN and JOINT_MAX; the rest default to f64::MAX.

3. Implement Motion Spaces

use robot_behavior::{FlangeSpace, JointSpace, MoveTo, Pose, RobotResult};

impl MoveTo<JointSpace<6>> for MyArm {
    fn move_to(&mut self, target: [f64; 6]) -> RobotResult<()> {
        self.q = target;
        Ok(())
    }
}

impl MoveTo<FlangeSpace> for MyArm {
    fn move_to(&mut self, target: Pose) -> RobotResult<()> {
        let _pose = target.quat();
        // IK -> joint target -> hardware command
        Ok(())
    }
}

A common funnel is: TcpSpace applies the tool offset into FlangeSpace; FlangeSpace uses IK into JointSpace<N>; JointSpace<N> talks to the hardware.

4. Implement Arm

Arm<N> groups state, payload and temporary bound overrides. with_* methods commonly return Self with one-shot bounds stored through OverrideOnce / ArmBound<N>.

use robot_behavior::{Arm, ArmState, LoadState, Pose};

impl Arm<6> for MyArm {
    fn state(&mut self) -> RobotResult<ArmState<6>> {
        self.read_state()
    }

    fn set_load(&mut self, _load: LoadState) -> RobotResult<()> {
        Ok(())
    }

    fn get_joint(&self) -> [f64; 6] { self.q }
    fn get_endpoint(&self) -> Pose { Pose::default() }

    fn with_joint_vel(self, _v: [f64; 6]) -> Self { self }
    fn with_joint_acc(self, _a: [f64; 6]) -> Self { self }
    fn with_joint_jerk(self, _j: [f64; 6]) -> Self { self }
    fn with_torque(self, _t: [f64; 6]) -> Self { self }
    fn with_torque_dot(self, _td: [f64; 6]) -> Self { self }
    fn with_cartesian_vel(self, _v: f64) -> Self { self }
    fn with_cartesian_acc(self, _a: f64) -> Self { self }
    fn with_cartesian_jerk(self, _j: f64) -> Self { self }
    fn with_rotation_vel(self, _v: f64) -> Self { self }
    fn with_rotation_acc(self, _a: f64) -> Self { self }
    fn with_rotation_jerk(self, _j: f64) -> Self { self }
}

5. Optional Capabilities

  • MoveTraj<S>: dense trajectories, paths and waypoints.
  • RealtimeControl<S>: torque, position, velocity or Cartesian-velocity closure loops.
  • RobotDescription: URDF asset path.
  • ArmForwardKinematics / ArmInverseKinematics: DH, FK/IK, Jacobians and IK updates.
  • PhysicsEngine, AddRobot, Renderer: simulation and visualization backends.