Skip to content

Commit 10a9d0a

Browse files
authored
Merge branch 'master' into add/overrun/parameters
2 parents 924f450 + 1506412 commit 10a9d0a

36 files changed

+1134
-164
lines changed

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ repos:
5050
args: ["--ignore=D100,D101,D102,D103,D104,D105,D106,D107,D203,D212,D404"]
5151

5252
- repo: https://github.com/psf/black
53-
rev: 25.1.0
53+
rev: 25.9.0
5454
hooks:
5555
- id: black
5656
args: ["--line-length=99"]
@@ -63,7 +63,7 @@ repos:
6363

6464
# CPP hooks
6565
- repo: https://github.com/pre-commit/mirrors-clang-format
66-
rev: v21.1.0
66+
rev: v21.1.2
6767
hooks:
6868
- id: clang-format
6969
args: ['-fallback-style=none', '-i']
@@ -133,7 +133,7 @@ repos:
133133
exclude: CHANGELOG\.rst|\.(svg|pyc|drawio)$
134134

135135
- repo: https://github.com/python-jsonschema/check-jsonschema
136-
rev: 0.33.3
136+
rev: 0.34.0
137137
hooks:
138138
- id: check-github-workflows
139139
args: ["--verbose"]

controller_interface/include/controller_interface/controller_interface_base.hpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717

1818
#include <memory>
1919
#include <string>
20+
#include <unordered_map>
2021
#include <utility>
2122
#include <vector>
2223

2324
#include "realtime_tools/async_function_handler.hpp"
2425

26+
#include "controller_interface/controller_interface_params.hpp"
2527
#include "hardware_interface/handle.hpp"
2628
#include "hardware_interface/introspection.hpp"
2729
#include "hardware_interface/loaned_command_interface.hpp"
@@ -151,10 +153,15 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
151153
*/
152154
virtual void release_interfaces();
153155

156+
[[deprecated(
157+
"Use the init(const controller_interface::ControllerInterfaceParams & params) method instead. "
158+
"This method will be removed in the future ROS 2 releases.")]]
154159
return_type init(
155160
const std::string & controller_name, const std::string & urdf, unsigned int cm_update_rate,
156161
const std::string & node_namespace, const rclcpp::NodeOptions & node_options);
157162

163+
return_type init(const controller_interface::ControllerInterfaceParams & params);
164+
158165
/// Custom configure method to read additional parameters for controller-nodes
159166
/*
160167
* Override default implementation for configure of LifecycleNode to get parameters.
@@ -201,6 +208,17 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
201208

202209
const std::string & get_robot_description() const;
203210

211+
/**
212+
* Get the unordered map of joint limits that are defined in the robot description.
213+
*/
214+
const std::unordered_map<std::string, joint_limits::JointLimits> & get_hard_joint_limits() const;
215+
216+
/**
217+
* Get the unordered map of soft joint limits that are defined in the robot description.
218+
*/
219+
const std::unordered_map<std::string, joint_limits::SoftJointLimits> & get_soft_joint_limits()
220+
const;
221+
204222
/**
205223
* Method used by the controller_manager for base NodeOptions to instantiate the Lifecycle node
206224
* of the controller upon loading the controller.
@@ -358,9 +376,8 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
358376

359377
std::shared_ptr<rclcpp_lifecycle::LifecycleNode> node_;
360378
std::unique_ptr<realtime_tools::AsyncFunctionHandler<return_type>> async_handler_;
361-
unsigned int update_rate_ = 0;
362379
bool is_async_ = false;
363-
std::string urdf_ = "";
380+
controller_interface::ControllerInterfaceParams ctrl_itf_params_;
364381
std::atomic_bool skip_async_triggers_ = false;
365382
ControllerUpdateStats trigger_stats_;
366383

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2025 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 CONTROLLER_INTERFACE__CONTROLLER_INTERFACE_PARAMS_HPP_
16+
#define CONTROLLER_INTERFACE__CONTROLLER_INTERFACE_PARAMS_HPP_
17+
18+
#include <string>
19+
#include <unordered_map>
20+
21+
#include "joint_limits/joint_limits.hpp"
22+
#include "rclcpp/node_options.hpp"
23+
24+
/**
25+
* @brief Parameters for the Controller Interface
26+
* This struct holds the parameters required to initialize a controller interface.
27+
*
28+
* @var controller_name Name of the controller.
29+
* @var robot_description The URDF or SDF description of the robot.
30+
* @var cm_update_rate The update rate of the controller manager in Hz.
31+
* @var node_namespace The namespace for the controller node.
32+
* @var node_options Options for the controller node.
33+
* @var joint_limits A map of joint names to their limits.
34+
* @var soft_joint_limits A map of joint names to their soft limits.
35+
*
36+
* This struct is used to pass parameters to the controller interface during initialization.
37+
* It allows for easy configuration of the controller's behavior and interaction with the robot's
38+
* joints.
39+
*/
40+
namespace controller_interface
41+
{
42+
43+
struct ControllerInterfaceParams
44+
{
45+
ControllerInterfaceParams() = default;
46+
47+
std::string controller_name = "";
48+
std::string robot_description = "";
49+
unsigned int update_rate = 0;
50+
51+
std::string node_namespace = "";
52+
rclcpp::NodeOptions node_options = rclcpp::NodeOptions();
53+
54+
std::unordered_map<std::string, joint_limits::JointLimits> hard_joint_limits = {};
55+
std::unordered_map<std::string, joint_limits::SoftJointLimits> soft_joint_limits = {};
56+
};
57+
58+
} // namespace controller_interface
59+
60+
#endif // CONTROLLER_INTERFACE__CONTROLLER_INTERFACE_PARAMS_HPP_

