1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
58 return os.path.join(roslib.packages.get_pkg_dir('roslaunch'), 'roscore.xml')
59
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
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
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
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 = []
93
94 self.roslaunch_files = []
95
96
97 self.resolved_node_names = []
98
99 self.tests = []
100 self.machines = {}
101 self.params = {}
102 self.clear_params = []
103 self.executables = []
104
105
106 self.config_errors = []
107
108 m = local_machine()
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
116 """
117 Add metadata about file used to create config
118 """
119 self.roslaunch_files.append(f)
120
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
130 """
131 Set the master configuration
132 @param m: Master
133 @type m: L{Master}
134 """
135 self.master = m
136
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
147 """
148 Assign nodes to machines and determine whether or not there are any remote machines
149 """
150
151 if self._assign_machines_complete:
152 return
153
154 machine_unify_dict = {}
155
156 self._assign_machines_complete = True
157
158 local_machine = self.machines['']
159 for n in self.nodes_core:
160 n.machine = local_machine
161
162
163 for n in self.nodes + self.tests:
164 m = self._select_machine(n)
165
166
167
168
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
181
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
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
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
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
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
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
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
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':
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
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
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
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
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
382
383
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
392
393
394 load_roscore(loader, config, verbose=verbose)
395
396
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
407
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
419 config.validate()
420
421
422 if assign_machines:
423 config.assign_machines()
424 return config
425