Skip to content

Commit 77ea9b0

Browse files
committed
Parse controller/hardware interface names to support namespacing
The names of controller and hardware interfaces can now be used to control the target namespace for that interface. For instance, creating a hardware interface with the name "/my/namespaced/hw_interface" will create a node called "hw_interface" in the namespace "/my/namespaced".
1 parent 4f96928 commit 77ea9b0

11 files changed

Lines changed: 136 additions & 5 deletions

File tree

controller_interface/src/controller_interface_base.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include <string>
1919
#include <vector>
2020

21+
#include "controller_interface/helpers.hpp"
22+
2123
#include "hardware_interface/introspection.hpp"
2224
#include "lifecycle_msgs/msg/state.hpp"
2325

@@ -73,9 +75,11 @@ return_type ControllerInterfaceBase::init(
7375
return_type ControllerInterfaceBase::init(
7476
const controller_interface::ControllerInterfaceParams & params)
7577
{
78+
std::string node_name = ros2_control::get_component_node_name(params.controller_name);
79+
7680
impl_->ctrl_itf_params_ = params;
7781
impl_->node_ = std::make_shared<rclcpp_lifecycle::LifecycleNode>(
78-
params.controller_name, params.node_namespace, params.node_options,
82+
node_name, params.node_namespace, params.node_options,
7983
false); // disable LifecycleNode service interfaces
8084
impl_->lifecycle_id_.store(this->get_lifecycle_state().id(), std::memory_order_release);
8185

controller_interface/test/test_controller_interface.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,33 @@ TEST(TestableControllerInterface, init)
6666
rclcpp::shutdown();
6767
}
6868

69+
TEST(TestableControllerInterface, init_with_namespace)
70+
{
71+
char const * const argv[] = {""};
72+
int argc = arrlen(argv);
73+
rclcpp::init(argc, argv);
74+
75+
TestableControllerInterface controller;
76+
77+
// initialize, create node
78+
controller_interface::ControllerInterfaceParams params;
79+
params.controller_name =
80+
std::string(TEST_CONTROLLER_NAMESPACE) + "/" + std::string(TEST_CONTROLLER_NAME);
81+
params.robot_description = "";
82+
params.update_rate = 10;
83+
params.node_namespace = TEST_CONTROLLER_NAMESPACE;
84+
params.node_options = controller.define_custom_node_options();
85+
controller.init(params);
86+
87+
// Check names
88+
ASSERT_EQ(std::string(controller.get_node()->get_name()), std::string(TEST_CONTROLLER_NAME));
89+
ASSERT_EQ(
90+
std::string(controller.get_node()->get_namespace()), std::string(TEST_CONTROLLER_NAMESPACE));
91+
92+
controller.get_node()->shutdown();
93+
rclcpp::shutdown();
94+
}
95+
6996
TEST(TestableControllerInterface, setting_negative_update_rate_in_configure)
7097
{
7198
// mocks the declaration of overrides parameters in a yaml file

controller_interface/test/test_controller_interface.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "controller_interface/controller_interface.hpp"
1919

2020
constexpr char TEST_CONTROLLER_NAME[] = "testable_controller_interface";
21+
constexpr char TEST_CONTROLLER_NAMESPACE[] = "/test/controller/namespace";
2122

2223
class TestableControllerInterface : public controller_interface::ControllerInterface
2324
{

controller_manager/controller_manager/spawner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ def get_ros_params_files(argv):
374374

375375
# Use the first controller name for the logger/lock
376376
first_controller_name = controllers[0]["name"]
377+
first_controller_name.replace("/", "_")
377378
logger = rclpy.logging.get_logger("ros2_control_controller_spawner_" + first_controller_name)
378379

379380
try:

controller_manager/doc/userdoc.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,32 @@ When dealing with multiple controller managers, you have two options for managin
152152
namespace="rrbot",
153153
)
154154
155+
Controller Namespacing
156+
----------------------
157+
Controllers may specify a namespace that the node should be placed in via its name:
158+
159+
.. code-block:: yaml
160+
controller_manager:
161+
ros__parameters:
162+
.....
163+
164+
/my/namespaced/joint_state_broadcaster:
165+
type: joint_state_broadcaster/JointStateBroadcaster
166+
167+
.....
168+
169+
In this example, the spawned controller node will be named ``joint_state_broadcaster`` and will be placed in the namespace ``/my/namedspaced``.
170+
171+
The name specified to the ``spawn`` helper or the CLI ``load_controller`` command will be the full name specified in the ``controller_manager`` configuration:
172+
173+
.. code-block:: console
174+
$ ros2 control load_controller /my/namespaced/joint_state_broadcaster
175+
176+
This is helpful when multiple controllers share the same ``controller_manager`` but require topics/services/actions to be namespaced per controller.
177+
178+
.. note::
179+
The namespace semantics are the same as when starting a node via the launch system. Names without a preceeding ``/`` are placed in the namespace relative to the ``controller_manager`` node. Names with a preceeding ``/`` are placed in an absolute namespace.
180+
155181
Helper scripts
156182
--------------
157183
There are two scripts to interact with controller manager from launch files:

controller_manager/src/controller_manager.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2470,6 +2470,9 @@ controller_interface::ControllerInterfaceBaseSharedPtr ControllerManager::add_co
24702470
return nullptr;
24712471
}
24722472

2473+
std::string node_namespace =
2474+
ros2_control::get_component_node_namespace(controller.info.name).value_or(get_namespace());
2475+
24732476
const rclcpp::NodeOptions controller_node_options = determine_controller_node_options(controller);
24742477
// Catch whatever exception the controller might throw
24752478
try
@@ -2479,7 +2482,7 @@ controller_interface::ControllerInterfaceBaseSharedPtr ControllerManager::add_co
24792482
controller_params.robot_description = robot_description_;
24802483
controller_params.update_rate = get_update_rate();
24812484
controller_params.controller_manager_update_rate = get_update_rate();
2482-
controller_params.node_namespace = get_namespace();
2485+
controller_params.node_namespace = node_namespace;
24832486
controller_params.node_options = controller_node_options;
24842487
controller_params.hard_joint_limits = resource_manager_->get_hard_joint_limits();
24852488
controller_params.soft_joint_limits = resource_manager_->get_soft_joint_limits();

hardware_interface/doc/hardware_interface_types_userdoc.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ A generic example which shows the structure is provided below. More specific exa
4040
</joint>
4141
</ros2_control>
4242
43+
Name and Node Namespaces
44+
*****************************
45+
The name parameter of the ``<ros2_control>``-tag has :ref:`similar semantics to controller namespacing <doc/ros2_control/controller_manager/doc/userdoc:controller-namespacing>`:
46+
47+
.. code:: xml
48+
<ros2_control name="/my/namespaced/hw_interface" type="system">
49+
50+
In this example, when the hardware component is activated, the attached lifecycle node will be named ``hw_interface`` and will be placed in the ``/my/namespaced`` namespace.
51+
4352
Joints
4453
*****************************
4554
``<joint>``-tag groups the interfaces associated with the joints of physical robots and actuators.

hardware_interface/include/hardware_interface/helpers.hpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <algorithm>
1919
#include <functional>
2020
#include <map>
21+
#include <optional>
2122
#include <string>
2223
#include <unordered_map>
2324
#include <vector>
@@ -206,6 +207,36 @@ inline std::string strip(const std::string & str)
206207
return str.substr(start, end - start + 1);
207208
}
208209

