Skip to content

Commit

Permalink
Run condition for composable nodes (ros2#311)
Browse files Browse the repository at this point in the history
* Added run condition for composable nodes
Signed-off-by: Aditya <aditya050995@gmail.com>
  • Loading branch information
adityapande-1995 committed Apr 27, 2022
1 parent 7fa69bc commit a14156e
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 5 deletions.
10 changes: 7 additions & 3 deletions launch_ros/launch_ros/actions/composable_node_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,19 @@ def execute(self, context: LaunchContext) -> Optional[List[Action]]:
composable nodes load action if it applies.
"""
load_actions = None # type: Optional[List[Action]]
valid_composable_nodes = []
for node_object in self.__composable_node_descriptions:
if node_object.condition() is None or node_object.condition().evaluate(context):
valid_composable_nodes.append(node_object)
if (
self.__composable_node_descriptions is not None and
len(self.__composable_node_descriptions) > 0
valid_composable_nodes is not None and
len(valid_composable_nodes) > 0
):
from .load_composable_nodes import LoadComposableNodes
# Perform load action once the container has started.
load_actions = [
LoadComposableNodes(
composable_node_descriptions=self.__composable_node_descriptions,
composable_node_descriptions=valid_composable_nodes,
target_container=self
)
]
Expand Down
23 changes: 23 additions & 0 deletions launch_ros/launch_ros/descriptions/composable_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from typing import List
from typing import Optional

from launch.condition import Condition
from launch.conditions import IfCondition, UnlessCondition
from launch.frontend import Entity
from launch.frontend import Parser
from launch.some_substitutions_type import SomeSubstitutionsType
Expand All @@ -43,6 +45,7 @@ def __init__(
parameters: Optional[SomeParameters] = None,
remappings: Optional[SomeRemapRules] = None,
extra_arguments: Optional[SomeParameters] = None,
condition: Optional[Condition] = None,
) -> None:
"""
Initialize a ComposableNode description.
Expand All @@ -54,6 +57,7 @@ def __init__(
:param parameters: list of either paths to yaml files or dictionaries of parameters
:param remappings: list of from/to pairs for remapping names
:param extra_arguments: container specific arguments to be passed to the loaded node
:param condition: action will be executed if the condition evaluates to true
"""
self.__package = normalize_to_list_of_substitutions(package)
self.__node_plugin = normalize_to_list_of_substitutions(plugin)
Expand All @@ -78,6 +82,8 @@ def __init__(
if extra_arguments:
self.__extra_arguments = normalize_parameters(extra_arguments)

self.__condition = condition

@classmethod
def parse(cls, parser: Parser, entity: Entity):
"""Parse composable_node."""
Expand All @@ -88,6 +94,19 @@ def parse(cls, parser: Parser, entity: Entity):
kwargs['plugin'] = parser.parse_substitution(entity.get_attr('plugin'))
kwargs['name'] = parser.parse_substitution(entity.get_attr('name'))

if_cond = entity.get_attr('if', optional=True)
unless_cond = entity.get_attr('unless', optional=True)
if if_cond is not None and unless_cond is not None:
raise RuntimeError("if and unless conditions can't be used simultaneously")
if if_cond is not None:
kwargs['condition'] = IfCondition(
predicate_expression=parser.parse_substitution(if_cond)
)
if unless_cond is not None:
kwargs['condition'] = UnlessCondition(
predicate_expression=parser.parse_substitution(unless_cond)
)

namespace = entity.get_attr('namespace', optional=True)
if namespace is not None:
kwargs['namespace'] = parser.parse_substitution(namespace)
Expand Down Expand Up @@ -158,3 +177,7 @@ def remappings(self) -> Optional[RemapRules]:
def extra_arguments(self) -> Optional[Parameters]:
"""Get container extra arguments YAML files or dicts with substitutions to be performed."""
return self.__extra_arguments

def condition(self) -> Optional[Condition]:
"""Getter for condition."""
return self.__condition
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

from launch import LaunchDescription
from launch import LaunchService
from launch.actions import DeclareLaunchArgument
from launch.actions import GroupAction
from launch.actions import DeclareLaunchArgument, GroupAction
from launch.conditions import IfCondition, UnlessCondition
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import ComposableNodeContainer
from launch_ros.descriptions import ComposableNode
Expand Down Expand Up @@ -119,3 +119,65 @@ def test_composable_node_container_in_group_with_launch_configuration_in_descrip
context = _assert_launch_no_errors(actions)
assert get_node_name_count(context, f'/{TEST_CONTAINER_NAMESPACE}/{TEST_CONTAINER_NAME}') == 1
assert get_node_name_count(context, f'/{TEST_NODE_NAMESPACE}/{TEST_NODE_NAME}') == 1


def test_composable_node_container_if_condition():
"""Nominal test for launching a ComposableNodeContainer."""
TEST_NODE_NAME_1 = 'test_component_container_node_name_1'
TEST_NODE_NAME_2 = 'test_component_container_node_name_2'
actions = [
DeclareLaunchArgument(name='flag', default_value='False'),
ComposableNodeContainer(
package='rclcpp_components',
executable='component_container',
name=TEST_CONTAINER_NAME,
namespace=TEST_CONTAINER_NAMESPACE,
composable_node_descriptions=[
ComposableNode(
package='composition',
plugin='composition::Listener',
name=TEST_NODE_NAME,
namespace=TEST_NODE_NAMESPACE,
condition=IfCondition(LaunchConfiguration('flag'))
)
],
),
]

context = _assert_launch_no_errors(actions)

assert get_node_name_count(context, f'/{TEST_CONTAINER_NAMESPACE}/{TEST_CONTAINER_NAME}') == 1
assert get_node_name_count(context, f'/{TEST_NODE_NAMESPACE}/{TEST_NODE_NAME}') == 0

actions = [
DeclareLaunchArgument(name='flag', default_value='False'),
ComposableNodeContainer(
package='rclcpp_components',
executable='component_container',
name=TEST_CONTAINER_NAME,
namespace=TEST_CONTAINER_NAMESPACE,
composable_node_descriptions=[
ComposableNode(
package='composition',
plugin='composition::Listener',
name=TEST_NODE_NAME_1,
namespace=TEST_NODE_NAMESPACE,
condition=UnlessCondition(LaunchConfiguration('flag'))
),
ComposableNode(
package='composition',
plugin='composition::Listener',
name=TEST_NODE_NAME_2,
namespace=TEST_NODE_NAMESPACE,
condition=IfCondition(LaunchConfiguration('flag'))
)

],
),
]

context = _assert_launch_no_errors(actions)

assert get_node_name_count(context, f'/{TEST_CONTAINER_NAMESPACE}/{TEST_CONTAINER_NAME}') == 1
assert get_node_name_count(context, f'/{TEST_NODE_NAMESPACE}/{TEST_NODE_NAME_1}') == 1
assert get_node_name_count(context, f'/{TEST_NODE_NAMESPACE}/{TEST_NODE_NAME_2}') == 0

0 comments on commit a14156e

Please sign in to comment.