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