Skip to content

Commit f7d076a

Browse files
Implement timeout
1 parent 583b815 commit f7d076a

File tree

5 files changed

+124
-1
lines changed

5 files changed

+124
-1
lines changed

joint_trajectory_controller/include/joint_trajectory_controller/joint_trajectory_controller.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ 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+
// Timeout to consider commands old
178+
std::chrono::milliseconds cmd_timeout_{500};
179+
// save the timestamp when the last msg was received
180+
rclcpp::Time last_msg_received_;
181+
177182
// TODO(karsten1987): eventually activate and deactivate subscriber directly when its supported
178183
bool subscriber_is_active_ = false;
179184
rclcpp::Subscription<trajectory_msgs::msg::JointTrajectory>::SharedPtr joint_command_subscriber_ =

joint_trajectory_controller/src/joint_trajectory_controller.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,16 @@ controller_interface::return_type JointTrajectoryController::update(
174174
// currently carrying out a trajectory
175175
if (traj_point_active_ptr_ && (*traj_point_active_ptr_)->has_trajectory_msg())
176176
{
177+
// is timeout configured?
178+
if (params_.cmd_timeout > 0)
179+
{
180+
if (time - last_msg_received_ > cmd_timeout_)
181+
{
182+
RCLCPP_WARN(get_node()->get_logger(), "Aborted due to timeout");
183+
set_hold_position();
184+
}
185+
}
186+
177187
bool first_sample = false;
178188
// if sampling the first time, set the point before you sample
179189
if (!(*traj_point_active_ptr_)->is_sampled_already())
@@ -856,6 +866,8 @@ controller_interface::CallbackReturn JointTrajectoryController::on_configure(
856866
std::string(get_node()->get_name()) + "/query_state",
857867
std::bind(&JointTrajectoryController::query_state_service, this, _1, _2));
858868

869+
cmd_timeout_ = std::chrono::milliseconds{static_cast<int>(params_.cmd_timeout * 1000.0)};
870+
859871
return CallbackReturn::SUCCESS;
860872
}
861873

@@ -1371,6 +1383,9 @@ bool JointTrajectoryController::validate_trajectory_msg(
13711383
void JointTrajectoryController::add_new_trajectory_msg(
13721384
const std::shared_ptr<trajectory_msgs::msg::JointTrajectory> & traj_msg)
13731385
{
1386+
// we have to save this timestamp, because header timestamp can be zero by design
1387+
last_msg_received_ = this->get_node()->get_clock()->now();
1388+
13741389
traj_msg_external_point_ptr_.writeFromNonRT(traj_msg);
13751390
}
13761391

joint_trajectory_controller/src/joint_trajectory_controller_parameters.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ joint_trajectory_controller:
6666
one_of<>: [["splines", "none"]],
6767
}
6868
}
69+
cmd_timeout: {
70+
type: double,
71+
default_value: 0.0, # seconds
72+
description: "Timeout after which input command is considered staled. If zero, timeout is deactivated",
73+
}
6974
gains:
7075
__map_joints:
7176
p: {

joint_trajectory_controller/test/test_trajectory_controller.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,104 @@ TEST_P(TrajectoryControllerTestParameterized, position_error_not_normalized)
657657
executor.cancel();
658658
}
659659