controller_interface/src/controller_interface_base.cpp

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,29 @@ return_type ControllerInterfaceBase::init(
4343
const std::string & controller_name, const std::string & urdf, unsigned int cm_update_rate,
4444
const std::string & node_namespace, const rclcpp::NodeOptions & node_options)
4545
{
46-
urdf_ = urdf;
47-
update_rate_ = cm_update_rate;
46+
controller_interface::ControllerInterfaceParams params;
47+
params.controller_name = controller_name;
48+
params.robot_description = urdf;
49+
params.update_rate = cm_update_rate;
50+
params.node_namespace = node_namespace;
51+
params.node_options = node_options;
52+
53+
return init(params);
54+
}
55+
56+
return_type ControllerInterfaceBase::init(
57+
const controller_interface::ControllerInterfaceParams & params)
58+
{
59+
ctrl_itf_params_ = params;
4860
node_ = std::make_shared<rclcpp_lifecycle::LifecycleNode>(
49-
controller_name, node_namespace, node_options,
61+
ctrl_itf_params_.controller_name, ctrl_itf_params_.node_namespace,
62+
ctrl_itf_params_.node_options,
5063
false); // disable LifecycleNode service interfaces
5164

5265
try
5366
{
5467
// no rclcpp::ParameterValue unsigned int specialization
55-
auto_declare<int>("update_rate", static_cast<int>(update_rate_));
68+
auto_declare<int>("update_rate", static_cast<int>(ctrl_itf_params_.update_rate));
5669

5770
auto_declare<bool>("is_async", false);
5871
auto_declare<int>("thread_priority", 50);
@@ -149,17 +162,17 @@ const rclcpp_lifecycle::State & ControllerInterfaceBase::configure()
149162
RCLCPP_ERROR(get_node()->get_logger(), "Update rate cannot be a negative value!");
150163
return get_lifecycle_state();
151164
}
152-
if (update_rate_ != 0u && update_rate > update_rate_)
165+
if (ctrl_itf_params_.update_rate != 0u && update_rate > ctrl_itf_params_.update_rate)
153166
{
154167
RCLCPP_WARN(
155168
get_node()->get_logger(),
156169
"The update rate of the controller : '%ld Hz' cannot be higher than the update rate of the "
157170
"controller manager : '%d Hz'. Setting it to the update rate of the controller manager.",
158-
update_rate, update_rate_);
171+
update_rate, ctrl_itf_params_.update_rate);
159172
}
160173
else
161174
{
162-
update_rate_ = static_cast<unsigned int>(update_rate);
175+
ctrl_itf_params_.update_rate = static_cast<unsigned int>(update_rate);
163176
}
164177
is_async_ = get_node()->get_parameter("is_async").as_bool();
165178
}
@@ -273,11 +286,29 @@ std::shared_ptr<const rclcpp_lifecycle::LifecycleNode> ControllerInterfaceBase::
273286
return node_;
274287
}
275288

276-
unsigned int ControllerInterfaceBase::get_update_rate() const { return update_rate_; }
289+
unsigned int ControllerInterfaceBase::get_update_rate() const
290+
{
291+
return ctrl_itf_params_.update_rate;
292+
}
277293

278294
bool ControllerInterfaceBase::is_async() const { return is_async_; }
279295

