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 General routines and representations for loading roslaunch model.
37 """
38
39 from __future__ import with_statement
40
41 import os
42 import sys
43 from copy import deepcopy
44
45 from roslaunch.core import Param, RosbinExecutable, Node, Test, Machine, \
46 RLException, PHASE_SETUP
47
48 try:
49 from roslib.names import make_global_ns, ns_join, PRIV_NAME, load_mappings, is_legal_name, canonicalize_name
50 except ImportError:
51 raise ImportError('Cannot import new roslib libraries.\nThis is probably due to incompatible "roslib" directories on your PYTHONPATH.\nPlease check your PYTHONPATH and try again')
52
53
54
55 yaml = None
56 rosparam = None
57
59 """Error loading data as specified (e.g. cannot find included files, etc...)"""
60 pass
61
62
64 """
65 Convert a value from a string representation into the specified
66 type
67
68 @param value: string representation of value
69 @type value: str
70 @param type_: int, double, string, bool, or auto
71 @type type_: str
72 @raise ValueError: if parameters are invalid
73 """
74 type_ = type_.lower()
75
76
77 if type_ == 'auto':
78
79 try:
80 if '.' in value:
81 return float(value)
82 else:
83 return int(value)
84 except ValueError, e:
85 pass
86
87 lval = value.lower()
88 if lval == 'true' or lval == 'false':
89 return convert_value(value, 'bool')
90
91 return value
92 elif type_ == 'str' or type_ == 'string':
93 return value
94 elif type_ == 'int':
95 return int(value)
96 elif type_ == 'double':
97 return float(value)
98 elif type_ == 'bool' or type_ == 'boolean':
99 value = value.lower()
100 if value == 'true' or value == '1':
101 return True
102 elif value == 'false' or value == '0':
103 return False
104 raise ValueError("%s is not a '%s' type"%(value, type_))
105 else:
106 raise ValueError("Unknown type '%s'"%type_)
107
109 """
110 Processes arg declarations in context and makes sure that they are
111 properly declared for passing into an included file. Also will
112 correctly setup the context for passing to the included file.
113 """
114
115
116
117 arg_dict = context.include_resolve_dict.get('arg', {})
118 for arg in context.arg_names:
119 if not arg in arg_dict:
120 raise LoadException("include args must have declared values")
121
122
123 context.args_passed = arg_dict.keys()[:]
124
125 context.arg_names = []
126
127
128 context.resolve_dict = context.include_resolve_dict
129 context.include_resolve_dict = None
130
132 bad = [a for a in context.args_passed if a not in context.arg_names]
133 if bad:
134 raise LoadException("unused args [%s] for include of [%s]"%(', '.join(bad), context.filename))
135
137 """
138 Load in ROS remapping arguments as arg assignments for context.
139
140 @param context: context to load into. context's resolve_dict for 'arg' will be reinitialized with values.
141 @type context: L{LoaderContext{
142 @param argv: command-line arguments
143 @type argv: [str]
144 """
145
146 mappings = load_mappings(argv)
147 context.resolve_dict['arg'] = mappings
148
149 -class LoaderContext(object):
150 """
151 Container for storing current loader context (e.g. namespace,
152 local parameter state, remapping state).
153 """
154
155 - def __init__(self, ns, filename, parent=None, params=None, env_args=None, \
156 resolve_dict=None, include_resolve_dict=None, arg_names=None):
157 """
158 @param ns: namespace
159 @type ns: str
160 @param filename: name of file this is being loaded from
161 @type filename: str
162 @param resolve_dict: (optional) resolution dictionary for substitution args
163 @type resolve_dict: dict
164 @param include_resolve_dict: special resolution dictionary for
165 <include> tags. Must be None if this is not an <include>
166 context.
167 @type include_resolve_dict: dict
168 @param arg_names: name of args that have been declared in this context
169 @type arg_names: [str]
170 """
171 self.parent = parent
172 self.ns = make_global_ns(ns or '/')
173 self._remap_args = []
174 self.params = params or []
175 self.env_args = env_args or []
176 self.filename = filename
177
178 self.resolve_dict = resolve_dict or {}
179
180 self.arg_names = arg_names or []
181
182 self.include_resolve_dict = include_resolve_dict or None
183
184 - def add_param(self, p):
185 """
186 Add a ~param to the context. ~params are evaluated by any node
187 declarations that occur later in the same context.
188
189 @param p: parameter
190 @type p: L{Param}
191 """
192
193
194 matches = [m for m in self.params if m.key == p.key]
195 for m in matches:
196 self.params.remove(m)
197 self.params.append(p)
198
199 - def add_remap(self, remap):
200 """
201 Add a new remap setting to the context. if a remap already
202 exists with the same from key, it will be removed
203
204 @param remap: remap setting
205 @type remap: (str, str)
206 """
207 remap = [canonicalize_name(x) for x in remap]
208 if not remap[0] or not remap[1]:
209 raise RLException("remap from/to attributes cannot be empty")
210 if not is_legal_name(remap[0]):
211 raise RLException("remap from [%s] is not a valid ROS name"%remap[0])
212 if not is_legal_name(remap[1]):
213 raise RLException("remap to [%s] is not a valid ROS name"%remap[1])
214
215 matches = [r for r in self._remap_args if r[0] == remap[0]]
216 for m in matches:
217 self._remap_args.remove(m)
218 self._remap_args.append(remap)
219
220 - def add_arg(self, name, default=None, value=None):
221 """
222 Add 'arg' to existing context. Args are only valid for their immediate context.
223 """
224 if name in self.arg_names:
225 raise LoadException("arg '%s' has already been declared"%name)
226 self.arg_names.append(name)
227
228 resolve_dict = self.resolve_dict if self.include_resolve_dict is None else self.include_resolve_dict
229
230 if not 'arg' in resolve_dict:
231 resolve_dict['arg'] = {}
232 arg_dict = resolve_dict['arg']
233
234
235
236 if value is not None:
237
238
239 if name in arg_dict:
240 raise LoadException("cannot override arg '%s', which has already been set"%name)
241 arg_dict[name] = value
242 elif default is not None:
243
244 if name not in arg_dict:
245 arg_dict[name] = default
246 else:
247
248
249 pass
250
251 - def remap_args(self):
252 """
253 @return: copy of the current remap arguments
254 @rtype: [(str, str)]
255 """
256 if self.parent:
257 args = []
258
259 for pr in self.parent.remap_args():
260 if not [r for r in self._remap_args if r[0] == pr[0]]:
261 args.append(pr)
262 args.extend(self._remap_args)
263 return args
264 return self._remap_args[:]
265
266 - def include_child(self, ns, filename):
267 """
268 Create child namespace based on include inheritance rules
269 @param ns: sub-namespace of child context, or None if the
270 child context shares the same namespace
271 @type ns: str
272 @param filename: name of include file
273 @type filename: str
274 @return: A child xml context that inherits from this context
275 @rtype: L{LoaderContext}jj
276 """
277 ctx = self.child(ns)
278
279 ctx.arg_names = []
280 ctx.filename = filename
281
282 ctx.include_resolve_dict = {}
283
284 return ctx
285
286 - def child(self, ns):
287 """
288 @param ns: sub-namespace of child context, or None if the
289 child context shares the same namespace
290 @type ns: str
291 @return: A child xml context that inherits from this context
292 @rtype: L{LoaderContext}
293 """
294 if ns:
295 if ns[0] == '/':
296 child_ns = ns
297 elif ns == PRIV_NAME:
298
299 child_ns = PRIV_NAME
300 else:
301 child_ns = ns_join(self.ns, ns)
302 else:
303 child_ns = self.ns
304 return LoaderContext(child_ns, self.filename, parent=self,
305 params=self.params, env_args=self.env_args[:],
306 resolve_dict=deepcopy(self.resolve_dict),
307 arg_names=self.arg_names[:], include_resolve_dict=self.include_resolve_dict)
308
309
310
311
312
313
315 """
316 Lower-level library for loading ROS launch model. It provides an
317 abstraction between the representation (e.g. XML) and the
318 validation of the property values.
319 """
320
321 - def add_param(self, ros_config, param_name, param_value, verbose=True):
322 """
323 Add L{Param} instances to launch config. Dictionary values are
324 unrolled into individual parameters.
325
326 @param ros_config: launch configuration
327 @type ros_config: L{ROSLaunchConfig}
328 @param param_name: name of parameter namespace to load values
329 into. If param_name is '/', param_value must be a dictionary
330 @type param_name: str
331 @param param_value: value to assign to param_name. If
332 param_value is a dictionary, it's values will be unrolled
333 into individual parameters.
334 @type param_value: str
335 @raise ValueError: if parameters cannot be processed into valid Params
336 """
337
338
339 if not param_name:
340 raise ValueError("no parameter name specified")
341
342 if param_name == '/' and type(param_value) != dict:
343 raise ValueError("Cannot load non-dictionary types into global namespace '/'")
344
345 if type(param_value) == dict:
346
347 for k, v in param_value.iteritems():
348 self.add_param(ros_config, ns_join(param_name, k), v, verbose=verbose)
349 else:
350 ros_config.add_param(Param(param_name, param_value), verbose=verbose)
351
352 - def load_rosparam(self, context, ros_config, cmd, param, file_, text, verbose=True):
353 """
354 Load rosparam setting
355
356 @param context: Loader context
357 @type context: L{LoaderContext}
358 @param ros_config: launch configuration
359 @type ros_config: L{ROSLaunchConfig}
360 @param cmd: 'load', 'dump', or 'delete'
361 @type cmd: str
362 @param file_: filename for rosparam to use or None
363 @type file_: str
364 @param text: text for rosparam to load. Ignored if file_ is set.
365 @type text: str
366 @raise ValueError: if parameters cannot be processed into valid rosparam setting
367 """
368 if not cmd in ('load', 'dump', 'delete'):
369 raise ValueError("command must be 'load', 'dump', or 'delete'")
370 if file_ is not None:
371 if cmd == 'load' and not os.path.isfile(file_):
372 raise ValueError("file does not exist [%s]"%file_)
373 if cmd == 'delete':
374 raise ValueError("'file' attribute is invalid with 'delete' command.")
375
376 full_param = ns_join(context.ns, param) if param else context.ns
377
378 if cmd == 'dump':
379 ros_config.add_executable(RosbinExecutable('rosparam', (cmd, file_, full_param), PHASE_SETUP))
380 elif cmd == 'delete':
381 ros_config.add_executable(RosbinExecutable('rosparam', (cmd, full_param), PHASE_SETUP))
382 elif cmd == 'load':
383
384 if file_:
385 with open(file_, 'r') as f:
386 text = f.read()
387
388
389
390 global yaml
391 if yaml is None:
392 import yaml
393
394 global rosparam
395 if rosparam is None:
396 import rosparam
397 try:
398 data = yaml.load(text)
399
400
401
402 if data is None:
403 data = {}
404 except yaml.MarkedYAMLError, e:
405 if not file_:
406 raise ValueError("Error within YAML block:\n\t%s\n\nYAML is:\n%s"%(str(e), text))
407 else:
408 raise ValueError("file %s contains invalid YAML:\n%s"%(file_, str(e)))
409 except Exception, e:
410 if not file_:
411 raise ValueError("invalid YAML: %s\n\nYAML is:\n%s"%(str(e), text))
412 else:
413 raise ValueError("file %s contains invalid YAML:\n%s"%(file_, str(e)))
414
415
416 if not param and type(data) != dict:
417 raise ValueError("'param' attribute must be set for non-dictionary values")
418
419 self.add_param(ros_config, full_param, data, verbose=verbose)
420
421 else:
422 raise ValueError("unknown command %s"%cmd)
423
424
425 - def load_env(self, context, ros_config, name, value):
426 """
427 Load environment variable setting
428
429 @param context: Loader context
430 @type context: L{LoaderContext}
431 @param ros_config: launch configuration
432 @type ros_config: L{ROSLaunchConfig}
433 @param name: environment variable name
434 @type name: str
435 @param value: environment variable value
436 @type value: str
437 """
438 if not name:
439 raise ValueError("'name' attribute must be non-empty")
440 context.env_args.append((name, value))
441
442
443 - def param_value(self, verbose, name, ptype, value, textfile, binfile, command):
444 """
445 Parse text representation of param spec into Python value
446 @param name: param name, for error message use only
447 @type name: str
448 @param verbose: print verbose output
449 @type verbose: bool
450 @param textfile: name of text file to load from, or None
451 @type textfile: str
452 @param binfile: name of binary file to load from, or None
453 @type binfile: str
454 @param command: command to execute for parameter value, or None
455 @type command: str
456 @raise ValueError: if parameters are invalid
457 """
458 if value is not None:
459 return convert_value(value.strip(), ptype)
460 elif textfile is not None:
461 with open(textfile, 'r') as f:
462 return f.read()
463 elif binfile is not None:
464 import xmlrpclib
465 with open(binfile, 'rb') as f:
466 return xmlrpclib.Binary(f.read())
467 elif command is not None:
468 if type(command) == unicode:
469 command = command.encode('UTF-8')
470 if verbose:
471 print "... executing command param [%s]"%command
472 import subprocess, shlex
473 try:
474 p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
475 c_value = p.communicate()[0]
476 if p.returncode != 0:
477 raise ValueError("Cannot load command parameter [%s]: command [%s] returned with code [%s]"%(name, command, p.returncode))
478 except OSError, (errno, strerr):
479 if errno == 2:
480 raise ValueError("Cannot load command parameter [%s]: no such command [%s]"%(name, command))
481 raise
482 if c_value is None:
483 raise ValueError("parameter: unable to get output of command [%s]"%command)
484 return c_value
485 else:
486 raise ValueError("unable to determine parameter value")
487