Skip to content

Commit 7c52bec

Browse files
ddengsterKarsten1987bmagyar
authored
Reorder incoming out of order joint_names in trajectory messages (#53)
* incoming action messages are now remapped based on initialized order * shift remapping code to remap_msg_trajectories function, applied it to subscriber as well * added jumbled_joint_order test * error message for incoming goal with unmatched joint name Co-authored-by: Karsten Knese <[email protected]> Co-authored-by: Bence Magyar <[email protected]>
1 parent b3ba1af commit 7c52bec

File tree

4 files changed

+149
-0
lines changed

4 files changed

+149
-0
lines changed

joint_trajectory_controller/include/joint_trajectory_controller/joint_trajectory_controller.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ class JointTrajectoryController : public controller_interface::ControllerInterfa
144144
void feedback_setup_callback(
145145
std::shared_ptr<rclcpp_action::ServerGoalHandle<FollowJTrajAction>> goal_handle);
146146

147+
// sorts the joints of the incoming message to our local order
148+
void sort_to_local_joint_order(
149+
std::shared_ptr<trajectory_msgs::msg::JointTrajectory> trajectory_msg);
150+
147151
SegmentTolerances default_tolerances_;
148152

149153
void preempt_active_goal();

joint_trajectory_controller/include/joint_trajectory_controller/trajectory.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,31 @@ class Trajectory
121121
bool sampled_already_ = false;
122122
};
123123

124+
/**
125+
* \return The map between \p t1 indices (implicitly encoded in return vector indices) to \p t2 indices.
126+
* If \p t1 is <tt>"{C, B}"</tt> and \p t2 is <tt>"{A, B, C, D}"</tt>, the associated mapping vector is
127+
* <tt>"{2, 1}"</tt>.
128+
*/
129+
template<class T>
130+
inline std::vector<size_t> mapping(const T & t1, const T & t2)
131+
{
132+
// t1 must be a subset of t2
133+
if (t1.size() > t2.size()) {return std::vector<size_t>();}
134+
135+
std::vector<size_t> mapping_vector(t1.size()); // Return value
136+
for (auto t1_it = t1.begin(); t1_it != t1.end(); ++t1_it) {
137+
auto t2_it = std::find(t2.begin(), t2.end(), *t1_it);
138+
if (t2.end() == t2_it) {
139+
return std::vector<size_t>();
140+
} else {
141+
const size_t t1_dist = std::distance(t1.begin(), t1_it);
142+
const size_t t2_dist = std::distance(t2.begin(), t2_it);
143+
mapping_vector[t1_dist] = t2_dist;
144+
}
145+
}
146+
return mapping_vector;
147+
}
148+
124149
} // namespace joint_trajectory_controller
125150

126151
#endif // JOINT_TRAJECTORY_CONTROLLER__TRAJECTORY_HPP_

joint_trajectory_controller/src/joint_trajectory_controller.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ JointTrajectoryController::on_configure(const rclcpp_lifecycle::State & previous
322322
// http://wiki.ros.org/joint_trajectory_controller/UnderstandingTrajectoryReplacement
323323
// always replace old msg with new one for now
324324
if (subscriber_is_active_) {
325+
sort_to_local_joint_order(msg);
325326
traj_external_point_ptr_->update(msg);
326327
}
327328
};
@@ -554,6 +555,18 @@ rclcpp_action::GoalResponse JointTrajectoryController::goal_callback(
554555
}
555556
}
556557

558+
for (auto i = 0ul; i < goal->trajectory.joint_names.size(); ++i) {
559+
const std::string & incoming_joint_name = goal->trajectory.joint_names[i];
560+
561+
auto it = std::find(joint_names_.begin(), joint_names_.end(), incoming_joint_name);
562+
if (it == joint_names_.end()) {
563+
RCLCPP_ERROR(
564+
lifecycle_node_->get_logger(),
565+
"Incoming joint %s doesn't match the controller's joints.",
566+
incoming_joint_name.c_str());
567+
return rclcpp_action::GoalResponse::REJECT;
568+
}
569+
}
557570
RCLCPP_INFO(lifecycle_node_->get_logger(), "Accepted new action goal");
558571
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
559572
}
@@ -593,6 +606,7 @@ void JointTrajectoryController::feedback_setup_callback(
593606
preempt_active_goal();
594607
auto traj_msg = std::make_shared<trajectory_msgs::msg::JointTrajectory>(
595608
goal_handle->get_goal()->trajectory);
609+
sort_to_local_joint_order(traj_msg);
596610
traj_external_point_ptr_->update(traj_msg);
597611

598612
RealtimeGoalHandlePtr rt_goal = std::make_shared<RealtimeGoalHandle>(goal_handle);
@@ -607,6 +621,47 @@ void JointTrajectoryController::feedback_setup_callback(
607621
std::bind(&RealtimeGoalHandle::runNonRealtime, rt_active_goal_));
608622
}
609623

