Deactivate controllers with command interfaces to hardware on DEACTIVATE#2334
Conversation
| 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); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
If you have suggestion for a more efficient way of finding the active command interfaces to a controller I'd appreciate it!
There was a problem hiding this comment.
is there anything here that could be refactored into a function? the idea being that you've got essentially the same code duplicated below
There was a problem hiding this comment.
I've got some ideas to optimize this in a better way. I can open a follow-up PR for this
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
readmethod, then it should behave exactly likeerror
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
writemethod, then onlyreadcalls are allowed (only those use command interfaces should be deactivated)
What do you think? @MarqRazz
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Well, but the component should go INACTIVE instead of ERROR right?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Codecov ReportAttention: Patch coverage is
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
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
bmagyar
left a comment
There was a problem hiding this comment.
the return type is a slight ABI break but it's internal API
| 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); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
I've got some ideas to optimize this in a better way. I can open a follow-up PR for this
| 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; | ||
| } |
There was a problem hiding this comment.
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??
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.