1
2
3
4
5
6
7
8
9
10
11 import os
12 import argparse
13 import subprocess
14 import signal
15 import sys
16 from time import sleep
17 import roslaunch
18 import tempfile
19
20
21 from .system import which, wait_pid
22 import rocon_utilities.console as console
23 import xml.etree.ElementTree as ElementTree
24 import ros_utilities
25
26
27
28
29
30 processes = []
31 roslaunch_pids = []
32
33
34
35
36
37
39 '''
40 Don't forward signals.
41
42 http://stackoverflow.com/questions/3791398/how-to-stop-python-from-propagating-signals-to-subprocesses
43 '''
44 os.setpgrp()
45
46
48 '''
49 Get the pid of the roslaunch process running in the terminal
50 specified by the parent pid.
51 '''
52 ps_command = subprocess.Popen("ps -o pid -o comm --ppid %d --noheaders" % parent_pid, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
53 ps_output = ps_command.stdout.read()
54 retcode = ps_command.wait()
55 pids = []
56 if retcode == 0:
57 for pair in ps_output.split("\n")[:-1]:
58 [pid, command] = pair.lstrip(' ').split(" ")
59 if command == 'roslaunch':
60 pids.append(int(pid))
61 else:
62
63
64 pass
65 return pids
66
67
87
88
90 '''
91 Parses an rocon multi-launcher (xml file).
92
93 @param rocon_launcher : xml file in rocon_launch format
94 @param default_roslaunch_options : options to pass to roslaunch (usually "--screen")
95 @return launchers : list with launcher parameters as dictionary elements of the list.
96
97 @raise IOError : if it can't find any of the individual launchers on the filesystem.
98 '''
99 tree = ElementTree.parse(rocon_launcher)
100 root = tree.getroot()
101
102 launchers = []
103 ports = []
104 default_port = 11311
105 for launch in root.findall('launch'):
106 parameters = {}
107 parameters['options'] = default_roslaunch_options
108 parameters['package'] = launch.get('package')
109 parameters['name'] = launch.get('name')
110 parameters['path'] = ros_utilities.find_resource(parameters['package'], parameters['name'])
111 parameters['port'] = launch.get('port', str(default_port))
112 if parameters['port'] == str(default_port):
113 default_port += 1
114 if parameters['port'] in ports:
115 parameters['options'] = parameters['options'] + " " + "--wait"
116 else:
117 ports.append(parameters['port'])
118 launchers.append(parameters)
119 return launchers
120
121
123 parser = argparse.ArgumentParser(description="Rocon's multiple master launcher.")
124 terminal_group = parser.add_mutually_exclusive_group()
125 terminal_group.add_argument('-k', '--konsole', default=False, action='store_true', help='spawn individual ros systems via multiple konsole terminals')
126 terminal_group.add_argument('-g', '--gnome', default=False, action='store_true', help='spawn individual ros systems via multiple gnome terminals')
127 parser.add_argument('--screen', action='store_true', help='run each roslaunch with the --screen option')
128 parser.add_argument('--no-terminals', action='store_true', help='do not spawn terminals for each roslaunch')
129
130 parser.add_argument('package', nargs='?', default='', help='name of the package in which to find the concert launcher')
131 parser.add_argument('launcher', nargs=1, help='name of the concert launch configuration (xml) file')
132
133 args = parser.parse_args()
134 return args
135
136
138 '''
139 Use ubuntu's x-terminal-emulator to choose the shell, or over-ride if it there is a flag.
140 '''
141 if konsole_flag:
142 if not which('konsole'):
143 console.error("Cannot find 'konsole' [hint: try --gnome for gnome-terminal instead]")
144 sys.exit(1)
145 return 'konsole'
146 elif gnome_flag:
147 if not which('gnome-terminal'):
148 console.error("Cannot find 'gnome' [hint: try --konsole for konsole instead]")
149 sys.exit(1)
150 return 'gnome-terminal'
151 else:
152 if not which('x-terminal-emulator'):
153 console.error("Cannot find 'x-terminal-emulator' [hint: try --gnome or --konsole instead]")
154 sys.exit(1)
155 p = subprocess.Popen([which('update-alternatives'), '--query', 'x-terminal-emulator'], stdout=subprocess.PIPE)
156 terminal = None
157 for line in p.stdout:
158 if line.startswith("Value:"):
159 terminal = os.path.basename(line.split()[1])
160 break
161 if terminal not in ["gnome-terminal", "gnome-terminal.wrapper", "konsole"]:
162 console.warning("You are using an esoteric unsupported terminal [%s]" % terminal)
163 if which('konsole'):
164 terminal = 'konsole'
165 console.warning(" --> falling back to 'konsole'")
166 elif which('gnome-terminal'):
167 console.warning(" --> falling back to 'gnome-terminal'")
168 terminal = 'gnome-terminal'
169 else:
170 console.error("Unsupported terminal set for 'x-terminal-emulator' [%s][hint: try --gnome or --konsole instead]" % terminal)
171 sys.exit(1)
172 return terminal
173
174
176 global processes
177 global roslaunch_pids
178 signal.signal(signal.SIGINT, signal_handler)
179 args = parse_arguments()
180 terminal = None
181 if not args.no_terminals:
182 if not which('konsole') and not which('gnome-terminal')and not which('x-terminal-emulator'):
183 console.error("Cannot find a suitable terminal [x-terminal-emulator, konsole, gnome-termional]")
184 sys.exit(1)
185 terminal = choose_terminal(args.gnome, args.konsole)
186
187 if args.package == '':
188 rocon_launcher = roslaunch.rlutil.resolve_launch_arguments(args.launcher)[0]
189 else:
190 rocon_launcher = roslaunch.rlutil.resolve_launch_arguments([args.package] + args.launcher)[0]
191 if args.screen:
192 roslaunch_options = "--screen"
193 else:
194 roslaunch_options = ""
195 launchers = parse_rocon_launcher(rocon_launcher, roslaunch_options)
196 temporary_launchers = []
197 for launcher in launchers:
198 console.pretty_println("Launching [%s, %s] on port %s" % (launcher['package'], launcher['name'], launcher['port']), console.bold)
199
200
201
202 temp = tempfile.NamedTemporaryFile(mode='w+t', delete=False)
203 print("Launching %s" % temp.name)
204 launcher_filename = ros_utilities.find_resource(launcher['package'], launcher['name'])
205 launch_text = '<launch>\n <include file="%s"/>\n' % launcher_filename
206 if args.screen:
207 launch_text += ' <param name="rocon/screen" value="true"/>\n'
208 else:
209 launch_text += ' <param name="rocon/screen" value="false"/>\n'
210 launch_text += '</launch>\n'
211 temp.write(launch_text)
212 temp.close()
213 temporary_launchers.append(temp)
214
215
216
217 if terminal == 'konsole':
218 p = subprocess.Popen([terminal, '--nofork', '--hold', '-e', "/bin/bash", "-c", "roslaunch %s --port %s %s" %
219 (launcher['options'], launcher['port'], temp.name)], preexec_fn=preexec)
220 elif terminal == 'gnome-terminal.wrapper' or terminal == 'gnome-terminal':
221
222 p = subprocess.Popen(['gnome-terminal', '--disable-factory', '-e', "/bin/bash", "-e", "roslaunch %s --port %s %s" %
223 (launcher['options'], launcher['port'], temp.name)], preexec_fn=preexec)
224 else:
225 cmd = ["roslaunch"]
226 if launcher['options']:
227 cmd.append(launcher['options'])
228 cmd.extend(["--port", launcher['port'], temp.name])
229 p = subprocess.Popen(cmd, preexec_fn=preexec)
230 processes.append(p)
231 signal.pause()
232
233
234
235 for temporary_launcher in temporary_launchers:
236 os.unlink(temporary_launcher.name)
237