def launch_process(launch_service, process_action, proc_info, proc_output, **kwargs): """ Launch and interact with a process. On context entering, start execution of a ``process_action`` using the given ``launch_service`` and yield a `ProcessProxy` to that ``process_action``. On context exiting, shut the process down if it has not been terminated yet. All additional arguments are forwarded to `ProcessProxy` on construction. """ ensure_argument_type( process_action, types=launch.actions.ExecuteProcess, argument_name='process_action' ) launch_service.emit_event( event=launch.events.IncludeLaunchDescription( launch_description=launch.LaunchDescription([process_action]) ) ) process_proxy = ProcessProxy(process_action, proc_info, proc_output, **kwargs) try: yield process_proxy finally: if not process_proxy.terminated: launch_service.emit_event( event=launch.events.process.ShutdownProcess( process_matcher=launch.events.matches_action(process_action) ) )
def normalize_parameters(parameters: SomeParameters) -> Parameters: """ Normalize the types used to store parameters to substitution types. The passed parameters must be an iterable where each element is a path to a yaml file or a dict. The normalized parameters will have all paths converted to a list of :class:`Substitution`, and dictionaries normalized using :meth:`normalize_parameter_dict`. """ if isinstance(parameters, str) or not isinstance(parameters, Sequence): raise TypeError( 'Expecting list of parameters, got {}'.format(parameters)) normalized_params = [] # type: List[Union[ParameterFile, ParametersDict]] for param in parameters: if isinstance(param, Mapping): normalized_params.append(normalize_parameter_dict(param)) else: # It's a path, normalize to a list of substitutions if isinstance(param, pathlib.Path): param = str(param) ensure_argument_type(param, SomeSubstitutionsType_types_tuple, 'parameters') normalized_params.append( tuple(normalize_to_list_of_substitutions(param))) return tuple(normalized_params)
def __init__(self, param_file: Union[FilePath, SomeSubstitutionsType], *, allow_substs: [bool, SomeSubstitutionsType] = False) -> None: """ Construct a parameter file description. :param param_file: Path to a parameter file. :param allow_subst: Allow substitutions in the parameter file. """ # In Python, __del__ is called even if the constructor throws an # exception. It is possible for ensure_argument_type() below to # throw an exception and try to access these member variables # during cleanup, so make sure to initialize them here. self.__evaluated_param_file: Optional[Path] = None self.__created_tmp_file = False ensure_argument_type( param_file, SomeSubstitutionsType_types_tuple + (os.PathLike, bytes), 'param_file', 'ParameterFile()') ensure_argument_type(allow_substs, bool, 'allow_subst', 'ParameterFile()') self.__param_file: Union[List[Substitution], FilePath] = param_file if isinstance(param_file, SomeSubstitutionsType_types_tuple): self.__param_file = normalize_to_list_of_substitutions(param_file) self.__allow_substs = normalize_typed_substitution(allow_substs, data_type=bool) self.__evaluated_allow_substs: Optional[bool] = None
def test_invalid_argument_types_with_caller(): """Test the ensure_argument_type function with invalid input and caller name.""" with pytest.raises(TypeError) as ex: ensure_argument_type(1, [float, complex, list, tuple], 'test arg name', 'My test caller') assert 'test arg name' in str(ex.value) assert 'My test caller' in str(ex.value)
def __init__(self, name: SomeSubstitutionsType, value: SomeValueType, *, value_type: Optional[AllowedTypesType] = None) -> None: """ Construct a parameter description. :param name: Name of the parameter. :param value: Value of the parameter. :param value_type: Used when `value` is a substitution, to coerce the result. Can be one of: - A scalar type: `int`, `str`, `float`, `bool`. `bool` are written like in `yaml`. Both `1` and `1.` are valid floats. - An uniform list: `List[int]`, `List[str]`, `List[float]`, `List[bool]`. Lists are written like in `yaml`. - `None`, which means that yaml rules will be used. The result of the convertion must be one of the above types, if not `ValueError` is raised. If value is not a substitution and this parameter is provided, it will be used to check `value` type. """ ensure_argument_type(name, SomeSubstitutionsType_types_tuple, 'name') self.__name = normalize_to_list_of_substitutions(name) self.__parameter_value = ParameterValue(value, value_type=value_type) self.__evaluated_parameter_name: Optional[Text] = None self.__evaluated_parameter_rule: Optional[Tuple[ Text, 'EvaluatedParameterValue']] = None
def __init__( self, *, composable_node_descriptions: List[ComposableNode], target_container: Union[SomeSubstitutionsType, ComposableNodeContainer], **kwargs, ) -> None: """ Construct a LoadComposableNodes action. The container node is expected to provide a `~/_container/load_node` service for loading purposes. Loading will be performed sequentially. When executed, this action will block until the container's load service is available. Make sure any LoadComposableNode action is executed only after its container processes has started. :param composable_node_descriptions: descriptions of composable nodes to be loaded :param target_container: the container to load the nodes into """ ensure_argument_type( target_container, list(SomeSubstitutionsType_types_tuple) + [ComposableNodeContainer], 'target_container', 'LoadComposableNodes' ) super().__init__(**kwargs) self.__composable_node_descriptions = composable_node_descriptions self.__target_container = target_container self.__final_target_container_name = None # type: Optional[Text] self.__logger = launch.logging.get_logger(__name__)
def expand_dict(input_dict): expanded_dict = {} for k, v in input_dict.items(): # Key (parameter/group name) can only be a string/Substitutions that evaluates # to a string. expanded_key = perform_substitutions( context, normalize_to_list_of_substitutions(k)) if isinstance(v, dict): # Expand the nested dict. expanded_value = expand_dict(v) elif isinstance(v, list): # Expand each element. expanded_value = [] for e in v: if isinstance(e, list): raise TypeError( 'Nested lists are not supported for parameters: {} found in {}' .format(e, v)) expanded_value.append( perform_substitution_if_applicable(context, e)) # Tuples are treated as Substitution(s) to be concatenated. elif isinstance(v, tuple): for e in v: ensure_argument_type( e, SomeSubstitutionsType_types_tuple, 'parameter dictionary tuple entry', 'Node') expanded_value = perform_substitutions( context, normalize_to_list_of_substitutions(v)) else: expanded_value = perform_substitution_if_applicable( context, v) expanded_dict[expanded_key] = expanded_value return expanded_dict
def _normalize_parameter_array_value( value: SomeParameterValue) -> ParameterValue: """Normalize substitutions while preserving the type if it's not a substitution.""" if not isinstance(value, Sequence): raise TypeError('Value {} must be a sequence'.format(repr(value))) # Figure out what type the list should be has_types = set() # type: Set[type] for subvalue in value: allowed_subtypes = (float, int, str, bool) + SomeSubstitutionsType_types_tuple ensure_argument_type(subvalue, allowed_subtypes, 'subvalue') if isinstance(subvalue, Substitution): has_types.add(Substitution) elif isinstance(subvalue, str): has_types.add(str) elif isinstance(subvalue, bool): has_types.add(bool) elif isinstance(subvalue, int): has_types.add(int) elif isinstance(subvalue, float): has_types.add(float) elif isinstance(subvalue, Sequence): has_types.add(tuple) else: raise RuntimeError('Failed to handle type {}'.format( repr(subvalue))) if {int} == has_types: # everything is an integer make_mypy_happy_int = cast(List[int], value) return tuple(int(e) for e in make_mypy_happy_int) elif has_types in ({float}, {int, float}): # all were floats or ints, so return floats make_mypy_happy_float = cast(List[Union[int, float]], value) return tuple(float(e) for e in make_mypy_happy_float) elif Substitution in has_types and has_types.issubset( {str, Substitution, tuple}): # make a list of substitutions forming a single string return tuple( normalize_to_list_of_substitutions( cast(SomeSubstitutionsType, value))) elif {bool} == has_types: # all where bools return tuple(bool(e) for e in value) else: # Should evaluate to a list of strings # Normalize to a list of lists of substitutions new_value = [] # type: List[SomeSubstitutionsType] for element in value: if isinstance(element, float) or isinstance( element, int) or isinstance(element, bool): new_value.append(str(element)) else: new_value.append(element) return tuple(normalize_to_list_of_substitutions(e) for e in new_value)
def normalize_remap_rule(remap_rule: SomeRemapRule) -> RemapRule: """Normalize a remap rule to a specific type.""" ensure_argument_type(remap_rule, (tuple), 'remap_rule') if len(remap_rule) != 2: raise TypeError( 'remap_rule must be a tuple of length 2, got length {}'.format( len(remap_rule))) from_sub = tuple(normalize_to_list_of_substitutions(remap_rule[0])) to_sub = tuple(normalize_to_list_of_substitutions(remap_rule[1])) return from_sub, to_sub
def __init__(self, *, period: Union[float, SomeSubstitutionsType], actions: Iterable[LaunchDescriptionEntity], **kwargs) -> None: """ Create a RosTimer. :param period: is the time (in seconds) to set the timer for. :param actions: is an iterable containing actions to be executed upon on timeout. """ super().__init__(period=period, actions=actions, **kwargs) period_types = list(SomeSubstitutionsType_types_tuple) + [float] ensure_argument_type(period, period_types, 'period', 'RosTimer') ensure_argument_type(actions, collections.abc.Iterable, 'actions', 'RosTimer') self.__period = type_utils.normalize_typed_substitution(period, float) self.__timer_future: Optional[asyncio.Future] = None
def __init__(self, process_action, proc_info, proc_output, *, output_filter=None): """ Construct a proxy for the given ``process_action``. :param process_action: `launch.actions.ExecuteProcess` instance to proxy. :param proc_info: `ActiveProcInfoHandler` tracking process state. :param proc_output: `ActiveIoHandler` tracking process output. :param output_filter: an optional callable to filter output text. """ ensure_argument_type(process_action, types=launch.actions.ExecuteProcess, argument_name='process_action') ensure_argument_type(proc_info, types=ActiveProcInfoHandler, argument_name='proc_info') ensure_argument_type(proc_output, types=ActiveIoHandler, argument_name='proc_output') if output_filter is not None and not callable(output_filter): raise TypeError( "Expected 'output_filter' to be callable but got '{!r}'". format(output_filter)) self._process_action = process_action self._proc_info = proc_info self._proc_output = proc_output self._output_filter = output_filter
def evaluate_parameter_dict( context: LaunchContext, parameters: ParametersDict ) -> Dict[str, EvaluatedParameterValue]: if not isinstance(parameters, Mapping): raise TypeError('expected dict') output_dict = {} # type: Dict[str, EvaluatedParameterValue] for name, value in parameters.items(): if not isinstance(name, tuple): raise TypeError('Expecting tuple of substitutions got {}'.format(repr(name))) evaluated_name = perform_substitutions(context, list(name)) # type: str evaluated_value = None # type: Optional[EvaluatedParameterValue] if isinstance(value, tuple) and len(value): if isinstance(value[0], Substitution): # Value is a list of substitutions, so perform them to make a string evaluated_value = perform_substitutions(context, list(value)) elif isinstance(value[0], Sequence): # Value is an array of a list of substitutions output_subvalue = [] # List[str] for subvalue in value: output_subvalue.append(perform_substitutions(context, list(subvalue))) evaluated_value = tuple(output_subvalue) else: # Value is an array of the same type, so nothing to evaluate. output_value = [] target_type = type(value[0]) for i, subvalue in enumerate(value): output_value.append(target_type(subvalue)) evaluated_value = tuple(output_value) else: # Value is a singular type, so nothing to evaluate ensure_argument_type(value, (float, int, str, bool, bytes), 'value') evaluated_value = cast(Union[float, int, str, bool, bytes], value) if evaluated_value is None: raise TypeError('given unnormalized parameters %r, %r' % (name, value)) output_dict[evaluated_name] = evaluated_value return output_dict
def __init__(self, param_file: Union[FilePath, SomeSubstitutionsType], *, allow_substs: [bool, SomeSubstitutionsType] = False) -> None: """ Construct a parameter file description. :param param_file: Path to a parameter file. :param allow_subst: Allow substitutions in the parameter file. """ ensure_argument_type( param_file, SomeSubstitutionsType_types_tuple + (os.PathLike, bytes), 'param_file', 'ParameterFile()') ensure_argument_type(allow_substs, bool, 'allow_subst', 'ParameterFile()') self.__param_file: Union[List[Substitution], FilePath] = param_file if isinstance(param_file, SomeSubstitutionsType_types_tuple): self.__param_file = normalize_to_list_of_substitutions(param_file) self.__allow_substs = normalize_typed_substitution(allow_substs, data_type=bool) self.__evaluated_allow_substs: Optional[bool] = None self.__evaluated_param_file: Optional[Path] = None self.__created_tmp_file = False
def test_invalid_argument_types(): """Test the ensure_argument_type function with invalid input.""" with pytest.raises(TypeError): ensure_argument_type(None, None, 'none') with pytest.raises(TypeError): ensure_argument_type(1, str, 'foo') with pytest.raises(TypeError): ensure_argument_type('bar', [int, float, list, tuple], 'arg_bar') class MockClass: pass class MockChildClass(MockClass): pass mock_class_obj = MockClass() with pytest.raises(TypeError): ensure_argument_type(mock_class_obj, MockChildClass, 'MockChildClass')
def __init__(self, *, package: SomeSubstitutionsType, node_executable: SomeSubstitutionsType, node_name: Optional[SomeSubstitutionsType] = None, node_namespace: SomeSubstitutionsType = '', parameters: Optional[SomeParameters] = None, remappings: Optional[SomeRemapRules] = None, arguments: Optional[Iterable[SomeSubstitutionsType]] = None, **kwargs) -> None: """ Construct an Node action. Many arguments are passed eventually to :class:`launch.actions.ExecuteProcess`, so see the documentation of that class for additional details. However, the `cmd` is not meant to be used, instead use the `node_executable` and `arguments` keyword arguments to this function. This action, once executed, delegates most work to the :class:`launch.actions.ExecuteProcess`, but it also converts some ROS specific arguments into generic command line arguments. The launch_ros.substitutions.ExecutableInPackage substitution is used to find the executable at runtime, so this Action also raise the exceptions that substituion can raise when the package or executable are not found. If the node_name is not given (or is None) then no name is passed to the node on creation and instead the default name specified within the code of the node is used instead. The node_namespace can either be absolute (i.e. starts with /) or relative. If absolute, then nothing else is considered and this is passed directly to the node to set the namespace. If relative, the namespace in the 'ros_namespace' LaunchConfiguration will be prepended to the given relative node namespace. If no node_namespace is given, then the default namespace `/` is assumed. The parameters are passed as a list, with each element either a yaml file that contains parameter rules (string or pathlib.Path to the full path of the file), or a dictionary that specifies parameter rules. Keys of the dictionary can be strings or an iterable of Substitutions that will be expanded to a string. Values in the dictionary can be strings, integers, floats, or tuples of Substitutions that will be expanded to a string. Additionally, values in the dictionary can be lists of the aforementioned types, or another dictionary with the same properties. A yaml file with the resulting parameters from the dictionary will be written to a temporary file, the path to which will be passed to the node. Multiple dictionaries/files can be passed: each file path will be passed in in order to the node (where the last definition of a parameter takes effect). :param: package the package in which the node executable can be found :param: node_executable the name of the executable to find :param: node_name the name of the node :param: node_namespace the ros namespace for this Node :param: parameters list of names of yaml files with parameter rules, or dictionaries of parameters. :param: remappings ordered list of 'to' and 'from' string pairs to be passed to the node as ROS remapping rules :param: arguments list of extra arguments for the node """ cmd = [ ExecutableInPackage(package=package, executable=node_executable) ] cmd += [] if arguments is None else arguments # Reserve space for ros specific arguments. # The substitutions will get expanded when the action is executed. cmd += ['--ros-args' ] # Prepend ros specific arguments with --ros-args flag if node_name is not None: cmd += [ '-r', LocalSubstitution("ros_specific_arguments['name']", description='node name') ] if parameters is not None: ensure_argument_type(parameters, (list), 'parameters', 'Node') # All elements in the list are paths to files with parameters (or substitutions that # evaluate to paths), or dictionaries of parameters (fields can be substitutions). i = 0 for param in parameters: cmd += [ LocalSubstitution( "ros_specific_arguments['params'][{}]".format(i), description='parameter {}'.format(i)) ] i += 1 normalized_params = normalize_parameters(parameters) if remappings is not None: i = 0 for remapping in normalize_remap_rules(remappings): k, v = remapping cmd += [ '-r', LocalSubstitution( "ros_specific_arguments['remaps'][{}]".format(i), description='remapping {}'.format(i)) ] i += 1 super().__init__(cmd=cmd, **kwargs) self.__package = package self.__node_executable = node_executable self.__node_name = node_name self.__node_namespace = node_namespace self.__parameters = [] if parameters is None else normalized_params self.__remappings = [] if remappings is None else remappings self.__arguments = arguments self.__expanded_node_name = '<node_name_unspecified>' self.__expanded_node_namespace = '' self.__final_node_name = None # type: Optional[Text] self.__expanded_parameter_files = None # type: Optional[List[Text]] self.__expanded_remappings = None # type: Optional[List[Tuple[Text, Text]]] self.__substitutions_performed = False self.__logger = launch.logging.get_logger(__name__)
def evaluate_parameters(context: LaunchContext, parameters: Parameters) -> EvaluatedParameters: """ Evaluate substitutions to produce paths and name/value pairs. The parameters must have been normalized with normalize_parameters() prior to calling this. :param parameters: normalized parameters :returns: values after evaluating lists of substitutions """ output_params = [ ] # type: List[Union[pathlib.Path, Dict[str, EvaluatedParameterValue]]] for param in parameters: # If it's a list of substitutions then evaluate them to a string and return a pathlib.Path if isinstance(param, tuple) and len(param) and isinstance( param[0], Substitution): # Evaluate a list of Substitution to a file path output_params.append( pathlib.Path(perform_substitutions(context, list(param)))) elif isinstance(param, Mapping): # It's a list of name/value pairs output_dict = {} # type: Dict[str, EvaluatedParameterValue] for name, value in param.items(): if not isinstance(name, tuple): raise TypeError( 'Expecting tuple of substitutions got {}'.format( repr(name))) evaluated_name = perform_substitutions(context, list(name)) # type: str evaluated_value = None # type: Optional[EvaluatedParameterValue] if isinstance(value, tuple) and len(value): if isinstance(value[0], Substitution): # Value is a list of substitutions, so perform them to make a string evaluated_value = perform_substitutions( context, list(value)) elif isinstance(value[0], Sequence): # Value is an array of a list of substitutions output_subvalue = [] # List[str] for subvalue in value: output_subvalue.append( perform_substitutions(context, list(subvalue))) evaluated_value = tuple(output_subvalue) else: # Value is an array of the same type, so nothing to evaluate. output_value = [] target_type = type(value[0]) for i, subvalue in enumerate(value): output_value.append(target_type(subvalue)) evaluated_value = tuple(output_value) else: # Value is a singular type, so nothing to evaluate ensure_argument_type(value, (float, int, str, bool, bytes), 'value') evaluated_value = cast(Union[float, int, str, bool, bytes], value) if evaluated_value is None: raise TypeError('given unnormalized parameters %r, %r' % (name, value)) output_dict[evaluated_name] = evaluated_value output_params.append(output_dict) return tuple(output_params)
def test_valid_argument_types(): """Test the ensure_argument_type function with valid input.""" ensure_argument_type('foo', str, 'arg_foo') ensure_argument_type(1, [str, int], 'arg_bar') ensure_argument_type([3.14159], [list, str, int], '') ensure_argument_type(3.14159, [float], ' ') class MockClass: pass mock_class_obj = MockClass() ensure_argument_type(mock_class_obj, MockClass, 'MockClass') # With inheritence class MockChildClass(MockClass): pass mock_child_obj = MockChildClass() ensure_argument_type(mock_child_obj, MockClass, 'MockChildClass')
def evaluate_parameter_dict( context: LaunchContext, parameters: ParametersDict) -> Dict[str, EvaluatedParameterValue]: def check_sequence_type_is_allowed(sequence): # Check if the items of the sequence aren't dissimilar. # Also, check if the item type is one of bool, int, float, str. if not sequence: # Don't allow empty sequences return False subtype = type(sequence[0]) if subtype not in (bool, int, float, str): return False for item in sequence: if not isinstance(item, subtype): return False return True if not isinstance(parameters, Mapping): raise TypeError('expected dict') output_dict: Dict[str, EvaluatedParameterValue] = {} for name, value in parameters.items(): if not isinstance(name, tuple): raise TypeError('Expecting tuple of substitutions got {}'.format( repr(name))) evaluated_name: str = perform_substitutions(context, list(name)) evaluated_value: Optional[EvaluatedParameterValue] = None if isinstance(value, tuple) and len(value): if isinstance(value[0], Substitution): # Value is a list of substitutions, so perform them to make a string evaluated_value = perform_substitutions(context, list(value)) yaml_evaluated_value = yaml.safe_load(evaluated_value) if yaml_evaluated_value is None: yaml_evaluated_value = '' if type(yaml_evaluated_value) in (bool, int, float, str, bytes): evaluated_value = yaml_evaluated_value elif isinstance(yaml_evaluated_value, Sequence): # str and bytes were already handled in the previous case # If it is a list with dissimilar types, don't evaluate the value as yaml. if not check_sequence_type_is_allowed( yaml_evaluated_value): raise TypeError( 'Expected a non-empty sequence, with items of uniform type. ' 'Allowed sequence item types are bool, int, float, str.' ) evaluated_value = tuple(yaml_evaluated_value) else: raise TypeError( 'Allowed value types are bytes, bool, int, float, str, Sequence[bool]' ', Sequence[int], Sequence[float], Sequence[str]. Got {}.' .format(type(yaml_evaluated_value))) elif isinstance(value[0], Sequence): # Value is an array of a list of substitutions output_subvalue: List[str] = [] for subvalue in value: value = perform_substitutions(context, list(subvalue)) output_subvalue.append(value) evaluated_value = tuple(output_subvalue) # All values in a list must have the same type. # If they don't then assume it is a list of strings yaml_evaluated_value = [ yaml.safe_load(item) for item in evaluated_value ] if not check_sequence_type_is_allowed(yaml_evaluated_value): raise TypeError( 'Expected a non-empty sequence, with items of uniform type. ' 'Allowed sequence item types are bool, int, float, str.' ) evaluated_value = tuple(yaml_evaluated_value) else: # Value is an array of the same type, so nothing to evaluate. output_value = [] target_type = type(value[0]) for i, subvalue in enumerate(value): output_value.append(target_type(subvalue)) evaluated_value = tuple(output_value) else: # Value is a singular type, so nothing to evaluate ensure_argument_type(value, (float, int, str, bool, bytes), 'value') evaluated_value = cast(Union[float, int, str, bool, bytes], value) if evaluated_value is None: raise TypeError('given unnormalized parameters %r, %r' % (name, value)) output_dict[evaluated_name] = evaluated_value return output_dict
def __init__(self, *, executable: Optional[SomeSubstitutionsType] = None, node_executable: Optional[SomeSubstitutionsType] = None, package: Optional[SomeSubstitutionsType] = None, name: Optional[SomeSubstitutionsType] = None, namespace: Optional[SomeSubstitutionsType] = None, node_name: Optional[SomeSubstitutionsType] = None, node_namespace: SomeSubstitutionsType = None, exec_name: Optional[SomeSubstitutionsType] = None, parameters: Optional[SomeParameters] = None, remappings: Optional[SomeRemapRules] = None, arguments: Optional[Iterable[SomeSubstitutionsType]] = None, **kwargs) -> None: """ Construct an Node action. Many arguments are passed eventually to :class:`launch.actions.ExecuteProcess`, so see the documentation of that class for additional details. However, the `cmd` is not meant to be used, instead use the `executable` and `arguments` keyword arguments to this function. This action, once executed, delegates most work to the :class:`launch.actions.ExecuteProcess`, but it also converts some ROS specific arguments into generic command line arguments. The launch_ros.substitutions.ExecutableInPackage substitution is used to find the executable at runtime, so this Action also raise the exceptions that substituion can raise when the package or executable are not found. If the name is not given (or is None) then no name is passed to the node on creation and instead the default name specified within the code of the node is used instead. The namespace can either be absolute (i.e. starts with /) or relative. If absolute, then nothing else is considered and this is passed directly to the node to set the namespace. If relative, the namespace in the 'ros_namespace' LaunchConfiguration will be prepended to the given relative node namespace. If no namespace is given, then the default namespace `/` is assumed. The parameters are passed as a list, with each element either a yaml file that contains parameter rules (string or pathlib.Path to the full path of the file), or a dictionary that specifies parameter rules. Keys of the dictionary can be strings or an iterable of Substitutions that will be expanded to a string. Values in the dictionary can be strings, integers, floats, or tuples of Substitutions that will be expanded to a string. Additionally, values in the dictionary can be lists of the aforementioned types, or another dictionary with the same properties. A yaml file with the resulting parameters from the dictionary will be written to a temporary file, the path to which will be passed to the node. Multiple dictionaries/files can be passed: each file path will be passed in in order to the node (where the last definition of a parameter takes effect). .. deprecated:: Foxy Parameters `node_executable`, `node_name`, and `node_namespace` are deprecated. Use `executable`, `name`, and `namespace` instead. :param: executable the name of the executable to find if a package is provided or otherwise a path to the executable to run. :param: node_executable (DEPRECATED) the name of the executable to find if a package is provided or otherwise a path to the executable to run. :param: package the package in which the node executable can be found :param: name the name of the node :param: namespace the ROS namespace for this Node :param: exec_name the label used to represent the process. Defaults to the basename of node executable. :param: node_name (DEPRECATED) the name of the node :param: node_namespace (DEPRECATED) the ros namespace for this Node :param: parameters list of names of yaml files with parameter rules, or dictionaries of parameters. :param: remappings ordered list of 'to' and 'from' string pairs to be passed to the node as ROS remapping rules :param: arguments list of extra arguments for the node """ if node_executable is not None: warnings.warn( "The parameter 'node_executable' is deprecated, use 'executable' instead", stacklevel=2) if executable is not None: raise RuntimeError( "Passing both 'node_executable' and 'executable' parameters. " "Only use 'executable'") executable = node_executable if package is not None: cmd = [ExecutableInPackage(package=package, executable=executable)] else: cmd = [executable] cmd += [] if arguments is None else arguments # Reserve space for ros specific arguments. # The substitutions will get expanded when the action is executed. cmd += ['--ros-args' ] # Prepend ros specific arguments with --ros-args flag if node_name is not None: warnings.warn( "The parameter 'node_name' is deprecated, use 'name' instead", stacklevel=2) if name is not None: raise RuntimeError( "Passing both 'node_name' and 'name' parameters. Only use 'name'." ) cmd += [ '-r', LocalSubstitution("ros_specific_arguments['name']", description='node name') ] name = node_name if name is not None: cmd += [ '-r', LocalSubstitution("ros_specific_arguments['name']", description='node name') ] if node_namespace: warnings.warn( "The parameter 'node_namespace' is deprecated, use 'namespace' instead" ) if namespace: raise RuntimeError( "Passing both 'node_namespace' and 'namespace' parameters. " "Only use 'namespace'.") namespace = node_namespace if parameters is not None: ensure_argument_type(parameters, (list), 'parameters', 'Node') # All elements in the list are paths to files with parameters (or substitutions that # evaluate to paths), or dictionaries of parameters (fields can be substitutions). normalized_params = normalize_parameters(parameters) # Forward 'exec_name' as to ExecuteProcess constructor kwargs['name'] = exec_name super().__init__(cmd=cmd, **kwargs) self.__package = package self.__node_executable = executable self.__node_name = name self.__node_namespace = namespace self.__parameters = [] if parameters is None else normalized_params self.__remappings = [] if remappings is None else list( normalize_remap_rules(remappings)) self.__arguments = arguments self.__expanded_node_name = self.UNSPECIFIED_NODE_NAME self.__expanded_node_namespace = self.UNSPECIFIED_NODE_NAMESPACE self.__expanded_parameter_arguments = None # type: Optional[List[Tuple[Text, bool]]] self.__final_node_name = None # type: Optional[Text] self.__expanded_remappings = None # type: Optional[List[Tuple[Text, Text]]] self.__substitutions_performed = False self.__logger = launch.logging.get_logger(__name__)