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 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
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
75
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
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
93 """
94 @raise RLException
95 """
96 if env is None:
97 env = os.environ
98
99 distro_name = rospkg.distro.current_distro_codename()
100
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
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
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
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 = []
127
128 self.roslaunch_files = []
129
130
131 self.resolved_node_names = []
132
133 self.tests = []
134 self.machines = {}
135 self.params = {}
136 self.clear_params = []
137 self.executables = []
138
139
140 self.config_errors = []
141
142 m = local_machine()
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
150 """
151 Add metadata about file used to create config
152 """
153 self.roslaunch_files.append(f)
154
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
164 """
165 Set the master configuration
166 @param m: Master
167 @type m: L{Master}
168 """
169 self.master = m
170
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
182 """
183 Assign nodes to machines and determine whether or not there are any remote machines
184 """
185
186 if self._assign_machines_complete:
187 return
188
189 machine_unify_dict = {}
190
191 self._assign_machines_complete = True
192
193 local_machine = self.machines['']
194 for n in self.nodes_core:
195 n.machine = local_machine
196
197
198 for n in self.nodes + self.tests:
199 m = self._select_machine(n)
200
201
202
203
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
216
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
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
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
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
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
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
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
310
311 if not m.env_loader:
312 m.env_loader = calculate_env_loader()
313 if m.address == 'localhost':
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
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
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
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
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
410
411
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
420
421
422 load_roscore(loader, config, verbose=verbose)
423
424
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
435
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
447 if assign_machines:
448 config.assign_machines()
449 return config
450