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