2424
2525import os
2626import pathlib
27- import yaml
2827
2928from tempfile import NamedTemporaryFile
3029
30+ from typing import Dict
3131from typing import Iterable
32+ from typing import List
3233from typing import Optional
3334from typing import Text
35+ from typing import Tuple
36+ from typing import Union
3437
3538from launch import LaunchContext
3639from launch import SomeSubstitutionsType
3740from launch .descriptions import Executable
3841import launch .logging
3942from launch .substitutions import LocalSubstitution
43+ from launch .utilities import ensure_argument_type
4044from launch .utilities import normalize_to_list_of_substitutions
4145from launch .utilities import perform_substitutions
4246
47+ from launch_ros .utilities import add_node_name
4348from launch_ros .utilities import evaluate_parameters
49+ from launch_ros .utilities import get_node_name_count
4450from launch_ros .utilities import make_namespace_absolute
51+ from launch_ros .utilities import normalize_parameters
52+ from launch_ros .utilities import normalize_remap_rules
4553from launch_ros .utilities import prefix_namespace
4654
4755from rclpy .validate_namespace import validate_namespace
4856from rclpy .validate_node_name import validate_node_name
4957
58+ import yaml
59+
5060from .node_trait import NodeTrait
5161from ..parameter_descriptions import Parameter
5262from ..parameters_type import SomeParameters
@@ -114,11 +124,16 @@ def __init__(
114124 :param: arguments list of extra arguments for the node
115125 :param: traits list of special traits of the node
116126 """
127+ if parameters is not None :
128+ ensure_argument_type (parameters , (list ), 'parameters' , 'Node' )
129+ # All elements in the list are paths to files with parameters (or substitutions that
130+ # evaluate to paths), or dictionaries of parameters (fields can be substitutions).
131+ normalized_params = normalize_parameters (parameters )
117132
118133 self .__node_name = node_name
119134 self .__node_namespace = node_namespace
120- self .__parameters = parameters
121- self .__remappings = remappings
135+ self .__parameters = [] if parameters is None else normalized_params
136+ self .__remappings = [] if remappings is None else list ( normalize_remap_rules ( remappings ))
122137 self .__arguments = arguments
123138 self .__traits = traits
124139
@@ -135,7 +150,9 @@ def __init__(
135150 @property
136151 def node_name (self ):
137152 """Getter for node_name."""
138- return self .__node_name
153+ if self .__final_node_name is None :
154+ raise RuntimeError ("cannot access 'node_name' before executing action" )
155+ return self .__final_node_name
139156
140157 @property
141158 def node_namespace (self ):
@@ -177,13 +194,6 @@ def expanded_parameter_arguments(self):
177194 """Getter for expanded_parameter_arguments."""
178195 return self .__expanded_parameter_arguments
179196
180- @property
181- def final_node_name (self ):
182- """Getter for final_node_name."""
183- if self .__substitutions_performed == True :
184- return self .__final_node_name
185- return None
186-
187197 @property
188198 def expanded_remappings (self ):
189199 """Getter for expanded_remappings."""
@@ -206,13 +216,15 @@ def _create_params_file_from_dict(self, params):
206216 def _get_parameter_rule (self , param : 'Parameter' , context : LaunchContext ):
207217 name , value = param .evaluate (context )
208218 return f'{ name } :={ yaml .dump (value )} '
209-
219+
210220 def prepare (self , context : LaunchContext , executable : Executable ) -> None :
211221 try :
212222 if self .__substitutions_performed :
213223 # This function may have already been called by a subclass' `execute`, for example.
214224 return
215225 self .__substitutions_performed = True
226+ cmd_ext = ['--ros-args' ] # Prepend ros specific arguments with --ros-args flag
227+ executable .cmd .extend ([normalize_to_list_of_substitutions (x ) for x in cmd_ext ])
216228 if self .__node_name is not None :
217229 self .__expanded_node_name = perform_substitutions (
218230 context , normalize_to_list_of_substitutions (self .__node_name ))
@@ -227,8 +239,8 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
227239 prefix_namespace (base_ns , expanded_node_namespace ))
228240 if expanded_node_namespace is not None :
229241 self .__expanded_node_namespace = expanded_node_namespace
230- cmd_extension = ['-r' , LocalSubstitution ("ros_specific_arguments['ns']" )]
231- executable .cmd .extend ([normalize_to_list_of_substitutions (x ) for x in cmd_extension ])
242+ cmd_ext = ['-r' , LocalSubstitution ("ros_specific_arguments['ns']" )]
243+ executable .cmd .extend ([normalize_to_list_of_substitutions (x ) for x in cmd_ext ])
232244 validate_namespace (self .__expanded_node_namespace )
233245 except Exception :
234246 self .__logger .error (
@@ -249,8 +261,8 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
249261 if global_params is not None :
250262 param_file_path = self ._create_params_file_from_dict (global_params )
251263 self .__expanded_parameter_arguments .append ((param_file_path , True ))
252- cmd_extension = ['--params-file' , f'{ param_file_path } ' ]
253- executable .cmd .extend ([normalize_to_list_of_substitutions (x ) for x in cmd_extension ])
264+ cmd_ext = ['--params-file' , f'{ param_file_path } ' ]
265+ executable .cmd .extend ([normalize_to_list_of_substitutions (x ) for x in cmd_ext ])
254266 assert os .path .isfile (param_file_path )
255267 # expand parameters too
256268 if self .__parameters is not None :
@@ -274,8 +286,8 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
274286 )
275287 continue
276288 self .__expanded_parameter_arguments .append ((param_argument , is_file ))
277- cmd_extension = ['--params-file' if is_file else '-p' , f'{ param_argument } ' ]
278- executable .cmd .extend ([normalize_to_list_of_substitutions (x ) for x in cmd_extension ])
289+ cmd_ext = ['--params-file' if is_file else '-p' , f'{ param_argument } ' ]
290+ executable .cmd .extend ([normalize_to_list_of_substitutions (x ) for x in cmd_ext ])
279291 # expand remappings too
280292 global_remaps = context .launch_configurations .get ('ros_remaps' , None )
281293 if global_remaps or self .__remappings :
@@ -288,7 +300,25 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
288300 for src , dst in self .__remappings
289301 ])
290302 if self .__expanded_remappings :
291- cmd_extension = []
303+ cmd_ext = []
292304 for src , dst in self .__expanded_remappings :
293- cmd_extension .extend (['-r' , f'{ src } :={ dst } ' ])
294- executable .cmd .extend ([normalize_to_list_of_substitutions (x ) for x in cmd_extension ])
305+ cmd_ext .extend (['-r' , f'{ src } :={ dst } ' ])
306+ executable .cmd .extend ([normalize_to_list_of_substitutions (x ) for x in cmd_ext ])
307+ # Prepare the ros_specific_arguments list and add it to the context so that the
308+ # LocalSubstitution placeholders added to the the cmd can be expanded using the contents.
309+ ros_specific_arguments : Dict [str , Union [str , List [str ]]] = {}
310+ if self .__node_name is not None :
311+ ros_specific_arguments ['name' ] = '__node:={}' .format (self .__expanded_node_name )
312+ if self .__expanded_node_namespace != '' :
313+ ros_specific_arguments ['ns' ] = '__ns:={}' .format (self .__expanded_node_namespace )
314+ context .extend_locals ({'ros_specific_arguments' : ros_specific_arguments })
315+
316+ if self .is_node_name_fully_specified ():
317+ add_node_name (context , self .node_name )
318+ node_name_count = get_node_name_count (context , self .node_name )
319+ if node_name_count > 1 :
320+ execute_process_logger = launch .logging .get_logger (self .name )
321+ execute_process_logger .warning (
322+ 'there are now at least {} nodes with the name {} created within this '
323+ 'launch context' .format (node_name_count , self .node_name )
324+ )
0 commit comments