280-
const std::string & ControllerInterfaceBase::get_robot_description() const { return urdf_; }
296+
const std::string & ControllerInterfaceBase::get_robot_description() const
297+
{
298+
return ctrl_itf_params_.robot_description;
299+
}
300+
301+
const std::unordered_map<std::string, joint_limits::JointLimits> &
302+
ControllerInterfaceBase::get_hard_joint_limits() const
303+
{
304+
return ctrl_itf_params_.hard_joint_limits;
305+
}
306+
307+
const std::unordered_map<std::string, joint_limits::SoftJointLimits> &
308+
ControllerInterfaceBase::get_soft_joint_limits() const
309+
{
310+
return ctrl_itf_params_.soft_joint_limits;
311+
}
281312

282313
void ControllerInterfaceBase::wait_for_trigger_update_to_finish()
283314
{

controller_interface/test/test_controller_interface.cpp

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,21 @@ TEST(TestableControllerInterface, setting_update_rate_in_configure)
104104
node_options_arguments.push_back("-p");
105105
node_options_arguments.push_back("update_rate:=2812");
106106
node_options = node_options.arguments(node_options_arguments);
107-
ASSERT_EQ(
108-
controller.init(TEST_CONTROLLER_NAME, "", 5000.0, "", node_options),
109-
controller_interface::return_type::OK);
107+
controller_interface::ControllerInterfaceParams params;
108+
params.controller_name = TEST_CONTROLLER_NAME;
109+
params.robot_description = "";
110+
params.update_rate = 5000; // set a different update rate than the one in the node options
111+
params.node_namespace = "";
112+
params.node_options = node_options;
113+
joint_limits::JointLimits joint_limits;
114+
joint_limits.has_velocity_limits = true;
115+
joint_limits.max_velocity = 1.0;
116+
params.hard_joint_limits["joint1"] = joint_limits;
117+
joint_limits::SoftJointLimits soft_joint_limits;
118+
soft_joint_limits.min_position = -1.0;
119+
soft_joint_limits.max_position = 1.0;
120+
params.soft_joint_limits["joint1"] = soft_joint_limits;
121+
ASSERT_EQ(controller.init(params), controller_interface::return_type::OK);
110122

111123
// initialize executor to be able to get parameter update
112124
auto executor =
@@ -122,6 +134,21 @@ TEST(TestableControllerInterface, setting_update_rate_in_configure)
122134

123135
// update_rate is set to controller_manager's rate
124136
ASSERT_EQ(controller.get_update_rate(), 5000u);
137+
const auto hard_limits = controller.get_hard_joint_limits();
138+
const auto soft_limits = controller.get_soft_joint_limits();
139+
ASSERT_THAT(hard_limits, testing::SizeIs(1));
140+
ASSERT_TRUE(hard_limits.find("joint1") != hard_limits.end());
141+
ASSERT_THAT(hard_limits.at("joint1").max_velocity, joint_limits.max_velocity);
142+
ASSERT_TRUE(hard_limits.at("joint1").has_velocity_limits);
143+
ASSERT_FALSE(hard_limits.at("joint1").has_position_limits);
144+
ASSERT_FALSE(hard_limits.at("joint1").has_acceleration_limits);
145+
ASSERT_FALSE(hard_limits.at("joint1").has_deceleration_limits);
146+
ASSERT_FALSE(hard_limits.at("joint1").has_jerk_limits);
147+
ASSERT_FALSE(hard_limits.at("joint1").has_effort_limits);
148+
ASSERT_THAT(soft_limits, testing::SizeIs(1));
149+
ASSERT_TRUE(soft_limits.find("joint1") != soft_limits.end());
150+
ASSERT_THAT(soft_limits.at("joint1").min_position, soft_joint_limits.min_position);
151+
ASSERT_THAT(soft_limits.at("joint1").max_position, soft_joint_limits.max_position);
125152

126153
// Even after configure is 0
127154
controller.configure();
@@ -156,6 +183,23 @@ TEST(TestableControllerInterface, setting_update_rate_in_configure)
156183
controller.configure();
157184
ASSERT_EQ(controller.get_update_rate(), 623u);
158185

186+
// Should stay same after multiple cleanups as it is set during initialization
187+
const auto hard_limits_final = controller.get_hard_joint_limits();
188+
const auto soft_limits_final = controller.get_soft_joint_limits();
189+
ASSERT_THAT(hard_limits_final, testing::SizeIs(1));
190+
ASSERT_TRUE(hard_limits_final.find("joint1") != hard_limits_final.end());
191+
ASSERT_THAT(hard_limits_final.at("joint1").max_velocity, joint_limits.max_velocity);
192+
ASSERT_TRUE(hard_limits_final.at("joint1").has_velocity_limits);
193+
ASSERT_FALSE(hard_limits_final.at("joint1").has_position_limits);
194+
ASSERT_FALSE(hard_limits_final.at("joint1").has_acceleration_limits);
195+
ASSERT_FALSE(hard_limits_final.at("joint1").has_deceleration_limits);
196+
ASSERT_FALSE(hard_limits_final.at("joint1").has_jerk_limits);
197+
ASSERT_FALSE(hard_limits_final.at("joint1").has_effort_limits);
198+
ASSERT_THAT(soft_limits_final, testing::SizeIs(1));
199+
ASSERT_TRUE(soft_limits_final.find("joint1") != soft_limits_final.end());
200+
ASSERT_THAT(soft_limits_final.at("joint1").min_position, soft_joint_limits.min_position);
201+
ASSERT_THAT(soft_limits_final.at("joint1").max_position, soft_joint_limits.max_position);
202+
159203
executor->cancel();
160204
controller.get_node()->shutdown();
161205
rclcpp::shutdown();

controller_manager/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,20 @@ if(BUILD_TESTING)
184184
DESTINATION lib
185185
)
186186

