Skip to content

Commit 7f9d20e

Browse files
authored
Fix disabling joint limits via URDF (#2992)
1 parent 4aecbd3 commit 7f9d20e

File tree

6 files changed

+271
-13
lines changed

6 files changed

+271
-13
lines changed

hardware_interface/include/hardware_interface/hardware_info.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ struct ComponentInfo
8686
/// Hold the value of the mimic attribute if given, NOT_SET otherwise
8787
MimicAttribute is_mimic = MimicAttribute::NOT_SET;
8888

89+
/// Whether limits are enabled for this component (set at joint level with <limits enable="..."/>)
90+
bool enable_limits = true;
91+
8992
/**
9093
* Name of the command interfaces that can be set, e.g. "position", "velocity", etc.
9194
* Used by joints and GPIOs.

hardware_interface/src/component_parser.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -454,19 +454,20 @@ ComponentInfo parse_component_from_xml(const tinyxml2::XMLElement * component_it
454454
}
455455

456456
// Option enable or disable the interface limits, by default they are enabled
457-
bool enable_limits = true;
457+
component.enable_limits = true;
458458
const auto * limits_it = component_it->FirstChildElement(kLimitsTag);
459459
if (limits_it)
460460
{
461-
enable_limits = parse_bool(get_attribute_value(limits_it, kEnableAttribute, limits_it->Name()));
461+
component.enable_limits =
462+
parse_bool(get_attribute_value(limits_it, kEnableAttribute, limits_it->Name()));
462463
}
463464

464465
// Parse all command interfaces
465466
const auto * command_interfaces_it = component_it->FirstChildElement(kCommandInterfaceTag);
466467
while (command_interfaces_it)
467468
{
468469
InterfaceInfo cmd_info = parse_interfaces_from_xml(command_interfaces_it);
469-
cmd_info.enable_limits &= enable_limits;
470+
cmd_info.enable_limits &= component.enable_limits;
470471
component.command_interfaces.push_back(cmd_info);
471472
command_interfaces_it = command_interfaces_it->NextSiblingElement(kCommandInterfaceTag);
472473
}
@@ -476,7 +477,7 @@ ComponentInfo parse_component_from_xml(const tinyxml2::XMLElement * component_it
476477
while (state_interfaces_it)
477478
{
478479
InterfaceInfo state_info = parse_interfaces_from_xml(state_interfaces_it);
479-
state_info.enable_limits &= enable_limits;
480+
state_info.enable_limits &= component.enable_limits;
480481
component.state_interfaces.push_back(state_info);
481482
state_interfaces_it = state_interfaces_it->NextSiblingElement(kStateInterfaceTag);
482483
}
@@ -905,13 +906,18 @@ void set_custom_interface_values(const InterfaceInfo & itr, joint_limits::JointL
905906
/**
906907
* @brief Retrieve the limits from ros2_control command interface tags and override URDF limits if
907908
* restrictive
908-
* @param interfaces The interfaces to retrieve the limits from.
909+
* @param joint The joint component info containing interfaces and joint-level enable_limits.
909910
* @param limits The joint limits to be set.
910911
*/
911-
void update_interface_limits(
912-
const std::vector<InterfaceInfo> & interfaces, joint_limits::JointLimits & limits)
912+
void update_interface_limits(const ComponentInfo & joint, joint_limits::JointLimits & limits)
913913
{
914-
for (auto & itr : interfaces)
914+
// If limits are disabled at the joint level, disable all limit flags
915+
if (!joint.enable_limits)
916+
{
917+
limits.disable_all_limits();
918+
return;
919+
}
920+
for (auto & itr : joint.command_interfaces)
915921
{
916922
if (itr.name == hardware_interface::HW_IF_POSITION)
917923
{
@@ -1084,7 +1090,7 @@ std::vector<HardwareInfo> parse_control_resources_from_urdf(const std::string &
10841090
joint_limits::JointLimits limits;
10851091
getJointLimits(urdf_joint, limits);
10861092
// Take the most restricted one. Also valid for continuous-joint type only
1087-
detail::update_interface_limits(joint.command_interfaces, limits);
1093+
detail::update_interface_limits(joint, limits);
10881094
hw_info.limits[joint.name] = limits;
10891095
joint_limits::SoftJointLimits soft_limits;
10901096
if (getSoftJointLimits(urdf_joint, soft_limits))

hardware_interface/src/resource_manager.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,10 +761,32 @@ class ResourceStorage
761761

762762
void import_joint_limiters(const std::vector<HardwareInfo> & hardware_infos)
763763
{
764+
const auto are_joint_limits_enabled =
765+
[&](
766+
const std::vector<hardware_interface::ComponentInfo> & comp_info,
767+
const std::string & joint_name) -> bool
768+
{
769+
for (const auto & joint_component_info : comp_info)
770+
{
771+
if (joint_component_info.name == joint_name && !joint_component_info.enable_limits)
772+
{
773+
return false;
774+
}
775+
}
776+
return true;
777+
};
764778
for (const auto & hw_info : hardware_infos)
765779
{
766780
for (const auto & [joint_name, limits] : hw_info.limits)
767781
{
782+
if (!are_joint_limits_enabled(hw_info.joints, joint_name))
783+
{
784+
RCLCPP_INFO(
785+
get_logger(), "Joint limits are disabled for joint '%s' in hardware '%s'",
786+
joint_name.c_str(), hw_info.name.c_str());
787+
continue;
788+
}
789+
768790
std::vector<joint_limits::SoftJointLimits> soft_limits;
769791
hard_joint_limits_.insert({joint_name, limits});
770792
const std::vector<joint_limits::JointLimits> hard_limits{limits};

hardware_interface/test/test_component_parser.cpp

Lines changed: 158 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,8 +1216,12 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_system_and_disabled_in
12161216
EXPECT_FALSE(hardware_info.limits.at("joint2").has_acceleration_limits);
12171217
EXPECT_FALSE(hardware_info.limits.at("joint2").has_deceleration_limits);
12181218
EXPECT_FALSE(hardware_info.limits.at("joint2").has_jerk_limits);
1219-
EXPECT_THAT(hardware_info.limits.at("joint2").max_velocity, DoubleNear(0.2, 1e-5));
1220-
EXPECT_THAT(hardware_info.limits.at("joint2").max_effort, DoubleNear(0.1, 1e-5));
1219+
EXPECT_THAT(
1220+
hardware_info.limits.at("joint2").max_velocity,
1221+
DoubleNear(std::numeric_limits<double>::max(), 1e-5));
1222+
EXPECT_THAT(
1223+
hardware_info.limits.at("joint2").max_effort,
1224+
DoubleNear(std::numeric_limits<double>::max(), 1e-5));
12211225
EXPECT_THAT(hardware_info.soft_limits.at("joint2").max_position, DoubleNear(0.5, 1e-5));
12221226
EXPECT_THAT(hardware_info.soft_limits.at("joint2").min_position, DoubleNear(-1.5, 1e-5));
12231227
EXPECT_THAT(hardware_info.soft_limits.at("joint2").k_position, DoubleNear(10.0, 1e-5));
@@ -1339,8 +1343,12 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_system_and_unavailable
13391343
EXPECT_FALSE(hardware_info.limits.at("joint2").has_acceleration_limits);
13401344
EXPECT_FALSE(hardware_info.limits.at("joint2").has_deceleration_limits);
13411345
EXPECT_FALSE(hardware_info.limits.at("joint2").has_jerk_limits);
1342-
EXPECT_THAT(hardware_info.limits.at("joint2").max_velocity, DoubleNear(0.2, 1e-5));
1343-
EXPECT_THAT(hardware_info.limits.at("joint2").max_effort, DoubleNear(0.1, 1e-5));
1346+
EXPECT_THAT(
1347+
hardware_info.limits.at("joint2").max_velocity,
1348+
DoubleNear(std::numeric_limits<double>::max(), 1e-5));
1349+
EXPECT_THAT(
1350+
hardware_info.limits.at("joint2").max_effort,
1351+
DoubleNear(std::numeric_limits<double>::max(), 1e-5));
13441352

13451353
EXPECT_TRUE(hardware_info.limits.at("joint3").has_position_limits);
13461354
EXPECT_THAT(hardware_info.limits.at("joint3").min_position, DoubleNear(-M_PI, 1e-5));
@@ -1925,6 +1933,152 @@ TEST_F(TestComponentParser, parse_gpio_command_interface_descriptions_from_hardw
19251933
EXPECT_EQ(gpio_state_descriptions[1].get_name(), "flange_vacuum/vacuum");
19261934
}
19271935

1936+
TEST_F(TestComponentParser, successfully_parse_urdf_with_disabled_limits)
1937+
{
1938+
const auto urdf_to_test =
1939+
std::string(ros2_control_test_assets::urdf_head) +
1940+
std::string(ros2_control_test_assets::hardware_resources_with_disabled_limits) +
1941+
std::string(ros2_control_test_assets::urdf_tail);
1942+
1943+
std::vector<hardware_interface::HardwareInfo> hw_info;
1944+
ASSERT_NO_THROW(hw_info = parse_control_resources_from_urdf(urdf_to_test));
1945+
ASSERT_THAT(hw_info, SizeIs(3));
1946+
1947+
// First hardware: TestActuatorHardware with joint1
1948+
// joint1 has <limits enable="false"/> at the joint level, so all interfaces should have
1949+
// enable_limits=false
1950+
EXPECT_EQ(hw_info[0].name, "TestActuatorHardware");
1951+
EXPECT_EQ(hw_info[0].type, "actuator");
1952+
ASSERT_THAT(hw_info[0].joints, SizeIs(1));
1953+
EXPECT_EQ(hw_info[0].joints[0].name, "joint1");
1954+
1955+
// All command interfaces on joint1 should have enable_limits=false
1956+
ASSERT_THAT(hw_info[0].joints[0].command_interfaces, SizeIs(4));
1957+
EXPECT_EQ(hw_info[0].joints[0].command_interfaces[0].name, HW_IF_POSITION);
1958+
EXPECT_FALSE(hw_info[0].joints[0].command_interfaces[0].enable_limits);
1959+
EXPECT_EQ(hw_info[0].joints[0].command_interfaces[1].name, HW_IF_VELOCITY);
1960+
EXPECT_FALSE(hw_info[0].joints[0].command_interfaces[1].enable_limits);
1961+
EXPECT_EQ(hw_info[0].joints[0].command_interfaces[2].name, HW_IF_EFFORT);
1962+
EXPECT_FALSE(hw_info[0].joints[0].command_interfaces[2].enable_limits);
1963+
EXPECT_EQ(hw_info[0].joints[0].command_interfaces[3].name, "max_velocity");
1964+
EXPECT_FALSE(hw_info[0].joints[0].command_interfaces[3].enable_limits);
1965+
1966+
// All state interfaces on joint1 should have enable_limits=false
1967+
ASSERT_THAT(hw_info[0].joints[0].state_interfaces, SizeIs(2));
1968+
EXPECT_EQ(hw_info[0].joints[0].state_interfaces[0].name, HW_IF_POSITION);
1969+
EXPECT_FALSE(hw_info[0].joints[0].state_interfaces[0].enable_limits);
1970+
EXPECT_EQ(hw_info[0].joints[0].state_interfaces[1].name, HW_IF_VELOCITY);
1971+
EXPECT_FALSE(hw_info[0].joints[0].state_interfaces[1].enable_limits);
1972+
1973+
// Verify that joint1 limits are disabled (has_*_limits should be false)
1974+
ASSERT_THAT(hw_info[0].limits, SizeIs(1));
1975+
EXPECT_FALSE(hw_info[0].limits.at("joint1").has_position_limits);
1976+
EXPECT_FALSE(hw_info[0].limits.at("joint1").has_velocity_limits);
1977+
EXPECT_FALSE(hw_info[0].limits.at("joint1").has_effort_limits);
1978+
EXPECT_FALSE(hw_info[0].limits.at("joint1").has_acceleration_limits);
1979+
EXPECT_FALSE(hw_info[0].limits.at("joint1").has_deceleration_limits);
1980+
EXPECT_FALSE(hw_info[0].limits.at("joint1").has_jerk_limits);
1981+
1982+
// Second hardware: TestSensorHardware - no limits element, so enable_limits should be true
1983+
// (default)
1984+
EXPECT_EQ(hw_info[1].name, "TestSensorHardware");
1985+
EXPECT_EQ(hw_info[1].type, "sensor");
1986+
ASSERT_THAT(hw_info[1].sensors, SizeIs(1));
1987+
EXPECT_EQ(hw_info[1].sensors[0].name, "sensor1");
1988+
ASSERT_THAT(hw_info[1].sensors[0].state_interfaces, SizeIs(1));
1989+
EXPECT_EQ(hw_info[1].sensors[0].state_interfaces[0].name, HW_IF_VELOCITY);
1990+
EXPECT_TRUE(hw_info[1].sensors[0].state_interfaces[0].enable_limits);
1991+
1992+
// Third hardware: TestSystemHardware with joint2 and joint3
1993+
EXPECT_EQ(hw_info[2].name, "TestSystemHardware");
1994+
EXPECT_EQ(hw_info[2].type, "system");
1995+
ASSERT_THAT(hw_info[2].joints, SizeIs(2));
1996+
1997+
// joint2 has <limits enable="false"/> at the joint level, so all interfaces should have
1998+
// enable_limits=false
1999+
EXPECT_EQ(hw_info[2].joints[0].name, "joint2");
2000+
ASSERT_THAT(hw_info[2].joints[0].command_interfaces, SizeIs(2));
2001+
EXPECT_EQ(hw_info[2].joints[0].command_interfaces[0].name, HW_IF_VELOCITY);
2002+
EXPECT_FALSE(hw_info[2].joints[0].command_interfaces[0].enable_limits);
2003+
EXPECT_EQ(hw_info[2].joints[0].command_interfaces[1].name, "max_acceleration");
2004+
EXPECT_FALSE(hw_info[2].joints[0].command_interfaces[1].enable_limits);
2005+
2006+
ASSERT_THAT(hw_info[2].joints[0].state_interfaces, SizeIs(3));
2007+
EXPECT_EQ(hw_info[2].joints[0].state_interfaces[0].name, HW_IF_POSITION);
2008+
EXPECT_FALSE(hw_info[2].joints[0].state_interfaces[0].enable_limits);
2009+
EXPECT_EQ(hw_info[2].joints[0].state_interfaces[1].name, HW_IF_VELOCITY);
2010+
EXPECT_FALSE(hw_info[2].joints[0].state_interfaces[1].enable_limits);
2011+
EXPECT_EQ(hw_info[2].joints[0].state_interfaces[2].name, HW_IF_ACCELERATION);
2012+
EXPECT_FALSE(hw_info[2].joints[0].state_interfaces[2].enable_limits);
2013+
2014+
// joint3 has <limits enable="false"/> only on the position command interface
2015+
// Other interfaces should have enable_limits=true (default)
2016+
EXPECT_EQ(hw_info[2].joints[1].name, "joint3");
2017+
ASSERT_THAT(hw_info[2].joints[1].command_interfaces, SizeIs(2));
2018+
// Position command interface should have enable_limits=false (explicitly set)
2019+
EXPECT_EQ(hw_info[2].joints[1].command_interfaces[0].name, HW_IF_POSITION);
2020+
EXPECT_FALSE(hw_info[2].joints[1].command_interfaces[0].enable_limits);
2021+
// Velocity command interface should have enable_limits=true (default, no limits element)
2022+
EXPECT_EQ(hw_info[2].joints[1].command_interfaces[1].name, HW_IF_VELOCITY);
2023+
EXPECT_TRUE(hw_info[2].joints[1].command_interfaces[1].enable_limits);
2024+
2025+
// All state interfaces on joint3 should have enable_limits=true (default)
2026+
ASSERT_THAT(hw_info[2].joints[1].state_interfaces, SizeIs(3));
2027+
EXPECT_EQ(hw_info[2].joints[1].state_interfaces[0].name, HW_IF_POSITION);
2028+
EXPECT_TRUE(hw_info[2].joints[1].state_interfaces[0].enable_limits);
2029+
EXPECT_EQ(hw_info[2].joints[1].state_interfaces[1].name, HW_IF_VELOCITY);
2030+
EXPECT_TRUE(hw_info[2].joints[1].state_interfaces[1].enable_limits);
2031+
EXPECT_EQ(hw_info[2].joints[1].state_interfaces[2].name, HW_IF_ACCELERATION);
2032+
EXPECT_TRUE(hw_info[2].joints[1].state_interfaces[2].enable_limits);
2033+
2034+
// Verify limits for TestSystemHardware joints
2035+
ASSERT_THAT(hw_info[2].limits, SizeIs(2));
2036+
// joint2 has limits disabled at the joint level, but only has velocity command interface, even
2037+
// then all the limits should be disabled by default
2038+
EXPECT_FALSE(hw_info[2].limits.at("joint2").has_position_limits);
2039+
EXPECT_THAT(
2040+
hw_info[2].limits.at("joint2").max_position,
2041+
DoubleNear(std::numeric_limits<double>::max(), 1e-5));
2042+
EXPECT_THAT(
2043+
hw_info[2].limits.at("joint2").min_position,
2044+
DoubleNear(-std::numeric_limits<double>::max(), 1e-5));
2045+
EXPECT_FALSE(hw_info[2].limits.at("joint2").has_velocity_limits);
2046+
EXPECT_FALSE(hw_info[2].limits.at("joint2").has_effort_limits);
2047+
EXPECT_FALSE(hw_info[2].limits.at("joint2").has_acceleration_limits);
2048+
EXPECT_FALSE(hw_info[2].limits.at("joint2").has_deceleration_limits);
2049+
EXPECT_FALSE(hw_info[2].limits.at("joint2").has_jerk_limits);
2050+
2051+
// joint3 has only position command interface limits disabled
2052+
// position limits should be disabled (has position cmd_if with enable_limits=false)
2053+
// velocity limits remain enabled (from URDF, velocity cmd_if has enable_limits=true)
2054+
EXPECT_FALSE(hw_info[2].limits.at("joint3").has_position_limits);
2055+
EXPECT_TRUE(hw_info[2].limits.at("joint3").has_velocity_limits);
2056+
EXPECT_THAT(hw_info[2].limits.at("joint3").max_velocity, DoubleNear(0.2, 1e-5));
2057+
EXPECT_TRUE(hw_info[2].limits.at("joint3").has_effort_limits);
2058+
EXPECT_THAT(hw_info[2].limits.at("joint3").max_effort, DoubleNear(0.1, 1e-5));
2059+
EXPECT_FALSE(hw_info[2].limits.at("joint3").has_acceleration_limits);
2060+
EXPECT_FALSE(hw_info[2].limits.at("joint3").has_deceleration_limits);
2061+
EXPECT_FALSE(hw_info[2].limits.at("joint3").has_jerk_limits);
2062+
2063+
// Verify soft_limits - joint2 has a safety_controller in the URDF but since joint2's
2064+
// limits are disabled, we should still have soft_limits parsed from the URDF
2065+
ASSERT_THAT(hw_info[2].soft_limits, SizeIs(1));
2066+
EXPECT_THAT(hw_info[2].soft_limits.at("joint2").max_position, DoubleNear(0.5, 1e-5));
2067+
EXPECT_THAT(hw_info[2].soft_limits.at("joint2").min_position, DoubleNear(-1.5, 1e-5));
2068+
EXPECT_THAT(hw_info[2].soft_limits.at("joint2").k_position, DoubleNear(10.0, 1e-5));
2069+
EXPECT_THAT(hw_info[2].soft_limits.at("joint2").k_velocity, DoubleNear(20.0, 1e-5));
2070+
2071+
// GPIO interfaces - no limits element, so enable_limits should be true (default)
2072+
ASSERT_THAT(hw_info[2].gpios, SizeIs(1));
2073+
EXPECT_EQ(hw_info[2].gpios[0].name, "configuration");
2074+
ASSERT_THAT(hw_info[2].gpios[0].command_interfaces, SizeIs(1));
2075+
EXPECT_EQ(hw_info[2].gpios[0].command_interfaces[0].name, "max_tcp_jerk");
2076+
EXPECT_TRUE(hw_info[2].gpios[0].command_interfaces[0].enable_limits);
2077+
ASSERT_THAT(hw_info[2].gpios[0].state_interfaces, SizeIs(1));
2078+
EXPECT_EQ(hw_info[2].gpios[0].state_interfaces[0].name, "max_tcp_jerk");
2079+
EXPECT_TRUE(hw_info[2].gpios[0].state_interfaces[0].enable_limits);
2080+
}
2081+
19282082
TEST_F(TestComponentParser, successfully_parse_valid_sdf)
19292083
{
19302084
std::string sdf_to_test = ros2_control_test_assets::diff_drive_robot_sdf;

joint_limits/include/joint_limits/joint_limits.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,23 @@ struct JointLimits
6767
bool has_effort_limits;
6868
bool angle_wraparound;
6969

70+
void disable_all_limits()
71+
{
72+
has_position_limits = false;
73+
has_velocity_limits = false;
74+
has_acceleration_limits = false;
75+
has_deceleration_limits = false;
76+
has_jerk_limits = false;
77+
has_effort_limits = false;
78+
min_position = -std::numeric_limits<double>::max();
79+
max_position = std::numeric_limits<double>::max();
80+
max_velocity = std::numeric_limits<double>::max();
81+
max_acceleration = std::numeric_limits<double>::max();
82+
max_deceleration = std::numeric_limits<double>::max();
83+
max_jerk = std::numeric_limits<double>::max();
84+
max_effort = std::numeric_limits<double>::max();
85+
}
86+
7087
std::string to_string() const
7188
{
7289
std::stringstream ss_output;

ros2_control_test_assets/include/ros2_control_test_assets/descriptions.hpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,62 @@ const auto hardware_resources =
695695
</ros2_control>
696696
)";
697697

698+
const auto hardware_resources_with_disabled_limits =
699+
R"(
700+
<ros2_control name="TestActuatorHardware" type="actuator">
701+
<hardware>
702+
<plugin>test_actuator</plugin>
703+
</hardware>
704+
<joint name="joint1">
705+
<limits enable="false"/>
706+
<command_interface name="position"/>
707+
<command_interface name="velocity"/>
708+
<command_interface name="effort"/>
709+
<state_interface name="position"/>
710+
<state_interface name="velocity"/>
711+
<command_interface name="max_velocity" />
712+
</joint>
713+
</ros2_control>
714+
<ros2_control name="TestSensorHardware" type="sensor">
715+
<hardware>
716+
<plugin>test_sensor</plugin>
717+
<param name="example_param_write_for_sec">2</param>
718+
<param name="example_param_read_for_sec">2</param>
719+
</hardware>
720+
<sensor name="sensor1">
721+
<state_interface name="velocity"/>
722+
</sensor>
723+
</ros2_control>
724+
<ros2_control name="TestSystemHardware" type="system">
725+
<hardware>
726+
<plugin>test_system</plugin>
727+
<param name="example_param_write_for_sec">2</param>
728+
<param name="example_param_read_for_sec">2</param>
729+
</hardware>
730+
<joint name="joint2">
731+
<limits enable="false"/>
732+
<command_interface name="velocity"/>
733+
<state_interface name="position"/>
734+
<state_interface name="velocity"/>
735+
<state_interface name="acceleration"/>
736+
<command_interface name="max_acceleration" />
737+
</joint>
738+
<joint name="joint3">
739+
<command_interface name="position">
740+
<limits enable="false"/>
741+
</command_interface>
742+
<command_interface name="velocity"/>
743+
<state_interface name="position"/>
744+
<state_interface name="velocity"/>
745+
<state_interface name="acceleration"/>
746+
</joint>
747+
<gpio name="configuration">
748+
<command_interface name="max_tcp_jerk"/>
749+
<state_interface name="max_tcp_jerk"/>
750+
</gpio>
751+
</ros2_control>
752+
)";
753+
698754
const auto async_hardware_resources =
699755
R"(
700756
<ros2_control name="TestActuatorHardware" type="actuator" is_async="true" thread_priority="30">

0 commit comments

Comments
 (0)