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 sys
43 import types
44
45 import roslib.names
46
47 from roslaunch.core import Master, local_machine, get_ros_root, is_machine_local, RLException
48 from roslaunch.rlutil import namespaces_of
49 import roslaunch.loader
50 import roslaunch.xmlloader
51
52 try:
53 from rosmaster import DEFAULT_MASTER_PORT
54 except:
55 DEFAULT_MASTER_PORT = 11311
56
58 """
59 Load roscore configuration into the ROSLaunchConfig using the specified XmlLoader
60 @param config ROSLaunchConfig
61 @param loader XmlLoader
62 """
63 import roslib.packages
64 f_roscore = os.path.join(roslib.packages.get_pkg_dir('roslaunch'), 'roscore.xml')
65 logging.getLogger('roslaunch').info('loading roscore config file %s'%f_roscore)
66 loader.load(f_roscore, config, core=True, verbose=verbose)
67
69 """
70 Generate summary label for node based on its package, type, and name
71 """
72 if node.name:
73 return "%s (%s/%s)"%(node.name, node.package, node.type)
74 else:
75 return "%s/%s"%(node.package, node.type)
76
78 """
79 ROSLaunchConfig is the container for the loaded roslaunch file state. It also
80 is responsible for validating then executing the desired state.
81 """
82
84 """
85 Initialize an empty config object. Master defaults to the environment's master.
86 """
87 self.master = Master()
88 self.nodes_core = []
89 self.nodes = []
90
91
92 self.resolved_node_names = []
93
94 self.tests = []
95 self.machines = {}
96 self.params = {}
97 self.clear_params = []
98 self.executables = []
99
100
101 self.config_errors = []
102
103 m = local_machine()
104 self.machines[m.name] = m
105 self._assign_machines_complete = False
106 self._remote_nodes_present = None
107
108 self.logger = logging.getLogger('roslaunch')
109
111 """
112 Report human-readable error message related to configuration error
113 @param msg: error message
114 @type msg: str
115 """
116 self.config_errors.append(msg)
117
119 """
120 Set the master configuration
121 @param m: Master
122 @type m: L{Master}
123 """
124 self.master = m
125
127 """
128 @return: True if roslaunch will launch nodes on a remote machine
129 @rtype: bool
130 """
131 if not self._assign_machines_complete:
132 raise Exception("ERROR: has_remote_nodes() cannot be called until prelaunch check is complete")
133 return self._remote_nodes_present
134
136 """
137 Assign nodes to machines and determine whether or not there are any remote machines
138 """
139
140 if self._assign_machines_complete:
141 return
142
143 machine_unify_dict = {}
144
145 self._assign_machines_complete = True
146
147 local_machine = self.machines['']
148 for n in self.nodes_core:
149 n.machine = local_machine
150
151
152 for n in self.nodes + self.tests:
153 m = self._select_machine(n)
154
155
156
157
158 config_key = m.config_key()
159 if config_key in machine_unify_dict:
160 new_m = machine_unify_dict[config_key]
161 if m != new_m:
162 self.logger.info("... changing machine assignment from [%s] to [%s] as they are equivalent", m.name, new_m.name)
163 m = new_m
164 else:
165 machine_unify_dict[config_key] = m
166 n.machine = m
167 self.logger.info("... selected machine [%s] for node of type [%s/%s]", m.name, n.package, n.type)
168
169
170
171 self._remote_nodes_present = False
172 if [m for m in machine_unify_dict.itervalues() if not is_machine_local(m)]:
173 self._remote_nodes_present = True
174
176 """
177 Perform basic checks on the local ROS environment, master, and core services.
178 master will be launched if configured to do so. Core services will be launched regardless.
179 if they are not already running.
180
181 @raise RLException: if validation fails
182 """
183 ros_root = get_ros_root()
184 if not os.path.isdir(ros_root):
185 raise RLException("ERROR: ROS_ROOT is not configured properly. Value is [%s]"%ros_root)
186
188 """
189 Get a human-readable string summary of the launch
190 @param local bool: if True, only print local nodes
191 @return: summary
192 @rtype: str
193 """
194 summary = '\nSUMMARY\n========'
195 if self.clear_params:
196 summary += '\n\nCLEAR PARAMETERS\n' + '\n'.join([' * %s'%p for p in self.clear_params])
197 if self.params:
198 summary += '\n\nPARAMETERS\n' + '\n'.join([' * %s'%k for k in self.params])
199 if not local:
200 summary += '\n\nMACHINES\n' + '\n'.join([' * %s'%k for k in self.machines if k])
201 summary += '\n\nNODES\n'
202 namespaces = {}
203 if local:
204 nodes = [n for n in self.nodes if is_machine_local(n.machine)]
205 else:
206 nodes = self.nodes
207 for n in nodes:
208 ns = n.namespace
209 if ns not in namespaces:
210 namespaces[ns] = [n]
211 else:
212 namespaces[ns].append(n)
213 for k,v in namespaces.iteritems():
214 summary += ' %s\n'%k + '\n'.join([' %s'%_summary_name(n) for n in v])
215 summary += '\n'
216 return summary
217
219 """
220 Declare an exectuable to be run during the launch
221 @param exe: Executable
222 @type exe: L{Executable}
223 """
224 if not exe:
225 raise ValueError("exe is None")
226 self.executables.append(exe)
227
229 """
230 Declare a parameter to be cleared before new parameters are set
231 @param param: parameter to clear
232 @type param: str
233 """
234 self.clear_params.append(param)
235
236 - def add_param(self, p, filename=None, verbose=True):
237 """
238 Declare a parameter to be set on the param server before launching nodes
239 @param p: parameter instance
240 @type p: L{Param}
241 """
242 key = p.key
243
244
245 if key in self.params and self.params[key] != p:
246 if filename:
247 self.logger.debug("[%s] overriding parameter [%s]"%(filename, p.key))
248 else:
249 self.logger.debug("overriding parameter [%s]"%p.key)
250
251 for parent_key in [pk for pk in namespaces_of(key) if pk in self.params]:
252 self.add_config_error("parameter [%s] conflicts with parent parameter [%s]"%(key, parent_key))
253
254 self.params[key] = p
255 if verbose:
256 print "Added parameter [%s]"%key
257 t = type(p.value)
258 if t in [str, unicode, types.InstanceType]:
259 self.logger.debug("add_param[%s]: type [%s]"%(p.key, t))
260 else:
261 self.logger.debug("add_param[%s]: type [%s] value [%s]"%(p.key, t, p.value))
262
264 """
265 Declare a machine and associated parameters so that it can be used for
266 running nodes.
267 @param m: machine instance
268 @type m: L{Machine}
269 @return: True if new machine added, False if machine already specified.
270 @rtype: bool
271 @raise RLException: if cannot add machine as specified
272 """
273 name = m.name
274 if m.address == 'localhost':
275 import roslib.network
276 address = roslib.network.get_local_address()
277 self.logger.info("addMachine[%s]: remapping localhost address to %s"%(name, address))
278 if name in self.machines:
279 if m != self.machines[name]:
280 raise RLException("Machine [%s] already added and does not match duplicate entry"%name)
281 return False
282 else:
283 self.machines[name] = m
284 if verbose:
285 print "Added machine [%s]"%name
286 return True
287
288 - def add_test(self, test, verbose=True):
289 """
290 Add test declaration. Used by rostest
291 @param test: test node instance to add to launch
292 @type test: L{Test}
293 """
294 self.tests.append(test)
295
296 - def add_node(self, node, core=False, verbose=True):
297 """
298 Add node declaration
299 @param node: node instance to add to launch
300 @type node: L{Node}
301 @param core: if True, node is a ROS core node
302 @type core: bool
303 @raise RLException: if ROS core node is missing required name
304 """
305 if node.name:
306
307 resolved_name = roslib.names.ns_join(node.namespace, node.name)
308 matches = [n for n in self.resolved_node_names if n == resolved_name]
309 if matches:
310 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)
311 else:
312 self.resolved_node_names.append(resolved_name)
313
314 if not core:
315 self.nodes.append(node)
316 if verbose:
317 print "Added node of type [%s/%s] in namespace [%s]"%(node.package, node.type, node.namespace)
318 self.logger.info("Added node of type [%s/%s] in namespace [%s]", node.package, node.type, node.namespace)
319 else:
320 if not node.name:
321 raise RLException("ROS core nodes must have a name. [%s/%s]"%(node.package, node.type))
322 self.nodes_core.append(node)
323 if verbose:
324 print "Added core node of type [%s/%s] in namespace [%s]"%(node.package, node.type, node.namespace)
325 self.logger.info("Added core node of type [%s/%s] in namespace [%s]", node.package, node.type, node.namespace)
326
328 """
329 Select a machine for a node to run on. For nodes that are
330 already assigned to a machine, this will map the string name to
331 a L{Machine} instance. If the node isn't already tagged with a
332 particular machine, one will be selected for it.
333 @param node: node to assign machine for
334 @type node: L{Node}
335 @return: machine to run on
336 @rtype: L{Machine}
337 @raise RLException: If machine state is improperly configured
338 """
339 machine = node.machine_name
340
341 if machine:
342 if not machine in self.machines:
343 raise RLException("ERROR: unknown machine [%s]"%machine)
344 return self.machines[machine]
345 else:
346
347 return self.machines['']
348
349 -def load_config_default(roslaunch_files, port, roslaunch_strs=None, loader=None, verbose=False, assign_machines=True):
350 """
351 Base routine for creating a ROSLaunchConfig from a set of
352 roslaunch_files and or launch XML strings and initializing it. This
353 config will have a core definition and also set the master to run
354 on port.
355 @param roslaunch_files: list of launch files to load
356 @type roslaunch_files: [str]
357 @param port: roscore/master port override. Set to 0 or None to use default.
358 @type port: int
359 @param roslaunch_strs: (optional) roslaunch XML strings to load
360 @type roslaunch_strs: [str]
361 @param verbose: (optional) print info to screen about model as it is loaded.
362 @type verbose: bool
363 @param assign_machines: (optional) assign nodes to machines (default: True)
364 @type assign_machines: bool
365 @return: initialized rosconfig instance
366 @rytpe: L{ROSLaunchConfig} initialized rosconfig instance
367 """
368 logger = logging.getLogger('roslaunch.config')
369
370
371
372
373
374 config = ROSLaunchConfig()
375 if port:
376 config.master.uri = roslib.network.create_local_xmlrpc_uri(port)
377
378 loader = loader or roslaunch.xmlloader.XmlLoader()
379
380
381
382
383 load_roscore(loader, config, verbose=verbose)
384
385
386 for f in roslaunch_files:
387 try:
388 logger.info('loading config file %s'%f)
389 loader.load(f, config, verbose=verbose)
390 except roslaunch.xmlloader.XmlParseException, e:
391 raise RLException(e)
392 except roslaunch.loader.LoadException, e:
393 raise RLException(e)
394
395
396
397 if roslaunch_strs:
398 for launch_str in roslaunch_strs:
399 try:
400 logger.info('loading config file from string')
401 loader.load_string(launch_str, config)
402 except roslaunch.xmlloader.XmlParseException, e:
403 raise RLException('Launch string: %s\nException: %s'%(launch_str, e))
404 except roslaunch.loader.LoadException, e:
405 raise RLException('Launch string: %s\nException: %s'%(launch_str, e))
406
407
408 config.validate()
409
410
411 if assign_machines:
412 config.assign_machines()
413 return config
414