Skip to content

Commit cde86ae

Browse files
authored
Calculate achievable update rate of controllers (#2828)
1 parent 30416ba commit cde86ae

File tree

6 files changed

+56
-16
lines changed

6 files changed

+56
-16
lines changed

controller_interface/include/controller_interface/controller_interface_params.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ struct ControllerInterfaceParams
4747
std::string controller_name = "";
4848
std::string robot_description = "";
4949
unsigned int update_rate = 0;
50+
unsigned int controller_manager_update_rate = 0;
5051

5152
std::string node_namespace = "";
5253
rclcpp::NodeOptions node_options = rclcpp::NodeOptions();

controller_interface/src/controller_interface_base.cpp

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ return_type ControllerInterfaceBase::init(
4747
params.controller_name = controller_name;
4848
params.robot_description = urdf;
4949
params.update_rate = cm_update_rate;
50+
params.controller_manager_update_rate = cm_update_rate;
5051
params.node_namespace = node_namespace;
5152
params.node_options = node_options;
5253

@@ -62,11 +63,26 @@ return_type ControllerInterfaceBase::init(
6263
ctrl_itf_params_.node_options,
6364
false); // disable LifecycleNode service interfaces
6465

66+
if (ctrl_itf_params_.controller_manager_update_rate == 0 && ctrl_itf_params_.update_rate != 0)
67+
{
68+
RCLCPP_WARN(
69+
node_->get_logger(), "%s",
70+
fmt::format(
71+
"The 'controller_manager_update_rate' variable of the ControllerInterfaceParams is unset "
72+
"or set to 0 Hz while the 'update_rate' variable is set to a non-zero value of '{} Hz'. "
73+
"Using the controller's update rate as the controller manager update rate. Please fix in "
74+
"the tests by initializing the 'controller_manager_update_rate' instead of the "
75+
"'update_rate' variable within the ControllerInterfaceParams struct",
76+
ctrl_itf_params_.update_rate)
77+
.c_str());
78+
ctrl_itf_params_.controller_manager_update_rate = ctrl_itf_params_.update_rate;
79+
}
80+
6581
try
6682
{
6783
// no rclcpp::ParameterValue unsigned int specialization
68-
auto_declare<int>("update_rate", static_cast<int>(ctrl_itf_params_.update_rate));
69-
84+
auto_declare<int>(
85+
"update_rate", static_cast<int>(ctrl_itf_params_.controller_manager_update_rate));
7086
auto_declare<bool>("is_async", false);
7187
auto_declare<int>("thread_priority", -100);
7288
}
@@ -168,14 +184,33 @@ const rclcpp_lifecycle::State & ControllerInterfaceBase::configure()
168184
get_node()->get_logger(), "%s",
169185
fmt::format(
170186
"The update rate of the controller : '{} Hz' cannot be higher than the update rate of "
171-
"the "
172-
"controller manager : '{} Hz'. Setting it to the update rate of the controller manager.",
187+
"the controller manager : '{} Hz'. Setting it to the update rate of the controller "
188+
"manager.",
173189
update_rate, ctrl_itf_params_.update_rate)
174190
.c_str());
175191
}
176192
else
177193
{
178-
ctrl_itf_params_.update_rate = static_cast<unsigned int>(update_rate);
194+
if (update_rate > 0 && ctrl_itf_params_.controller_manager_update_rate > 0)
195+
{
196+
// Calculate the update rate corresponding the periodicity of the controller manager
197+
const bool is_frequency_achievable = (ctrl_itf_params_.controller_manager_update_rate %
198+
static_cast<unsigned int>(update_rate)) == 0;
199+
const unsigned int ticks_per_controller_per_second = static_cast<unsigned int>(std::round(
200+
static_cast<double>(ctrl_itf_params_.controller_manager_update_rate) /
201+
static_cast<double>(update_rate)));
202+
const unsigned int achievable_hz =
203+
is_frequency_achievable
204+
? static_cast<unsigned int>(update_rate)
205+
: ctrl_itf_params_.controller_manager_update_rate / ticks_per_controller_per_second;
206+
207+
RCLCPP_WARN_EXPRESSION(
208+
get_node()->get_logger(), !is_frequency_achievable,
209+
"The requested update rate of '%ld Hz' is not achievable with the controller manager "
210+
"update rate of '%d Hz'. Setting it to the closest achievable frequency '%d Hz'.",
211+
update_rate, ctrl_itf_params_.controller_manager_update_rate, achievable_hz);
212+
ctrl_itf_params_.update_rate = achievable_hz;
213+
}
179214
}
180215
is_async_ = get_node()->get_parameter("is_async").as_bool();
181216
}

