diff --git a/launch_ros/launch_ros/actions/lifecycle_transition.py b/launch_ros/launch_ros/actions/lifecycle_transition.py index ed54870b6..236145d60 100644 --- a/launch_ros/launch_ros/actions/lifecycle_transition.py +++ b/launch_ros/launch_ros/actions/lifecycle_transition.py @@ -229,7 +229,7 @@ def match_node_name_start_goal(node_name: str, start_state: str, goal_state: str node_name = f'/{node_name}' return lambda event: ( isinstance(event, StateTransition) and - (event.action.node_name == node_name) and + (event.action.fully_qualified_node_name == node_name) and (event.goal_state == goal_state) and (event.start_state == start_state) ) @@ -240,6 +240,6 @@ def match_node_name_goal(node_name: str, goal_state: str): node_name = f'/{node_name}' return lambda event: ( isinstance(event, StateTransition) and - (event.action.node_name == node_name) and + (event.action.fully_qualified_node_name == node_name) and (event.goal_state == goal_state) ) diff --git a/launch_ros/launch_ros/actions/load_composable_nodes.py b/launch_ros/launch_ros/actions/load_composable_nodes.py index e16e39283..9a9a9707e 100644 --- a/launch_ros/launch_ros/actions/load_composable_nodes.py +++ b/launch_ros/launch_ros/actions/load_composable_nodes.py @@ -242,7 +242,13 @@ def execute( # If autostart is enabled, transition to the 'active' state. if hasattr(node_description, 'node_autostart') and node_description.node_autostart: - complete_node_name = request.node_namespace + request.node_name + if not node_description.is_node_name_fully_specified(): + self.__logger.error( + 'auto-starting ComposableLifecycleNode is not fully qualified ' + '(node_name must be specified) [ignoring autostart=True]' + ) + continue + complete_node_name = node_description.fully_qualified_node_name if not complete_node_name.startswith('/'): complete_node_name = '/' + complete_node_name self.__logger.info( @@ -300,6 +306,12 @@ def get_composable_node_load_request( if combined_ns is not None: request.node_namespace = combined_ns # request.log_level = perform_substitutions(context, node_description.log_level) + # TODO(SuperJappie08): Maybe better to use the response of the request. + if request.node_name: + composable_node_description.fully_qualified_node_name = prefix_namespace( + request.node_namespace, request.node_name + ) + remappings = [] global_remaps = context.launch_configurations.get('ros_remaps', None) if global_remaps: diff --git a/launch_ros/launch_ros/actions/node.py b/launch_ros/launch_ros/actions/node.py index aa03d751d..333792aa7 100644 --- a/launch_ros/launch_ros/actions/node.py +++ b/launch_ros/launch_ros/actions/node.py @@ -362,6 +362,11 @@ def node_name(self): raise RuntimeError("cannot access 'node_name' before executing action") return self.__final_node_name + @property + def fully_qualified_node_name(self): + """Getter for fully_qualified_node_name.""" + return self.node_name + def is_node_name_fully_specified(self): keywords = (self.UNSPECIFIED_NODE_NAME, self.UNSPECIFIED_NODE_NAMESPACE) return all(x not in self.node_name for x in keywords) diff --git a/launch_ros/launch_ros/descriptions/composable_lifecycle_node.py b/launch_ros/launch_ros/descriptions/composable_lifecycle_node.py index ecd30fdc4..3371ba485 100644 --- a/launch_ros/launch_ros/descriptions/composable_lifecycle_node.py +++ b/launch_ros/launch_ros/descriptions/composable_lifecycle_node.py @@ -19,7 +19,6 @@ import launch from launch.substitution import Substitution -from launch.utilities import perform_substitutions from launch_ros.parameters_type import Parameters from launch_ros.remap_rule_type import RemapRules from launch_ros.utilities import LifecycleEventManager @@ -44,11 +43,10 @@ def __init__( self.__autostart = autostart self.__lifecycle_event_manager = None - self.__node_name = super().node_name def init_lifecycle_event_manager(self, context: launch.LaunchContext) -> None: - # LifecycleEventManager needs a pre-substitution node name - self.__node_name = perform_substitutions(context, self.node_name) + # LifecycleEventManager needs qualified node name + assert self.is_node_name_fully_specified() self.__lifecycle_event_manager = LifecycleEventManager(self) self.__lifecycle_event_manager.setup_lifecycle_manager(context) @@ -65,7 +63,7 @@ def node_plugin(self) -> List[Substitution]: @property def node_name(self) -> Optional[List[Substitution]]: """Get node name as a sequence of substitutions to be performed.""" - return self.__node_name + return super().node_name @property def node_namespace(self) -> Optional[List[Substitution]]: diff --git a/launch_ros/launch_ros/descriptions/composable_node.py b/launch_ros/launch_ros/descriptions/composable_node.py index f5508daa8..89a48569a 100644 --- a/launch_ros/launch_ros/descriptions/composable_node.py +++ b/launch_ros/launch_ros/descriptions/composable_node.py @@ -69,6 +69,8 @@ def __init__( if namespace is not None: self.__node_namespace = normalize_to_list_of_substitutions(namespace) + self.__final_node_name = None # type: Optional[str] + self.__parameters = None # type: Optional[Parameters] if parameters is not None: self.__parameters = normalize_parameters(parameters) @@ -157,6 +159,23 @@ def node_name(self) -> Optional[List[Substitution]]: """Get node name as a sequence of substitutions to be performed.""" return self.__node_name + @property + def fully_qualified_node_name(self) -> str: + """Getter for fully_qualified_node_name.""" + if self.__final_node_name is None: + raise RuntimeError("cannot access 'fully_qualified_node_name' before executing action") + return self.__final_node_name + + @fully_qualified_node_name.setter + def fully_qualified_node_name(self, fully_qualified_node_name: Optional[str]): + """Setter for fully_qualified_node_name.""" + if self.__final_node_name is not None: + raise RuntimeError("'fully_qualified_node_name' is already set") + self.__final_node_name = fully_qualified_node_name + + def is_node_name_fully_specified(self) -> bool: + return self.__final_node_name is not None + @property def node_namespace(self) -> Optional[List[Substitution]]: """Get node namespace as a sequence of substitutions to be performed.""" diff --git a/launch_ros/launch_ros/events/matchers/matches_node_name.py b/launch_ros/launch_ros/events/matchers/matches_node_name.py index dc651932c..8cd6f62c4 100644 --- a/launch_ros/launch_ros/events/matchers/matches_node_name.py +++ b/launch_ros/launch_ros/events/matchers/matches_node_name.py @@ -26,4 +26,4 @@ def matches_node_name(node_name: Text) -> Callable[['Node'], bool]: """Return a matcher which matches based on the name of the node itself.""" if not node_name.startswith('/'): node_name = f'/{node_name}' - return lambda action: action.node_name == node_name + return lambda action: action.fully_qualified_node_name == node_name diff --git a/launch_ros/launch_ros/utilities/lifecycle_event_manager.py b/launch_ros/launch_ros/utilities/lifecycle_event_manager.py index 9e384d038..4bdfd9f53 100644 --- a/launch_ros/launch_ros/utilities/lifecycle_event_manager.py +++ b/launch_ros/launch_ros/utilities/lifecycle_event_manager.py @@ -61,7 +61,7 @@ def __init__(self, lifecycle_node) -> None: @property def node_name(self): - return self.__lifecycle_node.node_name + return self.__lifecycle_node.fully_qualified_node_name def _on_transition_event(self, context, msg): try: diff --git a/test_launch_ros/test/test_launch_ros/events/test_matchers.py b/test_launch_ros/test/test_launch_ros/events/test_matchers.py index 7556a9919..a518d44bf 100644 --- a/test_launch_ros/test/test_launch_ros/events/test_matchers.py +++ b/test_launch_ros/test/test_launch_ros/events/test_matchers.py @@ -23,6 +23,10 @@ class MockNode: def __init__(self, node_name): self.node_name = node_name + @property + def fully_qualified_node_name(self): + return self.node_name + def test_matches_node_name(): """Test the matches_node_name function."""