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 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
79
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
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
97 """
98 @raise RLException
99 """
100 if env is None:
101 env = os.environ
102
103 distro_name = rospkg.distro.current_distro_codename()
104
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
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
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
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 = []
131
132 self.roslaunch_files = []
133
134
135 self.resolved_node_names = []
136
137 self.tests = []
138 self.machines = {}
139 self.params = {}
140 self.clear_params = []
141 self.executables = []
142
143
144 self.config_errors = []
145
146 m = local_machine()
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
154 """
155 Add metadata about file used to create config
156 """
157 self.roslaunch_files.append(f)
158
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
168 """
169 Set the master configuration
170 @param m: Master
171 @type m: L{Master}
172 """
173 self.master = m
174
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
186 """
187 Assign nodes to machines and determine whether or not there are any remote machines
188 """
189
190 if self._assign_machines_complete:
191 return
192
193 machine_unify_dict = {}
194
195 self._assign_machines_complete = True
196
197 local_machine = self.machines['']
198 for n in self.nodes_core:
199 n.machine = local_machine
200
201
202 for n in self.nodes + self.tests:
203 m = self._select_machine(n)
204
205
206
207
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
220
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
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
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
246 for i, char in enumerate(value):
247 o = ord(char)
248 if o < 32 or o > 126:
249
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
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
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
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
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
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
334
335 if not m.env_loader:
336 m.env_loader = calculate_env_loader()
337 if m.address == 'localhost':
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
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
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
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
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
438
439
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
449
450
451 load_roscore(loader, config, verbose=verbose)
452
453
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
468
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
480 if assign_machines:
481 config.assign_machines()
482 return config
483