Skip to content

Commit 0f08731

Browse files
[JTC] Add time-out for trajectory interfaces (#609)
1 parent cad7894 commit 0f08731

7 files changed

+212
-12
lines changed

joint_trajectory_controller/doc/parameters.rst

+10
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ allow_nonzero_velocity_at_trajectory_end (boolean)
6868

6969
Default: true
7070

71+
cmd_timeout (double)
72+
Timeout after which the input command is considered stale.
73+
Timeout is counted from the end of the trajectory (the last point).
74+
``cmd_timeout`` must be greater than ``constraints.goal_time``,
75+
otherwise ignored.
76+
77+
If zero, timeout is deactivated"
78+
79+
Default: 0.0
80+
7181
constraints (structure)
7282
Default values for tolerances if no explicit values are states in JointTrajectory message.
7383

joint_trajectory_controller/include/joint_trajectory_controller/joint_trajectory_controller.hpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,10 @@ class JointTrajectoryController : public controller_interface::ControllerInterfa
174174
// reserved storage for result of the command when closed loop pid adapter is used
175175
std::vector<double> tmp_command_;
176176

177-
realtime_tools::RealtimeBuffer<bool> rt_is_holding_; ///< are we holding position?
177+
// Timeout to consider commands old
178+
double cmd_timeout_;
179+
// Are we holding position?
180+
realtime_tools::RealtimeBuffer<bool> rt_is_holding_;
178181
// TODO(karsten1987): eventually activate and deactivate subscriber directly when its supported
179182
bool subscriber_is_active_ = false;
180183
rclcpp::Subscription<trajectory_msgs::msg::JointTrajectory>::SharedPtr joint_command_subscriber_ =

joint_trajectory_controller/include/joint_trajectory_controller/trajectory.hpp

-3
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,6 @@ class Trajectory
139139
return trajectory_msg_;
140140
}
141141

142-
JOINT_TRAJECTORY_CONTROLLER_PUBLIC
143-
rclcpp::Time get_trajectory_start_time() const { return trajectory_start_time_; }
144-
145142
JOINT_TRAJECTORY_CONTROLLER_PUBLIC
146143
bool is_sampled_already() const { return sampled_already_; }
147144

joint_trajectory_controller/src/joint_trajectory_controller.cpp

