Launch wrapper for rclcpp::Node to make it a LifecycleNode

I have a proposal to run by the ROS2 community. Recently, I have been trying to integrate some in-house ROS2 Lifecycle nodes (rclcpp_lifecycle::LifecycleNode in Foxy) with regular non-lifecycle nodes (rclcpp::Node) that were developed externally. I want to manage all my nodes via their lifecycle interface, but obviously that is not possible with these external non-lifecycle nodes. The only currently supported way I am aware of would be to modify the node source code directly. This puts a big burden on the user of these external nodes if they want to integrate them with an existing lifecycle system, as I do.

I spent some time thinking about ways around this problem and came up with the idea to use components as a sort of wrapper. A ROS2 component node can be thought of as having 2 possible overarching states: Loaded and Unloaded This means that they can be conceptually mapped onto the Lifecycle node state machine. Specifically, nodes can be loaded on activation and unloaded on deactivation/error/shutdown. While loading/unloading a node may incur a fair bit of overhead, it has the benefit of ensuring to the user that the non-lifecycle node will not actively interfere with other nodes in the system unless in the ACTIVE state. Under such a system the only new requirement would be a node container capable of enforcing this architecture. This would effectively be an alternative to the component_managers currently available in rclcpp. Then the user could wrap their external nodes with the existing launch commands used for component loading. Here’s an example:
In XML

  <node_container pkg="rclcpp_components" name="lifecycle_wrapper" exec="lifecycle_wrapper_exec">

    <composable_node pkg="cool_pkg" 
                     plugin="cool_pkg_namespace::MyRegularNode" 
                     name="regular_node"/> <!-- This is now a minimal lifecycle node -->

  </node_container>

or Python

def generate_launch_description():
  
    lifecycle_container = ComposableNodeContainer(
        package='rclcpp_components',
        name='lifecycle_wrapper',
        executable='lifecycle_wrapper_exec',
        namespace="/",
        composable_node_descriptions=[
            ComposableNode(
                package='cool_pkg',
                name='regular_node',
                plugin='cool_pkg_namespace::MyRegularNode',
            ),
        ]
    )

    return LaunchDescription([
        lifecycle_container,
    ])

Using the above structure the “regular_node” could now be started or stopped based on tools like “ros2 lifecycle” or by other nodes managing system lifecycle. This approach does have the caveat that it will only work with component nodes, but many ros2 nodes are already being implemented as components and its easier to take an existing node and turn it into a component than to refactor it entirely to adhere to the lifecycle state machine.

At the moment, I have implemented a prototype of this using my teams internal base lifecycle node class and the concept does work, but I am writing this post to see if developing a more general implementation using the standard rclcpp_lifecycle::LifecycleNode as a base would be of interest to the wider ros community. Please let me know the following:

  1. Would you be interested in this functionality?
  2. Where should this new component manager implementation live? rclcpp_components?
  3. Any implementation considerations which you think are critical?
  4. Any suggestions on reducing code duplication with the existing component manager? Maybe implement this change as a constructor argument instead of a wholly separate class, etc.?
6 Likes

@msmcconnell I find this interesting and aligned with the work that I’ve been doing for the last couple of weeks. I just wrote about it at Adaptive ROS 2 Node computations and hardware acceleration contributions.

Regarding your proposal, have you disclosed the source code somwhere so that I can take a look at it?

@vmayoral The prototype I created for my team’s project is located here

(lifecycle_component_wrapper.hpp, and lifecycle_component_wrapper.cpp specifically)

But as mentioned in the post, this implementation is specific to my team’s project and still being tested. I will put up a more general implementation PR to the ROS repositories when I have a bit more free time around the new year.

2 Likes