Skip to content

Commit 1bfe623

Browse files
authored
Merge branch 'ros-controls:master' into issue-759
2 parents 80a243a + 64c9e2a commit 1bfe623

23 files changed

+339
-145
lines changed

Diff for: .github/workflows/reusable-ros-tooling-source-build.yml

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ jobs:
3636
with:
3737
target-ros2-distro: ${{ inputs.ros_distro }}
3838
# build all packages listed in the meta package
39+
ref: ${{ inputs.ref }} # otherwise the default branch is used for scheduled workflows
3940
package-name:
4041
controller_interface
4142
controller_manager

Diff for: .github/workflows/reviewer_lottery.yml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
jobs:
77
test:
88
runs-on: ubuntu-latest
9+
if: github.actor != 'dependabot[bot]' && github.actor != 'mergify[bot]'
910
steps:
1011
- uses: actions/checkout@v4
1112
- uses: uesteibar/reviewer-lottery@v3

Diff for: controller_manager/controller_manager/launch_utils.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ def generate_load_controller_launch_description(
3232
Examples # noqa: D416
3333
--------
3434
# Assuming the controller type and controller parameters are known to the controller_manager
35-
generate_load_controller_launch_description('joint_state_controller')
35+
generate_load_controller_launch_description('joint_state_broadcaster')
3636
3737
# Passing controller type and controller parameter file to load
3838
generate_load_controller_launch_description(
39-
'joint_state_controller',
40-
controller_type='joint_state_controller/JointStateController',
39+
'joint_state_broadcaster',
40+
controller_type='joint_state_broadcaster/JointStateBroadcaster',
4141
controller_params_file=os.path.join(get_package_share_directory('my_pkg'),
4242
'config', 'controller_params.yaml')
4343
)

Diff for: controller_manager/doc/userdoc.rst

+12-3
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ Determinism
1111
-----------
1212

1313
For best performance when controlling hardware you want the controller manager to have as little jitter as possible in the main control loop.
14-
The normal linux kernel is optimized for computational throughput and therefore is not well suited for hardware control.
15-
The two easiest kernel options are the `Real-time Ubuntu 22.04 LTS Beta <https://ubuntu.com/blog/real-time-ubuntu-released>`_ or `linux-image-rt-amd64 <https://packages.debian.org/bullseye/linux-image-rt-amd64>`_ on Debian Bullseye.
1614

17-
If you have a realtime kernel installed, the main thread of Controller Manager attempts to configure ``SCHED_FIFO`` with a priority of ``50``.
15+
Independent of the kernel installed, the main thread of Controller Manager attempts to
16+
configure ``SCHED_FIFO`` with a priority of ``50``.
1817
By default, the user does not have permission to set such a high priority.
1918
To give the user such permissions, add a group named realtime and add the user controlling your robot to this group:
2019

@@ -36,6 +35,16 @@ Afterwards, add the following limits to the realtime group in ``/etc/security/li
3635
3736
The limits will be applied after you log out and in again.
3837

38+
The normal linux kernel is optimized for computational throughput and therefore is not well suited for hardware control.
39+
Alternatives to the standard kernel include
40+
41+
- `Real-time Ubuntu 22.04 LTS Beta <https://ubuntu.com/blog/real-time-ubuntu-released>`_ on Ubuntu 22.04
42+
- `linux-image-rt-amd64 <https://packages.debian.org/bullseye/linux-image-rt-amd64>`_ on Debian Bullseye
43+
- lowlatency kernel (``sudo apt install linux-lowlatency``) on any ubuntu
44+
45+
Though installing a realtime-kernel will definitely get the best results when it comes to low
46+
jitter, using a lowlatency kernel can improve things a lot with being really easy to install.
47+
3948
Parameters
4049
-----------
4150

Diff for: controller_manager/src/controller_manager.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ controller_interface::return_type ControllerManager::switch_controller(
10071007
{
10081008
RCLCPP_WARN(
10091009
get_logger(),
1010-
"Controller with name '%s' is not inactive so its following"
1010+
"Controller with name '%s' is not inactive so its following "
10111011
"controllers do not have to be checked, because it cannot be activated.",
10121012
controller_it->info.name.c_str());
10131013
status = controller_interface::return_type::ERROR;

Diff for: controller_manager/src/ros2_control_node.cpp

+7-9
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,14 @@ int main(int argc, char ** argv)
4848
std::thread cm_thread(
4949
[cm]()
5050
{
51-
if (realtime_tools::has_realtime_kernel())
51+
if (!realtime_tools::configure_sched_fifo(kSchedPriority))
5252
{
53-
if (!realtime_tools::configure_sched_fifo(kSchedPriority))
54-
{
55-
RCLCPP_WARN(cm->get_logger(), "Could not enable FIFO RT scheduling policy");
56-
}
57-
}
58-
else
59-
{
60-
RCLCPP_INFO(cm->get_logger(), "RT kernel is recommended for better performance");
53+
RCLCPP_WARN(
54+
cm->get_logger(),
55+
"Could not enable FIFO RT scheduling policy. Consider setting up your user to do FIFO RT "
56+
"scheduling. See "
57+
"[https://control.ros.org/master/doc/ros2_control/controller_manager/doc/userdoc.html] "
58+
"for details.");
6159
}
6260

6361
// for calculating sleep time

Diff for: hardware_interface/include/hardware_interface/component_parser.hpp

-3
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,5 @@ namespace hardware_interface
3333
HARDWARE_INTERFACE_PUBLIC
3434
std::vector<HardwareInfo> parse_control_resources_from_urdf(const std::string & urdf);
3535

36-
HARDWARE_INTERFACE_PUBLIC
37-
bool parse_bool(const std::string & bool_string);
38-
3936
} // namespace hardware_interface
4037
#endif // HARDWARE_INTERFACE__COMPONENT_PARSER_HPP_
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2023 ros2_control Development Team
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef HARDWARE_INTERFACE__LEXICAL_CASTS_HPP_
16+
#define HARDWARE_INTERFACE__LEXICAL_CASTS_HPP_
17+
18+
#include <locale>
19+
#include <sstream>
20+
#include <stdexcept>
21+
#include <string>
22+
23+
namespace hardware_interface
24+
{
25+
26+
/** \brief Helper function to convert a std::string to double in a locale-independent way.
27+
\throws std::invalid_argument if not a valid number
28+
* from
29+
https://github.com/ros-planning/srdfdom/blob/ad17b8d25812f752c397a6011cec64aeff090c46/src/model.cpp#L53
30+
*/
31+
double stod(const std::string & s)
32+
{
33+
// convert from string using no locale
34+
std::istringstream stream(s);
35+
stream.imbue(std::locale::classic());
36+
double result;
37+
stream >> result;
38+
if (stream.fail() || !stream.eof())
39+
{
40+
throw std::invalid_argument("Failed converting string to real number");
41+
}
42+
return result;
43+
}
44+
45+
bool parse_bool(const std::string & bool_string)
46+
{
47+
return bool_string == "true" || bool_string == "True";
48+
}
49+
50+
} // namespace hardware_interface
51+
52+
#endif // HARDWARE_INTERFACE__LEXICAL_CASTS_HPP_

Diff for: hardware_interface/src/component_parser.cpp

+12-19
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include "hardware_interface/component_parser.hpp"
2525
#include "hardware_interface/hardware_info.hpp"
26+
#include "hardware_interface/lexical_casts.hpp"
2627

2728
namespace
2829
{
@@ -128,26 +129,23 @@ double get_parameter_value_or(
128129
{
129130
while (params_it)
130131
{
131-
// Fill the map with parameters
132-
const auto tag_name = params_it->Name();
133-
if (strcmp(tag_name, parameter_name) == 0)
132+
try
134133
{
135-
const auto tag_text = params_it->GetText();
136-
if (tag_text)
134+
// Fill the map with parameters
135+
const auto tag_name = params_it->Name();
136+
if (strcmp(tag_name, parameter_name) == 0)
137137
{
138-
// Parse and return double value if there is no parsing error
139-
double result_value;
140-
const auto parse_result =
141-
std::from_chars(tag_text, tag_text + std::strlen(tag_text), result_value);
142-
if (parse_result.ec == std::errc())
138+
const auto tag_text = params_it->GetText();
139+
if (tag_text)
143140
{
144-
return result_value;
141+
return hardware_interface::stod(tag_text);
145142
}
146-
147-
// Parsing failed - exit loop and return default value
148-
break;
149143
}
150144
}
145+
catch (const std::exception & e)
146+
{
147+
return default_value;
148+
}
151149

152150
params_it = params_it->NextSiblingElement();
153151
}
@@ -616,9 +614,4 @@ std::vector<HardwareInfo> parse_control_resources_from_urdf(const std::string &
616614
return hardware_info;
617615
}
618616

619-
bool parse_bool(const std::string & bool_string)
620-
{
621-
return bool_string == "true" || bool_string == "True";
622-
}
623-
624617
} // namespace hardware_interface

Diff for: hardware_interface/src/mock_components/generic_system.cpp

+4-14
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,12 @@
2626
#include <vector>
2727

2828
#include "hardware_interface/component_parser.hpp"
29+
#include "hardware_interface/lexical_casts.hpp"
2930
#include "hardware_interface/types/hardware_interface_type_values.hpp"
3031
#include "rcutils/logging_macros.h"
3132

3233
namespace mock_components
3334
{
34-
double parse_double(const std::string & text)
35-
{
36-
double result_value;
37-
const auto parse_result = std::from_chars(text.data(), text.data() + text.size(), result_value);
38-
if (parse_result.ec == std::errc())
39-
{
40-
return result_value;
41-
}
42-
43-
return 0.0;
44-
}
4535

4636
CallbackReturn GenericSystem::on_init(const hardware_interface::HardwareInfo & info)
4737
{
@@ -123,7 +113,7 @@ CallbackReturn GenericSystem::on_init(const hardware_interface::HardwareInfo & i
123113
it = info_.hardware_parameters.find("position_state_following_offset");
124114
if (it != info_.hardware_parameters.end())
125115
{
126-
position_state_following_offset_ = parse_double(it->second);
116+
position_state_following_offset_ = hardware_interface::stod(it->second);
127117
it = info_.hardware_parameters.find("custom_interface_with_following_offset");
128118
if (it != info_.hardware_parameters.end())
129119
{
@@ -169,7 +159,7 @@ CallbackReturn GenericSystem::on_init(const hardware_interface::HardwareInfo & i
169159
auto param_it = joint.parameters.find("multiplier");
170160
if (param_it != joint.parameters.end())
171161
{
172-
mimic_joint.multiplier = parse_double(joint.parameters.at("multiplier"));
162+
mimic_joint.multiplier = hardware_interface::stod(joint.parameters.at("multiplier"));
173163
}
174164
mimic_joints_.push_back(mimic_joint);
175165
}
@@ -696,7 +686,7 @@ void GenericSystem::initialize_storage_vectors(
696686
// Check the initial_value param is used
697687
if (!interface.initial_value.empty())
698688
{
699-
states[index][i] = parse_double(interface.initial_value);
689+
states[index][i] = hardware_interface::stod(interface.initial_value);
700690
}
701691
}
702692
}

Diff for: hardware_interface/test/test_components/test_actuator.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,15 @@ class TestActuator : public ActuatorInterface
120120
double max_velocity_command_ = 0.0;
121121
};
122122

123+
class TestUnitilizableActuator : public TestActuator
124+
{
125+
CallbackReturn on_init(const hardware_interface::HardwareInfo & info) override
126+
{
127+
ActuatorInterface::on_init(info);
128+
return CallbackReturn::ERROR;
129+
}
130+
};
131+
123132
#include "pluginlib/class_list_macros.hpp" // NOLINT
124133
PLUGINLIB_EXPORT_CLASS(TestActuator, hardware_interface::ActuatorInterface)
134+
PLUGINLIB_EXPORT_CLASS(TestUnitilizableActuator, hardware_interface::ActuatorInterface)

Diff for: hardware_interface/test/test_components/test_components.xml

+18
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,22 @@
1717
Test System
1818
</description>
1919
</class>
20+
21+
<class name="test_unitilizable_actuator" type="TestUnitilizableActuator" base_class_type="hardware_interface::ActuatorInterface">
22+
<description>
23+
Test Unitilizable Actuator
24+
</description>
25+
</class>
26+
27+
<class name="test_unitilizable_sensor" type="TestUnitilizableSensor" base_class_type="hardware_interface::SensorInterface">
28+
<description>
29+
Test Unitilizable Sensor
30+
</description>
31+
</class>
32+
33+
<class name="test_unitilizable_system" type="TestUnitilizableSystem" base_class_type="hardware_interface::SystemInterface">
34+
<description>
35+
Test Unitilizable System
36+
</description>
37+
</class>
2038
</library>

Diff for: hardware_interface/test/test_components/test_sensor.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,15 @@ class TestSensor : public SensorInterface
5555
double velocity_state_ = 0.0;
5656
};
5757

58+
class TestUnitilizableSensor : public TestSensor
59+
{
60+
CallbackReturn on_init(const hardware_interface::HardwareInfo & info) override
61+
{
62+
SensorInterface::on_init(info);
63+
return CallbackReturn::ERROR;
64+
}
65+
};
66+
5867
#include "pluginlib/class_list_macros.hpp" // NOLINT
5968
PLUGINLIB_EXPORT_CLASS(TestSensor, hardware_interface::SensorInterface)
69+
PLUGINLIB_EXPORT_CLASS(TestUnitilizableSensor, hardware_interface::SensorInterface)

Diff for: hardware_interface/test/test_components/test_system.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,15 @@ class TestSystem : public SystemInterface
123123
double configuration_command_ = 0.0;
124124
};
125125

126+
class TestUnitilizableSystem : public TestSystem
127+
{
128+
CallbackReturn on_init(const hardware_interface::HardwareInfo & info) override
129+
{
130+
SystemInterface::on_init(info);
131+
return CallbackReturn::ERROR;
132+
}
133+
};
134+
126135
#include "pluginlib/class_list_macros.hpp" // NOLINT
127136
PLUGINLIB_EXPORT_CLASS(TestSystem, hardware_interface::SystemInterface)
137+
PLUGINLIB_EXPORT_CLASS(TestUnitilizableSystem, hardware_interface::SystemInterface)

Diff for: hardware_interface/test/test_resource_manager.cpp

+40
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class TestableResourceManager : public hardware_interface::ResourceManager
6969
FRIEND_TEST(ResourceManagerTest, post_initialization_add_components);
7070
FRIEND_TEST(ResourceManagerTest, managing_controllers_reference_interfaces);
7171
FRIEND_TEST(ResourceManagerTest, resource_availability_and_claiming_in_lifecycle);
72+
FRIEND_TEST(ResourceManagerTest, test_unitilizable_hardware_no_validation);
7273

7374
TestableResourceManager() : hardware_interface::ResourceManager() {}
7475

@@ -154,6 +155,45 @@ TEST_F(ResourceManagerTest, post_initialization_with_urdf)
154155
ASSERT_NO_THROW(rm.load_urdf(ros2_control_test_assets::minimal_robot_urdf));
155156
}
156157

158+
TEST_F(ResourceManagerTest, test_unitilizable_hardware_validation)
159+
{
160+
// If the the hardware can not be initialized and load_urdf tried to validate the interfaces a
161+
// runtime exception is thrown
162+
TestableResourceManager rm;
163+
ASSERT_THROW(
164+
rm.load_urdf(ros2_control_test_assets::minimal_unitilizable_robot_urdf, true),
165+
std::runtime_error);
166+
}
167+
168+
TEST_F(ResourceManagerTest, test_unitilizable_hardware_no_validation)
169+
{
170+
// If the the hardware can not be initialized and load_urdf didn't try to validate the interfaces,
171+
// the interface should not show up
172+
TestableResourceManager rm;
173+
EXPECT_NO_THROW(rm.load_urdf(ros2_control_test_assets::minimal_unitilizable_robot_urdf, false));
174+
175+
// test actuator
176+
EXPECT_FALSE(rm.state_interface_exists("joint1/position"));
177+
EXPECT_FALSE(rm.state_interface_exists("joint1/velocity"));
178+
EXPECT_FALSE(rm.command_interface_exists("joint1/position"));
179+
EXPECT_FALSE(rm.command_interface_exists("joint1/max_velocity"));
180+
181+
// test sensor
182+
EXPECT_FALSE(rm.state_interface_exists("sensor1/velocity"));
183+
184+
// test system
185+
EXPECT_FALSE(rm.state_interface_exists("joint2/position"));
186+
EXPECT_FALSE(rm.state_interface_exists("joint2/velocity"));
187+
EXPECT_FALSE(rm.state_interface_exists("joint2/acceleration"));
188+
EXPECT_FALSE(rm.command_interface_exists("joint2/velocity"));
189+
EXPECT_FALSE(rm.command_interface_exists("joint2/max_acceleration"));
190+
EXPECT_FALSE(rm.state_interface_exists("joint3/position"));
191+
EXPECT_FALSE(rm.state_interface_exists("joint3/velocity"));
192+
EXPECT_FALSE(rm.state_interface_exists("joint3/acceleration"));
193+
EXPECT_FALSE(rm.command_interface_exists("joint3/velocity"));
194+
EXPECT_FALSE(rm.command_interface_exists("joint3/max_acceleration"));
195+
}
196+
157197
TEST_F(ResourceManagerTest, initialization_with_urdf_manual_validation)
158198
{
159199
// we validate the results manually

0 commit comments

Comments
 (0)