210+
/**
211+
* @brief Extract the namespace from a component's (hardware/controller interface) full name.
212+
* @param str The component's full name.
213+
* @return The namespace of the component without the trailing slash
214+
*/
215+
inline std::optional<std::string> get_component_node_namespace(const std::string & component_name)
216+
{
217+
if (component_name.find("/") != std::string::npos)
218+
{
219+
return component_name.substr(0, component_name.find_last_of('/'));
220+
}
221+
222+
return {};
223+
}
224+
225+
/**
226+
* @brief Extract the name from a component's (hardware/controller interface) full name.
227+
* @param str The component's full name.
228+
* @return The name of the component without the preceeding slash
229+
*/
230+
inline std::string get_component_node_name(const std::string & component_name)
231+
{
232+
if (component_name.find("/") != std::string::npos)
233+
{
234+
return component_name.substr(component_name.find_last_of('/') + 1, component_name.length());
235+
}
236+
237+
return component_name;
238+
}
239+
209240
} // namespace ros2_control
210241

211242
#endif // HARDWARE_INTERFACE__HELPERS_HPP_

hardware_interface/src/hardware_component_interface.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include <string>
2020
#include <vector>
2121

22+
#include "hardware_interface/helpers.hpp"
23+
2224
#include "rclcpp/node_options.hpp"
2325

2426
namespace hardware_interface
@@ -122,8 +124,8 @@ CallbackReturn HardwareComponentInterface::init(
122124

123125
if (auto locked_executor = params.executor.lock())
124126
{
125-
std::string node_name = hardware_interface::to_lower_case(params.hardware_info.name);
126-
std::replace(node_name.begin(), node_name.end(), '/', '_');
127+
std::string node_name = ros2_control::get_component_node_name(
128+
hardware_interface::to_lower_case(params.hardware_info.name));
127129

128130
auto options = define_custom_node_options();
129131
options.arguments({"--ros-args", "-r", "__node:=" + node_name});

hardware_interface/src/resource_manager.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1566,12 +1566,17 @@ bool ResourceManager::load_and_initialize_components(
15661566
components_are_loaded_and_initialized_ = false;
15671567
break;
15681568
}
1569+
1570+
std::string node_namespace =
1571+
ros2_control::get_component_node_namespace(individual_hardware_info.name)
1572+
.value_or(params.node_namespace);
1573+
15691574
hardware_interface::HardwareComponentParams interface_params;
15701575
interface_params.hardware_info = individual_hardware_info;
15711576
interface_params.executor = params.executor;
15721577
interface_params.clock = params.clock;
15731578
interface_params.logger = params.logger;
1574-
interface_params.node_namespace = params.node_namespace;
1579+
interface_params.node_namespace = node_namespace;
15751580

15761581
if (individual_hardware_info.type == actuator_type)
15771582
{

0 commit comments

Comments
 (0)