624+
void JointTrajectoryController::sort_to_local_joint_order(
625+
std::shared_ptr<trajectory_msgs::msg::JointTrajectory> trajectory_msg)
626+
{
627+
// rearrange all points in the trajectory message based on mapping
628+
std::vector<size_t> mapping_vector = mapping(trajectory_msg->joint_names, joint_names_);
629+
auto remap = [this](const std::vector<double> & to_remap, const std::vector<size_t> & mapping)
630+
-> std::vector<double>
631+
{
632+
if (to_remap.empty()) {
633+
return to_remap;
634+
}
635+
if (to_remap.size() != mapping.size()) {
636+
RCLCPP_WARN(
637+
lifecycle_node_->get_logger(),
638+
"Invalid input size (%d) for sorting", to_remap.size());
639+
return to_remap;
640+
}
641+
std::vector<double> output;
642+
output.resize(mapping.size(), 0.0);
643+
for (auto index = 0ul; index < mapping.size(); ++index) {
644+
auto map_index = mapping[index];
645+
output[map_index] = to_remap[index];
646+
}
647+
return output;
648+
};
649+
650+
for (auto index = 0ul; index < trajectory_msg->points.size(); ++index) {
651+
trajectory_msg->points[index].positions =
652+
remap(trajectory_msg->points[index].positions, mapping_vector);
653+
654+
trajectory_msg->points[index].velocities =
655+
remap(trajectory_msg->points[index].velocities, mapping_vector);
656+
657+
trajectory_msg->points[index].accelerations =
658+
remap(trajectory_msg->points[index].accelerations, mapping_vector);
659+
660+
trajectory_msg->points[index].effort =
661+
remap(trajectory_msg->points[index].effort, mapping_vector);
662+
}
663+
}
664+
610665
void JointTrajectoryController::preempt_active_goal()
611666
{
612667
if (rt_active_goal_) {

joint_trajectory_controller/test/test_trajectory_controller.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,3 +526,68 @@ TEST_F(TestTrajectoryController, zero_state_publish_rate) {
526526

527527
test_state_publish_rate_target(traj_controller, 0);
528528
}
529+
530+
TEST_F(TestTrajectoryController, test_jumbled_joint_order) {
531+
auto traj_controller = std::make_shared<joint_trajectory_controller::JointTrajectoryController>();
532+
auto ret = traj_controller->init(test_robot, controller_name);
533+
if (ret != controller_interface::CONTROLLER_INTERFACE_RET_SUCCESS) {
534+
FAIL();
535+
}
536+
537+
auto traj_lifecycle_node = traj_controller->get_lifecycle_node();
538+
std::vector<std::string> joint_names = {"joint1", "joint2", "joint3"};
539+
rclcpp::Parameter joint_parameters("joints", joint_names);
540+
traj_lifecycle_node->set_parameter(joint_parameters);
541+
542+
std::vector<std::string> operation_mode_names = {"write1", "write2"};
543+
rclcpp::Parameter operation_mode_parameters("write_op_modes", operation_mode_names);
544+
traj_lifecycle_node->set_parameter(operation_mode_parameters);
545+
546+
rclcpp::executors::SingleThreadedExecutor executor;
547+
executor.add_node(traj_lifecycle_node->get_node_base_interface());
548+
549+
traj_controller->on_configure(traj_lifecycle_node->get_current_state());
550+
traj_controller->on_activate(traj_lifecycle_node->get_current_state());
551+
552+
auto future_handle = std::async(
553+
std::launch::async, [&executor]() -> void {
554+
executor.spin();
555+
});
556+
// wait for things to setup
557+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
558+
559+
{
560+
trajectory_msgs::msg::JointTrajectory traj_msg;
561+
std::vector<std::string> jumbled_joint_names {
562+
test_robot->joint_name2, test_robot->joint_name3, test_robot->joint_name1
563+
};
564+
traj_msg.joint_names = jumbled_joint_names;
565+
traj_msg.header.stamp = rclcpp::Time(0);
566+
traj_msg.points.resize(1);
567+
568+
traj_msg.points[0].time_from_start = rclcpp::Duration::from_seconds(0.25);
569+
traj_msg.points[0].positions.resize(3);
570+
traj_msg.points[0].positions[0] = 2.0;
571+
traj_msg.points[0].positions[1] = 3.0;
572+
traj_msg.points[0].positions[2] = 1.0;
573+
574+
trajectory_publisher->publish(traj_msg);
575+
}
576+
577+
// update for 0.5 seconds
578+
auto start_time = rclcpp::Clock().now();
579+
rclcpp::Duration wait = rclcpp::Duration::from_seconds(0.5);
580+
auto end_time = start_time + wait;
581+
while (rclcpp::Clock().now() < end_time) {
582+
test_robot->read();
583+
traj_controller->update();
584+
test_robot->write();
585+
}
586+
587+
double threshold = 0.001;
588+
EXPECT_NEAR(1.0, test_robot->pos1, threshold);
589+
EXPECT_NEAR(2.0, test_robot->pos2, threshold);
590+
EXPECT_NEAR(3.0, test_robot->pos3, threshold);
591+
592+
executor.cancel();
593+
}

0 commit comments

Comments
 (0)