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 Core roslaunch model and lower-level utility routines.
35 """
36
37 import os
38 import logging
39
40 import socket
41 import sys
42 import xmlrpclib
43
44 import rospkg
45
46 import rosgraph
47 import rosgraph.names
48 import rosgraph.network
49
50 from xml.sax.saxutils import escape
51 try:
52 unicode
53 except NameError:
54
55 basestring = unicode = str
56
58 """Base roslaunch exception type"""
59 pass
60
61
62 PHASE_SETUP = 'setup'
63 PHASE_RUN = 'run'
64 PHASE_TEARDOWN = 'teardown'
65
66 _child_mode = False
68 """
69 :returns: ``True`` if roslaunch is running in remote child mode, ``bool``
70 """
71 return _child_mode
73 """
74 :param child_mode: True if roslaunch is running in remote
75 child mode, ``bool``
76 """
77 global _child_mode
78 _child_mode = child_mode
79
81 """
82 Check to see if machine is local. NOTE: a machine is not local if
83 its user credentials do not match the current user.
84 :param machine: Machine, ``Machine``
85 :returns: True if machine is local and doesn't require remote login, ``bool``
86 """
87 try:
88 machine_ips = [host[4][0] for host in socket.getaddrinfo(machine.address, 0, 0, 0, socket.SOL_TCP)]
89 except socket.gaierror:
90 raise RLException("cannot resolve host address for machine [%s]"%machine.address)
91 local_addresses = ['localhost'] + rosgraph.network.get_local_addresses()
92
93 is_local = ([ip for ip in machine_ips if (ip.startswith('127.') or ip == '::1')] != []) or (set(machine_ips) & set(local_addresses) != set())
94
95
96 if is_local and machine.user:
97 import getpass
98 is_local = machine.user == getpass.getuser()
99 return is_local
100
101 _printlog_handlers = []
102 _printerrlog_handlers = []
104 """
105 Core utility for printing message to stdout as well as printlog handlers
106 :param msg: message to print, ``str``
107 """
108 for h in _printlog_handlers:
109 try:
110 h(msg)
111 except:
112 pass
113 try:
114 print msg
115 except:
116 pass
117
119 """
120 Similar to L{printlog()}, but the message printed to screen is bolded for greater clarity
121 :param msg: message to print, ``str``
122 """
123 for h in _printlog_handlers:
124 try:
125 h(msg)
126 except:
127 pass
128 try:
129 if sys.platform in ['win32']:
130 print '%s'%msg
131 else:
132 print '\033[1m%s\033[0m'%msg
133 except:
134 pass
135
137 """
138 Core utility for printing message to stderr as well as printerrlog handlers
139 :param msg: message to print, ``str``
140 """
141 for h in _printerrlog_handlers:
142 try:
143 h(msg)
144 except:
145 pass
146
147
148 try:
149 print >> sys.stderr, '\033[31m%s\033[0m'%msg
150 except:
151 pass
152
154 """
155 Register additional handler for printlog()
156 """
157 _printlog_handlers.append(h)
158
164
166 """
167 Delete all printlog handlers. required for testing
168 """
169 del _printlog_handlers[:]
170
172 """
173 Delete all printerrlog handlers. required for testing
174 """
175 del _printerrlog_handlers[:]
176
177 -def setup_env(node, machine, master_uri, env=None):
178 """
179 Create dictionary of environment variables to set for launched
180 process.
181
182 setup_env() will only set ROS_*, PYTHONPATH, and user-specified
183 environment variables.
184
185 :param machine: machine being launched on, ``Machine``
186 :param node: node that is being launched or None, ``Node``
187 :param master_uri: ROS master URI, ``str``
188 :param env: base environment configuration, defaults to ``os.environ``
189 :returns: process env dictionary, ``dict``
190 """
191 if env is None:
192 env = os.environ
193
194 d = env.copy()
195 d[rosgraph.ROS_MASTER_URI] = master_uri
196
197
198 if node:
199 if rosgraph.ROS_NAMESPACE in d:
200 del d[rosgraph.ROS_NAMESPACE]
201 ns = node.namespace
202 if ns[-1] == '/':
203 ns = ns[:-1]
204 if ns:
205 d[rosgraph.ROS_NAMESPACE] = ns
206 for name, value in node.env_args:
207 d[name] = value
208
209 return d
210
212 """
213 Wrap lower-level exceptions in RLException class
214 :returns:: function wrapper that throws an RLException if the
215 wrapped function throws an Exception, ``fn``
216 """
217 def wrapped_fn(*args):
218 try:
219 return fn(*args)
220 except Exception as e:
221
222 raise RLException("ERROR: %s"%e)
223 return wrapped_fn
224
225 get_ros_root = rospkg.get_ros_root
226 get_master_uri_env = rle_wrapper(rosgraph.get_master_uri)
227 get_ros_package_path = rospkg.get_ros_package_path
228
230 """
231 Resolve localhost addresses to an IP address so that
232 :param uri: XML-RPC URI, ``str``
233 :param force_localhost: if True, URI is mapped onto the local machine no matter what, ``bool``
234 """
235 hostname, port = rosgraph.network.parse_http_host_and_port(uri)
236 if force_localhost or hostname == 'localhost':
237 return rosgraph.network.create_local_xmlrpc_uri(port)
238 else:
239 return uri
240
241
242
243
245 """
246 Data structure for representing and querying state of master
247 """
248 __slots__ = ['type', 'auto', 'uri']
249 ROSMASTER = 'rosmaster'
250
251
252 ZENMASTER = 'zenmaster'
253
254 - def __init__(self, type_=None, uri=None, auto=None):
255 """
256 Create new Master instance.
257 :param uri: master URI. Defaults to ROS_MASTER_URI environment variable, ``str``
258 :param type_: Currently only support 'rosmaster', ``str``
259 """
260 self.auto = None
261 self.type = type_ or Master.ROSMASTER
262 self.uri = uri or get_master_uri_env()
263
265
266 host, _ = rosgraph.network.parse_http_host_and_port(self.uri)
267 return host
268
270 """
271 Get the port this master is configured for.
272 """
273
274 _, urlport = rosgraph.network.parse_http_host_and_port(self.uri)
275 return urlport
276
278 if not isinstance(m2, Master):
279 return False
280 else:
281 return m2.type == self.type and m2.uri == self.uri
282
284 """
285 :returns:: XMLRPC proxy for communicating with master, ``xmlrpclib.ServerProxy``
286 """
287 return xmlrpclib.ServerProxy(self.uri)
288
290 """
291 :returns:: multicall XMLRPC proxy for communicating with master, ``xmlrpclib.MultiCall``
292 """
293 return xmlrpclib.MultiCall(self.get())
294
296 """
297 Check if master is running.
298 :returns:: True if the master is running, ``bool``
299 """
300 try:
301 try:
302 to_orig = socket.getdefaulttimeout()
303
304 socket.setdefaulttimeout(5.0)
305 logging.getLogger('roslaunch').info('master.is_running[%s]'%self.uri)
306 code, status, val = self.get().getPid('/roslaunch')
307 if code != 1:
308 raise RLException("ERROR: master failed status check: %s"%msg)
309 logging.getLogger('roslaunch.core').debug('master.is_running[%s]: True'%self.uri)
310 return True
311 finally:
312 socket.setdefaulttimeout(to_orig)
313 except:
314 logging.getLogger('roslaunch.core').debug('master.is_running[%s]: False'%self.uri)
315 return False
316
317
318
319 _DEFAULT_REGISTER_TIMEOUT = 10.0
320
322 """
323 Data structure for storing information about a machine in the ROS
324 system. Corresponds to the 'machine' tag in the launch
325 specification.
326 """
327 __slots__ = ['name', 'address', 'ssh_port', 'user', 'password', 'assignable',
328 'env_loader', 'timeout']
329 - def __init__(self, name, address,
330 env_loader=None, ssh_port=22, user=None, password=None,
331 assignable=True, env_args=[], timeout=None):
348
357
359 """
360 Get a key that represents the configuration of the
361 machine. machines with identical configurations have identical
362 keys
363
364 :returns:: configuration key, ``str``
365 """
366 return "Machine(address[%s] env_loader[%s] ssh_port[%s] user[%s] password[%s] timeout[%s])"%(self.address, self.env_loader, self.ssh_port, self.user or '', self.password or '', self.timeout)
367
369 """
370 :returns:: True if machines have identical configurations, ``bool``
371 """
372 if not isinstance(m2, Machine):
373 return False
374 return self.config_key() == m2.config_key()
375
377 return not self.__eq__(m2)
378
380 """
381 Data structure for storing information about a desired parameter in
382 the ROS system Corresponds to the 'param' tag in the launch
383 specification.
384 """
386 self.key = rosgraph.names.canonicalize_name(key)
387 self.value = value
389 if not isinstance(p, Param):
390 return False
391 return p.key == self.key and p.value == self.value
395 return "%s=%s"%(self.key, self.value)
397 return "%s=%s"%(self.key, self.value)
398
399 _local_m = None
401 """
402 :returns:: Machine instance representing the local machine, ``Machine``
403 """
404 global _local_m
405 if _local_m is None:
406 _local_m = Machine('', 'localhost')
407 return _local_m
408
410 """
411 Data structure for storing information about a desired node in
412 the ROS system Corresponds to the 'node' tag in the launch
413 specification.
414 """
415 __slots__ = ['package', 'type', 'name', 'namespace', \
416 'machine_name', 'machine', 'args', 'respawn', \
417 'remap_args', 'env_args',\
418 'process_name', 'output', 'cwd',
419 'launch_prefix', 'required',
420 'filename']
421
422 - def __init__(self, package, node_type, name=None, namespace='/', \
423 machine_name=None, args='', respawn=False, \
424 remap_args=None,env_args=None, output=None, cwd=None, \
425 launch_prefix=None, required=False, filename='<unknown>'):
426 """
427 :param package: node package name, ``str``
428 :param node_type: node type, ``str``
429 :param name: node name, ``str``
430 :param namespace: namespace for node, ``str``
431 :param machine_name: name of machine to run node on, ``str``
432 :param args: argument string to pass to node executable, ``str``
433 :param respawn: if True, respawn node if it dies, ``bool``
434 :param remap_args: list of [(from, to)] remapping arguments, ``[(str, str)]``
435 :param env_args: list of [(key, value)] of
436 additional environment vars to set for node, ``[(str, str)]``
437 :param output: where to log output to, either Node, 'screen' or 'log', ``str``
438 :param cwd: current working directory of node, either 'node', 'ROS_HOME'. Default: ROS_HOME, ``str``
439 :param launch_prefix: launch command/arguments to prepend to node executable arguments, ``str``
440 :param required: node is required to stay running (launch fails if node dies), ``bool``
441 :param filename: name of file Node was parsed from, ``str``
442
443 :raises: :exc:`ValueError` If parameters do not validate
444 """
445
446 self.package = package
447 self.type = node_type
448 self.name = name or None
449 self.namespace = rosgraph.names.make_global_ns(namespace or '/')
450 self.machine_name = machine_name or None
451 self.respawn = respawn
452 self.args = args or ''
453 self.remap_args = remap_args or []
454 self.env_args = env_args or []
455 self.output = output
456 self.cwd = cwd
457 if self.cwd == 'ros_home':
458 self.cwd = 'ROS_HOME'
459
460 self.launch_prefix = launch_prefix or None
461 self.required = required
462 self.filename = filename
463
464 if self.respawn and self.required:
465 raise ValueError("respawn and required cannot both be set to true")
466
467
468 if self.name and rosgraph.names.SEP in self.name:
469 raise ValueError("node name cannot contain a namespace")
470 if not len(self.package.strip()):
471 raise ValueError("package must be non-empty")
472 if not len(self.type.strip()):
473 raise ValueError("type must be non-empty")
474 if not self.output in ['log', 'screen', None]:
475 raise ValueError("output must be one of 'log', 'screen'")
476 if not self.cwd in ['ROS_HOME', 'node', None]:
477 raise ValueError("cwd must be one of 'ROS_HOME', 'node'")
478
479
480
481
482
483 self.process_name = None
484
485
486
487
488
489 self.machine = None
490
491
492
495
497 name_str = cwd_str = respawn_str = None
498 if self.name:
499 name_str = self.name
500 if self.cwd:
501 cwd_str = self.cwd
502
503 return [
504 ('pkg', self.package),
505 ('type', self.type),
506 ('machine', self.machine_name),
507 ('ns', self.namespace),
508 ('args', self.args),
509 ('output', self.output),
510 ('cwd', cwd_str),
511 ('respawn', self.respawn),
512 ('name', name_str),
513 ('launch-prefix', self.launch_prefix),
514 ('required', self.required),
515 ]
516
517
519 """
520 convert representation into XML representation. Currently cannot represent private parameters.
521 :returns:: XML representation for remote machine, ``str``
522 """
523 t = self.xmltype()
524 attrs = [(a, v) for a, v in self.xmlattrs() if v != None]
525 xmlstr = '<%s %s>\n'%(t, ' '.join(['%s="%s"'%(val[0], _xml_escape(val[1])) for val in attrs]))
526 xmlstr += ''.join([' <remap from="%s" to="%s" />\n'%tuple(r) for r in self.remap_args])
527 xmlstr += ''.join([' <env name="%s" value="%s" />\n'%tuple(e) for e in self.env_args])
528 xmlstr += "</%s>"%t
529 return xmlstr
530
532 """
533 convert representation into remote representation. Remote representation does
534 not include parameter settings or 'machine' attribute
535 :returns:: XML representation for remote machine, ``str``
536 """
537 t = self.xmltype()
538 attrs = [(a, v) for a, v in self.xmlattrs() if v != None and a != 'machine']
539 xmlstr = '<%s %s>\n'%(t, ' '.join(['%s="%s"'%(val[0], _xml_escape(val[1])) for val in attrs]))
540 xmlstr += ''.join([' <remap from="%s" to="%s" />\n'%tuple(r) for r in self.remap_args])
541 xmlstr += ''.join([' <env name="%s" value="%s" />\n'%tuple(e) for e in self.env_args])
542 xmlstr += "</%s>"%t
543 return xmlstr
544
546 """
547 Escape string for XML
548 :param s: string to escape, ``str``
549 :returns:: string with XML entities (<, >, \", &) escaped, ``str``
550 """
551
552
553 if isinstance(s, basestring):
554 return escape(s, entities={'"': '"'})
555 else:
556
557 return s
558
559 TEST_TIME_LIMIT_DEFAULT = 1 * 60
560
561
563 """
564 A Test is a Node with special semantics that it performs a
565 unit/integration test. The data model is the same except the
566 option to set the respawn flag is removed.
567 """
568 __slots__ = ['test_name', 'time_limit', 'retry']
569
570 - def __init__(self, test_name, package, node_type, name=None, \
571 namespace='/', machine_name=None, args='', \
572 remap_args=None, env_args=None, time_limit=None, cwd=None,
573 launch_prefix=None, retry=None, filename="<unknown>"):
574 """
575 Construct a new test node.
576 :param test_name: name of test for recording in test results, ``str``
577 :param time_limit: number of seconds that a test
578 should run before marked as a failure, ``int/float/long``
579 """
580 super(Test, self).__init__(package, node_type, name=name, \
581 namespace=namespace, \
582 machine_name=machine_name, args=args, \
583 remap_args=remap_args,
584 env_args=env_args,
585
586 output='log', cwd=cwd,
587 launch_prefix=launch_prefix, filename=filename)
588 self.test_name = test_name
589
590 self.retry = retry or 0
591 time_limit = time_limit or TEST_TIME_LIMIT_DEFAULT
592 if not type(time_limit) in (float, int, long):
593 raise ValueError("'time-limit' must be a number")
594 time_limit = float(time_limit)
595 if time_limit <= 0:
596 raise ValueError("'time-limit' must be a positive number")
597
598 self.time_limit = time_limit
599
602
604 """
605 NOTE: xmlattrs does not necessarily produce identical XML as
606 to what it was initialized with, though the properties are the same
607 """
608 attrs = Node.xmlattrs(self)
609 attrs = [(a, v) for (a, v) in attrs if a != 'respawn']
610 attrs.append(('test-name', self.test_name))
611
612 if self.retry:
613 attrs.append(('retry', str(self.retry)))
614 if self.time_limit != TEST_TIME_LIMIT_DEFAULT:
615 attrs.append(('time-limit', self.time_limit))
616 return attrs
617
618
620 """
621 Executable is a generic container for exectuable commands.
622 """
623
625 """
626 :param cmd: name of command to run, ``str``
627 :param args: arguments to command, ``(str,)``
628 :param phase: PHASE_SETUP|PHASE_RUN|PHASE_TEARDOWN. Indicates whether the
629 command should be run before, during, or after launch, ``str``
630 """
631 self.command = cmd
632 self.args = args
633 self.phase = phase
635 return "%s %s"%(self.command, ' '.join(self.args))
637 return "%s %s"%(self.command, ' '.join(self.args))
638
640 """
641 RosbinExecutables are exectuables stored in ROS_ROOT/bin.
642 """
646 return "ros/bin/%s %s"%(self.command, ' '.join(self.args))
648 return "ros/bin/%s %s"%(self.command, ' '.join(self.args))
649
650
652 """
653 Utility routine for generating run IDs (UUIDs)
654 :returns: guid, ``str``
655 """
656 import uuid
657 return str(uuid.uuid1())
658