diff --git a/joint_trajectory_controller/CMakeLists.txt b/joint_trajectory_controller/CMakeLists.txt
index c92d61b3f3..4b0ca82df8 100644
--- a/joint_trajectory_controller/CMakeLists.txt
+++ b/joint_trajectory_controller/CMakeLists.txt
@@ -19,6 +19,7 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS
rsl
tl_expected
trajectory_msgs
+ urdf
)
find_package(ament_cmake REQUIRED)
diff --git a/joint_trajectory_controller/doc/parameters_context.yaml b/joint_trajectory_controller/doc/parameters_context.yaml
index 2ffe8aed36..4e4dacf202 100644
--- a/joint_trajectory_controller/doc/parameters_context.yaml
+++ b/joint_trajectory_controller/doc/parameters_context.yaml
@@ -9,5 +9,10 @@ gains: |
.. math:: u = k_{ff} v_d + k_p e + k_i \sum e dt + k_d (v_d - v)
- with the desired velocity :math:`v_d`, the measured velocity :math:`v`, the position error :math:`e` (definition see ``angle_wraparound`` below),
+ with the desired velocity :math:`v_d`, the measured velocity :math:`v`, the position error :math:`e` (definition see below),
the controller period :math:`dt`, and the ``velocity`` or ``effort`` manipulated variable (control variable) :math:`u`, respectively.
+
+ If the joint is of continuous type, the position error :math:`e = normalize(s_d - s)` is normalized between :math:`-\pi, \pi`,
+ i.e., the shortest rotation to the target position is the desired motion.
+ Otherwise :math:`e = s_d - s` is used, with the desired position :math:`s_d` and the measured
+ position :math:`s` from the state interface.
diff --git a/joint_trajectory_controller/include/joint_trajectory_controller/joint_trajectory_controller.hpp b/joint_trajectory_controller/include/joint_trajectory_controller/joint_trajectory_controller.hpp
index 111837cc17..ae370df0a6 100644
--- a/joint_trajectory_controller/include/joint_trajectory_controller/joint_trajectory_controller.hpp
+++ b/joint_trajectory_controller/include/joint_trajectory_controller/joint_trajectory_controller.hpp
@@ -43,6 +43,7 @@
#include "realtime_tools/realtime_server_goal_handle.h"
#include "trajectory_msgs/msg/joint_trajectory.hpp"
#include "trajectory_msgs/msg/joint_trajectory_point.hpp"
+#include "urdf/model.h"
// auto-generated by generate_parameter_library
#include "joint_trajectory_controller_parameters.hpp"
@@ -298,6 +299,8 @@ class JointTrajectoryController : public controller_interface::ControllerInterfa
void resize_joint_trajectory_point_command(
trajectory_msgs::msg::JointTrajectoryPoint & point, size_t size);
+ urdf::Model model_;
+
/**
* @brief Assigns the values from a trajectory point interface to a joint interface.
*
diff --git a/joint_trajectory_controller/package.xml b/joint_trajectory_controller/package.xml
index 8cd2e5becc..86e757d337 100644
--- a/joint_trajectory_controller/package.xml
+++ b/joint_trajectory_controller/package.xml
@@ -25,6 +25,7 @@
rsl
tl_expected
trajectory_msgs
+ urdf
ament_cmake_gmock
controller_manager
diff --git a/joint_trajectory_controller/src/joint_trajectory_controller.cpp b/joint_trajectory_controller/src/joint_trajectory_controller.cpp
index 46544bf875..6cae29e083 100644
--- a/joint_trajectory_controller/src/joint_trajectory_controller.cpp
+++ b/joint_trajectory_controller/src/joint_trajectory_controller.cpp
@@ -36,6 +36,7 @@
#include "rclcpp_action/create_server.hpp"
#include "rclcpp_action/server_goal_handle.hpp"
#include "rclcpp_lifecycle/state.hpp"
+#include "urdf/model.h"
namespace joint_trajectory_controller
{
@@ -46,6 +47,23 @@ JointTrajectoryController::JointTrajectoryController()
controller_interface::CallbackReturn JointTrajectoryController::on_init()
{
+ if (!urdf_.empty())
+ {
+ if (!model_.initString(urdf_))
+ {
+ RCLCPP_ERROR(get_node()->get_logger(), "Failed to parse URDF file");
+ }
+ else
+ {
+ RCLCPP_DEBUG(get_node()->get_logger(), "Successfully parsed URDF file");
+ }
+ }
+ else
+ {
+ // empty URDF is used for some tests
+ RCLCPP_DEBUG(get_node()->get_logger(), "No URDF file given");
+ }
+
try
{
// Create the parameter listener and get the parameters
@@ -684,12 +702,33 @@ controller_interface::CallbackReturn JointTrajectoryController::on_configure(
update_pids();
}
- // Configure joint position error normalization from ROS parameters (angle_wraparound)
+ // Configure joint position error normalization (angle_wraparound)
joints_angle_wraparound_.resize(dof_);
for (size_t i = 0; i < dof_; ++i)
{
const auto & gains = params_.gains.joints_map.at(params_.joints[i]);
- joints_angle_wraparound_[i] = gains.angle_wraparound;
+ if (gains.angle_wraparound)
+ {
+ // TODO(christophfroehlich): remove this warning in a future release (ROS-J)
+ RCLCPP_WARN(
+ logger,
+ "[Deprecated] Parameter 'gains..angle_wraparound' is deprecated. The "
+ "angle_wraparound is now used if a continuous joint is configured in the URDF.");
+ joints_angle_wraparound_[i] = true;
+ }
+
+ if (!urdf_.empty())
+ {
+ auto urdf_joint = model_.getJoint(params_.joints[i]);
+ if (urdf_joint && urdf_joint->type == urdf::Joint::CONTINUOUS)
+ {
+ RCLCPP_DEBUG(
+ logger, "joint '%s' is of type continuous, use angle_wraparound.",
+ params_.joints[i].c_str());
+ joints_angle_wraparound_[i] = true;
+ }
+ // do nothing if joint is not found in the URDF
+ }
}
if (params_.state_interfaces.empty())
diff --git a/joint_trajectory_controller/src/joint_trajectory_controller_parameters.yaml b/joint_trajectory_controller/src/joint_trajectory_controller_parameters.yaml
index ded5bb7ca3..b17229cab8 100644
--- a/joint_trajectory_controller/src/joint_trajectory_controller_parameters.yaml
+++ b/joint_trajectory_controller/src/joint_trajectory_controller_parameters.yaml
@@ -125,11 +125,8 @@ joint_trajectory_controller:
angle_wraparound: {
type: bool,
default_value: false,
- description: 'For joints that wrap around (without end stop, ie. are continuous),
- where the shortest rotation to the target position is the desired motion.
- If true, the position error :math:`e = normalize(s_d - s)` is normalized between :math:`-\pi, \pi`.
- Otherwise :math:`e = s_d - s` is used, with the desired position :math:`s_d` and the measured
- position :math:`s` from the state interface.'
+ description: "[deprecated] For joints that wrap around (ie. are continuous).
+ Normalizes position-error to -pi to pi."
}
constraints:
stopped_velocity_tolerance: {
diff --git a/joint_trajectory_controller/test/test_assets.hpp b/joint_trajectory_controller/test/test_assets.hpp
new file mode 100644
index 0000000000..ccdbaf4c8a
--- /dev/null
+++ b/joint_trajectory_controller/test/test_assets.hpp
@@ -0,0 +1,278 @@
+// Copyright 2023 ros2_control Development Team
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef TEST_ASSETS_HPP_
+#define TEST_ASSETS_HPP_
+
+#include
+
+namespace test_trajectory_controllers
+{
+const auto urdf_rrrbot_revolute =
+ R"(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)";
+
+const auto urdf_rrrbot_continuous =
+ R"(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)";
+
+} // namespace test_trajectory_controllers
+
+#endif // TEST_ASSETS_HPP_
diff --git a/joint_trajectory_controller/test/test_trajectory_controller.cpp b/joint_trajectory_controller/test/test_trajectory_controller.cpp
index 2bfe6f150d..67eb959df2 100644
--- a/joint_trajectory_controller/test/test_trajectory_controller.cpp
+++ b/joint_trajectory_controller/test/test_trajectory_controller.cpp
@@ -45,6 +45,7 @@
#include "trajectory_msgs/msg/joint_trajectory.hpp"
#include "trajectory_msgs/msg/joint_trajectory_point.hpp"
+#include "test_assets.hpp"
#include "test_trajectory_controller_utils.hpp"
using lifecycle_msgs::msg::State;
@@ -463,14 +464,15 @@ TEST_P(TrajectoryControllerTestParameterized, hold_on_startup)
const double EPS = 1e-6;
/**
- * @brief check if calculated trajectory error is correct with angle wraparound=true
+ * @brief check if calculated trajectory error is correct (angle wraparound) for continuous joints
*/
TEST_P(TrajectoryControllerTestParameterized, compute_error_angle_wraparound_true)
{
rclcpp::executors::MultiThreadedExecutor executor;
std::vector params = {};
SetUpAndActivateTrajectoryController(
- executor, params, true, 0.0, 1.0, true /* angle_wraparound */);
+ executor, params, true, 0.0, 1.0, INITIAL_POS_JOINTS, INITIAL_VEL_JOINTS, INITIAL_ACC_JOINTS,
+ INITIAL_EFF_JOINTS, test_trajectory_controllers::urdf_rrrbot_continuous);
size_t n_joints = joint_names_.size();
@@ -554,14 +556,15 @@ TEST_P(TrajectoryControllerTestParameterized, compute_error_angle_wraparound_tru
}
/**
- * @brief check if calculated trajectory error is correct with angle wraparound=false
+ * @brief check if calculated trajectory error is correct (no angle wraparound) for revolute joints
*/
TEST_P(TrajectoryControllerTestParameterized, compute_error_angle_wraparound_false)
{
rclcpp::executors::MultiThreadedExecutor executor;
std::vector params = {};
SetUpAndActivateTrajectoryController(
- executor, params, true, 0.0, 1.0, false /* angle_wraparound */);
+ executor, params, true, 0.0, 1.0, INITIAL_POS_JOINTS, INITIAL_VEL_JOINTS, INITIAL_ACC_JOINTS,
+ INITIAL_EFF_JOINTS, test_trajectory_controllers::urdf_rrrbot_revolute);
size_t n_joints = joint_names_.size();
@@ -636,15 +639,16 @@ TEST_P(TrajectoryControllerTestParameterized, compute_error_angle_wraparound_fal
}
/**
- * @brief check if position error of revolute joints are wrapped around if not configured so
+ * @brief check if position error of revolute joints aren't wrapped around (state topic)
*/
TEST_P(TrajectoryControllerTestParameterized, position_error_not_angle_wraparound)
{
rclcpp::executors::MultiThreadedExecutor executor;
constexpr double k_p = 10.0;
std::vector params = {};
- bool angle_wraparound = false;
- SetUpAndActivateTrajectoryController(executor, params, true, k_p, 0.0, angle_wraparound);
+ SetUpAndActivateTrajectoryController(
+ executor, params, true, k_p, 0.0, INITIAL_POS_JOINTS, INITIAL_VEL_JOINTS, INITIAL_ACC_JOINTS,
+ INITIAL_EFF_JOINTS, test_trajectory_controllers::urdf_rrrbot_revolute);
subscribeToState();
size_t n_joints = joint_names_.size();
@@ -737,14 +741,16 @@ TEST_P(TrajectoryControllerTestParameterized, position_error_not_angle_wraparoun
}
/**
- * @brief check if position error of revolute joints are wrapped around if configured so
+ * @brief check if position error of continuous joints are wrapped around (state topic)
*/
TEST_P(TrajectoryControllerTestParameterized, position_error_angle_wraparound)
{
rclcpp::executors::MultiThreadedExecutor executor;
constexpr double k_p = 10.0;
std::vector params = {};
- SetUpAndActivateTrajectoryController(executor, params, true, k_p, 0.0, true);
+ SetUpAndActivateTrajectoryController(
+ executor, params, true, k_p, 0.0, INITIAL_POS_JOINTS, INITIAL_VEL_JOINTS, INITIAL_ACC_JOINTS,
+ INITIAL_EFF_JOINTS, test_trajectory_controllers::urdf_rrrbot_continuous);
size_t n_joints = joint_names_.size();
@@ -1844,7 +1850,7 @@ TEST_P(TrajectoryControllerTestParameterized, test_hw_states_has_offset_first_co
std::vector initial_acc_cmd{3, std::numeric_limits::quiet_NaN()};
SetUpAndActivateTrajectoryController(
- executor, {is_open_loop_parameters}, true, 0., 1., false, initial_pos_cmd, initial_vel_cmd,
+ executor, {is_open_loop_parameters}, true, 0., 1., initial_pos_cmd, initial_vel_cmd,
initial_acc_cmd);
// no call of update method, so the values should be read from state interfaces
@@ -1888,7 +1894,7 @@ TEST_P(TrajectoryControllerTestParameterized, test_hw_states_has_offset_later_co
initial_acc_cmd.push_back(0.02 + static_cast(i) / 10.0);
}
SetUpAndActivateTrajectoryController(
- executor, {is_open_loop_parameters}, true, 0., 1., false, initial_pos_cmd, initial_vel_cmd,
+ executor, {is_open_loop_parameters}, true, 0., 1., initial_pos_cmd, initial_vel_cmd,
initial_acc_cmd);
// no call of update method, so the values should be read from command interfaces
diff --git a/joint_trajectory_controller/test/test_trajectory_controller_utils.hpp b/joint_trajectory_controller/test/test_trajectory_controller_utils.hpp
index 6978d0e452..baa4111239 100644
--- a/joint_trajectory_controller/test/test_trajectory_controller_utils.hpp
+++ b/joint_trajectory_controller/test/test_trajectory_controller_utils.hpp
@@ -215,9 +215,10 @@ class TrajectoryControllerTest : public ::testing::Test
}
void SetUpTrajectoryController(
- rclcpp::Executor & executor, const std::vector & parameters = {})
+ rclcpp::Executor & executor, const std::vector & parameters = {},
+ const std::string & urdf = "")
{
- auto ret = SetUpTrajectoryControllerLocal(parameters);
+ auto ret = SetUpTrajectoryControllerLocal(parameters, urdf);
if (ret != controller_interface::return_type::OK)
{
FAIL();
@@ -226,7 +227,7 @@ class TrajectoryControllerTest : public ::testing::Test
}
controller_interface::return_type SetUpTrajectoryControllerLocal(
- const std::vector & parameters = {})
+ const std::vector & parameters = {}, const std::string & urdf = "")
{
traj_controller_ = std::make_shared();
@@ -241,11 +242,10 @@ class TrajectoryControllerTest : public ::testing::Test
traj_controller_->set_node_options(node_options);
return traj_controller_->init(
- controller_name_, "", 0, "", traj_controller_->define_custom_node_options());
+ controller_name_, urdf, 0, "", traj_controller_->define_custom_node_options());
}
- void SetPidParameters(
- double p_value = 0.0, double ff_value = 1.0, bool angle_wraparound_value = false)
+ void SetPidParameters(double p_value = 0.0, double ff_value = 1.0)
{
traj_controller_->trigger_declare_parameters();
auto node = traj_controller_->get_node();
@@ -258,20 +258,18 @@ class TrajectoryControllerTest : public ::testing::Test
const rclcpp::Parameter k_d(prefix + ".d", 0.0);
const rclcpp::Parameter i_clamp(prefix + ".i_clamp", 0.0);
const rclcpp::Parameter ff_velocity_scale(prefix + ".ff_velocity_scale", ff_value);
- const rclcpp::Parameter angle_wraparound(
- prefix + ".angle_wraparound", angle_wraparound_value);
- node->set_parameters({k_p, k_i, k_d, i_clamp, ff_velocity_scale, angle_wraparound});
+ node->set_parameters({k_p, k_i, k_d, i_clamp, ff_velocity_scale});
}
}
void SetUpAndActivateTrajectoryController(
rclcpp::Executor & executor, const std::vector & parameters = {},
bool separate_cmd_and_state_values = false, double k_p = 0.0, double ff = 1.0,
- bool angle_wraparound = false,
const std::vector initial_pos_joints = INITIAL_POS_JOINTS,
const std::vector initial_vel_joints = INITIAL_VEL_JOINTS,
const std::vector initial_acc_joints = INITIAL_ACC_JOINTS,
- const std::vector initial_eff_joints = INITIAL_EFF_JOINTS)
+ const std::vector initial_eff_joints = INITIAL_EFF_JOINTS,
+ const std::string & urdf = "")
{
auto has_nonzero_vel_param =
std::find_if(
@@ -287,10 +285,10 @@ class TrajectoryControllerTest : public ::testing::Test
parameters_local.emplace_back("allow_nonzero_velocity_at_trajectory_end", true);
}
// read-only parameters have to be set before init -> won't be read otherwise
- SetUpTrajectoryController(executor, parameters_local);
+ SetUpTrajectoryController(executor, parameters_local, urdf);
// set pid parameters before configure
- SetPidParameters(k_p, ff, angle_wraparound);
+ SetPidParameters(k_p, ff);
traj_controller_->get_node()->configure();
ActivateTrajectoryController(