+42-8
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ controller_interface::return_type JointTrajectoryController::update(
186186
state_current_.time_from_start.set__sec(0);
187187
read_state_from_hardware(state_current_);
188188

189-
// currently carrying out a trajectory.
189+
// currently carrying out a trajectory
190190
if (has_active_trajectory())
191191
{
192192
bool first_sample = false;
@@ -211,12 +211,32 @@ controller_interface::return_type JointTrajectoryController::update(
211211

212212
if (valid_point)
213213
{
214+
const rclcpp::Time traj_start = traj_external_point_ptr_->time_from_start();
215+
// this is the time instance
216+
// - started with the first segment: when the first point will be reached (in the future)
217+
// - later: when the point of the current segment was reached
218+
const rclcpp::Time segment_time_from_start = traj_start + start_segment_itr->time_from_start;
219+
// time_difference is
220+
// - negative until first point is reached
221+
// - counting from zero to time_from_start of next point
222+
double time_difference = time.seconds() - segment_time_from_start.seconds();
214223
bool tolerance_violated_while_moving = false;
215224
bool outside_goal_tolerance = false;
216225
bool within_goal_time = true;
217-
double time_difference = 0.0;
218226
const bool before_last_point = end_segment_itr != traj_external_point_ptr_->end();
219227

228+
// have we reached the end, are not holding position, and is a timeout configured?
229+
// Check independently of other tolerances
230+
if (
231+
!before_last_point && *(rt_is_holding_.readFromRT()) == false && cmd_timeout_ > 0.0 &&
232+
time_difference > cmd_timeout_)
233+
{
234+
RCLCPP_WARN(get_node()->get_logger(), "Aborted due to command timeout");
235+
236+
traj_msg_external_point_ptr_.reset();
237+
traj_msg_external_point_ptr_.initRT(set_hold_position());
238+
}
239+
220240
// Check state/goal tolerance
221241
for (size_t index = 0; index < dof_; ++index)
222242
{
@@ -243,12 +263,6 @@ controller_interface::return_type JointTrajectoryController::update(
243263

244264
if (default_tolerances_.goal_time_tolerance != 0.0)
245265
{
246-
// if we exceed goal_time_tolerance set it to aborted
247-
const rclcpp::Time traj_start = traj_external_point_ptr_->get_trajectory_start_time();
248-
const rclcpp::Time traj_end = traj_start + start_segment_itr->time_from_start;
249-
250-
time_difference = time.seconds() - traj_end.seconds();
251-
252266
if (time_difference > default_tolerances_.goal_time_tolerance)
253267
{
254268
within_goal_time = false;
@@ -894,6 +908,26 @@ controller_interface::CallbackReturn JointTrajectoryController::on_configure(
894908
std::string(get_node()->get_name()) + "/query_state",
895909
std::bind(&JointTrajectoryController::query_state_service, this, _1, _2));
896910

911+
if (params_.cmd_timeout > 0.0)
912+
{
913+
if (params_.cmd_timeout > default_tolerances_.goal_time_tolerance)
914+
{
915+
cmd_timeout_ = params_.cmd_timeout;
916+
}
917+
else
918+
{
919+
// deactivate timeout
920+
RCLCPP_WARN(
921+
logger, "Command timeout must be higher than goal_time tolerance (%f vs. %f)",
922+
params_.cmd_timeout, default_tolerances_.goal_time_tolerance);
923+
cmd_timeout_ = 0.0;
924+
}
925+
}
926+
else
927+
{
928+
cmd_timeout_ = 0.0;
929+
}
930+
897931
return CallbackReturn::SUCCESS;
898932
}
899933

joint_trajectory_controller/src/joint_trajectory_controller_parameters.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ joint_trajectory_controller:
8383
default_value: true,
8484
description: "If false, the last velocity point has to be zero or the goal will be rejected",
8585
}
86+
cmd_timeout: {
87+
type: double,
88+
default_value: 0.0, # seconds
89+
description: "Timeout after which the input command is considered stale.
90+
Timeout is counted from the end of the trajectory (the last point).
91+
cmd_timeout must be greater than constraints.goal_time, otherwise ignored.
92+
If zero, timeout is deactivated",
93+
}
8694
gains:
8795
__map_joints:
8896
p: {

joint_trajectory_controller/test/test_trajectory_controller.cpp

+141
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,147 @@ TEST_P(TrajectoryControllerTestParameterized, position_error_not_normalized)
565565
executor.cancel();
566566
}
567567

568+
/**
569+
* @brief cmd_timeout must be greater than constraints.goal_time
570+
*/
571+
TEST_P(TrajectoryControllerTestParameterized, accept_correct_cmd_timeout)
572+
{
573+
rclcpp::executors::MultiThreadedExecutor executor;
574+
// zero is default value, just for demonstration
575+
double cmd_timeout = 3.0;
576+
rclcpp::Parameter cmd_timeout_parameter("cmd_timeout", cmd_timeout);
577+
rclcpp::Parameter goal_time_parameter("constraints.goal_time", 2.0);
578+
SetUpAndActivateTrajectoryController(
579+
executor, {cmd_timeout_parameter, goal_time_parameter}, false);
580+
581+
EXPECT_DOUBLE_EQ(cmd_timeout, traj_controller_->get_cmd_timeout());
582+
}
583+
584+
/**
585+
* @brief cmd_timeout must be greater than constraints.goal_time
586+
*/
587+
TEST_P(TrajectoryControllerTestParameterized, decline_false_cmd_timeout)
588+
{
589+
rclcpp::executors::MultiThreadedExecutor executor;
590+
// zero is default value, just for demonstration
591+
rclcpp::Parameter cmd_timeout_parameter("cmd_timeout", 1.0);
592+
rclcpp::Parameter goal_time_parameter("constraints.goal_time", 2.0);
593+
SetUpAndActivateTrajectoryController(
594+
executor, {cmd_timeout_parameter, goal_time_parameter}, false);
595+
596+
EXPECT_DOUBLE_EQ(0.0, traj_controller_->get_cmd_timeout());
597+
}
598+
599+
/**
600+
* @brief check if no timeout is triggered
601+
*/
602+
TEST_P(TrajectoryControllerTestParameterized, no_timeout)
603+
{
604+
rclcpp::executors::MultiThreadedExecutor executor;
605+
// zero is default value, just for demonstration
606+
rclcpp::Parameter cmd_timeout_parameter("cmd_timeout", 0.0);
607+
SetUpAndActivateTrajectoryController(executor, {cmd_timeout_parameter}, false);
608+
subscribeToState();
609+
610+
size_t n_joints = joint_names_.size();
611+
612+
// send msg
613+
constexpr auto FIRST_POINT_TIME = std::chrono::milliseconds(250);
614+
builtin_interfaces::msg::Duration time_from_start{rclcpp::Duration(FIRST_POINT_TIME)};
615+
// *INDENT-OFF*
616+
std::vector<std::vector<double>> points{
617+
{{3.3, 4.4, 6.6}}, {{7.7, 8.8, 9.9}}, {{10.10, 11.11, 12.12}}};
618+
std::vector<std::vector<double>> points_velocities{
619+
{{0.01, 0.01, 0.01}}, {{0.05, 0.05, 0.05}}, {{0.06, 0.06, 0.06}}};
620+
// *INDENT-ON*
621+
publish(time_from_start, points, rclcpp::Time(0, 0, RCL_STEADY_TIME), {}, points_velocities);
622+
traj_controller_->wait_for_trajectory(executor);
623+
624+
// first update
625+
updateController(rclcpp::Duration(FIRST_POINT_TIME) * 4);
626+
627+
// Spin to receive latest state
628+
executor.spin_some();
629+
auto state_msg = getState();
630+
ASSERT_TRUE(state_msg);
631+
632+
// has the msg the correct vector sizes?
633+
EXPECT_EQ(n_joints, state_msg->reference.positions.size());
634+
635+
// is the trajectory still active?
636+
EXPECT_TRUE(traj_controller_->has_active_traj());
637+
// should still hold the points from above
638+
EXPECT_TRUE(traj_controller_->has_nontrivial_traj());
639+
EXPECT_NEAR(state_msg->reference.positions[0], points.at(2).at(0), 1e-2);
640+
EXPECT_NEAR(state_msg->reference.positions[1], points.at(2).at(1), 1e-2);
641+
EXPECT_NEAR(state_msg->reference.positions[2], points.at(2).at(2), 1e-2);
642+
// value of velocities is different from above due to spline interpolation
643+
EXPECT_GT(state_msg->reference.velocities[0], 0.0);
644+
EXPECT_GT(state_msg->reference.velocities[1], 0.0);
645+
EXPECT_GT(state_msg->reference.velocities[2], 0.0);
646+
647+
executor.cancel();
648+
}
649+
650+
/**
651+
* @brief check if timeout is triggered
652+
*/
653+
TEST_P(TrajectoryControllerTestParameterized, timeout)
654+
{
655+
rclcpp::executors::MultiThreadedExecutor executor;
656+
constexpr double cmd_timeout = 0.1;
657+
rclcpp::Parameter cmd_timeout_parameter("cmd_timeout", cmd_timeout);
658+
double kp = 1.0; // activate feedback control for testing velocity/effort PID
659+
SetUpAndActivateTrajectoryController(executor, {cmd_timeout_parameter}, false, kp);
660+
subscribeToState();
661+
662+
// send msg
663+
constexpr auto FIRST_POINT_TIME = std::chrono::milliseconds(250);
664+
builtin_interfaces::msg::Duration time_from_start{rclcpp::Duration(FIRST_POINT_TIME)};
665+
// *INDENT-OFF*
666+
std::vector<std::vector<double>> points{
667+
{{3.3, 4.4, 6.6}}, {{7.7, 8.8, 9.9}}, {{10.10, 11.11, 12.12}}};
668+
std::vector<std::vector<double>> points_velocities{
669+
{{0.01, 0.01, 0.01}}, {{0.05, 0.05, 0.05}}, {{0.06, 0.06, 0.06}}};
670+
// *INDENT-ON*
671+
672+
publish(time_from_start, points, rclcpp::Time(0, 0, RCL_STEADY_TIME), {}, points_velocities);
673+
traj_controller_->wait_for_trajectory(executor);
674+
675+
// update until end of trajectory -> no timeout should have occurred
676+
updateController(rclcpp::Duration(FIRST_POINT_TIME) * 3);
677+
// is a trajectory active?
678+
EXPECT_TRUE(traj_controller_->has_active_traj());
679+
// should have the trajectory with three points
680+
EXPECT_TRUE(traj_controller_->has_nontrivial_traj());
681+
682+
// update until timeout should have happened
683+
updateController(rclcpp::Duration(FIRST_POINT_TIME));
684+
685+
// Spin to receive latest state
686+
executor.spin_some();
687+
auto state_msg = getState();
688+
ASSERT_TRUE(state_msg);
689+
690+
// after timeout, set_hold_position adds a new trajectory
691+
// is a trajectory active?
692+
EXPECT_TRUE(traj_controller_->has_active_traj());
693+
// should be not more than one point now (from hold position)
694+
EXPECT_FALSE(traj_controller_->has_nontrivial_traj());
695+
// should hold last position with zero velocity
696+
if (traj_controller_->has_position_command_interface())
697+
{
698+
expectHoldingPoint(points.at(2));
699+
}
700+
else
701+
{
702+
// no integration to position state interface from velocity/acceleration
703+
expectHoldingPoint(INITIAL_POS_JOINTS);
704+
}
705+
706+
executor.cancel();
707+
}
708+
568709
/**
569710
* @brief check if position error of revolute joints are normalized if configured so
570711
*/

joint_trajectory_controller/test/test_trajectory_controller_utils.hpp

+7
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ class TestableJointTrajectoryController
137137
return has_active_trajectory() && traj_external_point_ptr_->has_nontrivial_msg() == false;
138138
}
139139

140+
bool has_nontrivial_traj()
141+
{
142+
return has_active_trajectory() && traj_external_point_ptr_->has_nontrivial_msg();
143+
}
144+
145+
double get_cmd_timeout() { return cmd_timeout_; }
146+
140147
rclcpp::WaitSet joint_cmd_sub_wait_set_;
141148
};
142149

0 commit comments

Comments
 (0)