def __init__(self, node_name, *, cli_args=None, namespace=None, use_global_arguments=True): self._handle = None self.publishers = [] self.subscriptions = [] self.clients = [] self.services = [] self.timers = [] self.guards = [] self._default_callback_group = MutuallyExclusiveCallbackGroup() namespace = namespace or '' if not ok(): raise NotInitializedException('cannot create node') try: self._handle = _rclpy.rclpy_create_node(node_name, namespace, cli_args, use_global_arguments) except ValueError: # these will raise more specific errors if the name or namespace is bad validate_node_name(node_name) # emulate what rcl_node_init() does to accept '' and relative namespaces if not namespace: namespace = '/' if not namespace.startswith('/'): namespace = '/' + namespace validate_namespace(namespace) # Should not get to this point raise RuntimeError('rclpy_create_node failed for unknown reason') self._logger = get_logger( _rclpy.rclpy_get_node_logger_name(self.handle))
def _validate_topic_or_service_name(self, topic_or_service_name, *, is_service=False): name = self.get_name() namespace = self.get_namespace() validate_node_name(name) validate_namespace(namespace) validate_topic_name(topic_or_service_name, is_service=is_service) expanded_topic_or_service_name = expand_topic_name(topic_or_service_name, name, namespace) validate_full_topic_name(expanded_topic_or_service_name, is_service=is_service)
def test_validate_namespace_failures(self): # namespace must not be empty with self.assertRaisesRegex(InvalidNamespaceException, 'empty'): validate_namespace('') # namespace must start with / with self.assertRaisesRegex(InvalidNamespaceException, 'must be absolute'): validate_namespace('invalid_namespace')
def test_validate_namespace(self): tests = [ '/my_ns', '/', ] for topic in tests: # Will raise if invalid validate_namespace(topic)
def __init__( self, node_name, *, context=None, cli_args=None, namespace=None, use_global_arguments=True, start_parameter_services=True, initial_parameters=None ): self._handle = None self._context = get_default_context() if context is None else context self._parameters = {} self.publishers = [] self.subscriptions = [] self.clients = [] self.services = [] self.timers = [] self.guards = [] self.waitables = [] self._default_callback_group = MutuallyExclusiveCallbackGroup() self._parameters_callback = None namespace = namespace or '' if not self._context.ok(): raise NotInitializedException('cannot create node') try: self._handle = _rclpy.rclpy_create_node( node_name, namespace, self._context.handle, cli_args, use_global_arguments) except ValueError: # these will raise more specific errors if the name or namespace is bad validate_node_name(node_name) # emulate what rcl_node_init() does to accept '' and relative namespaces if not namespace: namespace = '/' if not namespace.startswith('/'): namespace = '/' + namespace validate_namespace(namespace) # Should not get to this point raise RuntimeError('rclpy_create_node failed for unknown reason') self._logger = get_logger(_rclpy.rclpy_get_node_logger_name(self.handle)) # Clock that has support for ROS time. # TODO(dhood): use sim time if parameter has been set on the node. self._clock = ROSClock() self._time_source = TimeSource(node=self) self._time_source.attach_clock(self._clock) self.__executor_weakref = None self._parameter_event_publisher = self.create_publisher( ParameterEvent, 'parameter_events', qos_profile=qos_profile_parameter_events) node_parameters = _rclpy.rclpy_get_node_parameters(Parameter, self.handle) # Combine parameters from params files with those from the node constructor and # use the set_parameters_atomically API so a parameter event is published. if initial_parameters is not None: node_parameters.update({p.name: p for p in initial_parameters}) self.set_parameters_atomically(node_parameters.values()) if start_parameter_services: self._parameter_service = ParameterService(self)
def __init__(self, node_name, *, cli_args=None, namespace=None, use_global_arguments=True, start_parameter_services=True): self._handle = None self._parameters = {} self.publishers = [] self.subscriptions = [] self.clients = [] self.services = [] self.timers = [] self.guards = [] self._default_callback_group = MutuallyExclusiveCallbackGroup() self._parameters_callback = None namespace = namespace or '' if not ok(): raise NotInitializedException('cannot create node') try: self._handle = _rclpy.rclpy_create_node(node_name, namespace, cli_args, use_global_arguments) except ValueError: # these will raise more specific errors if the name or namespace is bad validate_node_name(node_name) # emulate what rcl_node_init() does to accept '' and relative namespaces if not namespace: namespace = '/' if not namespace.startswith('/'): namespace = '/' + namespace validate_namespace(namespace) # Should not get to this point raise RuntimeError('rclpy_create_node failed for unknown reason') self._logger = get_logger( _rclpy.rclpy_get_node_logger_name(self.handle)) # Clock that has support for ROS time. # TODO(dhood): use sim time if parameter has been set on the node. self._clock = ROSClock() self._time_source = TimeSource(node=self) self._time_source.attach_clock(self._clock) self.__executor_weakref = None if start_parameter_services: self._parameter_service = ParameterService(self)
def execute(self, context: LaunchContext): """Execute the action.""" pushed_namespace = perform_substitutions(context, self.namespace) previous_namespace = context.launch_configurations.get( 'ros_namespace', None) namespace = make_namespace_absolute( prefix_namespace(previous_namespace, pushed_namespace)) try: validate_namespace(namespace) except Exception: raise SubstitutionFailure( 'The resulting namespace is invalid:' " [previous_namespace='{}', pushed_namespace='{}']".format( previous_namespace, pushed_namespace)) context.launch_configurations['ros_namespace'] = namespace
def _is_enclave_name_valid(name: str) -> bool: # TODO(ivanpauno): Use validate_enclave_name when it's propagated to `rclpy`. # This is not to bad for the moment. # Related with https://github.com/ros2/rclpy/issues/528. try: return validate_namespace(name) except InvalidNamespaceException as e: print(e) return False
def execute(self, context: LaunchContext): """Execute the action.""" pushed_namespace = perform_substitutions(context, self.namespace) previous_namespace = context.launch_configurations.get( 'ros_namespace', '') namespace = pushed_namespace if not pushed_namespace.startswith('/'): namespace = previous_namespace + '/' + pushed_namespace namespace = namespace.rstrip('/') if namespace != '': try: validate_namespace(namespace) except Exception: raise SubstitutionFailure( 'The resulting namespace is invalid:' " [previous_namespace='{}', pushed_namespace='{}']".format( previous_namespace, pushed_namespace)) context.launch_configurations['ros_namespace'] = namespace
def _perform_substitutions(self, context: LaunchContext) -> None: try: if self.__substitutions_performed: # This function may have already been called by a subclass' `execute`, for example. return self.__substitutions_performed = True if self.__node_name is not None: self.__expanded_node_name = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_name)) validate_node_name(self.__node_name) self.__expanded_node_name.lstrip('/') if self.__node_namespace is not None: self.__expanded_node_namespace = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_namespace)) if not self.__expanded_node_namespace.startswith('/'): self.__expanded_node_namespace = '/' + self.__expanded_node_namespace validate_namespace(self.__expanded_node_namespace) except Exception: print( "Error while expanding or validating node name or namespace for '{}':" .format( 'package={}, node_executable={}, name={}, namespace={}'. format( self.__package, self.__node_executable, self.__node_name, self.__node_namespace, ))) raise self.__final_node_name = '' if self.__expanded_node_namespace not in ['', '/']: self.__final_node_name += self.__expanded_node_namespace self.__final_node_name += '/' + self.__expanded_node_name # expand remappings too if self.__remappings is not None: self.__expanded_remappings = {} for k, v in self.__remappings: key = perform_substitutions( context, normalize_to_list_of_substitutions(k)) value = perform_substitutions( context, normalize_to_list_of_substitutions(v)) self.__expanded_remappings[key] = value
def is_key_name_valid(name): ns_and_name = name.rsplit('/', 1) if len(ns_and_name) != 2: print("The key name needs to start with '/'") return False node_ns = ns_and_name[0] if ns_and_name[0] else '/' node_name = ns_and_name[1] try: return (validate_namespace(node_ns) and validate_node_name(node_name)) except (InvalidNamespaceException, InvalidNodeNameException) as e: print('{}'.format(e)) return False
def _perform_substitutions(self, context: LaunchContext) -> None: try: if self.__substitutions_performed: # This function may have already been called by a subclass' `execute`, for example. return self.__substitutions_performed = True if self.__node_name is not None: self.__expanded_node_name = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_name)) validate_node_name(self.__expanded_node_name) self.__expanded_node_name.lstrip('/') self.__expanded_node_namespace = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_namespace)) if not self.__expanded_node_namespace.startswith('/'): base_ns = context.launch_configurations.get( 'ros_namespace', '') self.__expanded_node_namespace = ( base_ns + '/' + self.__expanded_node_namespace).rstrip('/') if (self.__expanded_node_namespace != '' and not self.__expanded_node_namespace.startswith('/')): self.__expanded_node_namespace = '/' + self.__expanded_node_namespace if self.__expanded_node_namespace != '': cmd_extension = [ '-r', LocalSubstitution("ros_specific_arguments['ns']") ] self.cmd.extend([ normalize_to_list_of_substitutions(x) for x in cmd_extension ]) validate_namespace(self.__expanded_node_namespace) except Exception: self.__logger.error( "Error while expanding or validating node name or namespace for '{}':" .format( 'package={}, node_executable={}, name={}, namespace={}'. format( self.__package, self.__node_executable, self.__node_name, self.__node_namespace, ))) raise self.__final_node_name = '' if self.__expanded_node_namespace not in ['', '/']: self.__final_node_name += self.__expanded_node_namespace self.__final_node_name += '/' + self.__expanded_node_name # expand parameters too if self.__parameters is not None: self.__expanded_parameter_files = [] evaluated_parameters = evaluate_parameters(context, self.__parameters) for params in evaluated_parameters: if isinstance(params, dict): param_file_path = self._create_params_file_from_dict( params) elif isinstance(params, pathlib.Path): param_file_path = str(params) else: raise RuntimeError( 'invalid normalized parameters {}'.format( repr(params))) if not os.path.isfile(param_file_path): self.__logger.warning( 'Parameter file path is not a file: {}'.format( param_file_path), ) # Don't skip adding the file to the parameter list since space has been # reserved for it in the ros_specific_arguments. self.__expanded_parameter_files.append(param_file_path) # expand remappings too if self.__remappings is not None: self.__expanded_remappings = [] for k, v in self.__remappings: key = perform_substitutions( context, normalize_to_list_of_substitutions(k)) value = perform_substitutions( context, normalize_to_list_of_substitutions(v)) self.__expanded_remappings.append((key, value))
def _perform_substitutions(self, context: LaunchContext) -> None: # Here to avoid cyclic import from ..descriptions import Parameter try: if self.__substitutions_performed: # This function may have already been called by a subclass' `execute`, for example. return self.__substitutions_performed = True if self.__node_name is not None: self.__expanded_node_name = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_name)) validate_node_name(self.__expanded_node_name) self.__expanded_node_name.lstrip('/') expanded_node_namespace: Optional[Text] = None if self.__node_namespace is not None: expanded_node_namespace = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_namespace)) base_ns = context.launch_configurations.get('ros_namespace', None) expanded_node_namespace = make_namespace_absolute( prefix_namespace(base_ns, expanded_node_namespace)) if expanded_node_namespace is not None: self.__expanded_node_namespace = expanded_node_namespace cmd_extension = ['-r', LocalSubstitution("ros_specific_arguments['ns']")] self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension]) validate_namespace(self.__expanded_node_namespace) except Exception: self.__logger.error( "Error while expanding or validating node name or namespace for '{}':" .format('package={}, executable={}, name={}, namespace={}'.format( self.__package, self.__node_executable, self.__node_name, self.__node_namespace, )) ) raise self.__final_node_name = prefix_namespace( self.__expanded_node_namespace, self.__expanded_node_name) # expand global parameters first, # so they can be overriden with specific parameters of this Node global_params = context.launch_configurations.get('ros_params', None) if global_params is not None or self.__parameters is not None: self.__expanded_parameter_arguments = [] if global_params is not None: param_file_path = self._create_params_file_from_dict(global_params) self.__expanded_parameter_arguments.append((param_file_path, True)) cmd_extension = ['--params-file', f'{param_file_path}'] self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension]) assert os.path.isfile(param_file_path) # expand parameters too if self.__parameters is not None: evaluated_parameters = evaluate_parameters(context, self.__parameters) for params in evaluated_parameters: is_file = False if isinstance(params, dict): param_argument = self._create_params_file_from_dict(params) is_file = True assert os.path.isfile(param_argument) elif isinstance(params, pathlib.Path): param_argument = str(params) is_file = True elif isinstance(params, Parameter): param_argument = self._get_parameter_rule(params, context) else: raise RuntimeError('invalid normalized parameters {}'.format(repr(params))) if is_file and not os.path.isfile(param_argument): self.__logger.warning( 'Parameter file path is not a file: {}'.format(param_argument), ) continue self.__expanded_parameter_arguments.append((param_argument, is_file)) cmd_extension = ['--params-file' if is_file else '-p', f'{param_argument}'] self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension]) # expand remappings too global_remaps = context.launch_configurations.get('ros_remaps', None) if global_remaps or self.__remappings: self.__expanded_remappings = [] if global_remaps: self.__expanded_remappings.extend(global_remaps) if self.__remappings: self.__expanded_remappings.extend([ (perform_substitutions(context, src), perform_substitutions(context, dst)) for src, dst in self.__remappings ]) if self.__expanded_remappings: cmd_extension = [] for src, dst in self.__expanded_remappings: cmd_extension.extend(['-r', f'{src}:={dst}']) self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension])
def __init__( self, node_name: str, *, context: Context = None, cli_args: List[str] = None, namespace: str = None, use_global_arguments: bool = True, start_parameter_services: bool = True, initial_parameters: List[Parameter] = None, allow_undeclared_parameters: bool = False, automatically_declare_initial_parameters: bool = True) -> None: """ Constructor. :param node_name: A name to give to this node. Validated by :func:`validate_node_name`. :param context: The context to be associated with, or ``None`` for the default global context. :param cli_args: A list of strings of command line args to be used only by this node. :param namespace: The namespace to which relative topic and service names will be prefixed. Validated by :func:`validate_namespace`. :param use_global_arguments: ``False`` if the node should ignore process-wide command line args. :param start_parameter_services: ``False`` if the node should not create parameter services. :param initial_parameters: A list of parameters to be set during node creation. :param allow_undeclared_parameters: True if undeclared parameters are allowed. This flag affects the behavior of parameter-related operations. :param automatically_declare_initial_parameters: True if initial parameters have to be declared upon node creation, false otherwise. """ self.__handle = None self._context = get_default_context() if context is None else context self._parameters: dict = {} self.__publishers: List[Publisher] = [] self.__subscriptions: List[Subscription] = [] self.__clients: List[Client] = [] self.__services: List[Service] = [] self.__timers: List[WallTimer] = [] self.__guards: List[GuardCondition] = [] self.__waitables: List[Waitable] = [] self._default_callback_group = MutuallyExclusiveCallbackGroup() self._parameters_callback = None self._allow_undeclared_parameters = allow_undeclared_parameters self._initial_parameters = {} self._descriptors = {} namespace = namespace or '' if not self._context.ok(): raise NotInitializedException('cannot create node') try: self.__handle = Handle( _rclpy.rclpy_create_node(node_name, namespace, self._context.handle, cli_args, use_global_arguments)) except ValueError: # these will raise more specific errors if the name or namespace is bad validate_node_name(node_name) # emulate what rcl_node_init() does to accept '' and relative namespaces if not namespace: namespace = '/' if not namespace.startswith('/'): namespace = '/' + namespace validate_namespace(namespace) # Should not get to this point raise RuntimeError('rclpy_create_node failed for unknown reason') with self.handle as capsule: self._logger = get_logger( _rclpy.rclpy_get_node_logger_name(capsule)) # Clock that has support for ROS time. self._clock = ROSClock() self._time_source = TimeSource(node=self) self._time_source.attach_clock(self._clock) self.__executor_weakref = None self._parameter_event_publisher = self.create_publisher( ParameterEvent, 'parameter_events', qos_profile=qos_profile_parameter_events) with self.handle as capsule: self._initial_parameters = _rclpy.rclpy_get_node_parameters( Parameter, capsule) # Combine parameters from params files with those from the node constructor and # use the set_parameters_atomically API so a parameter event is published. if initial_parameters is not None: self._initial_parameters.update( {p.name: p for p in initial_parameters}) if automatically_declare_initial_parameters: self._parameters.update(self._initial_parameters) self._descriptors.update( {p: ParameterDescriptor() for p in self._parameters}) if start_parameter_services: self._parameter_service = ParameterService(self)
def _perform_substitutions(self, context: LaunchContext) -> None: try: if self.__substitutions_performed: # This function may have already been called by a subclass' `execute`, for example. return self.__substitutions_performed = True if self.__node_name is not None: self.__expanded_node_name = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_name)) validate_node_name(self.__expanded_node_name) self.__expanded_node_name.lstrip('/') expanded_node_namespace = None if self.__node_namespace is not None: expanded_node_namespace = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_namespace)) base_ns = context.launch_configurations.get('ros_namespace', None) if base_ns is not None or expanded_node_namespace is not None: if expanded_node_namespace is None: expanded_node_namespace = '' if base_ns is None: base_ns = '' if not expanded_node_namespace.startswith('/'): expanded_node_namespace = ( base_ns + '/' + expanded_node_namespace).rstrip('/') if not expanded_node_namespace.startswith('/'): expanded_node_namespace = '/' + expanded_node_namespace self.__expanded_node_namespace = expanded_node_namespace if expanded_node_namespace is not None: cmd_extension = [ '-r', LocalSubstitution("ros_specific_arguments['ns']") ] self.cmd.extend([ normalize_to_list_of_substitutions(x) for x in cmd_extension ]) validate_namespace(self.__expanded_node_namespace) except Exception: self.__logger.error( "Error while expanding or validating node name or namespace for '{}':" .format( 'package={}, executable={}, name={}, namespace={}'.format( self.__package, self.__node_executable, self.__node_name, self.__node_namespace, ))) raise self.__final_node_name = '' if self.__expanded_node_namespace != '/': self.__final_node_name += self.__expanded_node_namespace self.__final_node_name += '/' + self.__expanded_node_name # expand global parameters first, # so they can be overriden with specific parameters of this Node global_params = context.launch_configurations.get('ros_params', None) if global_params is not None or self.__parameters is not None: self.__expanded_parameter_files = [] if global_params is not None: param_file_path = self._create_params_file_from_dict(global_params) self.__expanded_parameter_files.append(param_file_path) cmd_extension = ['--params-file', f'{param_file_path}'] self.cmd.extend( [normalize_to_list_of_substitutions(x) for x in cmd_extension]) assert os.path.isfile(param_file_path) # expand parameters too if self.__parameters is not None: evaluated_parameters = evaluate_parameters(context, self.__parameters) for i, params in enumerate(evaluated_parameters): if isinstance(params, dict): param_file_path = self._create_params_file_from_dict( params) assert os.path.isfile(param_file_path) elif isinstance(params, pathlib.Path): param_file_path = str(params) else: raise RuntimeError( 'invalid normalized parameters {}'.format( repr(params))) if not os.path.isfile(param_file_path): self.__logger.warning( 'Parameter file path is not a file: {}'.format( param_file_path), ) continue self.__expanded_parameter_files.append(param_file_path) cmd_extension = ['--params-file', f'{param_file_path}'] self.cmd.extend([ normalize_to_list_of_substitutions(x) for x in cmd_extension ]) # expand remappings too if self.__remappings is not None: self.__expanded_remappings = [] for k, v in self.__remappings: key = perform_substitutions( context, normalize_to_list_of_substitutions(k)) value = perform_substitutions( context, normalize_to_list_of_substitutions(v)) self.__expanded_remappings.append((key, value))
def _perform_substitutions(self, context: LaunchContext) -> None: try: if self.__substitutions_performed: # This function may have already been called by a subclass' `execute`, for example. return self.__substitutions_performed = True if self.__node_name is not None: self.__expanded_node_name = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_name)) validate_node_name(self.__expanded_node_name) self.__expanded_node_name.lstrip('/') if self.__node_namespace is not None: self.__expanded_node_namespace = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_namespace)) if not self.__expanded_node_namespace.startswith('/'): self.__expanded_node_namespace = '/' + self.__expanded_node_namespace validate_namespace(self.__expanded_node_namespace) except Exception: _logger.error( "Error while expanding or validating node name or namespace for '{}':" .format( 'package={}, node_executable={}, name={}, namespace={}'. format( self.__package, self.__node_executable, self.__node_name, self.__node_namespace, ))) raise self.__final_node_name = '' if self.__expanded_node_namespace not in ['', '/']: self.__final_node_name += self.__expanded_node_namespace self.__final_node_name += '/' + self.__expanded_node_name # expand parameters too if self.__parameters is not None: self.__expanded_parameter_files = [] for params in self.__parameters: if isinstance(params, dict): param_file_path = self._create_params_file_from_dict( context, params) else: if isinstance(params, pathlib.Path): param_file_path = str(params) else: param_file_path = perform_substitutions( context, normalize_to_list_of_substitutions(params)) if not os.path.isfile(param_file_path): _logger.warn( 'Parameter file path is not a file: {}'.format( param_file_path)) # Don't skip adding the file to the parameter list since space has been # reserved for it in the ros_specific_arguments. self.__expanded_parameter_files.append(param_file_path) # expand remappings too if self.__remappings is not None: self.__expanded_remappings = [] for k, v in self.__remappings: key = perform_substitutions( context, normalize_to_list_of_substitutions(k)) value = perform_substitutions( context, normalize_to_list_of_substitutions(v)) self.__expanded_remappings.append((key, value))