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 Core roslaunch model and lower-level utility routines.
37 """
38
39 import os
40 import logging
41 import socket
42 import sys
43 import xmlrpclib
44
45
46 import roslib.names
47 import roslib.network
48 import roslib.substitution_args
49 import roslib.rosenv
50
51 from xml.sax.saxutils import escape
52
53
54 resolve_args = roslib.substitution_args.resolve_args
55
57 """Base roslaunch exception type"""
58 pass
59
60
61
62 PHASE_SETUP = 'setup'
63 PHASE_RUN = 'run'
64 PHASE_TEARDOWN = 'teardown'
65
66 _child_mode = False
68 """
69 @return: True if roslaunch is running in remote child mode
70 @rtype: bool
71 """
72 return _child_mode
74 """
75 @param child_mode: True if roslaunch is running in remote
76 child mode
77 @type child_mode: bool
78 """
79 global _child_mode
80 _child_mode = child_mode
81
83 """
84 Check to see if machine is local. NOTE: a machine is not local if
85 its user credentials do not match the current user.
86 @param machine: Machine
87 @type machine: L{Machine}
88 @return: True if machine is local and doesn't require remote login
89 @rtype: bool
90 """
91 try:
92 machine_addr = socket.gethostbyname(machine.address)
93 except socket.gaierror:
94 raise RLException("cannot resolve host address for machine [%s]"%machine.address)
95 local_addresses = ['localhost'] + roslib.network.get_local_addresses()
96
97 is_local = machine_addr.startswith('127.') or machine_addr in local_addresses
98
99
100 if is_local and machine.user:
101 import getpass
102 is_local = machine.user == getpass.getuser()
103 return is_local
104
105 _printlog_handlers = []
106 _printerrlog_handlers = []
108 """
109 Core utility for printing message to stdout as well as printlog handlers
110 @param msg: message to print
111 @type msg: str
112 """
113 for h in _printlog_handlers:
114 try:
115 h(msg)
116 except:
117 pass
118 try:
119 print msg
120 except:
121 pass
122
124 """
125 Similar to L{printlog()}, but the message printed to screen is bolded for greater clarity
126 @param msg: message to print
127 @type msg: str
128 """
129 for h in _printlog_handlers:
130 try:
131 h(msg)
132 except:
133 pass
134 try:
135 if sys.platform in ['win32']:
136 print '%s'%msg
137 else:
138 print '\033[1m%s\033[0m'%msg
139 except:
140 pass
141
143 """
144 Core utility for printing message to stderr as well as printerrlog handlers
145 @param msg: message to print
146 @type msg: str
147 """
148 for h in _printerrlog_handlers:
149 try:
150 h(msg)
151 except:
152 pass
153
154
155 try:
156 print >> sys.stderr, '\033[31m%s\033[0m'%msg
157 except:
158 pass
159
161 """
162 Register additional handler for printlog()
163 """
164 _printlog_handlers.append(h)
165
171
173 """
174 Delete all printlog handlers. required for testing
175 """
176 del _printlog_handlers[:]
177
179 """
180 Delete all printerrlog handlers. required for testing
181 """
182 del _printerrlog_handlers[:]
183
185 """
186 Create dictionary of environment variables to set for launched
187 process.
188
189 setup_env() will only set ROS_*, PYTHONPATH, and user-specified
190 environment variables.
191
192 @param machine Machine: machine being launched on
193 @type machine: L{Machine}
194 @param node: node that is being launched or None
195 @type node: L{Node}
196 @param master_uri: ROS master URI
197 @type master_uri: str
198 @return: process env dictionary
199 @rtype: dict
200 """
201 d = {}
202 d[roslib.rosenv.ROS_MASTER_URI] = master_uri
203 d[roslib.rosenv.ROS_ROOT] = machine.ros_root or get_ros_root()
204 if machine.ros_package_path:
205 d[roslib.rosenv.ROS_PACKAGE_PATH] = machine.ros_package_path
206 if machine.ros_ip:
207 d[roslib.rosenv.ROS_IP] = machine.ros_ip
208
209
210
211 d['PYTHONPATH'] = os.path.join(d[roslib.rosenv.ROS_ROOT],'core','roslib', 'src')
212
213
214 for name, value in machine.env_args:
215 d[name] = value
216
217
218 if node:
219 ns = node.namespace
220 if ns[-1] == '/':
221 ns = ns[:-1]
222 if ns:
223 d[roslib.rosenv.ROS_NAMESPACE] = ns
224 for name, value in node.env_args:
225 d[name] = value
226
227 return d
228
230 """
231 Wrap lower-level exceptions in RLException class
232 @return: function wrapper that throws an RLException if the
233 wrapped function throws an Exception
234 @rtype: fn
235 """
236 def wrapped_fn(*args):
237 try:
238 return fn(*args)
239 except Exception, e:
240
241 raise RLException("ERROR: %s"%e)
242 return wrapped_fn
243
244 get_ros_root = rle_wrapper(roslib.rosenv.get_ros_root)
245 get_master_uri_env = rle_wrapper(roslib.rosenv.get_master_uri)
247 """
248 @return: ROS_PACKAGE_PATH value
249 @rtype: str
250 """
251
252 return roslib.rosenv.get_ros_package_path(required=False)
253
255 """
256 Resolve localhost addresses to an IP address so that
257 @param uri: XML-RPC URI
258 @type uri: str
259 @param force_localhost: if True, URI is mapped onto the local machine no matter what
260 @type force_localhost: bool
261 """
262 hostname, port = roslib.network.parse_http_host_and_port(uri)
263 if force_localhost or hostname == 'localhost':
264 return roslib.network.create_local_xmlrpc_uri(port)
265 else:
266 return uri
267
268
269
270
272 """
273 Data structure for representing and querying state of master
274 """
275 __slots__ = ['type', 'auto', 'uri']
276 ROSMASTER = 'rosmaster'
277
278
279 ZENMASTER = 'zenmaster'
280
281 - def __init__(self, type_=None, uri=None, auto=None):
282 """
283 Create new Master instance.
284 @param uri: master URI. Defaults to ROS_MASTER_URI environment variable.
285 @type uri: str
286 @param type_: Currently only support 'rosmaster'
287 @type type_: str
288 """
289 self.auto = None
290 self.type = type_ or Master.ROSMASTER
291 self.uri = uri or get_master_uri_env()
292
294
295 host, _ = roslib.network.parse_http_host_and_port(self.uri)
296 return host
297
299 """
300 Get the port this master is configured for.
301 """
302
303 _, urlport = roslib.network.parse_http_host_and_port(self.uri)
304 return urlport
305
307 if not isinstance(m2, Master):
308 return False
309 else:
310 return m2.type == self.type and m2.uri == self.uri
311
313 """
314 @return: XMLRPC proxy for communicating with master
315 @rtype: xmlrpclib.ServerProxy
316 """
317 return xmlrpclib.ServerProxy(self.uri)
318
320 """
321 @return: multicall XMLRPC proxy for communicating with master
322 @rtype: xmlrpclib.MultiCall
323 """
324 return xmlrpclib.MultiCall(self.get())
325
327 """
328 Check if master is running.
329 @return: True if the master is running
330 @rtype: bool
331 """
332 try:
333 try:
334 to_orig = socket.getdefaulttimeout()
335
336 socket.setdefaulttimeout(5.0)
337 logging.getLogger('roslaunch').info('master.is_running[%s]'%self.uri)
338 code, status, val = self.get().getPid('/roslaunch')
339 if code != 1:
340 raise RLException("ERROR: master failed status check: %s"%msg)
341 logging.getLogger('roslaunch.core').debug('master.is_running[%s]: True'%self.uri)
342 return True
343 finally:
344 socket.setdefaulttimeout(to_orig)
345 except:
346 logging.getLogger('roslaunch.core').debug('master.is_running[%s]: True'%self.uri)
347 return False
348
349
350
351 _DEFAULT_REGISTER_TIMEOUT = 10.0
352
354 """
355 Data structure for storing information about a machine in the ROS
356 system. Corresponds to the 'machine' tag in the launch
357 specification.
358 """
359 __slots__ = ['name', 'ros_root', 'ros_package_path', 'ros_ip',\
360 'address', 'ssh_port', 'user', 'password', 'assignable',\
361 'env_args', 'timeout']
362 - def __init__(self, name, ros_root, ros_package_path, \
363 address, ros_ip=None, ssh_port=22, user=None, password=None, \
364 assignable=True, env_args=[], timeout=None):
365 """
366 @param name: machine name
367 @type name: str
368 @param ros_root: ROS_ROOT on machine
369 @type ros_root: str
370 @param ros_package_path: ROS_PACKAGE_PATH on machine
371 @type ros_package_path: str
372 @param address: network address of machine
373 @type address: str
374 @param ros_ip: ROS_IP on machine
375 @type ros_ip: str
376 @param ssh_port: SSH port number
377 @type ssh_port: int
378 @param user: SSH username
379 @type user: str
380 @param password: SSH password. Not recommended for use. Use SSH keys instead.
381 @type password: str
382 """
383 self.name = name
384 self.ros_root = ros_root
385 self.ros_package_path = ros_package_path
386 self.ros_ip = ros_ip or None
387 self.user = user or None
388 self.password = password or None
389 self.address = address
390 self.ssh_port = ssh_port
391 self.assignable = assignable
392 self.env_args = env_args
393 self.timeout = timeout or _DEFAULT_REGISTER_TIMEOUT
394
396 return "Machine(name[%s] ros_root[%s] ros_package_path[%s] ros_ip[%s] address[%s] ssh_port[%s] user[%s] assignable[%s] env_args[%s] timeout[%s])"%(self.name, self.ros_root, self.ros_package_path, self.ros_ip, self.address, self.ssh_port, self.user, self.assignable, str(self.env_args), self.timeout)
398 if not isinstance(m2, Machine):
399 return False
400 return self.name == m2.name and \
401 self.assignable == m2.assignable and \
402 self.config_equals(m2)
403
405 """
406 Get a key that represents the configuration of the
407 machine. machines with identical configurations have identical
408 keys
409
410 @return: configuration key
411 @rtype: str
412 """
413 return "Machine(address[%s] ros_root[%s] ros_package_path[%s] ros_ip[%s] ssh_port[%s] user[%s] password[%s] env_args[%s] timeout[%s])"%(self.address, self.ros_root, self.ros_package_path, self.ros_ip or '', self.ssh_port, self.user or '', self.password or '', str(self.env_args), self.timeout)
414
416 """
417 @return: True if machines have identical configurations
418 @rtype: bool
419 """
420 if not isinstance(m2, Machine):
421 return False
422 return self.ros_root == m2.ros_root and \
423 self.ros_package_path == m2.ros_package_path and \
424 self.ros_ip == m2.ros_ip and \
425 self.address == m2.address and \
426 self.ssh_port == m2.ssh_port and \
427 self.user == m2.user and \
428 self.password == m2.password and \
429 set(self.env_args) == set(m2.env_args) and \
430 self.timeout == m2.timeout
431
433 return not self.__eq__(m2)
434
436 """
437 Data structure for storing information about a desired parameter in
438 the ROS system Corresponds to the 'param' tag in the launch
439 specification.
440 """
442 self.key = roslib.names.canonicalize_name(key)
443 self.value = value
445 if not isinstance(p, Param):
446 return False
447 return p.key == self.key and p.value == self.value
451 return "%s=%s"%(self.key, self.value)
453 return "%s=%s"%(self.key, self.value)
454
455 _local_m = None
467
469 """
470 Data structure for storing information about a desired node in
471 the ROS system Corresponds to the 'node' tag in the launch
472 specification.
473 """
474 __slots__ = ['package', 'type', 'name', 'namespace', \
475 'machine_name', 'machine', 'args', 'respawn', \
476 'remap_args', 'env_args',\
477 'process_name', 'output', 'cwd',
478 'launch_prefix', 'required',
479 'filename']
480
481 - def __init__(self, package, node_type, name=None, namespace='/', \
482 machine_name=None, args='', respawn=False, \
483 remap_args=None,env_args=None, output=None, cwd=None, \
484 launch_prefix=None, required=False, filename='<unknown>'):
485 """
486 @param package: node package name
487 @type package: str
488 @param node_type: node type
489 @type node_type: str
490 @param name: node name
491 @type name: str
492 @param namespace: namespace for node
493 @type namespace: str
494 @param machine_name: name of machine to run node on
495 @type machine_name: str
496 @param args: argument string to pass to node executable
497 @type args: str
498 @param respawn: if True, respawn node if it dies
499 @type respawn: bool
500 @param remap_args: list of [(from, to)] remapping arguments
501 @type remap_args: [(str, str)]:
502 @param env_args: list of [(key, value)] of
503 additional environment vars to set for node
504 @type env_args: [(str, str)]
505 @param output: where to log output to, either Node, 'screen' or 'log'
506 @type output: str
507 @param cwd: current working directory of node, either 'node', 'ROS_HOME' or 'ros-root'. Default: ROS_HOME
508 @type cwd: str
509 @param launch_prefix: launch command/arguments to prepend to node executable arguments
510 @type launch_prefix: str
511 @param required: node is required to stay running (launch fails if node dies)
512 @type required: bool
513 @param filename: name of file Node was parsed from
514 @type filename: str
515
516 @raise ValueError: if parameters do not validate
517 """
518
519 self.package = package
520 self.type = node_type
521 self.name = name or None
522 self.namespace = roslib.names.make_global_ns(namespace or '/')
523 self.machine_name = machine_name or None
524 self.respawn = respawn
525 self.args = args or ''
526 self.remap_args = remap_args or []
527 self.env_args = env_args or []
528 self.output = output
529 self.cwd = cwd
530 if self.cwd == 'ros_home':
531 self.cwd = 'ROS_HOME'
532 elif self.cwd == 'ros-root':
533 printerrlog("WARNING: 'ros-root' value for <node> 'cwd' attribute is deprecated.")
534
535 self.launch_prefix = launch_prefix or None
536 self.required = required
537 self.filename = filename
538
539 if self.respawn and self.required:
540 raise ValueError("respawn and required cannot both be set to true")
541
542
543 if self.name and roslib.names.SEP in self.name:
544 raise ValueError("node name cannot contain a namespace")
545 if not len(self.package.strip()):
546 raise ValueError("package must be non-empty")
547 if not len(self.type.strip()):
548 raise ValueError("type must be non-empty")
549 if not self.output in ['log', 'screen', None]:
550 raise ValueError("output must be one of 'log', 'screen'")
551 if not self.cwd in ['ROS_HOME', 'ros-root', 'node', None]:
552 raise ValueError("cwd must be one of 'ROS_HOME', 'ros-root', 'node'")
553
554
555
556
557
558 self.process_name = None
559
560
561
562
563
564 self.machine = None
565
566
567
570
572 name_str = cwd_str = respawn_str = None
573 if self.name:
574 name_str = self.name
575 if self.cwd:
576 cwd_str = self.cwd
577
578 return [
579 ('pkg', self.package),
580 ('type', self.type),
581 ('machine', self.machine_name),
582 ('ns', self.namespace),
583 ('args', self.args),
584 ('output', self.output),
585 ('cwd', cwd_str),
586 ('respawn', self.respawn),
587 ('name', name_str),
588 ('launch-prefix', self.launch_prefix),
589 ('required', self.required),
590 ]
591
592
594 """
595 convert representation into XML representation. Currently cannot represent private parameters.
596 @return: XML representation for remote machine
597 @rtype: str
598 """
599 t = self.xmltype()
600 attrs = [(a, v) for a, v in self.xmlattrs() if v != None]
601 xmlstr = '<%s %s>\n'%(t, ' '.join(['%s="%s"'%(val[0], _xml_escape(val[1])) for val in attrs]))
602 xmlstr += ''.join([' <remap from="%s" to="%s" />\n'%tuple(r) for r in self.remap_args])
603 xmlstr += ''.join([' <env name="%s" value="%s" />\n'%tuple(e) for e in self.env_args])
604 xmlstr += "</%s>"%t
605 return xmlstr
606
608 """
609 convert representation into remote representation. Remote representation does
610 not include parameter settings or 'machine' attribute
611 @return: XML representation for remote machine
612 @rtype: str
613 """
614 t = self.xmltype()
615 attrs = [(a, v) for a, v in self.xmlattrs() if v != None and a != 'machine']
616 xmlstr = '<%s %s>\n'%(t, ' '.join(['%s="%s"'%(val[0], _xml_escape(val[1])) for val in attrs]))
617 xmlstr += ''.join([' <remap from="%s" to="%s" />\n'%tuple(r) for r in self.remap_args])
618 xmlstr += ''.join([' <env name="%s" value="%s" />\n'%tuple(e) for e in self.env_args])
619 xmlstr += "</%s>"%t
620 return xmlstr
621
623 """
624 Escape string for XML
625 @param s: string to escape
626 @type s: str
627 @return: string with XML entities (<, >, ", &) escaped.
628 @rtype: str
629 """
630 if isinstance(s,basestring):
631
632 return escape(s, entities={'"': '"'})
633 else:
634 return s
635
636 TEST_TIME_LIMIT_DEFAULT = 1 * 60
637
638
640 """
641 A Test is a Node with special semantics that it performs a
642 unit/integration test. The data model is the same except the
643 option to set the respawn flag is removed.
644 """
645 __slots__ = ['test_name', 'time_limit', 'retry']
646
647 - def __init__(self, test_name, package, node_type, name=None, \
648 namespace='/', machine_name=None, args='', \
649 remap_args=None, env_args=None, time_limit=None, cwd=None,
650 launch_prefix=None, retry=None, filename="<unknown>"):
651 """
652 Construct a new test node.
653 @param test_name: name of test for recording in test results
654 @type test_name: str
655 @param time_limit: number of seconds that a test
656 should run before marked as a failure
657 @type time_limit: int/float/long
658 """
659 super(Test, self).__init__(package, node_type, name=name, \
660 namespace=namespace, \
661 machine_name=machine_name, args=args, \
662 remap_args=remap_args,
663 env_args=env_args,
664
665 output='log', cwd=cwd,
666 launch_prefix=launch_prefix, filename=filename)
667 self.test_name = test_name
668
669 self.retry = retry or 0
670 time_limit = time_limit or TEST_TIME_LIMIT_DEFAULT
671 if not type(time_limit) in (float, int, long):
672 raise ValueError("'time-limit' must be a number")
673 time_limit = float(time_limit)
674 if time_limit <= 0:
675 raise ValueError("'time-limit' must be a positive number")
676
677 self.time_limit = time_limit
678
681
683 """
684 NOTE: xmlattrs does not necessarily produce identical XML as
685 to what it was initialized with, though the properties are the same
686 """
687 attrs = Node.xmlattrs(self)
688 attrs = [(a, v) for (a, v) in attrs if a != 'respawn']
689 attrs.append(('test-name', self.test_name))
690
691 if self.retry:
692 attrs.append(('retry', str(self.retry)))
693 if self.time_limit != TEST_TIME_LIMIT_DEFAULT:
694 attrs.append(('time-limit', self.time_limit))
695 return attrs
696
697
699 """
700 Executable is a generic container for exectuable commands.
701 """
702
704 """
705 @param cmd: name of command to run
706 @type cmd: str
707 @param args: arguments to command
708 @type args: (str,)
709 @param phase: PHASE_SETUP|PHASE_RUN|PHASE_TEARDOWN. Indicates whether the
710 command should be run before, during, or after launch.
711 @type phase: str
712 """
713 self.command = cmd
714 self.args = args
715 self.phase = phase
717 return "%s %s"%(self.command, ' '.join(self.args))
719 return "%s %s"%(self.command, ' '.join(self.args))
720
722 """
723 RosbinExecutables are exectuables stored in ROS_ROOT/bin.
724 """
728 return "ros/bin/%s %s"%(self.command, ' '.join(self.args))
730 return "ros/bin/%s %s"%(self.command, ' '.join(self.args))
731
732
734 """
735 Utility routine for generating run IDs (UUIDs)
736 @return: guid
737 @rtype: str
738 """
739 import uuid
740 return str(uuid.uuid1())
741