controller_interface/test/test_controller_interface.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ TEST(TestableControllerInterface, setting_update_rate_in_configure)
115115
params.controller_name = TEST_CONTROLLER_NAME;
116116
params.robot_description = "";
117117
params.update_rate = 5000; // set a different update rate than the one in the node options
118+
params.controller_manager_update_rate = 5000;
118119
params.node_namespace = "";
119120
params.node_options = node_options;
120121
joint_limits::JointLimits joint_limits;
@@ -159,36 +160,37 @@ TEST(TestableControllerInterface, setting_update_rate_in_configure)
159160

160161
// Even after configure is 0
161162
controller.configure();
162-
ASSERT_EQ(controller.get_update_rate(), 2812u);
163+
ASSERT_EQ(controller.get_update_rate(), 2500u) << "Needs to be achievable rate based on cm rate";
163164

164165
// Test updating of update_rate parameter
165166
auto res = controller.get_node()->set_parameter(rclcpp::Parameter("update_rate", 623));
166167
EXPECT_EQ(res.successful, true);
167168
// Keep the same update rate until transition from 'UNCONFIGURED' TO 'INACTIVE' does not happen
168169
controller.configure(); // No transition so the update rate should stay intact
169170
ASSERT_NE(controller.get_update_rate(), 623u);
170-
ASSERT_EQ(controller.get_update_rate(), 2812u);
171+
ASSERT_EQ(controller.get_update_rate(), 2500u);
171172

172173
controller.get_node()->activate();
173174
controller.configure(); // No transition so the update rate should stay intact
174175
ASSERT_NE(controller.get_update_rate(), 623u);
175-
ASSERT_EQ(controller.get_update_rate(), 2812u);
176+
ASSERT_EQ(controller.get_update_rate(), 2500u);
176177

177178
controller.update(controller.get_node()->now(), rclcpp::Duration::from_seconds(0.1));
178179
controller.configure(); // No transition so the update rate should stay intact
179180
ASSERT_NE(controller.get_update_rate(), 623u);
180-
ASSERT_EQ(controller.get_update_rate(), 2812u);
181+
ASSERT_EQ(controller.get_update_rate(), 2500u);
181182

182183
controller.get_node()->deactivate();
183184
controller.configure(); // No transition so the update rate should stay intact
184185
ASSERT_NE(controller.get_update_rate(), 623u);
185-
ASSERT_EQ(controller.get_update_rate(), 2812u);
186+
ASSERT_EQ(controller.get_update_rate(), 2500u);
186187

187188
controller.get_node()->cleanup();
188-
ASSERT_EQ(controller.get_update_rate(), 2812u);
189+
ASSERT_EQ(controller.get_update_rate(), 2500u);
189190
// It is first changed after controller is configured again.
190191
controller.configure();
191-
ASSERT_EQ(controller.get_update_rate(), 623u);
192+
ASSERT_EQ(controller.get_update_rate(), 625u)
193+
<< "It needs to be 625 as it is closest achievable rate wrt to the CM rate";
192194

193195
// Should stay same after multiple cleanups as it is set during initialization
194196
const auto hard_limits_final = controller.get_hard_joint_limits();