660+
/**
661+
* @brief check if no timeout is triggered
662+
*/
663+
TEST_P(TrajectoryControllerTestParameterized, no_timeout)
664+
{
665+
rclcpp::executors::MultiThreadedExecutor executor;
666+
// zero is default value, just for demonstration
667+
rclcpp::Parameter cmd_timeout_parameter("cmd_timeout", 0.0);
668+
SetUpAndActivateTrajectoryController(executor, true, {cmd_timeout_parameter}, false);
669+
subscribeToState();
670+
671+
size_t n_joints = joint_names_.size();
672+
673+
// send msg
674+
constexpr auto FIRST_POINT_TIME = std::chrono::milliseconds(250);
675+
builtin_interfaces::msg::Duration time_from_start{rclcpp::Duration(FIRST_POINT_TIME)};
676+
// *INDENT-OFF*
677+
std::vector<std::vector<double>> points{
678+
{{3.3, 4.4, 6.6}}, {{7.7, 8.8, 9.9}}, {{10.10, 11.11, 12.12}}};
679+
std::vector<std::vector<double>> points_velocities{
680+
{{0.01, 0.01, 0.01}}, {{0.05, 0.05, 0.05}}, {{0.06, 0.06, 0.06}}};
681+
// *INDENT-ON*
682+
publish(time_from_start, points, rclcpp::Time(), {}, points_velocities);
683+
traj_controller_->wait_for_trajectory(executor);
684+
685+
// first update
686+
updateController(rclcpp::Duration(FIRST_POINT_TIME));
687+
688+
// Spin to receive latest state
689+
executor.spin_some();
690+
auto state_msg = getState();
691+
ASSERT_TRUE(state_msg);
692+
693+
// has the msg the correct vector sizes?
694+
EXPECT_EQ(n_joints, state_msg->reference.positions.size());
695+
696+
// is the desired position sampled?
697+
EXPECT_NEAR(state_msg->reference.positions[0], points.at(0).at(0), 1e-2);
698+
EXPECT_NEAR(state_msg->reference.positions[1], points.at(0).at(1), 1e-2);
699+
EXPECT_NEAR(state_msg->reference.positions[2], points.at(0).at(2), 1e-2);
700+
701+
executor.cancel();
702+
}
703+
704+
/**
705+
* @brief check if timeout is triggered
706+
*/
707+
TEST_P(TrajectoryControllerTestParameterized, timeout)
708+
{
709+
rclcpp::executors::MultiThreadedExecutor executor;
710+
constexpr double cmd_timeout = 0.1;
711+
rclcpp::Parameter cmd_timeout_parameter("cmd_timeout", cmd_timeout);
712+
SetUpAndActivateTrajectoryController(executor, true, {cmd_timeout_parameter}, false);
713+
subscribeToState();
714+
715+
size_t n_joints = joint_names_.size();
716+
717+
// send msg
718+
constexpr auto FIRST_POINT_TIME = std::chrono::milliseconds(250);
719+
builtin_interfaces::msg::Duration time_from_start{rclcpp::Duration(FIRST_POINT_TIME)};
720+
// *INDENT-OFF*
721+
std::vector<std::vector<double>> points{
722+
{{3.3, 4.4, 6.6}}, {{7.7, 8.8, 9.9}}, {{10.10, 11.11, 12.12}}};
723+
std::vector<std::vector<double>> points_velocities{
724+
{{0.01, 0.01, 0.01}}, {{0.05, 0.05, 0.05}}, {{0.06, 0.06, 0.06}}};
725+
// *INDENT-ON*
726+
727+
// auto clock = rclcpp::Clock(RCL_STEADY_TIME);
728+
// rclcpp::Duration wait_time = rclcpp::Duration::from_seconds(1.0);
729+
publish(time_from_start, points, rclcpp::Time(), {}, points_velocities);
730+
traj_controller_->wait_for_trajectory(executor);
731+
732+
// first update
733+
updateController(rclcpp::Duration(FIRST_POINT_TIME));
734+
735+
// Spin to receive latest state
736+
executor.spin_some();
737+
auto state_msg = getState();
738+
ASSERT_TRUE(state_msg);
739+
740+
// has the msg the correct vector sizes?
741+
EXPECT_EQ(n_joints, state_msg->reference.positions.size());
742+
743+
// TODO(christophfroehlich): using EXPECT_LT now ->
744+
// better calculate the true expected value after timeout
745+
EXPECT_LT(
746+
state_msg->reference.positions[0],
747+
INITIAL_POS_JOINTS[0] + (points.at(0).at(0) - INITIAL_POS_JOINTS[0]) * cmd_timeout / 0.25);
748+
EXPECT_LT(
749+
state_msg->reference.positions[1],
750+
INITIAL_POS_JOINTS[1] + (points.at(0).at(1) - INITIAL_POS_JOINTS[0]) * cmd_timeout / 0.25);
751+
EXPECT_LT(
752+
state_msg->reference.positions[2],
753+
INITIAL_POS_JOINTS[2] + (points.at(0).at(2) - INITIAL_POS_JOINTS[0]) * cmd_timeout / 0.25);
754+
755+
executor.cancel();
756+
}
757+
660758
/**
661759
* @brief check if position error of revolute joints are normalized if configured so
662760
*/

joint_trajectory_controller/test/test_trajectory_controller_utils.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ class TrajectoryControllerTest : public ::testing::Test
363363
const auto end_time = start_time + wait_time;
364364
while (clock.now() < end_time)
365365
{
366-
traj_controller_->update(clock.now(), clock.now() - start_time);
366+
traj_controller_->update(node_->get_clock()->now(), clock.now() - start_time);
367367
}
368368
}
369369

0 commit comments

Comments
 (0)