Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions launch_ros/launch_ros/actions/lifecycle_transition.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
Expand All @@ -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)
)
14 changes: 13 additions & 1 deletion launch_ros/launch_ros/actions/load_composable_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions launch_ros/launch_ros/actions/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand All @@ -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]]:
Expand Down
19 changes: 19 additions & 0 deletions launch_ros/launch_ros/descriptions/composable_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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."""
Expand Down
2 changes: 1 addition & 1 deletion launch_ros/launch_ros/events/matchers/matches_node_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion launch_ros/launch_ros/utilities/lifecycle_event_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions test_launch_ros/test/test_launch_ros/events/test_matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down