Joint Trajectory Controller#

The Joint Trajectory Controller commands trajectories across multiple joints simultaneously. Trajectories are specified as a sequence of waypoints : each with a target time and optionally positions, velocities, and accelerations : and the controller interpolates between them using cubic splines to produce smooth motion.

This controller is required for:

  • Planned trajectories coming from MoveIt

  • Playback of pre-defined motions

Overview#

Parameter

Value

Controller type

joint_trajectory_controller/JointTrajectoryController

ROS 2 node name

left_leg_controller, right_leg_controller

Command interfaces

position or effort (configured per instance)

State interfaces

position (position mode); position + velocity (effort mode)

Topics#

Direction

Topic

Type

Description

Subscribes

<controller_name>/joint_trajectory

trajectory_msgs/JointTrajectory

Fire-and-forget trajectory commands

Publishes

<controller_name>/controller_state

control_msgs/JointTrajectoryControllerState

Internal state published at the controller manager update rate

Actions#

Action

Type

Description

<controller_name>/follow_joint_trajectory

control_msgs/action/FollowJointTrajectory

Primary interface for sending trajectories with execution monitoring and result feedback

Services#

Service

Type

Description

<controller_name>/query_state

control_msgs/srv/QueryTrajectoryState

Query the expected controller state at any future time point

Key Features#

  • Spline interpolation : cubic splines are used by default to produce smooth, continuous joint motion between waypoints. When velocities and accelerations are provided alongside positions, a quintic spline is used for higher-order continuity.

  • Flexible command interfaces : supports position, velocity, acceleration, and effort command interfaces. The active interface is selected per controller instance via the command_interfaces parameter.

  • Trajectory replacement : a new trajectory can be sent at any time and will seamlessly replace the current one, with the controller splicing in from the present state to avoid discontinuities.

  • Partial joint goals : when allow_partial_joints_goal is enabled, a trajectory that only specifies a subset of the controller’s joints is accepted; unspecified joints hold their last commanded state.

  • Tolerance checking : path tolerances (per-point), goal tolerances (at the end of the trajectory), and a goal time tolerance are all configurable per joint. Violations abort the trajectory and report a failure result on the action.

  • Open-loop and closed-loop control : with open_loop_control: true the controller interpolates purely from the last commanded state, which avoids integrating sensor noise; with closed-loop control it re-seeds interpolation from the measured joint state at each update cycle.

How it Works#

Trajectory reception#

Trajectories arrive either through the follow_joint_trajectory action (recommended : provides feedback and a result) or the joint_trajectory topic (fire-and-forget). Both paths feed the same internal trajectory queue.

Interpolation#

At each control update the controller determines the current desired state by evaluating the active spline at the elapsed time. If the incoming waypoints include only positions, a cubic spline is fit; if velocities are also present, the spline matches first derivatives at each knot; if accelerations are included, a quintic spline matches second derivatives as well.

Command generation#

The interpolated desired state is forwarded to the hardware command interfaces. In position mode the desired position is written directly. In effort mode the controller runs an internal PID loop : using the per-joint gains : on the position (and optionally velocity) error to produce an effort command.

Tolerance monitoring#

After each update the controller compares the actual joint state against the desired state. If any joint exceeds its configured path tolerance the trajectory is aborted immediately. Once the final waypoint’s time has elapsed, the controller checks goal tolerances and the goal time window and reports success or failure back through the action result.

Configuration Examples#

Position Command Mode#

Used by the default leg controllers. The controller commands joint positions directly; no PID gains are needed at this level since the actuator’s own position loop handles tracking.

${LEG_SIDE_PREFIX}_position_controller:
  ros__parameters:
    type: joint_trajectory_controller/JointTrajectoryController
    open_loop_control: true
    joints:
      - ${LEG_SIDE_PREFIX}_1_joint
      - ${LEG_SIDE_PREFIX}_2_joint
      - ${LEG_SIDE_PREFIX}_3_joint
      - ${LEG_SIDE_PREFIX}_length_joint
      - ${LEG_SIDE_PREFIX}_4_joint
      - ${LEG_SIDE_PREFIX}_5_joint
    command_interfaces:
      - position
    state_interfaces:
      - position
    constraints:
      goal_time: 0.6
      stopped_velocity_tolerance: 5.0
      ${LEG_SIDE_PREFIX}_1_joint: {goal: 0.02}
      ${LEG_SIDE_PREFIX}_2_joint: {goal: 0.02}
      ${LEG_SIDE_PREFIX}_3_joint: {goal: 0.02}
      ${LEG_SIDE_PREFIX}_4_joint: {goal: 0.02}
      ${LEG_SIDE_PREFIX}_5_joint: {goal: 0.02}

Effort Command Mode#

Commands joint efforts (torques) rather than positions. The controller closes its own PID loop on position error and outputs effort commands, so per-joint gains are required. Both position and velocity state interfaces are used for feedback.

${LEG_SIDE_PREFIX}_jtc_effort_controller:
  ros__parameters:
    type: joint_trajectory_controller/JointTrajectoryController
    open_loop_control: true
    joints:
      - ${LEG_SIDE_PREFIX}_1_joint
      - ${LEG_SIDE_PREFIX}_2_joint
      - ${LEG_SIDE_PREFIX}_3_joint
      - ${LEG_SIDE_PREFIX}_length_joint
      - ${LEG_SIDE_PREFIX}_4_joint
      - ${LEG_SIDE_PREFIX}_5_joint
    command_interfaces:
      - effort
    state_interfaces:
      - position
      - velocity
    constraints:
      goal_time: 0.6
      stopped_velocity_tolerance: 5.0
      ${LEG_SIDE_PREFIX}_1_joint: {goal: 0.02}
      ${LEG_SIDE_PREFIX}_2_joint: {goal: 0.02}
      ${LEG_SIDE_PREFIX}_3_joint: {goal: 0.02}
      ${LEG_SIDE_PREFIX}_4_joint: {goal: 0.02}
      ${LEG_SIDE_PREFIX}_5_joint: {goal: 0.02}
    gains:
      ${LEG_SIDE_PREFIX}_1_joint:      {p: 100.0,  d:  0.0, i:  0.0, i_clamp_min: -1.0, i_clamp_max: 1.0}
      ${LEG_SIDE_PREFIX}_2_joint:      {p: 250.0,  d:  0.0, i:  0.0, i_clamp_min: -1.0, i_clamp_max: 1.0}
      ${LEG_SIDE_PREFIX}_3_joint:      {p: 350.0,  d:  0.0, i: 10.0, i_clamp_min: -5.0, i_clamp_max: 5.0}
      ${LEG_SIDE_PREFIX}_4_joint:      {p:  75.0,  d:  0.0, i:  0.0, i_clamp_min: -1.0, i_clamp_max: 1.0}
      ${LEG_SIDE_PREFIX}_5_joint:      {p: 150.0,  d:  0.0, i:  0.0, i_clamp_min: -1.0, i_clamp_max: 1.0}
      ${LEG_SIDE_PREFIX}_length_joint: {p: 3000.0, d: 10.0, i:  3.0, i_clamp_min: -1.0, i_clamp_max: 1.0}

Further Reading#