controller_manager/src/controller_manager.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2114,6 +2114,7 @@ controller_interface::ControllerInterfaceBaseSharedPtr ControllerManager::add_co
21142114
controller_params.controller_name = controller.info.name;
21152115
controller_params.robot_description = robot_description_;
21162116
controller_params.update_rate = get_update_rate();
2117+
controller_params.controller_manager_update_rate = get_update_rate();
21172118
controller_params.node_namespace = get_namespace();
21182119
controller_params.node_options = controller_node_options;
21192120
controller_params.hard_joint_limits = resource_manager_->get_hard_joint_limits();

controller_manager/test/test_controller_manager.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,18 +1030,18 @@ TEST_P(TestControllerUpdateRates, check_the_controller_update_rate)
10301030
EXPECT_EQ(
10311031
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, test_controller->get_lifecycle_state().id());
10321032

1033-
EXPECT_EQ(test_controller->get_update_rate(), ctrl_update_rate);
10341033
const auto cm_update_rate = cm_->get_update_rate();
10351034
const auto controller_update_rate = test_controller->get_update_rate();
10361035
const double controller_period = 1.0 / controller_update_rate;
10371036
const double exp_controller_period =
10381037
std::round((static_cast<double>(cm_update_rate) / controller_update_rate) - 0.01) /
10391038
cm_update_rate;
1039+
const auto exp_periodicity = 1.0 / exp_controller_period;
1040+
EXPECT_EQ(test_controller->get_update_rate(), std::floor(exp_periodicity));
10401041

10411042
const auto initial_counter = test_controller->internal_counter;
10421043
// don't start with zero to check if the period is correct if controller is activated anytime
10431044
rclcpp::Time time = time_;
1044-
const auto exp_periodicity = 1.0 / exp_controller_period;
10451045
for (size_t update_counter = 0; update_counter <= 10 * cm_update_rate; ++update_counter)
10461046
{
10471047
rclcpp::Time old_time = time;
@@ -1183,18 +1183,18 @@ TEST_F(TestAsyncControllerUpdateRates, check_the_async_controller_update_rate_an
11831183
EXPECT_EQ(
11841184
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, test_controller->get_lifecycle_state().id());
11851185

1186-
EXPECT_EQ(test_controller->get_update_rate(), ctrl_update_rate);
11871186
const auto cm_update_rate = cm_->get_update_rate();
11881187
const auto controller_update_rate = test_controller->get_update_rate();
11891188
const double controller_period = 1.0 / controller_update_rate;
11901189
const double exp_controller_period =
11911190
std::round((static_cast<double>(cm_update_rate) / controller_update_rate) - 0.01) /
11921191
cm_update_rate;
1192+
const auto exp_periodicity = 1.0 / exp_controller_period;
1193+
EXPECT_EQ(test_controller->get_update_rate(), std::round(exp_periodicity));
11931194

11941195
const auto initial_counter = test_controller->internal_counter;
11951196
// don't start with zero to check if the period is correct if controller is activated anytime
11961197
rclcpp::Time time = time_;
1197-
const auto exp_periodicity = 1.0 / exp_controller_period;
11981198
for (size_t update_counter = 0; update_counter <= 10 * cm_update_rate; ++update_counter)
11991199
{
12001200
rclcpp::Time old_time = time;

doc/release_notes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ controller_interface
99
********************
1010
* The new ``MagneticFieldSensor`` semantic component provides an interface for reading data from magnetometers. (`#2627 <https://github.com/ros-controls/ros2_control/pull/2627>`__)
1111
* The controller_manager will now deactivate the entire controller chain if a controller in the chain fails during the update cycle. `(#2681 <https://github.com/ros-controls/ros2_control/pull/2681>`__)
12+
* The update rate of the controller will now be approximated to a closer achievable frequency, when its frequency is not achievable with the current controller manager update rate. (`#2828 <https://github.com/ros-controls/ros2_control/pull/2828>`__)
1213

1314
controller_manager
1415
******************

0 commit comments

Comments
 (0)