Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cartesian_impedance_moveit_plugin.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<library path="libmoveit_ros_control_interface_trajectory_plugin">
<class
name="Cartesian-Impedance-Controller/CartesianImpedance_trajectory_controller"
type="moveit_ros_control_interface::JointTrajectoryControllerAllocator"
base_class_type="moveit_ros_control_interface::ControllerHandleAllocator"
>
<description>
Controller description
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description field is generic and uninformative. It should provide a meaningful description of the Cartesian Impedance Controller's purpose and capabilities for MoveIt users.

Suggested change
Controller description
A Cartesian Impedance Controller that enables compliant motion in Cartesian space by regulating end-effector stiffness and damping. Suitable for tasks requiring force control and safe interaction with the environment.

Copilot uses AI. Check for mistakes.
</description>
</class>
</library>
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ namespace cartesian_impedance_controller
ros::Duration traj_duration_; //!< Duration of the current trajectory
unsigned int traj_index_{0}; //!< Index of the current trajectory point
bool traj_running_{false}; //!< True when running a trajectory

// Cartesian trajectory interpolation
std::vector<Eigen::Vector3d> traj_positions_; //!< Cached Cartesian positions for trajectory points
std::vector<Eigen::Quaterniond> traj_orientations_; //!< Cached Cartesian orientations for trajectory points

// Extra output
bool verbose_print_{false}; //!< Verbose printing enabled
Expand Down
3 changes: 2 additions & 1 deletion package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@
<exec_depend>tf_conversions</exec_depend>
<exec_depend>trajectory_msgs</exec_depend>
<exec_depend>yaml-cpp</exec_depend>

<exec_depend>moveit_ros_control_interface</exec_depend>
<export>
<!-- Other tools can request additional information be placed here -->
<controller_interface plugin="${prefix}/controller_plugins.xml"/>
<moveit_ros_control_interface plugin="${prefix}/cartesian_impedance_moveit_plugin.xml"/>
</export>

<test_depend>gtest</test_depend>
Expand Down
148 changes: 132 additions & 16 deletions src/cartesian_impedance_controller_ros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,49 @@ namespace cartesian_impedance_controller
this->traj_running_ = true;
this->traj_start_ = ros::Time::now();
this->traj_index_ = 0;
trajUpdate();

// Pre-compute Cartesian poses for all trajectory points
this->traj_positions_.clear();
this->traj_orientations_.clear();
this->traj_positions_.reserve(trajectory.points.size());
this->traj_orientations_.reserve(trajectory.points.size());

for (size_t i = 0; i < trajectory.points.size(); ++i)
{
// Robustness Check: Joint size
if (trajectory.points.at(i).positions.size() != this->n_joints_) {
ROS_ERROR("Trajectory point %zu has incorrect number of joints (%zu vs %zu). Aborting trajectory.",
i, trajectory.points.at(i).positions.size(), this->n_joints_);
Comment on lines +575 to +576
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message should clarify which value is 'expected' vs 'received' for better debugging. Consider rephrasing to: 'Trajectory point %zu has incorrect number of joints (expected %zu, got %zu). Aborting trajectory.' to make the order unambiguous.

Suggested change
ROS_ERROR("Trajectory point %zu has incorrect number of joints (%zu vs %zu). Aborting trajectory.",
i, trajectory.points.at(i).positions.size(), this->n_joints_);
ROS_ERROR("Trajectory point %zu has incorrect number of joints (expected %zu, got %zu). Aborting trajectory.",
i, this->n_joints_, trajectory.points.at(i).positions.size());

Copilot uses AI. Check for mistakes.
this->traj_positions_.clear();
this->traj_orientations_.clear();
this->traj_running_ = false;
if (this->traj_as_->isActive()) { this->traj_as_->setAborted(); }
return; // Exit trajStart
}

Eigen::VectorXd q = Eigen::VectorXd::Map(trajectory.points.at(i).positions.data(),
trajectory.points.at(i).positions.size());
Eigen::Vector3d position;
Eigen::Quaterniond orientation;
getFk(q, &position, &orientation);

// Robustness Check: Ensure quaternion hemisphere consistency for Slerp
if (i > 0 && this->traj_orientations_.back().dot(orientation) < 0.0) {
orientation.coeffs() *= -1.0;
}

this->traj_positions_.push_back(position);
this->traj_orientations_.push_back(orientation);

if (this->verbose_print_)
{
ROS_INFO_STREAM("Trajectory point " << i << " position: " << position.transpose()
<< " orientation: " << orientation.coeffs().transpose());
}
}

