24
24
25
25
import os
26
26
import pathlib
27
- import yaml
28
27
29
28
from tempfile import NamedTemporaryFile
30
29
30
+ from typing import Dict
31
31
from typing import Iterable
32
+ from typing import List
32
33
from typing import Optional
33
34
from typing import Text
35
+ from typing import Tuple
36
+ from typing import Union
34
37
35
38
from launch import LaunchContext
36
39
from launch import SomeSubstitutionsType
37
40
from launch .descriptions import Executable
38
41
import launch .logging
39
42
from launch .substitutions import LocalSubstitution
43
+ from launch .utilities import ensure_argument_type
40
44
from launch .utilities import normalize_to_list_of_substitutions
41
45
from launch .utilities import perform_substitutions
42
46
47
+ from launch_ros .utilities import add_node_name
43
48
from launch_ros .utilities import evaluate_parameters
49
+ from launch_ros .utilities import get_node_name_count
44
50
from launch_ros .utilities import make_namespace_absolute
51
+ from launch_ros .utilities import normalize_parameters
52
+ from launch_ros .utilities import normalize_remap_rules
45
53
from launch_ros .utilities import prefix_namespace
46
54
47
55
from rclpy .validate_namespace import validate_namespace
48
56
from rclpy .validate_node_name import validate_node_name
49
57
58
+ import yaml
59
+
50
60
from .node_trait import NodeTrait
51
61
from ..parameter_descriptions import Parameter
52
62
from ..parameters_type import SomeParameters
@@ -114,11 +124,16 @@ def __init__(
114
124
:param: arguments list of extra arguments for the node
115
125
:param: traits list of special traits of the node
116
126
"""
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 )
117
132
118
133
self .__node_name = node_name
119
134
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 ))
122
137
self .__arguments = arguments
123
138
self .__traits = traits
124
139
@@ -135,7 +150,9 @@ def __init__(
135
150
@property
136
151
def node_name (self ):
137
152
"""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
139
156
140
157
@property
141
158
def node_namespace (self ):
@@ -177,13 +194,6 @@ def expanded_parameter_arguments(self):
177
194
"""Getter for expanded_parameter_arguments."""
178
195
return self .__expanded_parameter_arguments
179
196
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
-
187
197
@property
188
198
def expanded_remappings (self ):
189
199
"""Getter for expanded_remappings."""
@@ -206,13 +216,15 @@ def _create_params_file_from_dict(self, params):
206
216
def _get_parameter_rule (self , param : 'Parameter' , context : LaunchContext ):
207
217
name , value = param .evaluate (context )
208
218
return f'{ name } :={ yaml .dump (value )} '
209
-
219
+
210
220
def prepare (self , context : LaunchContext , executable : Executable ) -> None :
211
221
try :
212
222
if self .__substitutions_performed :
213
223
# This function may have already been called by a subclass' `execute`, for example.
214
224
return
215
225
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 ])
216
228
if self .__node_name is not None :
217
229
self .__expanded_node_name = perform_substitutions (
218
230
context , normalize_to_list_of_substitutions (self .__node_name ))
@@ -227,8 +239,8 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
227
239
prefix_namespace (base_ns , expanded_node_namespace ))
228
240
if expanded_node_namespace is not None :
229
241
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 ])
232
244
validate_namespace (self .__expanded_node_namespace )
233
245
except Exception :
234
246
self .__logger .error (
@@ -249,8 +261,8 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
249
261
if global_params is not None :
250
262
param_file_path = self ._create_params_file_from_dict (global_params )
251
263
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 ])
254
266
assert os .path .isfile (param_file_path )
255
267
# expand parameters too
256
268
if self .__parameters is not None :
@@ -274,8 +286,8 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
274
286
)
275
287
continue
276
288
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 ])
279
291
# expand remappings too
280
292
global_remaps = context .launch_configurations .get ('ros_remaps' , None )
281
293
if global_remaps or self .__remappings :
@@ -288,7 +300,25 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
288
300
for src , dst in self .__remappings
289
301
])
290
302
if self .__expanded_remappings :
291
- cmd_extension = []
303
+ cmd_ext = []
292
304
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