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 sys 
 43  import types 
 44   
 45  import roslib.names 
 46   
 47  from roslaunch.core import Master, local_machine, get_ros_root, is_machine_local, RLException 
 48  from roslaunch.rlutil import namespaces_of 
 49  import roslaunch.loader 
 50  import roslaunch.xmlloader 
 51   
 52  try: 
 53      from rosmaster import DEFAULT_MASTER_PORT 
 54  except: 
 55      DEFAULT_MASTER_PORT = 11311 
 56       
57 -def get_roscore_filename():
58 return os.path.join(roslib.packages.get_pkg_dir('roslaunch'), 'roscore.xml')
59
60 -def load_roscore(loader, config, verbose=True):
61 """ 62 Load roscore configuration into the ROSLaunchConfig using the specified XmlLoader 63 @param config ROSLaunchConfig 64 @param loader XmlLoader 65 """ 66 import roslib.packages 67 f_roscore = get_roscore_filename() 68 logging.getLogger('roslaunch').info('loading roscore config file %s'%f_roscore) 69 loader.load(f_roscore, config, core=True, verbose=verbose)
70
71 -def _summary_name(node):
72 """ 73 Generate summary label for node based on its package, type, and name 74 """ 75 if node.name: 76 return "%s (%s/%s)"%(node.name, node.package, node.type) 77 else: 78 return "%s/%s"%(node.package, node.type)
79
80 -class ROSLaunchConfig(object):
81 """ 82 ROSLaunchConfig is the container for the loaded roslaunch file state. It also 83 is responsible for validating then executing the desired state. 84 """ 85
86 - def __init__(self):
87 """ 88 Initialize an empty config object. Master defaults to the environment's master. 89 """ 90 self.master = Master() 91 self.nodes_core = [] 92 self.nodes = [] #nodes are unnamed 93 94 self.roslaunch_files = [] # metadata about files used to create config 95 96 # list of resolved node names. This is so that we can check for naming collisions 97 self.resolved_node_names = [] 98 99 self.tests = [] 100 self.machines = {} #key is name 101 self.params = {} #key is name 102 self.clear_params = [] 103 self.executables = [] 104 105 # for tools like roswtf 106 self.config_errors = [] 107 108 m = local_machine() #for local exec 109 self.machines[m.name] = m 110 self._assign_machines_complete = False 111 self._remote_nodes_present = None 112 113 self.logger = logging.getLogger('roslaunch')
114
115 - def add_roslaunch_file(self, f):
116 """ 117 Add metadata about file used to create config 118 """ 119 self.roslaunch_files.append(f)
120
121 - def add_config_error(self, msg):
122 """ 123 Report human-readable error message related to configuration error 124 @param msg: error message 125 @type msg: str 126 """ 127 self.config_errors.append(msg)
128
129 - def set_master(self, m):
130 """ 131 Set the master configuration 132 @param m: Master 133 @type m: L{Master} 134 """ 135 self.master = m
136
137 - def has_remote_nodes(self):
138 """ 139 @return: True if roslaunch will launch nodes on a remote machine 140 @rtype: bool 141 """ 142 if not self._assign_machines_complete: 143 raise Exception("ERROR: has_remote_nodes() cannot be called until prelaunch check is complete") 144 return self._remote_nodes_present
145
146 - def assign_machines(self):
147 """ 148 Assign nodes to machines and determine whether or not there are any remote machines 149 """ 150 # don't repeat machine assignment 151 if self._assign_machines_complete: 152 return 153 154 machine_unify_dict = {} 155 156 self._assign_machines_complete = True 157 # #653: current have to set all core nodes to local launch 158 local_machine = self.machines[''] 159 for n in self.nodes_core: 160 n.machine = local_machine 161 162 #for n in self.nodes_core + self.nodes + self.tests: 163 for n in self.nodes + self.tests: 164 m = self._select_machine(n) 165 166 # if machines have the same config keys it means that they are identical except 167 # for their name. we unify the machine assignments so that we don't use 168 # extra resources. 169 config_key = m.config_key() 170 if config_key in machine_unify_dict: 171 new_m = machine_unify_dict[config_key] 172 if m != new_m: 173 self.logger.info("... changing machine assignment from [%s] to [%s] as they are equivalent", m.name, new_m.name) 174 m = new_m 175 else: 176 machine_unify_dict[config_key] = m 177 n.machine = m 178 self.logger.info("... selected machine [%s] for node of type [%s/%s]", m.name, n.package, n.type) 179 180 # determine whether or not there are any machines we will need 181 # to setup remote roslaunch clients for 182 self._remote_nodes_present = False 183 if [m for m in machine_unify_dict.itervalues() if not is_machine_local(m)]: 184 self._remote_nodes_present = True
185
186 - def validate(self):
187 """ 188 Perform basic checks on the local ROS environment, master, and core services. 189 master will be launched if configured to do so. Core services will be launched regardless. 190 if they are not already running. 191 192 @raise RLException: if validation fails 193 """ 194 ros_root = get_ros_root() 195 if not os.path.isdir(ros_root): 196 raise RLException("ERROR: ROS_ROOT is not configured properly. Value is [%s]"%ros_root)
197
198 - def summary(self, local=False):
199 """ 200 Get a human-readable string summary of the launch 201 @param local bool: if True, only print local nodes 202 @return: summary 203 @rtype: str 204 """ 205 summary = '\nSUMMARY\n========' 206 if self.clear_params: 207 summary += '\n\nCLEAR PARAMETERS\n' + '\n'.join([' * %s'%p for p in self.clear_params]) 208 if self.params: 209 summary += '\n\nPARAMETERS\n' + '\n'.join([' * %s'%k for k in self.params]) 210 if not local: 211 summary += '\n\nMACHINES\n' + '\n'.join([' * %s'%k for k in self.machines if k]) 212 summary += '\n\nNODES\n' 213 namespaces = {} 214 if local: 215 nodes = [n for n in self.nodes if is_machine_local(n.machine)] 216 else: 217 nodes = self.nodes 218 for n in nodes: 219 ns = n.namespace 220 if ns not in namespaces: 221 namespaces[ns] = [n] 222 else: 223 namespaces[ns].append(n) 224 for k,v in namespaces.iteritems(): 225 summary += ' %s\n'%k + '\n'.join([' %s'%_summary_name(n) for n in v]) 226 summary += '\n' 227 return summary
228
229 - def add_executable(self, exe):
230 """ 231 Declare an exectuable to be run during the launch 232 @param exe: Executable 233 @type exe: L{Executable} 234 """ 235 if not exe: 236 raise ValueError("exe is None") 237 self.executables.append(exe)
238
239 - def add_clear_param(self, param):
240 """ 241 Declare a parameter to be cleared before new parameters are set 242 @param param: parameter to clear 243 @type param: str 244 """ 245 self.clear_params.append(param)
246
247 - def add_param(self, p, filename=None, verbose=True):
248 """ 249 Declare a parameter to be set on the param server before launching nodes 250 @param p: parameter instance 251 @type p: L{Param} 252 """ 253 key = p.key 254 255 # check for direct overrides 256 if key in self.params and self.params[key] != p: 257 if filename: 258 self.logger.debug("[%s] overriding parameter [%s]"%(filename, p.key)) 259 else: 260 self.logger.debug("overriding parameter [%s]"%p.key) 261 # check for parent conflicts 262 for parent_key in [pk for pk in namespaces_of(key) if pk in self.params]: 263 self.add_config_error("parameter [%s] conflicts with parent parameter [%s]"%(key, parent_key)) 264 265 self.params[key] = p 266 if verbose: 267 print "Added parameter [%s]"%key 268 t = type(p.value) 269 if t in [str, unicode, types.InstanceType]: 270 self.logger.debug("add_param[%s]: type [%s]"%(p.key, t)) 271 else: 272 self.logger.debug("add_param[%s]: type [%s] value [%s]"%(p.key, t, p.value))
273
274 - def add_machine(self, m, verbose=True):
275 """ 276 Declare a machine and associated parameters so that it can be used for 277 running nodes. 278 @param m: machine instance 279 @type m: L{Machine} 280 @return: True if new machine added, False if machine already specified. 281 @rtype: bool 282 @raise RLException: if cannot add machine as specified 283 """ 284 name = m.name 285 if m.address == 'localhost': #simplify address comparison 286 import roslib.network 287 address = roslib.network.get_local_address() 288 self.logger.info("addMachine[%s]: remapping localhost address to %s"%(name, address)) 289 if name in self.machines: 290 if m != self.machines[name]: 291 raise RLException("Machine [%s] already added and does not match duplicate entry"%name) 292 return False 293 else: 294 self.machines[name] = m 295 if verbose: 296 print "Added machine [%s]"%name 297 return True
298
299 - def add_test(self, test, verbose=True):
300 """ 301 Add test declaration. Used by rostest 302 @param test: test node instance to add to launch 303 @type test: L{Test} 304 """ 305 self.tests.append(test)
306
307 - def add_node(self, node, core=False, verbose=True):
308 """ 309 Add node declaration 310 @param node: node instance to add to launch 311 @type node: L{Node} 312 @param core: if True, node is a ROS core node 313 @type core: bool 314 @raise RLException: if ROS core node is missing required name 315 """ 316 if node.name: 317 # check for duplicates 318 resolved_name = roslib.names.ns_join(node.namespace, node.name) 319 matches = [n for n in self.resolved_node_names if n == resolved_name] 320 if matches: 321 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) 322 else: 323 self.resolved_node_names.append(resolved_name) 324 325 if not core: 326 self.nodes.append(node) 327 if verbose: 328 print "Added node of type [%s/%s] in namespace [%s]"%(node.package, node.type, node.namespace) 329 self.logger.info("Added node of type [%s/%s] in namespace [%s]", node.package, node.type, node.namespace) 330 else: 331 if not node.name: 332 raise RLException("ROS core nodes must have a name. [%s/%s]"%(node.package, node.type)) 333 self.nodes_core.append(node) 334 if verbose: 335 print "Added core node of type [%s/%s] in namespace [%s]"%(node.package, node.type, node.namespace) 336 self.logger.info("Added core node of type [%s/%s] in namespace [%s]", node.package, node.type, node.namespace)
337
338 - def _select_machine(self, node):
339 """ 340 Select a machine for a node to run on. For nodes that are 341 already assigned to a machine, this will map the string name to 342 a L{Machine} instance. If the node isn't already tagged with a 343 particular machine, one will be selected for it. 344 @param node: node to assign machine for 345 @type node: L{Node} 346 @return: machine to run on 347 @rtype: L{Machine} 348 @raise RLException: If machine state is improperly configured 349 """ 350 machine = node.machine_name 351 #Lookup machine 352 if machine: 353 if not machine in self.machines: 354 raise RLException("ERROR: unknown machine [%s]"%machine) 355 return self.machines[machine] 356 else: 357 # assign to local machine 358 return self.machines['']
359
360 -def load_config_default(roslaunch_files, port, roslaunch_strs=None, loader=None, verbose=False, assign_machines=True):
361 """ 362 Base routine for creating a ROSLaunchConfig from a set of 363 roslaunch_files and or launch XML strings and initializing it. This 364 config will have a core definition and also set the master to run 365 on port. 366 @param roslaunch_files: list of launch files to load 367 @type roslaunch_files: [str] 368 @param port: roscore/master port override. Set to 0 or None to use default. 369 @type port: int 370 @param roslaunch_strs: (optional) roslaunch XML strings to load 371 @type roslaunch_strs: [str] 372 @param verbose: (optional) print info to screen about model as it is loaded. 373 @type verbose: bool 374 @param assign_machines: (optional) assign nodes to machines (default: True) 375 @type assign_machines: bool 376 @return: initialized rosconfig instance 377 @rytpe: L{ROSLaunchConfig} initialized rosconfig instance 378 """ 379 logger = logging.getLogger('roslaunch.config') 380 381 # This is the main roslaunch server process. Load up the 382 # files specified on the command line and launch the 383 # requested resourcs. 384 385 config = ROSLaunchConfig() 386 if port: 387 config.master.uri = roslib.network.create_local_xmlrpc_uri(port) 388 389 loader = loader or roslaunch.xmlloader.XmlLoader() 390 391 # load the roscore file first. we currently have 392 # last-declaration wins rules. roscore is just a 393 # roslaunch file with special load semantics 394 load_roscore(loader, config, verbose=verbose) 395 396 # load the roslaunch_files into the config 397 for f in roslaunch_files: 398 try: 399 logger.info('loading config file %s'%f) 400 loader.load(f, config, verbose=verbose) 401 except roslaunch.xmlloader.XmlParseException, e: 402 raise RLException(e) 403 except roslaunch.loader.LoadException, e: 404 raise RLException(e) 405 406 # we need this for the hardware test systems, which builds up 407 # roslaunch launch files in memory 408 if roslaunch_strs: 409 for launch_str in roslaunch_strs: 410 try: 411 logger.info('loading config file from string') 412 loader.load_string(launch_str, config) 413 except roslaunch.xmlloader.XmlParseException, e: 414 raise RLException('Launch string: %s\nException: %s'%(launch_str, e)) 415 except roslaunch.loader.LoadException, e: 416 raise RLException('Launch string: %s\nException: %s'%(launch_str, e)) 417 418 # make sure our environment is correct 419 config.validate() 420 421 # choose machines for the nodes 422 if assign_machines: 423 config.assign_machines() 424 return config
425