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 try: 67 if not isinstance(name, basestring): 68 raise TypeError('name') 69 except NameError: 70 if not isinstance(name, str): 71 raise TypeError('name') 72 if not name: 73 return ['/'] 74 75 splits = [x for x in name.split('/') if x] 76 return ['/'] + ['/'+'/'.join(splits[:i]) for i in range(1, len(splits))]
77
78 -def get_roscore_filename():
79 # precedence: look for version in /etc/ros. If it's not there, fall back to roslaunch package 80 filename = os.path.join(rospkg.get_etc_ros_dir(), 'roscore.xml') 81 if os.path.isfile(filename): 82 return filename 83 r = rospkg.RosPack() 84 return os.path.join(r.get_path('roslaunch'), 'resources', 'roscore.xml')
85
86 -def load_roscore(loader, config, verbose=True):
87 """ 88 Load roscore configuration into the ROSLaunchConfig using the specified XmlLoader 89 @param config ROSLaunchConfig 90 @param loader XmlLoader 91 """ 92 f_roscore = get_roscore_filename() 93 logging.getLogger('roslaunch').info('loading roscore config file %s'%f_roscore) 94 loader.load(f_roscore, config, core=True, verbose=verbose)
95
96 -def calculate_env_loader(env=None):
97 """ 98 @raise RLException 99 """ 100 if env is None: 101 env = os.environ 102 # guess the env loader based on distro name 103 distro_name = rospkg.distro.current_distro_codename() 104 # sanity check 105 if distro_name in ['electric', 'diamondback', 'cturtle']: 106 raise RLException("This version of roslaunch is not compatible with pre-Fuerte ROS distributions") 107 return '/opt/ros/%s/env.sh'%(distro_name)
108
109 -def _summary_name(node):
110 """ 111 Generate summary label for node based on its package, type, and name 112 """ 113 if node.name: 114 return "%s (%s/%s)"%(node.name, node.package, node.type) 115 else: 116 return "%s/%s"%(node.package, node.type)
117
118 -class ROSLaunchConfig(object):
119 """ 120 ROSLaunchConfig is the container for the loaded roslaunch file state. It also 121 is responsible for validating then executing the desired state. 122 """ 123
124 - def __init__(self):
125 """ 126 Initialize an empty config object. Master defaults to the environment's master. 127 """ 128 self.master = Master() 129 self.nodes_core = [] 130 self.nodes = [] #nodes are unnamed 131 132 self.roslaunch_files = [] # metadata about files used to create config 133 134 # list of resolved node names. This is so that we can check for naming collisions 135 self.resolved_node_names = [] 136 137 self.tests = [] 138 self.machines = {} #key is name 139 self.params = {} #key is name 140 self.clear_params = [] 141 self.executables = [] 142 143 # for tools like roswtf 144 self.config_errors = [] 145 146 m = local_machine() #for local exec 147 self.machines[m.name] = m 148 self._assign_machines_complete = False 149 self._remote_nodes_present = None 150 151 self.logger = logging.getLogger('roslaunch')
152
153 - def add_roslaunch_file(self, f):
154 """ 155 Add metadata about file used to create config 156 """ 157 self.roslaunch_files.append(f)
158
159 - def add_config_error(self, msg):
160 """ 161 Report human-readable error message related to configuration error 162 @param msg: error message 163 @type msg: str 164 """ 165 self.config_errors.append(msg)
166
167 - def set_master(self, m):
168 """ 169 Set the master configuration 170 @param m: Master 171 @type m: L{Master} 172 """ 173 self.master = m
174
175 - def has_remote_nodes(self):
176 """ 177 @return: True if roslaunch will launch nodes on a remote machine 178 @rtype: bool 179 @raises: RLException 180 """ 181 if not self._assign_machines_complete: 182 raise RLException("ERROR: has_remote_nodes() cannot be called until prelaunch check is complete") 183 return self._remote_nodes_present
184
185 - def assign_machines(self):
186 """ 187 Assign nodes to machines and determine whether or not there are any remote machines 188 """ 189 # don't repeat machine assignment 190 if self._assign_machines_complete: 191 return 192 193 machine_unify_dict = {} 194 195 self._assign_machines_complete = True 196 # #653: current have to set all core nodes to local launch 197 local_machine = self.machines[''] 198 for n in self.nodes_core: 199 n.machine = local_machine 200 201 #for n in self.nodes_core + self.nodes + self.tests: 202 for n in self.nodes + self.tests: 203 m = self._select_machine(n) 204 205 # if machines have the same config keys it means that they are identical except 206 # for their name. we unify the machine assignments so that we don't use 207 # extra resources. 208 config_key = m.config_key() 209 if config_key in machine_unify_dict: 210 new_m = machine_unify_dict[config_key] 211 if m != new_m: 212 self.logger.info("... changing machine assignment from [%s] to [%s] as they are equivalent", m.name, new_m.name) 213 m = new_m 214 else: 215 machine_unify_dict[config_key] = m 216 n.machine = m 217 self.logger.info("... selected machine [%s] for node of type [%s/%s]", m.name, n.package, n.type) 218 219 # determine whether or not there are any machines we will need 220 # to setup remote roslaunch clients for 221 self._remote_nodes_present = False 222 if [m for m in machine_unify_dict.values() if not is_machine_local(m)]: 223 self._remote_nodes_present = True
224
225 - def summary(self, local=False):
226 """ 227 Get a human-readable string summary of the launch 228 @param local bool: if True, only print local nodes 229 @return: summary 230 @rtype: str 231 """ 232 summary = '\nSUMMARY\n========' 233 if self.clear_params: 234 summary += '\n\nCLEAR PARAMETERS\n' + '\n'.join(sorted([' * %s'%p for p in self.clear_params])) 235 if self.params: 236 def strip_string(value): 237 # not dealing with non-ascii characters here 238 try: 239 value = str(value) 240 except UnicodeEncodeError: 241 return '<...>' 242 max_length = 20 243 if len(value) > max_length: 244 value = value[:max_length - 3] + '...' 245 # if non printable characters are present return replacement 246 for i, char in enumerate(value): 247 o = ord(char) 248 if o < 32 or o > 126: 249 # skip when the special characters are only trailing whitespaces 250 value = value.rstrip() 251 if i >= len(value): 252 break 253 return '<...>' 254 return value
255 summary += '\n\nPARAMETERS\n' + '\n'.join(sorted([' * %s: %s' % (k, strip_string(v.value)) for k, v in self.params.items()])) 256 if not local: 257 summary += '\n\nMACHINES\n' + '\n'.join(sorted([' * %s'%k for k in self.machines if k])) 258 summary += '\n\nNODES\n' 259 namespaces = {} 260 if local: 261 nodes = [n for n in self.nodes if is_machine_local(n.machine)] 262 else: 263 nodes = self.nodes 264 for n in nodes: 265 ns = n.namespace 266 if ns not in namespaces: 267 namespaces[ns] = [n] 268 else: 269 namespaces[ns].append(n) 270 for k in sorted(namespaces): 271 v = namespaces[k] 272 summary += ' %s\n'%k + '\n'.join(sorted([' %s'%_summary_name(n) for n in v])) 273 summary += '\n' 274 return summary
275
276 - def add_executable(self, exe):
277 """ 278 Declare an executable to be run during the launch 279 @param exe: Executable 280 @type exe: L{Executable} 281 @raises ValueError 282 """ 283 if not exe: 284 raise ValueError("exe is None") 285 self.executables.append(exe)
286
287 - def add_clear_param(self, param):
288 """ 289 Declare a parameter to be cleared before new parameters are set 290 @param param: parameter to clear 291 @type param: str 292 """ 293 self.clear_params.append(param)
294
295 - def add_param(self, p, filename=None, verbose=True):
296 """ 297 Declare a parameter to be set on the param server before launching nodes 298 @param p: parameter instance 299 @type p: L{Param} 300 """ 301 key = p.key 302 303 # check for direct overrides 304 if key in self.params and self.params[key] != p: 305 if filename: 306 self.logger.debug("[%s] overriding parameter [%s]"%(filename, p.key)) 307 else: 308 self.logger.debug("overriding parameter [%s]"%p.key) 309 # check for parent conflicts 310 for parent_key in [pk for pk in namespaces_of(key) if pk in self.params]: 311 self.add_config_error("parameter [%s] conflicts with parent parameter [%s]"%(key, parent_key)) 312 313 self.params[key] = p 314 if verbose: 315 print("Added parameter [%s]" % key) 316 t = type(p.value) 317 if t in [bool, int, float]: 318 self.logger.debug("add_param[%s]: type [%s] value [%s]"%(p.key, t, p.value)) 319 else: 320 self.logger.debug("add_param[%s]: type [%s]"%(p.key, t))
321
322 - def add_machine(self, m, verbose=True):
323 """ 324 Declare a machine and associated parameters so that it can be used for 325 running nodes. 326 @param m: machine instance 327 @type m: L{Machine} 328 @return: True if new machine added, False if machine already specified. 329 @rtype: bool 330 @raises RLException: if cannot add machine as specified 331 """ 332 name = m.name 333 # Fuerte: all machines must have an env loader. We can guess 334 # it from the distro name for easier migration. 335 if not m.env_loader: 336 m.env_loader = calculate_env_loader() 337 if m.address == 'localhost': #simplify address comparison 338 address = rosgraph.network.get_local_address() 339 self.logger.info("addMachine[%s]: remapping localhost address to %s"%(name, address)) 340 if name in self.machines: 341 if m != self.machines[name]: 342 raise RLException("Machine [%s] already added and does not match duplicate entry"%name) 343 return False 344 else: 345 self.machines[name] = m 346 if verbose: 347 print("Added machine [%s]" % name) 348 return True
349
350 - def add_test(self, test, verbose=True):
351 """ 352 Add test declaration. Used by rostest 353 @param test: test node instance to add to launch 354 @type test: L{Test} 355 """ 356 self.tests.append(test)
357
358 - def add_node(self, node, core=False, verbose=True):
359 """ 360 Add node declaration 361 @param node: node instance to add to launch 362 @type node: L{Node} 363 @param core: if True, node is a ROS core node 364 @type core: bool 365 @raises RLException: if ROS core node is missing required name 366 """ 367 if node.name: 368 # check for duplicates 369 resolved_name = rosgraph.names.ns_join(node.namespace, node.name) 370 matches = [n for n in self.resolved_node_names if n == resolved_name] 371 if matches: 372 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) 373 else: 374 self.resolved_node_names.append(resolved_name) 375 376 if not core: 377 self.nodes.append(node) 378 if verbose: 379 print("Added node of type [%s/%s] in namespace [%s]" % (node.package, node.type, node.namespace)) 380 self.logger.info("Added node of type [%s/%s] in namespace [%s]", node.package, node.type, node.namespace) 381 else: 382 if not node.name: 383 raise RLException("ROS core nodes must have a name. [%s/%s]"%(node.package, node.type)) 384 self.nodes_core.append(node) 385 if verbose: 386 print("Added core node of type [%s/%s] in namespace [%s]" % (node.package, node.type, node.namespace)) 387 self.logger.info("Added core node of type [%s/%s] in namespace [%s]", node.package, node.type, node.namespace)
388
389 - def _select_machine(self, node):
390 """ 391 Select a machine for a node to run on. For nodes that are 392 already assigned to a machine, this will map the string name to 393 a L{Machine} instance. If the node isn't already tagged with a 394 particular machine, one will be selected for it. 395 @param node: node to assign machine for 396 @type node: L{Node} 397 @return: machine to run on 398 @rtype: L{Machine} 399 @raises RLException: If machine state is improperly configured 400 """ 401 machine = node.machine_name 402 #Lookup machine 403 if machine: 404 if not machine in self.machines: 405 raise RLException("ERROR: unknown machine [%s]"%machine) 406 return self.machines[machine] 407 else: 408 # assign to local machine 409 return self.machines['']
410
411 -def load_config_default(roslaunch_files, port, roslaunch_strs=None, loader=None, verbose=False, assign_machines=True, ignore_unset_args=False):
412 """ 413 Base routine for creating a ROSLaunchConfig from a set of 414 roslaunch_files and or launch XML strings and initializing it. This 415 config will have a core definition and also set the master to run 416 on port. 417 @param roslaunch_files: list of launch files to load. Each item may also 418 be a tuple where the first item is the launch file and the second item 419 is a string containing arguments. 420 @type roslaunch_files: [str|(str, str)] 421 @param port: roscore/master port override. Set to 0 or None to use default. 422 @type port: int 423 @param roslaunch_strs: (optional) roslaunch XML strings to load 424 @type roslaunch_strs: [str] 425 @param verbose: (optional) print info to screen about model as it is loaded. 426 @type verbose: bool 427 @param assign_machines: (optional) assign nodes to machines (default: True) 428 @type assign_machines: bool 429 @param ignore_unset_args: (optional) ignore default arg requirements (default: False) 430 @type ignore_unset_args: bool 431 @return: initialized rosconfig instance 432 @rytpe: L{ROSLaunchConfig} initialized rosconfig instance 433 @raises: RLException 434 """ 435 logger = logging.getLogger('roslaunch.config') 436 437 # This is the main roslaunch server process. Load up the 438 # files specified on the command line and launch the 439 # requested resourcs. 440 441 config = ROSLaunchConfig() 442 if port: 443 config.master.uri = rosgraph.network.create_local_xmlrpc_uri(port) 444 445 loader = loader or roslaunch.xmlloader.XmlLoader() 446 loader.ignore_unset_args = ignore_unset_args 447 448 # load the roscore file first. we currently have 449 # last-declaration wins rules. roscore is just a 450 # roslaunch file with special load semantics 451 load_roscore(loader, config, verbose=verbose) 452 453 # load the roslaunch_files into the config 454 for f in roslaunch_files: 455 if isinstance(f, tuple): 456 f, args = f 457 else: 458 args = None 459 try: 460 logger.info('loading config file %s'%f) 461 loader.load(f, config, argv=args, verbose=verbose) 462 except roslaunch.xmlloader.XmlParseException as e: 463 raise RLException(e) 464 except roslaunch.loader.LoadException as e: 465 raise RLException(e) 466 467 # we need this for the hardware test systems, which builds up 468 # roslaunch launch files in memory 469 if roslaunch_strs: 470 for launch_str in roslaunch_strs: 471 try: 472 logger.info('loading config file from string') 473 loader.load_string(launch_str, config) 474 except roslaunch.xmlloader.XmlParseException as e: 475 raise RLException('Launch string: %s\nException: %s'%(launch_str, e)) 476 except roslaunch.loader.LoadException as e: 477 raise RLException('Launch string: %s\nException: %s'%(launch_str, e)) 478 479 # choose machines for the nodes 480 if assign_machines: 481 config.assign_machines() 482 return config
483