Package roslaunch :: Module config
[frames] | no frames]

Source Code for Module roslaunch.config

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2008, Willow Garage, Inc. 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Willow Garage, Inc. nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32  # 
 33  # Revision $Id: launch.py 2165 2008-09-17 22:38:49Z sfkwc $ 
 34   
 35  """ 
 36  Defines the L{ROSLaunchConfig} object, which holds and the state of 
 37  the roslaunch file. 
 38  """ 
 39   
 40  import os 
 41  import logging 
 42  import types 
 43   
 44  import rospkg 
 45  import rospkg.distro 
 46  import rosgraph.names 
 47  import rosgraph.network 
 48   
 49  from .core import Master, local_machine, is_machine_local, RLException 
 50  import roslaunch.loader 
 51  import roslaunch.xmlloader 
 52   
 53  try: 
 54      from rosmaster import DEFAULT_MASTER_PORT 
 55  except: 
 56      DEFAULT_MASTER_PORT = 11311 
 57       
58 -def namespaces_of(name):
59 """ 60 utility to determine namespaces of a name 61 @raises ValueError 62 @raises TypeError 63 """ 64 if name is None: 65 raise ValueError('name') 66 if not isinstance(name, basestring): 67 raise TypeError('name') 68 if not name: 69 return ['/'] 70 71 splits = [x for x in name.split('/') if x] 72 return ['/'] + ['/'+'/'.join(splits[:i]) for i in xrange(1, len(splits))]
73
74 -def get_roscore_filename():
75 # precedence: look for version in /etc/ros. If it's not there, fall back to roslaunch package 76 filename = os.path.join(rospkg.get_etc_ros_dir(), 'roscore.xml') 77 if os.path.isfile(filename): 78 return filename 79 r = rospkg.RosPack() 80 return os.path.join(r.get_path('roslaunch'), 'resources', 'roscore.xml')
81
82 -def load_roscore(loader, config, verbose=True):
83 """ 84 Load roscore configuration into the ROSLaunchConfig using the specified XmlLoader 85 @param config ROSLaunchConfig 86 @param loader XmlLoader 87 """ 88 f_roscore = get_roscore_filename() 89 logging.getLogger('roslaunch').info('loading roscore config file %s'%f_roscore) 90 loader.load(f_roscore, config, core=True, verbose=verbose)
91
92 -def calculate_env_loader(env=None):
93 """ 94 @raise RLException 95 """ 96 if env is None: 97 env = os.environ 98 # guess the env loader based on distro name 99 distro_name = rospkg.distro.current_distro_codename() 100 # sanity check 101 if distro_name in ['electric', 'diamondback', 'cturtle']: 102 raise RLException("This version of roslaunch is not compatible with pre-Fuerte ROS distributions") 103 return '/opt/ros/%s/env.sh'%(distro_name)
104
105 -def _summary_name(node):
106 """ 107 Generate summary label for node based on its package, type, and name 108 """ 109 if node.name: 110 return "%s (%s/%s)"%(node.name, node.package, node.type) 111 else: 112 return "%s/%s"%(node.package, node.type)
113
114 -class ROSLaunchConfig(object):
115 """ 116 ROSLaunchConfig is the container for the loaded roslaunch file state. It also 117 is responsible for validating then executing the desired state. 118 """ 119
120 - def __init__(self):
121 """ 122 Initialize an empty config object. Master defaults to the environment's master. 123 """ 124 self.master = Master() 125 self.nodes_core = [] 126 self.nodes = [] #nodes are unnamed 127 128 self.roslaunch_files = [] # metadata about files used to create config 129 130 # list of resolved node names. This is so that we can check for naming collisions 131 self.resolved_node_names = [] 132 133 self.tests = [] 134 self.machines = {} #key is name 135 self.params = {} #key is name 136 self.clear_params = [] 137 self.executables = [] 138 139 # for tools like roswtf 140 self.config_errors = [] 141 142 m = local_machine() #for local exec 143 self.machines[m.name] = m 144 self._assign_machines_complete = False 145 self._remote_nodes_present = None 146 147 self.logger = logging.getLogger('roslaunch')
148
149 - def add_roslaunch_file(self, f):
150 """ 151 Add metadata about file used to create config 152 """ 153 self.roslaunch_files.append(f)
154
155 - def add_config_error(self, msg):
156 """ 157 Report human-readable error message related to configuration error 158 @param msg: error message 159 @type msg: str 160 """ 161 self.config_errors.append(msg)
162
163 - def set_master(self, m):
164 """ 165 Set the master configuration 166 @param m: Master 167 @type m: L{Master} 168 """ 169 self.master = m
170
171 - def has_remote_nodes(self):
172 """ 173 @return: True if roslaunch will launch nodes on a remote machine 174 @rtype: bool 175 @raises: RLException 176 """ 177 if not self._assign_machines_complete: 178 raise RLException("ERROR: has_remote_nodes() cannot be called until prelaunch check is complete") 179 return self._remote_nodes_present
180
181 - def assign_machines(self):
182 """ 183 Assign nodes to machines and determine whether or not there are any remote machines 184 """ 185 # don't repeat machine assignment 186 if self._assign_machines_complete: 187 return 188 189 machine_unify_dict = {} 190 191 self._assign_machines_complete = True 192 # #653: current have to set all core nodes to local launch 193 local_machine = self.machines[''] 194 for n in self.nodes_core: 195 n.machine = local_machine 196 197 #for n in self.nodes_core + self.nodes + self.tests: 198 for n in self.nodes + self.tests: 199 m = self._select_machine(n) 200 201 # if machines have the same config keys it means that they are identical except 202 # for their name. we unify the machine assignments so that we don't use 203 # extra resources. 204 config_key = m.config_key() 205 if config_key in machine_unify_dict: 206 new_m = machine_unify_dict[config_key] 207 if m != new_m: 208 self.logger.info("... changing machine assignment from [%s] to [%s] as they are equivalent", m.name, new_m.name) 209 m = new_m 210 else: 211 machine_unify_dict[config_key] = m 212 n.machine = m 213 self.logger.info("... selected machine [%s] for node of type [%s/%s]", m.name, n.package, n.type) 214 215 # determine whether or not there are any machines we will need 216 # to setup remote roslaunch clients for 217 self._remote_nodes_present = False 218 if [m for m in machine_unify_dict.itervalues() if not is_machine_local(m)]: 219 self._remote_nodes_present = True
220
221 - def summary(self, local=False):
222 """ 223 Get a human-readable string summary of the launch 224 @param local bool: if True, only print local nodes 225 @return: summary 226 @rtype: str 227 """ 228 summary = '\nSUMMARY\n========' 229 if self.clear_params: 230 summary += '\n\nCLEAR PARAMETERS\n' + '\n'.join(sorted([' * %s'%p for p in self.clear_params])) 231 if self.params: 232 summary += '\n\nPARAMETERS\n' + '\n'.join(sorted([' * %s'%k for k in self.params])) 233 if not local: 234 summary += '\n\nMACHINES\n' + '\n'.join(sorted([' * %s'%k for k in self.machines if k])) 235 summary += '\n\nNODES\n' 236 namespaces = {} 237 if local: 238 nodes = [n for n in self.nodes if is_machine_local(n.machine)] 239 else: 240 nodes = self.nodes 241 for n in nodes: 242 ns = n.namespace 243 if ns not in namespaces: 244 namespaces[ns] = [n] 245 else: 246 namespaces[ns].append(n) 247 for k,v in namespaces.iteritems(): 248 summary += ' %s\n'%k + '\n'.join(sorted([' %s'%_summary_name(n) for n in v])) 249 summary += '\n' 250 return summary
251
252 - def add_executable(self, exe):
253 """ 254 Declare an exectuable to be run during the launch 255 @param exe: Executable 256 @type exe: L{Executable} 257 @raises ValueError 258 """ 259 if not exe: 260 raise ValueError("exe is None") 261 self.executables.append(exe)
262
263 - def add_clear_param(self, param):
264 """ 265 Declare a parameter to be cleared before new parameters are set 266 @param param: parameter to clear 267 @type param: str 268 """ 269 self.clear_params.append(param)
270
271 - def add_param(self, p, filename=None, verbose=True):
272 """ 273 Declare a parameter to be set on the param server before launching nodes 274 @param p: parameter instance 275 @type p: L{Param} 276 """ 277 key = p.key 278 279 # check for direct overrides 280 if key in self.params and self.params[key] != p: 281 if filename: 282 self.logger.debug("[%s] overriding parameter [%s]"%(filename, p.key)) 283 else: 284 self.logger.debug("overriding parameter [%s]"%p.key) 285 # check for parent conflicts 286 for parent_key in [pk for pk in namespaces_of(key) if pk in self.params]: 287 self.add_config_error("parameter [%s] conflicts with parent parameter [%s]"%(key, parent_key)) 288 289 self.params[key] = p 290 if verbose: 291 print "Added parameter [%s]"%key 292 t = type(p.value) 293 if t in [str, unicode, types.InstanceType]: 294 self.logger.debug("add_param[%s]: type [%s]"%(p.key, t)) 295 else: 296 self.logger.debug("add_param[%s]: type [%s] value [%s]"%(p.key, t, p.value))
297
298 - def add_machine(self, m, verbose=True):
299 """ 300 Declare a machine and associated parameters so that it can be used for 301 running nodes. 302 @param m: machine instance 303 @type m: L{Machine} 304 @return: True if new machine added, False if machine already specified. 305 @rtype: bool 306 @raises RLException: if cannot add machine as specified 307 """ 308 name = m.name 309 # Fuerte: all machines must have an env loader. We can guess 310 # it from the distro name for easier migration. 311 if not m.env_loader: 312 m.env_loader = calculate_env_loader() 313 if m.address == 'localhost': #simplify address comparison 314 address = rosgraph.network.get_local_address() 315 self.logger.info("addMachine[%s]: remapping localhost address to %s"%(name, address)) 316 if name in self.machines: 317 if m != self.machines[name]: 318 raise RLException("Machine [%s] already added and does not match duplicate entry"%name) 319 return False 320 else: 321 self.machines[name] = m 322 if verbose: 323 print "Added machine [%s]"%name 324 return True
325
326 - def add_test(self, test, verbose=True):
327 """ 328 Add test declaration. Used by rostest 329 @param test: test node instance to add to launch 330 @type test: L{Test} 331 """ 332 self.tests.append(test)
333
334 - def add_node(self, node, core=False, verbose=True):
335 """ 336 Add node declaration 337 @param node: node instance to add to launch 338 @type node: L{Node} 339 @param core: if True, node is a ROS core node 340 @type core: bool 341 @raises RLException: if ROS core node is missing required name 342 """ 343 if node.name: 344 # check for duplicates 345 resolved_name = rosgraph.names.ns_join(node.namespace, node.name) 346 matches = [n for n in self.resolved_node_names if n == resolved_name] 347 if matches: 348 raise RLException("roslaunch file contains multiple nodes named [%s].\nPlease check all <node> 'name' attributes to make sure they are unique.\nAlso check that $(anon id) use different ids."%resolved_name) 349 else: 350 self.resolved_node_names.append(resolved_name) 351 352 if not core: 353 self.nodes.append(node) 354 if verbose: 355 print "Added node of type [%s/%s] in namespace [%s]"%(node.package, node.type, node.namespace) 356 self.logger.info("Added node of type [%s/%s] in namespace [%s]", node.package, node.type, node.namespace) 357 else: 358 if not node.name: 359 raise RLException("ROS core nodes must have a name. [%s/%s]"%(node.package, node.type)) 360 self.nodes_core.append(node) 361 if verbose: 362 print "Added core node of type [%s/%s] in namespace [%s]"%(node.package, node.type, node.namespace) 363 self.logger.info("Added core node of type [%s/%s] in namespace [%s]", node.package, node.type, node.namespace)
364
365 - def _select_machine(self, node):
366 """ 367 Select a machine for a node to run on. For nodes that are 368 already assigned to a machine, this will map the string name to 369 a L{Machine} instance. If the node isn't already tagged with a 370 particular machine, one will be selected for it. 371 @param node: node to assign machine for 372 @type node: L{Node} 373 @return: machine to run on 374 @rtype: L{Machine} 375 @raises RLException: If machine state is improperly configured 376 """ 377 machine = node.machine_name 378 #Lookup machine 379 if machine: 380 if not machine in self.machines: 381 raise RLException("ERROR: unknown machine [%s]"%machine) 382 return self.machines[machine] 383 else: 384 # assign to local machine 385 return self.machines['']
386
387 -def load_config_default(roslaunch_files, port, roslaunch_strs=None, loader=None, verbose=False, assign_machines=True):
388 """ 389 Base routine for creating a ROSLaunchConfig from a set of 390 roslaunch_files and or launch XML strings and initializing it. This 391 config will have a core definition and also set the master to run 392 on port. 393 @param roslaunch_files: list of launch files to load 394 @type roslaunch_files: [str] 395 @param port: roscore/master port override. Set to 0 or None to use default. 396 @type port: int 397 @param roslaunch_strs: (optional) roslaunch XML strings to load 398 @type roslaunch_strs: [str] 399 @param verbose: (optional) print info to screen about model as it is loaded. 400 @type verbose: bool 401 @param assign_machines: (optional) assign nodes to machines (default: True) 402 @type assign_machines: bool 403 @return: initialized rosconfig instance 404 @rytpe: L{ROSLaunchConfig} initialized rosconfig instance 405 @raises: RLException 406 """ 407 logger = logging.getLogger('roslaunch.config') 408 409 # This is the main roslaunch server process. Load up the 410 # files specified on the command line and launch the 411 # requested resourcs. 412 413 config = ROSLaunchConfig() 414 if port: 415 config.master.uri = rosgraph.network.create_local_xmlrpc_uri(port) 416 417 loader = loader or roslaunch.xmlloader.XmlLoader() 418 419 # load the roscore file first. we currently have 420 # last-declaration wins rules. roscore is just a 421 # roslaunch file with special load semantics 422 load_roscore(loader, config, verbose=verbose) 423 424 # load the roslaunch_files into the config 425 for f in roslaunch_files: 426 try: 427 logger.info('loading config file %s'%f) 428 loader.load(f, config, verbose=verbose) 429 except roslaunch.xmlloader.XmlParseException, e: 430 raise RLException(e) 431 except roslaunch.loader.LoadException, e: 432 raise RLException(e) 433 434 # we need this for the hardware test systems, which builds up 435 # roslaunch launch files in memory 436 if roslaunch_strs: 437 for launch_str in roslaunch_strs: 438 try: 439 logger.info('loading config file from string') 440 loader.load_string(launch_str, config) 441 except roslaunch.xmlloader.XmlParseException, e: 442 raise RLException('Launch string: %s\nException: %s'%(launch_str, e)) 443 except roslaunch.loader.LoadException, e: 444 raise RLException('Launch string: %s\nException: %s'%(launch_str, e)) 445 446 # choose machines for the nodes 447 if assign_machines: 448 config.assign_machines() 449 return config
450