Skip to content

Add controller dependency graph for ::AUTO switch mode#2578

Open
saikishor wants to merge 30 commits into
ros-controls:masterfrom
pal-robotics-forks:add/controllers/dependency_graph
Open

Add controller dependency graph for ::AUTO switch mode#2578
saikishor wants to merge 30 commits into
ros-controls:masterfrom
pal-robotics-forks:add/controllers/dependency_graph

Conversation

@saikishor
Copy link
Copy Markdown
Member

This PR aims to add the things needed for the ::AUTO switch mode and addresses few other things.

With the ::AUTO switch mode, you can activate and deactivate the whole chain

graph LR;
    A-->B;
    B-->C;
    C-->D;
    D-->E;
Loading

Here, you can activate the whole chain, activating controller A alone and deactivate the whole chain deactivating the controller E

graph LR;
    A-->B;
    B-->C;
    B-->D;
    D-->E;
Loading

Here, you can activate the whole chain, activating controller A alone and deactivate the whole chain deactivating the controller E and C

Activating controller D will only activate D and E
Activating controller B will activate B, C, D and E

@saikishor saikishor force-pushed the add/controllers/dependency_graph branch from aeea81a to 9c9ef20 Compare September 24, 2025 13:18
@codecov
Copy link
Copy Markdown

codecov Bot commented Sep 24, 2025

Codecov Report

❌ Patch coverage is 91.12150% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.35%. Comparing base (56fc052) to head (cb75751).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...ger/include/controller_manager/controller_spec.hpp 86.86% 13 Missing and 13 partials ⚠️
controller_manager/src/controller_manager.cpp 86.20% 7 Missing and 5 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2578      +/-   ##
==========================================
+ Coverage   89.31%   89.35%   +0.04%     
==========================================
  Files         145      145              
  Lines       16541    16931     +390     
  Branches     1396     1426      +30     
==========================================
+ Hits        14773    15129     +356     
- Misses       1231     1249      +18     
- Partials      537      553      +16     
Flag Coverage Δ
unittests 89.35% <91.12%> (+0.04%) ⬆️

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