// Call trajUpdate once to set initial targets
trajUpdate();
if (this->nullspace_stiffness_ < 5.)
{
ROS_WARN("Nullspace stiffness is low. The joints might not follow the planned path.");
Expand All @@ -570,29 +612,103 @@ namespace cartesian_impedance_controller

void CartesianImpedanceControllerRos::trajUpdate()
{
if (ros::Time::now() > (this->traj_start_ + trajectory_.points.at(this->traj_index_).time_from_start))
{
// Get end effector pose
Eigen::VectorXd q = Eigen::VectorXd::Map(trajectory_.points.at(this->traj_index_).positions.data(),
trajectory_.points.at(this->traj_index_).positions.size());
if (this->verbose_print_)
{
ROS_INFO_STREAM("Index " << this->traj_index_ << " q_nullspace: " << q.transpose());
// Check if trajectory is finished
ros::Time current_time = ros::Time::now();
if (current_time > (this->traj_start_ + this->traj_duration_))
{
// Ensure final pose is set
if (!this->traj_positions_.empty()) {
this->position_d_target_ = this->traj_positions_.back();
this->orientation_d_target_ = this->traj_orientations_.back();
Eigen::VectorXd q_last = Eigen::VectorXd::Map(trajectory_.points.back().positions.data(),
trajectory_.points.back().positions.size());
this->setNullspaceConfig(q_last);
}
// Update end-effector pose and nullspace
getFk(q, &this->position_d_target_, &this->orientation_d_target_);
this->setNullspaceConfig(q);
this->traj_index_++;
}

if (ros::Time::now() > (this->traj_start_ + this->traj_duration_))
{
ROS_INFO_STREAM("Finished executing trajectory.");
if (this->traj_as_->isActive())
{
this->traj_as_->setSucceeded();
}
this->traj_running_ = false;
return;
}

// Calculate current time within trajectory
ros::Duration time_from_start = current_time - this->traj_start_;

// Find the two waypoints that bracket the current time
// traj_index_ is updated here to avoid re-searching from start every time
while (this->traj_index_ < trajectory_.points.size() &&
time_from_start >= trajectory_.points.at(this->traj_index_).time_from_start)
{
this->traj_index_++;
}

size_t next_index = this->traj_index_;

// If we're past the last point (should be handled by finish check, but safety), use last point
if (next_index >= trajectory_.points.size())
{
this->position_d_target_ = this->traj_positions_.back();
this->orientation_d_target_ = this->traj_orientations_.back();
Eigen::VectorXd q_last = Eigen::VectorXd::Map(trajectory_.points.back().positions.data(),
trajectory_.points.back().positions.size());
this->setNullspaceConfig(q_last);
return;
}

// If we're before the first point (or exactly at it), use the first point
if (next_index == 0)
{
this->position_d_target_ = this->traj_positions_[0];
this->orientation_d_target_ = this->traj_orientations_[0];
Eigen::VectorXd q_first = Eigen::VectorXd::Map(trajectory_.points[0].positions.data(),
trajectory_.points[0].positions.size());
this->setNullspaceConfig(q_first);
return;
}

// Otherwise, interpolate between the two points: prev_index and next_index
size_t prev_index = next_index - 1;

// Calculate interpolation factor (alpha) between 0 and 1
double t_prev = trajectory_.points.at(prev_index).time_from_start.toSec();
double t_next = trajectory_.points.at(next_index).time_from_start.toSec();
double t_curr = time_from_start.toSec();

double dt = t_next - t_prev;
double alpha = 1.0; // Default alpha if segment duration is invalid
if (dt > 1e-6) // Use a small epsilon to avoid floating point issues near zero
{
alpha = (t_curr - t_prev) / dt;
} else {
ROS_WARN_THROTTLE(1.0, "Trajectory segment duration is near-zero or negative. Clamping alpha=1.");
}
Comment on lines +680 to +687
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on line 681 is misleading. The default alpha is set to 1.0 regardless of whether the segment duration is invalid. When dt <= 1e-6, alpha remains at the initial value of 1.0 (not clamped to it), which jumps directly to the next waypoint. Consider clarifying the comment to reflect that alpha=1.0 is used as a fallback when segment duration is too small or negative.

Copilot uses AI. Check for mistakes.
// Clamp alpha strictly between 0 and 1
alpha = std::max(0.0, std::min(1.0, alpha));

if (this->verbose_print_)
{
ROS_INFO_STREAM_THROTTLE(0.5, "Interpolating between points " << prev_index << " and " << next_index
<< " with alpha=" << alpha << " (t=" << t_curr << ")");
}

// Linear interpolation for position
this->position_d_target_ = (1.0 - alpha) * this->traj_positions_[prev_index] +
alpha * this->traj_positions_[next_index];

// SLERP for orientation (using cached, hemisphere-consistent quaternions)
this->orientation_d_target_ = this->traj_orientations_[prev_index].slerp(
alpha, this->traj_orientations_[next_index]);

// Linear interpolation for nullspace configuration
Eigen::VectorXd q_prev = Eigen::VectorXd::Map(trajectory_.points.at(prev_index).positions.data(),
trajectory_.points.at(prev_index).positions.size());
Eigen::VectorXd q_next = Eigen::VectorXd::Map(trajectory_.points.at(next_index).positions.data(),
trajectory_.points.at(next_index).positions.size());
Eigen::VectorXd q_interp = (1.0 - alpha) * q_prev + alpha * q_next;

this->setNullspaceConfig(q_interp);
}
} // namespace cartesian_impedance_controller