Skip to content

Deactivate controllers with command interfaces to hardware on DEACTIVATE #2334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 26, 2025

Conversation

MarqRazz
Copy link
Contributor

Closes #2318

While testing with hardware I found it was easiest to only return DEACTIVATE from write() so that my hardware_interface didn't have to keep track of the number of times DEACTIVATE has been returned in read(). I also found that allowing write() while the hardware is INACTIVE does not make sense because ALL commanding controllers are stopped on deactivation and the hardware would not know which command_interfaces might be allowed returning DEACTIVATE again which would cause the controller_manager to get stuck in a loop attempting to stop any controllers with with command interfaces to the hardware.

I also added 2 new test to make sure the controller_manager correctly handles DEACTIVATE on read/write.

Comment on lines 2702 to 2726
for (const auto & controller : controllers)
{
auto controller_spec = std::find_if(
loaded_controllers.begin(), loaded_controllers.end(),
[&](const controller_manager::ControllerSpec & spec)
{ return spec.c->get_name() == controller; });
if (controller_spec == loaded_controllers.end())
{
RCLCPP_WARN(
get_logger(),
"Deactivate failed to find controller [%s] in loaded controllers. "
"This can happen due to multiple returns of 'DEACTIVATE' from [%s] read()",
controller.c_str(), hardware_name.c_str());
continue;
}
std::vector<std::string> command_interface_names;
extract_command_interfaces_for_controller(
*controller_spec, resource_manager_, command_interface_names);
// if this controller has command interfaces add it to the deactivate_controllers_list
if (!command_interface_names.empty())
{
rt_buffer_.deactivate_controllers_list.push_back(controller);
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have suggestion for a more efficient way of finding the active command interfaces to a controller I'd appreciate it!

Copy link
Member

@bmagyar bmagyar Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there anything here that could be refactored into a function? the idea being that you've got essentially the same code duplicated below

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got some ideas to optimize this in a better way. I can open a follow-up PR for this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it makes sense to return DEACTIVATE from read() because that interface is expected to continue to work after the hardware goes inactive. What do you guys think about warning users that this is not supported in read() and to update their code to only return DEACTIVATE in write()? If that is okay I can remove all of this code here and update the tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rethinking about this. When we receive DEACTIVATE from the read, it should work similar to the ERROR state, both read and write calls shouldn't work and for write, only write method shouldn't be called.

  • When DEACTIVATE is returned through read method, then it should behave exactly like error
    state (No further read and write calls, unless activated) and all controllers that use any state and command interfaces should be deactivated)
  • When DEACTIVATE is returned through write method, then only read calls are allowed (only those use command interfaces should be deactivated)

What do you think? @MarqRazz

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that is how I was thinking it should work, but with the addition of warning the user when they return DEACTIVATE in read that it is treated the same as ERROR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, but the component should go INACTIVE instead of ERROR right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My takeaway from Marq's comments is that the component should transition to ERROR if the user needs to return DEACTIVATE from read(). In that case, we could provide an unsupported warning and recommend ERROR as the return value. This simplifies things conceptually.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is what I implemented and added comments to the new section of code below. Let me know if we would prefer the hardware go INACTIVE vs ERROR.

Copy link

codecov bot commented Jun 23, 2025

Codecov Report

Attention: Patch coverage is 97.48428% with 4 lines in your changes missing coverage. Please review.

Project coverage is 89.01%. Comparing base (8c52010) to head (b3e6886).
Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
controller_manager/src/controller_manager.cpp 88.00% 2 Missing and 1 partial ⚠️
hardware_interface/src/resource_manager.cpp 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2334      +/-   ##
==========================================
+ Coverage   88.98%   89.01%   +0.03%     
==========================================
  Files         147      147              
  Lines       16708    16785      +77     
  Branches     1433     1438       +5     
==========================================
+ Hits        14867    14941      +74     
- Misses       1285     1286       +1     
- Partials      556      558       +2     
Flag Coverage Δ
unittests 89.01% <97.48%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...est_controller_manager_hardware_error_handling.cpp 100.00% <100.00%> (ø)
hardware_interface/src/system.cpp 81.02% <100.00%> (+0.41%) ⬆️
...rface/test/mock_components/test_generic_system.cpp 99.72% <ø> (ø)
...dware_interface/test/test_component_interfaces.cpp 97.65% <100.00%> (-0.03%) ⬇️
...e_interface_testing/test/test_resource_manager.cpp 99.45% <100.00%> (ø)
hardware_interface/src/resource_manager.cpp 78.26% <85.71%> (-0.03%) ⬇️
controller_manager/src/controller_manager.cpp 74.24% <88.00%> (+0.13%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bmagyar bmagyar added the backport-jazzy This label should be used by maintainers only! Label triggers PR backport to ROS 2 jazzy. label Jun 24, 2025
bmagyar
bmagyar previously approved these changes Jun 24, 2025
Copy link
Member

@bmagyar bmagyar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the return type is a slight ABI break but it's internal API

saikishor
saikishor previously approved these changes Jun 24, 2025
Copy link
Member

@saikishor saikishor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines 2702 to 2726
for (const auto & controller : controllers)
{
auto controller_spec = std::find_if(
loaded_controllers.begin(), loaded_controllers.end(),
[&](const controller_manager::ControllerSpec & spec)
{ return spec.c->get_name() == controller; });
if (controller_spec == loaded_controllers.end())
{
RCLCPP_WARN(
get_logger(),
"Deactivate failed to find controller [%s] in loaded controllers. "
"This can happen due to multiple returns of 'DEACTIVATE' from [%s] read()",
controller.c_str(), hardware_name.c_str());
continue;
}
std::vector<std::string> command_interface_names;
extract_command_interfaces_for_controller(
*controller_spec, resource_manager_, command_interface_names);
// if this controller has command interfaces add it to the deactivate_controllers_list
if (!command_interface_names.empty())
{
rt_buffer_.deactivate_controllers_list.push_back(controller);
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got some ideas to optimize this in a better way. I can open a follow-up PR for this

@MarqRazz MarqRazz dismissed stale reviews from saikishor and bmagyar via 8622bca June 25, 2025 16:30
Comment on lines 2391 to 2396
if (ret_val == hardware_interface::return_type::DEACTIVATE)
{
RCLCPP_WARN(
get_logger(), "DEACTIVATE returned from read cycle is treated the same as ERROR.");
ret_val = hardware_interface::return_type::ERROR;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented this as DEACTIVATE == ERROR in read(). If we want the hardware to go INACTIVE here it would be a simple change but can't think of a use case where a user would want to return DEACTIVATE vs ERROR in read. Would that mean they can't read from the device but they are still connected to the hardware and want to stop updating state??

@bmagyar bmagyar merged commit 24d60ed into ros-controls:master Jun 26, 2025
20 of 26 checks passed
mergify bot pushed a commit that referenced this pull request Jun 26, 2025
@MarqRazz MarqRazz deleted the deactivate_controllers branch June 27, 2025 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport-jazzy This label should be used by maintainers only! Label triggers PR backport to ROS 2 jazzy.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Deactivate command_interfaces when hardware_interface goes inactive
4 participants