Files with missing lines Coverage Δ
...controller_interface/controller_interface_base.hpp 91.66% <ø> (ø)
.../include/controller_manager/controller_manager.hpp 96.87% <ø> (ø)
...ontroller_manager/test/test_controller_manager.cpp 95.92% <100.00%> (+0.38%) ⬆️
...ller_manager/test/test_controller_manager_srvs.cpp 99.36% <100.00%> (+0.02%) ⬆️
...e_interface/include/hardware_interface/helpers.hpp 100.00% <100.00%> (+3.44%) ⬆️
hardware_interface/test/test_helpers.cpp 93.33% <100.00%> (+7.61%) ⬆️
controller_manager/src/controller_manager.cpp 75.33% <86.20%> (+0.16%) ⬆️
...ger/include/controller_manager/controller_spec.hpp 87.19% <86.86%> (-12.81%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread controller_manager/src/controller_manager.cpp
saikishor and others added 2 commits September 24, 2025 21:27
Co-authored-by: Christoph Fröhlich <christophfroehlich@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 1, 2025

This PR is stale because it has been open for 45 days with no activity. Please tag a maintainer for help on completing this PR, or close it if you think it has become obsolete.

@github-actions github-actions Bot added the stale label Dec 1, 2025
@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented Dec 1, 2025

This pull request is in conflict. Could you fix it @saikishor?

@github-actions
Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for 45 days with no activity. Please tag a maintainer for help on completing this PR, or close it if you think it has become obsolete.

@github-actions github-actions Bot added the stale label Jan 28, 2026
Copy link
Copy Markdown
Member

@christophfroehlich christophfroehlich left a comment

Choose a reason for hiding this comment

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

Oops, I found an unfinished review here. What is the current state of this feature?

pal_statistics::RegistrationsRAII stats_registrations_;

public:
using SharedPtr = std::shared_ptr<ControllerInterfaceBase>;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should we use RCLCPP_SHARED_PTR_DEFINITIONS and RCLCPP_WEAK_PTR_DEFINITIONS from "rclcpp/macros.hpp"?

@github-actions github-actions Bot removed the stale label Feb 3, 2026
Copy link
Copy Markdown

@PeterMitrano PeterMitrano left a comment

Choose a reason for hiding this comment

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

Very excited about this feature and eventually fulling implementing FORCE_AUTO! I wouldn't say that I looked closely enough for this to be a "review", but I just wanted to point out a few small things, including one of the tests not making sense to me. Thanks for all the effort here!

graph.add_dependency(E, F);

EXPECT_THAT(
graph.get_dependencies_to_activate("A"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kind of surprising that these include the node itself. in this case "A", and "B" in the case below 🤔

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, it is not so intuitive I suppose, but yes, it gives the whole chain with dependencies


EXPECT_THAT(
graph.get_dependencies_to_activate("A"),
testing::UnorderedElementsAre("A", "B", "C", "D", "E", "F"));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why E and F? am I understanding this correctly?

graph LR;
    A-->B;
    B-->C;
    C-->D;
    E-->B;
    E-->F;
Loading

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes,

Inorder to activate A, we need to activate B and B needs E and E needs F, that's why. It was quite complicated while implementing it

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

But according to the diagram, B does not need E. Rather, E needs B, but that's irrelevant for activating A.
Am I misunderstanding this?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I am imagining it like this: Let's say B is a low level joint level controller, and A offers Cartesian control and B offers trajectory control, both on top of E. If I activate both at the same time that would be a resource conflict. So if I activate A, then we can't activate E also.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hello!

That's not how it works

Let's say, B exports 5 interfaces and then A takes 3 of those and E takes 2 of them. If you are activating A, then as it needs B to activate, but B cannot be left with half of the interfaces taken right?, so it'll have to also activate E

Did i explain myself?

If not, I can give a longer explanation when I get access to my PC

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry I got "B" and "E" reversed in the statement "B offers trajectory control, both on top of E" above.

But anyways, ok let's assume that A and E do not claim the same interfaces, but rather difference ones. Even then I'm not sure I agree about leaving unclaimed interfaces. In my experience it's perfectly fine to have unclaimed interfaces. From B's perspective, it just won't be receiving any valid commands on that interface, which seems fine to me.

Thanks for taking the time to explain! I am interested in contributing more to ros controls repos :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hello!

We cannot leave unclaimed interfaces because we cannot know what reference to take to do the control strategy. it depends on the situation though

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

True, but usually that's indicated with "inf". At least , in the hardware interfaces I've implemented that's how it works. But ok, I understand your point.

Maybe I just need to think of get_dependencies_to_activate as "here are all the controllers we might need to activate". Because there will need to be some filter for the case where A and E are in conflict, because they try to claim the same interfaces, right?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry for coming late to this discussion.

Have you considered a case where there's two possible controllers in the place corresponding to E in the example above? If two different controllers can potentially claim the same interfaces from B how can you go about picking which to activate?

I think supporting unclaimed interfaces is a must here, since the only unequivocal dependencies are from A to B, B to C, etc., whereas B to E may be ambiguous as stated above.

Consider the parallelism with hardware interfaces themselves. There's usually no problem with running them with no controller claiming their interfaces, is there? Should this not be handled the same way?

EXPECT_THAT(graph.get_dependencies_to_activate("D"), testing::UnorderedElementsAre("D"));
EXPECT_THAT(
graph.get_dependencies_to_activate("E"),
testing::UnorderedElementsAre("E", "F", "B", "C", "D", "A"));
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

See above

// controllers can be activated together with the current controller.

mutually_exclusive_predecessor_groups.clear();
const auto are_all_reference_interfaces_found =
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this and the are_all_command_interfaces_found lambdas seem to have no captures and do essentially the same thing. Maybe it could be pulled out into one function at file scope?

void get_controllers_to_activate(std::vector<std::string> & controllers_to_activate) const
{
// Check the predecessors of the controller and check if they belong to the controller's state
// interfaces If they do, add them to the list of controllers to activatestate_itf
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

possible typo at the end here?

size_t successors_reference_interfaces_count = 0;
for (const auto & successor : successors)
{
successors_reference_interfaces_count += successor->reference_interfaces.size();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe I missed it, but this variable doesn't seem to be used anywhere else?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'll take a look

EXPECT_THAT(graph.get_dependencies_to_activate("D"), testing::UnorderedElementsAre("D"));
EXPECT_THAT(
graph.get_dependencies_to_activate("E"),
testing::UnorderedElementsAre("E", "F", "B", "C", "D", "A"));
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

See above

@github-actions
Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for 45 days with no activity. Please tag a maintainer for help on completing this PR, or close it if you think it has become obsolete.

@github-actions github-actions Bot added the stale label May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: WIP

Development

Successfully merging this pull request may close these issues.

5 participants