def resolve_name_without_node_name(name): """ The need for this function is complicated -- Topics and Services can be created before init_node is called. In general, this is okay, unless the name is a ~name, in which case we have to raise an ValueError @param name: ROS name to resolve @type name: str @raise ValueError: if name is a ~name @raise ROSInitException: if name is remapped to a ~name """ if is_private(name): raise ValueError("~name topics cannot be created before init_node() has been called") # we use the underlying roslib.names.resolve_name to avoid dependencies on nodename/remappings fake_caller_id = ns_join(get_namespace(), 'node') fake_resolved = roslib.names.resolve_name(name, fake_caller_id) for m, v in _mappings.items(): if roslib.names.resolve_name(m, fake_caller_id) == fake_resolved: if is_private(name): raise ROSInitException("due to the way this node is written, %s cannot be remapped to a ~name. \nThe declaration of topics/services must be moved after the call to init_node()"%name) else: return roslib.names.resolve_name(v, fake_caller_id) return fake_resolved
def resolve_name_without_node_name(name): """ The need for this function is complicated -- Topics and Services can be created before init_node is called. In general, this is okay, unless the name is a ~name, in which case we have to raise an ValueError @param name: ROS name to resolve @type name: str @raise ValueError: if name is a ~name @raise ROSInitException: if name is remapped to a ~name """ if is_private(name): raise ValueError("~name topics cannot be created before init_node() has been called") # we use the underlying roslib.names.resolve_name to avoid dependencies on nodename/remappings fake_caller_id = ns_join(get_namespace(), 'node') fake_resolved = roslib.names.resolve_name(name, fake_caller_id) for m, v in _mappings.iteritems(): if roslib.names.resolve_name(m, fake_caller_id) == fake_resolved: if is_private(name): raise ROSInitException("due to the way this node is written, %s cannot be remapped to a ~name. \nThe declaration of topics/services must be moved after the call to init_node()"%name) else: return roslib.names.resolve_name(v, fake_caller_id) return fake_resolved
def _param_tag(self, tag, context, ros_config, force_local=False, verbose=True): """ @param force_local: if True, param must be added to context instead of ros_config @type force_local: bool """ try: self._check_attrs(tag, context, ros_config, XmlLoader.PARAM_ATTRS) # compute name and value ptype = (tag.getAttribute('type') or 'auto').lower().strip() vals = self.opt_attrs(tag, context, ('value', 'textfile', 'binfile', 'command')) if len([v for v in vals if v is not None]) != 1: raise XmlParseException( "<param> tag must have one and only one of value/textfile/binfile.") # compute name. if name is a tilde name, it is placed in # the context. otherwise it is placed in the ros config. name = self.resolve_args(tag.attributes['name'].value.strip(), context) value = self.param_value(verbose, name, ptype, *vals) if is_private(name) or force_local: p = Param(name, value) context.add_param(p) else: p = Param(ns_join(context.ns, name), value) ros_config.add_param(Param(ns_join(context.ns, name), value), filename=context.filename, verbose=verbose) return p except KeyError, e: raise XmlParseException( "<param> tag is missing required attribute: %s. \n\nParam xml is %s"%(e, tag.toxml()))
def resolve_name(name, caller_id=None): """ Resolve a ROS name to its global, canonical form. Private ~names are resolved relative to the node name. @param name: name to resolve. @type name: str @param caller_id: node name to resolve relative to. To resolve to local namespace, omit this parameter (or use None) @type caller_id: str @return: Resolved name. If name is empty/None, resolve_name returns parent namespace. If namespace is empty/None, @rtype: str """ if not caller_id: caller_id = get_name() if not name: #empty string resolves to namespace return namespace(caller_id) name = canonicalize_name(name) if name[0] == SEP: #global name resolved_name = name elif is_private(name): #~name resolved_name = ns_join(caller_id, name[1:]) else: #relative resolved_name = namespace(caller_id) + name #Mappings override general namespace-based resolution # - do this before canonicalization as remappings are meant to # match the name as specified in the code if resolved_name in _resolved_mappings: return _resolved_mappings[resolved_name] else: return resolved_name
def _node_tag(self, tag, context, ros_config, default_machine, is_test=False, verbose=True): """ Process XML <node> or <test> tag @param tag: DOM node @type tag: Node @param context: namespace context @type context: L{LoaderContext} @param params: ROS parameter list @type params: [L{Param}] @param clear_params: list of ROS parameter names to clear before setting parameters @type clear_params: [str] @param default_machine: default machine to assign to node @type default_machine: str @param is_test: if set, will load as L{Test} object instead of L{Node} object @type is_test: bool """ try: if is_test: self._check_attrs(tag, context, ros_config, XmlLoader.TEST_ATTRS) (name,) = self.opt_attrs(tag, context, ('name',)) test_name, time_limit, retry = self._test_attrs(tag, context) if not name: name = test_name else: self._check_attrs(tag, context, ros_config, XmlLoader.NODE_ATTRS) (name,) = self.reqd_attrs(tag, context, ('name',)) if not roslib.names.is_legal_name(name): ros_config.add_config_error("WARN: illegal <node> name '%s'.\nhttp://ros.org/wiki/Names\nThis will likely cause problems with other ROS tools.\nNode xml is %s"%(name, tag.toxml())) child_ns = self._ns_clear_params_attr('node', tag, context, ros_config, node_name=name) param_ns = child_ns.child(name) # required attributes pkg, node_type = self.reqd_attrs(tag, context, ('pkg', 'type')) # optional attributes machine, args, output, respawn, cwd, launch_prefix, required = \ self.opt_attrs(tag, context, ('machine', 'args', 'output', 'respawn', 'cwd', 'launch-prefix', 'required')) if tag.hasAttribute('machine') and not len(machine.strip()): raise XmlParseException("<node> 'machine' must be non-empty: [%s]"%machine) if not machine and default_machine: machine = default_machine.name # validate respawn, required required, respawn = [_bool_attr(*rr) for rr in ((required, False, 'required'),\ (respawn, False, 'respawn'))] # each node gets its own copy of <remap> arguments, which # it inherits from its parent remap_context = context.child('') # nodes can have individual env args set in addition to # the ROS-specific ones. for t in [c for c in tag.childNodes if c.nodeType == DomNode.ELEMENT_NODE]: tag_name = t.tagName.lower() if tag_name == 'remap': r = self._remap_tag(t, context, ros_config) if r is not None: remap_context.add_remap(r) elif tag_name == 'param': self._param_tag(t, param_ns, ros_config, force_local=True, verbose=verbose) elif tag_name == 'rosparam': self._rosparam_tag(t, param_ns, ros_config, verbose=verbose) elif tag_name == 'env': self._env_tag(t, context, ros_config) else: ros_config.add_config_error("WARN: unrecognized '%s' tag in <node> tag. Node xml is %s"%(t.tagName, tag.toxml())) # #1036 evaluate all ~params in context # TODO: can we get rid of force_local (above), remove this for loop, and just rely on param_tag logic instead? for p in itertools.chain(context.params, param_ns.params): pkey = p.key if is_private(pkey): # strip leading ~, which is optional/inferred pkey = pkey[1:] pkey = param_ns.ns + pkey ros_config.add_param(Param(pkey, p.value), verbose=verbose) if not is_test: return Node(pkg, node_type, name=name, namespace=child_ns.ns, machine_name=machine, args=args, respawn=respawn, remap_args=remap_context.remap_args(), env_args=context.env_args, output=output, cwd=cwd, launch_prefix=launch_prefix, required=required, filename=context.filename) else: return Test(test_name, pkg, node_type, name=name, namespace=child_ns.ns, machine_name=machine, args=args, remap_args=remap_context.remap_args(), env_args=context.env_args, time_limit=time_limit, cwd=cwd, launch_prefix=launch_prefix, retry=retry, filename=context.filename) except KeyError, e: raise XmlParseException( "<%s> tag is missing required attribute: %s. Node xml is %s"%(tag.tagName, e, tag.toxml()))
def search_param(self, ns, key): """ Search for matching parameter key for search param key. Search for key starts at ns and proceeds upwards to the root. As such, search_param should only be called with a relative parameter name. search_param's behavior is to search for the first partial match. For example, imagine that there are two 'robot_description' parameters: - /robot_description - /robot_description/arm - /robot_description/base - /pr2/robot_description - /pr2/robot_description/base If I start in the namespace /pr2/foo and search for 'robot_description', search_param will match /pr2/robot_description. If I search for 'robot_description/arm' it will return /pr2/robot_description/arm, even though that parameter does not exist (yet). @param ns: namespace to begin search from. @type ns: str @param key: Parameter key. @type key: str @return: key of matching parameter or None if no matching parameter. @rtype: str """ if not key or is_private(key): raise ValueError("invalid key") if not is_global(ns): raise ValueError("namespace must be global") if is_global(key): if self.has_param(key): return key else: return None # there are more efficient implementations, but our hiearchy # is not very deep and this is fairly clean code to read. # - we only search for the first namespace in the key to check for a match key_namespaces = [x for x in key.split(SEP) if x] key_ns = key_namespaces[0] # - corner case: have to test initial namespace first as # negative indices won't work with 0 search_key = ns_join(ns, key_ns) if self.has_param(search_key): # resolve to full key return ns_join(ns, key) namespaces = [x for x in ns.split(SEP) if x] for i in xrange(1, len(namespaces)+1): search_key = SEP + SEP.join(namespaces[0:-i] + [key_ns]) if self.has_param(search_key): # we have a match on the namespace of the key, so # compose the full key and return it full_key = SEP + SEP.join(namespaces[0:-i] + [key]) return full_key return None