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

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