Skip to content

Commit e4f1700

Browse files
[JTC] Continue with last trajectory-point on success (ros-controls#842)
1 parent 62ce487 commit e4f1700

File tree

6 files changed

+162
-76
lines changed

6 files changed

+162
-76
lines changed

joint_trajectory_controller/doc/userdoc.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,14 @@ Actions [#f1]_
150150
<controller_name>/follow_joint_trajectory [control_msgs::action::FollowJointTrajectory]
151151
Action server for commanding the controller
152152

153-
154153
The primary way to send trajectories is through the action interface, and should be favored when execution monitoring is desired.
154+
155155
Action goals allow to specify not only the trajectory to execute, but also (optionally) path and goal tolerances.
156156
When no tolerances are specified, the defaults given in the parameter interface are used (see :ref:`parameters`).
157157
If tolerances are violated during trajectory execution, the action goal is aborted, the client is notified, and the current position is held.
158158

159+
The action server returns success to the client and continues with the last commanded point after the target is reached within the specified tolerances.
160+
159161
.. _Subscriber:
160162

161163
Subscriber [#f1]_

joint_trajectory_controller/include/joint_trajectory_controller/joint_trajectory_controller.hpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class JointTrajectoryController : public controller_interface::ControllerInterfa
174174

175175
// Timeout to consider commands old
176176
double cmd_timeout_;
177-
// Are we holding position?
177+
// True if holding position or repeating last trajectory point in case of success
178178
realtime_tools::RealtimeBuffer<bool> rt_is_holding_;
179179
// TODO(karsten1987): eventually activate and deactivate subscriber directly when its supported
180180
bool subscriber_is_active_ = false;
@@ -244,9 +244,19 @@ class JointTrajectoryController : public controller_interface::ControllerInterfa
244244

245245
JOINT_TRAJECTORY_CONTROLLER_PUBLIC
246246
void preempt_active_goal();
247+
248+
/** @brief set the current position with zero velocity and acceleration as new command
249+
*/
247250
JOINT_TRAJECTORY_CONTROLLER_PUBLIC
248251
std::shared_ptr<trajectory_msgs::msg::JointTrajectory> set_hold_position();
249252

253+
/** @brief set last trajectory point to be repeated at success
254+
*
255+
* no matter if it has nonzero velocity or acceleration
256+
*/
257+
JOINT_TRAJECTORY_CONTROLLER_PUBLIC
258+
std::shared_ptr<trajectory_msgs::msg::JointTrajectory> set_success_trajectory_point();
259+
250260
JOINT_TRAJECTORY_CONTROLLER_PUBLIC
251261
bool reset();
252262

joint_trajectory_controller/src/joint_trajectory_controller.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ controller_interface::return_type JointTrajectoryController::update(
362362
RCLCPP_INFO(get_node()->get_logger(), "Goal reached, success!");
363363

364364
traj_msg_external_point_ptr_.reset();
365-
traj_msg_external_point_ptr_.initRT(set_hold_position());
365+
traj_msg_external_point_ptr_.initRT(set_success_trajectory_point());
366366
}
367367
else if (!within_goal_time)
368368
{
@@ -1466,6 +1466,20 @@ JointTrajectoryController::set_hold_position()
14661466
return hold_position_msg_ptr_;
14671467
}
14681468

1469+
std::shared_ptr<trajectory_msgs::msg::JointTrajectory>
1470+
JointTrajectoryController::set_success_trajectory_point()
1471+
{
1472+
// set last command to be repeated at success, no matter if it has nonzero velocity or
1473+
// acceleration
1474+
hold_position_msg_ptr_->points[0] = traj_external_point_ptr_->get_trajectory_msg()->points.back();
1475+
hold_position_msg_ptr_->points[0].time_from_start = rclcpp::Duration(0, 0);
1476+
1477+
// set flag, otherwise tolerances will be checked with success_trajectory_point too
1478+
rt_is_holding_.writeFromNonRT(true);
1479+
1480+
return hold_position_msg_ptr_;
1481+
}
1482+
14691483
bool JointTrajectoryController::contains_interface_type(
14701484
const std::vector<std::string> & interface_type_list, const std::string & interface_type)
14711485
{

joint_trajectory_controller/test/test_trajectory_actions.cpp

Lines changed: 103 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,12 @@ class TestTrajectoryActions : public TrajectoryControllerTest
6868

6969
void SetUpExecutor(
7070
const std::vector<rclcpp::Parameter> & parameters = {},
71-
bool separate_cmd_and_state_values = false, double kp = 0.0)
71+
bool separate_cmd_and_state_values = false, double kp = 0.0, double ff = 1.0)
7272
{
7373
setup_executor_ = true;
7474

75-
SetUpAndActivateTrajectoryController(executor_, parameters, separate_cmd_and_state_values, kp);
75+
SetUpAndActivateTrajectoryController(
76+
executor_, parameters, separate_cmd_and_state_values, kp, ff);
7677

7778
SetUpActionClient();
7879

@@ -218,7 +219,10 @@ class TestTrajectoryActionsTestParameterized
218219

219220
TEST_P(TestTrajectoryActionsTestParameterized, test_success_single_point_sendgoal)
220221
{
221-
SetUpExecutor();
222+
// deactivate velocity tolerance
223+
std::vector<rclcpp::Parameter> params = {
224+
rclcpp::Parameter("constraints.stopped_velocity_tolerance", 0.0)};
225+
SetUpExecutor(params, false, 1.0, 0.0);
222226
SetUpControllerHardware();
223227

224228
std::shared_future<typename GoalHandle::SharedPtr> gh_future;
@@ -228,8 +232,6 @@ TEST_P(TestTrajectoryActionsTestParameterized, test_success_single_point_sendgoa
228232
std::vector<JointTrajectoryPoint> points;
229233
JointTrajectoryPoint point;
230234
point.time_from_start = rclcpp::Duration::from_seconds(0.5);
231-
point.positions.resize(joint_names_.size());
232-
233235
point.positions = point_positions;
234236

235237
points.push_back(point);
@@ -247,20 +249,53 @@ TEST_P(TestTrajectoryActionsTestParameterized, test_success_single_point_sendgoa
247249
// it should be holding the last position goal
248250
// i.e., active but trivial trajectory (one point only)
249251
// note: the action goal also is a trivial trajectory
250-
if (traj_controller_->has_position_command_interface())
251-
{
252-
expectHoldingPoint(point_positions);
253-
}
254-
else
252+
expectCommandPoint(point_positions);
253+
}
254+
255+
TEST_P(TestTrajectoryActionsTestParameterized, test_success_single_point_with_velocity_sendgoal)
256+
{
257+
// deactivate velocity tolerance and allow velocity at trajectory end
258+
std::vector<rclcpp::Parameter> params = {
259+
rclcpp::Parameter("constraints.stopped_velocity_tolerance", 0.0),
260+
rclcpp::Parameter("allow_nonzero_velocity_at_trajectory_end", true)};
261+
SetUpExecutor(params, false, 1.0, 0.0);
262+
SetUpControllerHardware();
263+
264+
std::shared_future<typename GoalHandle::SharedPtr> gh_future;
265+
// send goal
266+
std::vector<double> point_positions{1.0, 2.0, 3.0};
267+
std::vector<double> point_velocities{1.0, 1.0, 1.0};
255268
{
256-
// no integration to position state interface from velocity/acceleration
257-
expectHoldingPoint(INITIAL_POS_JOINTS);
269+
std::vector<JointTrajectoryPoint> points;
270+
JointTrajectoryPoint point;
271+
point.time_from_start = rclcpp::Duration::from_seconds(0.5);
272+
point.positions = point_positions;
273+
point.velocities = point_velocities;
274+
275+
points.push_back(point);
276+
277+
gh_future = sendActionGoal(points, 1.0, goal_options_);
258278
}
279+
controller_hw_thread_.join();
280+
281+
EXPECT_TRUE(gh_future.get());
282+
EXPECT_EQ(rclcpp_action::ResultCode::SUCCEEDED, common_resultcode_);
283+
284+
// run an update
285+
updateControllerAsync(rclcpp::Duration::from_seconds(0.01));
286+
287+
// it should be holding the last position goal
288+
// i.e., active but trivial trajectory (one point only)
289+
// note: the action goal also is a trivial trajectory
290+
expectCommandPoint(point_positions, point_velocities);
259291
}
260292

261293
TEST_P(TestTrajectoryActionsTestParameterized, test_success_multi_point_sendgoal)
262294
{
263-
SetUpExecutor();
295+
// deactivate velocity tolerance
296+
std::vector<rclcpp::Parameter> params = {
297+
rclcpp::Parameter("constraints.stopped_velocity_tolerance", 0.0)};
298+
SetUpExecutor({params}, false, 1.0, 0.0);
264299
SetUpControllerHardware();
265300

266301
// add feedback
@@ -277,15 +312,11 @@ TEST_P(TestTrajectoryActionsTestParameterized, test_success_multi_point_sendgoal
277312
std::vector<JointTrajectoryPoint> points;
278313
JointTrajectoryPoint point1;
279314
point1.time_from_start = rclcpp::Duration::from_seconds(0.2);
280-
point1.positions.resize(joint_names_.size());
281-
282315
point1.positions = points_positions.at(0);
283316
points.push_back(point1);
284317

285318
JointTrajectoryPoint point2;
286319
point2.time_from_start = rclcpp::Duration::from_seconds(0.3);
287-
point2.positions.resize(joint_names_.size());
288-
289320
point2.positions = points_positions.at(1);
290321
points.push_back(point2);
291322

@@ -302,15 +333,57 @@ TEST_P(TestTrajectoryActionsTestParameterized, test_success_multi_point_sendgoal
302333

303334
// it should be holding the last position goal
304335
// i.e., active but trivial trajectory (one point only)
305-
if (traj_controller_->has_position_command_interface())
306-
{
307-
expectHoldingPoint(points_positions.at(1));
308-
}
309-
else
336+
expectCommandPoint(points_positions.at(1));
337+
}
338+
339+
TEST_P(TestTrajectoryActionsTestParameterized, test_success_multi_point_with_velocity_sendgoal)
340+
{
341+
// deactivate velocity tolerance and allow velocity at trajectory end
342+
std::vector<rclcpp::Parameter> params = {
343+
rclcpp::Parameter("constraints.stopped_velocity_tolerance", 0.0),
344+
rclcpp::Parameter("allow_nonzero_velocity_at_trajectory_end", true)};
345+
SetUpExecutor(params, false, 1.0, 0.0);
346+
SetUpControllerHardware();
347+
348+
// add feedback
349+
bool feedback_recv = false;
350+
goal_options_.feedback_callback =
351+
[&](
352+
rclcpp_action::ClientGoalHandle<FollowJointTrajectoryMsg>::SharedPtr,
353+
const std::shared_ptr<const FollowJointTrajectoryMsg::Feedback>) { feedback_recv = true; };
354+
355+
std::shared_future<typename GoalHandle::SharedPtr> gh_future;
356+
// send goal with multiple points
357+
std::vector<std::vector<double>> points_positions{{{4.0, 5.0, 6.0}}, {{7.0, 8.0, 9.0}}};
358+
std::vector<std::vector<double>> points_velocities{{{1.0, 1.0, 1.0}}, {{2.0, 2.0, 2.0}}};
310359
{
311-
// no integration to position state interface from velocity/acceleration
312-
expectHoldingPoint(INITIAL_POS_JOINTS);
360+
std::vector<JointTrajectoryPoint> points;
361+
JointTrajectoryPoint point1;
362+
point1.time_from_start = rclcpp::Duration::from_seconds(0.2);
363+
point1.positions = points_positions.at(0);
364+
point1.velocities = points_velocities.at(0);
365+
points.push_back(point1);
366+
367+
JointTrajectoryPoint point2;
368+
point2.time_from_start = rclcpp::Duration::from_seconds(0.3);
369+
point2.positions = points_positions.at(1);
370+
point2.velocities = points_velocities.at(1);
371+
points.push_back(point2);
372+
373+
gh_future = sendActionGoal(points, 1.0, goal_options_);
313374
}
375+
controller_hw_thread_.join();
376+
377+
EXPECT_TRUE(feedback_recv);
378+
EXPECT_TRUE(gh_future.get());
379+
EXPECT_EQ(rclcpp_action::ResultCode::SUCCEEDED, common_resultcode_);
380+
381+
// run an update
382+
updateControllerAsync(rclcpp::Duration::from_seconds(0.01));
383+
384+
// it should be holding the last position goal
385+
// i.e., active but trivial trajectory (one point only)
386+
expectCommandPoint(points_positions.at(1), points_velocities.at(1));
314387
}
315388

316389
/**
@@ -354,7 +427,7 @@ TEST_F(TestTrajectoryActions, test_goal_tolerances_single_point_success)
354427

355428
// it should be holding the last position goal
356429
// i.e., active but trivial trajectory (one point only)
357-
expectHoldingPoint(points_positions.at(0));
430+
expectCommandPoint(points_positions.at(0));
358431
}
359432

360433
/**
@@ -413,7 +486,7 @@ TEST_F(TestTrajectoryActions, test_goal_tolerances_multi_point_success)
413486

414487
// it should be holding the last position goal
415488
// i.e., active but trivial trajectory (one point only)
416-
expectHoldingPoint(points_positions.at(1));
489+
expectCommandPoint(points_positions.at(1));
417490
}
418491

419492
TEST_P(TestTrajectoryActionsTestParameterized, test_state_tolerances_fail)
@@ -464,8 +537,7 @@ TEST_P(TestTrajectoryActionsTestParameterized, test_state_tolerances_fail)
464537

465538
// it should be holding the position (being the initial one)
466539
// i.e., active but trivial trajectory (one point only)
467-
std::vector<double> initial_positions{INITIAL_POS_JOINT1, INITIAL_POS_JOINT2, INITIAL_POS_JOINT3};
468-
expectHoldingPoint(initial_positions);
540+
expectCommandPoint(INITIAL_POS_JOINTS);
469541
}
470542

471543
TEST_P(TestTrajectoryActionsTestParameterized, test_goal_tolerances_fail)
@@ -513,8 +585,7 @@ TEST_P(TestTrajectoryActionsTestParameterized, test_goal_tolerances_fail)
513585

514586
// it should be holding the position (being the initial one)
515587
// i.e., active but trivial trajectory (one point only)
516-
std::vector<double> initial_positions{INITIAL_POS_JOINT1, INITIAL_POS_JOINT2, INITIAL_POS_JOINT3};
517-
expectHoldingPoint(initial_positions);
588+
expectCommandPoint(INITIAL_POS_JOINTS);
518589
}
519590

520591
TEST_P(TestTrajectoryActionsTestParameterized, test_no_time_from_start_state_tolerance_fail)
@@ -559,8 +630,7 @@ TEST_P(TestTrajectoryActionsTestParameterized, test_no_time_from_start_state_tol
559630

560631
// it should be holding the position (being the initial one)
561632
// i.e., active but trivial trajectory (one point only)
562-
std::vector<double> initial_positions{INITIAL_POS_JOINT1, INITIAL_POS_JOINT2, INITIAL_POS_JOINT3};
563-
expectHoldingPoint(initial_positions);
633+
expectCommandPoint(INITIAL_POS_JOINTS);
564634
}
565635

566636
TEST_P(TestTrajectoryActionsTestParameterized, test_cancel_hold_position)
@@ -607,7 +677,7 @@ TEST_P(TestTrajectoryActionsTestParameterized, test_cancel_hold_position)
607677

608678
// it should be holding the last position,
609679
// i.e., active but trivial trajectory (one point only)
610-
expectHoldingPoint(cancelled_position);
680+
expectCommandPoint(cancelled_position);
611681
}
612682

613683
TEST_P(TestTrajectoryActionsTestParameterized, test_allow_nonzero_velocity_at_trajectory_end_true)

joint_trajectory_controller/test/test_trajectory_controller.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ TEST_P(TrajectoryControllerTestParameterized, correct_initialization_using_param
248248
// it should still be holding the position at time of deactivation
249249
// i.e., active but trivial trajectory (one point only)
250250
traj_controller_->update(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.1));
251-
expectHoldingPoint(deactivated_positions);
251+
expectCommandPoint(deactivated_positions);
252252

253253
executor.cancel();
254254
}
@@ -459,7 +459,7 @@ TEST_P(TrajectoryControllerTestParameterized, hold_on_startup)
459459
ASSERT_TRUE(traj_controller_->has_active_traj());
460460
// one point, being the position at startup
461461
std::vector<double> initial_positions{INITIAL_POS_JOINT1, INITIAL_POS_JOINT2, INITIAL_POS_JOINT3};
462-
expectHoldingPoint(initial_positions);
462+
expectCommandPoint(initial_positions);
463463

464464
executor.cancel();
465465
}
@@ -819,12 +819,12 @@ TEST_P(TrajectoryControllerTestParameterized, timeout)
819819
// should hold last position with zero velocity
820820
if (traj_controller_->has_position_command_interface())
821821
{
822-
expectHoldingPoint(points.at(2));
822+
expectCommandPoint(points.at(2));
823823
}
824824
else
825825
{
826826
// no integration to position state interface from velocity/acceleration
827-
expectHoldingPoint(INITIAL_POS_JOINTS);
827+
expectCommandPoint(INITIAL_POS_JOINTS);
828828
}
829829

830830
executor.cancel();
@@ -1795,7 +1795,7 @@ TEST_P(TrajectoryControllerTestParameterized, test_state_tolerances_fail)
17951795
updateControllerAsync(rclcpp::Duration(FIRST_POINT_TIME));
17961796

17971797
// it should have aborted and be holding now
1798-
expectHoldingPoint(joint_state_pos_);
1798+
expectCommandPoint(joint_state_pos_);
17991799
}
18001800

18011801
TEST_P(TrajectoryControllerTestParameterized, test_goal_tolerances_fail)
@@ -1827,14 +1827,14 @@ TEST_P(TrajectoryControllerTestParameterized, test_goal_tolerances_fail)
18271827
auto end_time = updateControllerAsync(rclcpp::Duration(4 * FIRST_POINT_TIME));
18281828

18291829
// it should have aborted and be holding now
1830-
expectHoldingPoint(joint_state_pos_);
1830+
expectCommandPoint(joint_state_pos_);
18311831

18321832
// what happens if we wait longer but it harms the tolerance again?
18331833
auto hold_position = joint_state_pos_;
18341834
joint_state_pos_.at(0) = -3.3;
18351835
updateControllerAsync(rclcpp::Duration(FIRST_POINT_TIME), end_time);
18361836
// it should be still holding the old point
1837-
expectHoldingPoint(hold_position);
1837+
expectCommandPoint(hold_position);
18381838
}
18391839

18401840
// position controllers

0 commit comments

Comments
 (0)