187+
add_library(test_controller_failed_activate SHARED
188+
test/test_controller_failed_activate/test_controller_failed_activate.cpp
189+
)
190+
target_link_libraries(test_controller_failed_activate PUBLIC
191+
controller_manager
192+
)
193+
target_compile_definitions(test_controller_failed_activate PRIVATE "CONTROLLER_MANAGER_BUILDING_DLL")
194+
pluginlib_export_plugin_description_file(
195+
controller_interface test/test_controller_failed_activate/test_controller_failed_activate.xml)
196+
install(
197+
TARGETS test_controller_failed_activate
198+
DESTINATION lib
199+
)
200+
187201
ament_add_gmock(test_release_interfaces
188202
test/test_release_interfaces.cpp
189203
APPEND_ENV AMENT_PREFIX_PATH=${ament_index_build_path}_$<CONFIG>
@@ -192,6 +206,7 @@ if(BUILD_TESTING)
192206
controller_manager
193207
test_controller
194208
test_controller_with_interfaces
209+
test_controller_failed_activate
195210
ros2_control_test_assets::ros2_control_test_assets
196211
)
197212

controller_manager/controller_manager/spawner.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@ def main(args=None):
326326
if not unload_controllers_upon_exit:
327327
return 0
328328

329+
# The lock has to be released to not block other spawner instances while waiting for the interrupt
330+
lock.release()
329331
logger.info("Waiting until interrupt to unload controllers")
330332
while True:
331333
time.sleep(1)
@@ -381,7 +383,8 @@ def main(args=None):
381383
finally:
382384
if node:
383385
node.destroy_node()
384-
lock.release()
386+
if lock.is_locked:
387+
lock.release()
385388
rclpy.shutdown()
386389

387390

controller_manager/doc/userdoc.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,12 @@ Hardware and Controller Errors
409409
If the hardware during it's ``read`` or ``write`` method returns ``return_type::ERROR``, the controller manager will stop all controllers that are using the hardware's command and state interfaces.
410410
Likewise, if a controller returns ``return_type::ERROR`` from its ``update`` method, the controller manager will deactivate the respective controller. In future, the controller manager will try to start any fallback controllers if available.
411411

412+
Factors that affect Determinism
413+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
414+
When run under the conditions determined in the above section, the determinism is assured up to the limitations of the hardware and the real-time kernel. However, there are some situations that can affect determinism:
415+
416+
* When a controller fails to activate, the controller_manager will call the methods ``prepare_command_mode_switch`` and ``perform_command_mode_switch`` to stop the started interfaces. These calls can cause jitter in the main control loop.
417+
412418
Support for Asynchronous Updates
413419
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
414420
For some applications, it is desirable to run a controller at a lower frequency than the controller manager's update rate. For instance, if the ``update_rate`` for the controller manager is 100Hz, the sum of the execution times of all controllers' ``update`` calls and hardware components ``read`` and ``write`` calls must be below 10ms. If one controller requires 15ms of execution time, it cannot be executed synchronously without affecting the overall system update rate. Running a controller asynchronously can be beneficial in this scenario.

controller_manager/include/controller_manager/controller_manager.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
namespace controller_manager
5252
{
5353
class ParamListener;
54-
class Params;
54+
struct Params;
5555
using ControllersListIterator = std::vector<controller_manager::ControllerSpec>::const_iterator;
5656

5757
rclcpp::NodeOptions get_cm_node_options();

0 commit comments

Comments
 (0)