37 | 37 |
# classes to allow runtime reconfiguration. Documentation of a node's
|
38 | 38 |
# parameters is a handy byproduct.
|
39 | 39 |
|
40 | |
## @todo
|
|
40 |
# @todo
|
41 | 41 |
# Need to check types of min max and default
|
42 | 42 |
# Need to put sane error on exceptions
|
43 | 43 |
|
44 | 44 |
from __future__ import print_function
|
45 | 45 |
|
|
46 |
import inspect
|
|
47 |
import os
|
|
48 |
import re
|
46 | 49 |
from string import Template
|
47 | |
import os
|
48 | |
import inspect
|
49 | |
import string
|
50 | 50 |
import sys
|
51 | |
import re
|
|
51 |
import textwrap
|
52 | 52 |
|
53 | 53 |
# LINEDEBUG="#line"
|
54 | 54 |
LINEDEBUG = "//#line"
|
|
75 | 75 |
raise Exception("The name of field \'%s\' does not follow the ROS naming conventions, see http://wiki.ros.org/ROS/Patterns/Conventions" % name)
|
76 | 76 |
|
77 | 77 |
|
78 | |
class ParameterGenerator:
|
|
78 |
class ParameterGenerator(object):
|
79 | 79 |
minval = {
|
80 | 80 |
'int': -0x80000000, # 'INT_MIN',
|
81 | 81 |
'double': '-std::numeric_limits<double>::infinity()',
|
|
97 | 97 |
'bool': False,
|
98 | 98 |
}
|
99 | 99 |
|
100 | |
class Group:
|
|
100 |
class Group(object):
|
101 | 101 |
instances = {}
|
102 | 102 |
|
103 | 103 |
def __init__(self, gen, name, type, state, id, parent):
|
|
138 | 138 |
'srcfile': inspect.getsourcefile(inspect.currentframe().f_back.f_code),
|
139 | 139 |
'edit_method': edit_method,
|
140 | 140 |
}
|
141 | |
if type == str_t and (max is not None or min is not None):
|
142 | |
raise Exception("Max or min specified for %s, which is of string type" % name)
|
|
141 |
if (paramtype == str_t or paramtype == bool_t) and (max is not None or min is not None):
|
|
142 |
raise Exception(
|
|
143 |
"Max or min specified for %s, which is of '%s' type" % (name, paramtype))
|
143 | 144 |
check_name(name)
|
144 | 145 |
self.gen.fill_type(newparam)
|
145 | 146 |
self.gen.check_type_fill_default(newparam, 'default', self.gen.defval[paramtype])
|
|
245 | 246 |
id = 1
|
246 | 247 |
self.constants = []
|
247 | 248 |
if len(sys.argv) < 5:
|
248 | |
msg = """
|
249 | |
ahhhh! Unexpected command line syntax!
|
250 | |
|
251 | |
Are you trying to call a dynamic_reconfigure configuration generation script
|
252 | |
directly? When you are using dynamic_reconfigure with python, you don't ever
|
253 | |
need to invoke the configuration generator script yourself; it loads
|
254 | |
automatically. If you are using dynamic_reconfigure from C++, you need to
|
255 | |
add a call to generate_dynamic_reconfigure_options() in your CMakeLists.txt
|
256 | |
|
257 | |
For an example, see http://wiki.ros.org/dynamic_reconfigure/Tutorials
|
258 | |
|
259 | |
Have a nice day
|
260 | |
"""
|
261 | |
print(msg)
|
262 | |
sys.exit(1)
|
|
249 |
raise SystemExit(textwrap.dedent("""\
|
|
250 |
ahhhh! Unexpected command line syntax!
|
|
251 |
|
|
252 |
Are you trying to call a dynamic_reconfigure configuration generation script
|
|
253 |
directly? When you are using dynamic_reconfigure with python, you don't ever
|
|
254 |
need to invoke the configuration generator script yourself; it loads
|
|
255 |
automatically. If you are using dynamic_reconfigure from C++, you need to
|
|
256 |
add a call to generate_dynamic_reconfigure_options() in your CMakeLists.txt
|
|
257 |
|
|
258 |
For an example, see http://wiki.ros.org/dynamic_reconfigure/Tutorials
|
|
259 |
|
|
260 |
Have a nice day
|
|
261 |
"""))
|
263 | 262 |
self.dynconfpath = sys.argv[1] # FIXME this is awful
|
264 | 263 |
self.binary_dir = sys.argv[2]
|
265 | 264 |
self.cpp_gen_dir = sys.argv[3]
|
|
316 | 315 |
# causes imports of dynamic_reconfigure.msg to fail from at
|
317 | 316 |
# least some .cfg files. (Not sure why)
|
318 | 317 |
return
|
319 | |
except:
|
|
318 |
except Exception:
|
320 | 319 |
pass
|
321 | 320 |
try:
|
322 | 321 |
self.pkgname = pkgname
|
|
343 | 342 |
|
344 | 343 |
def generatewikidoc(self):
|
345 | 344 |
self.mkdirabs(os.path.join(self.binary_dir, "docs"))
|
346 | |
f = open(os.path.join(self.binary_dir, "docs", self.msgname + ".wikidoc"), 'w')
|
347 | |
print(
|
348 | |
"""# Autogenerated param section. Do not hand edit.
|
349 | |
param {
|
350 | |
group.0 {
|
351 | |
name=Dynamically Reconfigurable Parameters
|
352 | |
desc=See the [[dynamic_reconfigure]] package for details on dynamically reconfigurable parameters.""", file=f)
|
353 | |
i = -1
|
354 | |
for param in self.group.get_parameters():
|
355 | |
i = i + 1
|
356 | |
range = ""
|
357 | |
try:
|
358 | |
enum = eval(param['edit_method'])['enum']
|
359 | |
range = ", ".join(Template("$name ($value): $description").substitute(const) for const in enum)
|
360 | |
range = "Possible values are: " + range
|
361 | |
except:
|
362 | |
if param['type'] == int_t or param['type'] == double_t:
|
363 | |
range = Template("Range: $min to $max").substitute(param)
|
364 | |
print(Template(
|
365 | |
"""$i.name= ~$name
|
366 | |
$i.default= $default
|
367 | |
$i.type= $type
|
368 | |
$i.desc=$description $range"""
|
369 | |
).substitute(param, range=range, i=i), file=f)
|
370 | |
print("}\n}\n# End of autogenerated section. You may edit below.", file=f)
|
371 | |
f.close()
|
372 | |
|
373 | |
def generateusage(self):
|
374 | |
self.mkdirabs("docs")
|
375 | |
f = open(os.path.join(self.binary_dir, "docs", self.msgname + "-usage.dox"), 'w')
|
376 | |
# print("/**", file=f)
|
377 | |
print("\\subsubsection usage Usage", file=f)
|
378 | |
print('\\verbatim', file=f)
|
379 | |
print(Template('<node name="$nodename" pkg="$pkgname" type="$nodename">').substitute(
|
380 | |
pkgname=self.pkgname, nodename=self.nodename), file=f)
|
381 | |
for param in self.group.get_parameters():
|
382 | |
print(Template(' <param name="$name" type="$type" value="$default" />').substitute(param), file=f)
|
383 | |
print('</node>', file=f)
|
384 | |
print('\\endverbatim', file=f)
|
385 | |
print('', file=f)
|
386 | |
# print("*/", file=f)
|
387 | |
f.close()
|
|
345 |
with open(os.path.join(self.binary_dir, "docs", self.msgname + ".wikidoc"), 'w') as f:
|
|
346 |
print(textwrap.dedent("""\
|
|
347 |
# Autogenerated param section. Do not hand edit.
|
|
348 |
param {
|
|
349 |
group.0 {
|
|
350 |
name=Dynamically Reconfigurable Parameters
|
|
351 |
desc=See the [[dynamic_reconfigure]] package for details on dynamically reconfigurable parameters."""
|
|
352 |
), file=f)
|
|
353 |
i = -1
|
|
354 |
for param in self.group.get_parameters():
|
|
355 |
i = i + 1
|
|
356 |
range = ""
|
|
357 |
try:
|
|
358 |
enum = eval(param['edit_method'])['enum']
|
|
359 |
range = ", ".join(Template("$name ($value): $description").substitute(const) for const in enum)
|
|
360 |
range = "Possible values are: " + range
|
|
361 |
except Exception:
|
|
362 |
if param['type'] == int_t or param['type'] == double_t:
|
|
363 |
range = Template("Range: $min to $max").substitute(param)
|
|
364 |
print(Template(textwrap.dedent("""\
|
|
365 |
$i.name= ~$name
|
|
366 |
$i.default= $default
|
|
367 |
$i.type= $type
|
|
368 |
$i.desc=$description $range"""
|
|
369 |
)).substitute(param, range=range, i=i), file=f)
|
|
370 |
print("}\n}\n# End of autogenerated section. You may edit below.", file=f)
|
388 | 371 |
|
389 | 372 |
def generatedoc(self):
|
390 | 373 |
self.mkdirabs("docs")
|
391 | 374 |
dir_path = os.path.join(self.binary_dir, "docs")
|
392 | 375 |
self.mkdirabs(dir_path)
|
393 | |
f = open(os.path.join(dir_path, self.msgname + ".dox"), 'w')
|
394 | |
# print("/**", file=f)
|
395 | |
print("\\subsubsection parameters ROS parameters", file=f)
|
396 | |
print("", file=f)
|
397 | |
print("Reads and maintains the following parameters on the ROS server", file=f)
|
398 | |
print("", file=f)
|
399 | |
for param in self.group.get_parameters():
|
400 | |
print(Template("- \\b \"~$name\" : \\b [$type] $description min: $min, default: $default, max: $max").substitute(param), file=f)
|
401 | |
print("", file=f)
|
402 | |
# print("*/", file=f)
|
403 | |
f.close()
|
|
376 |
with open(os.path.join(dir_path, self.msgname + ".dox"), 'w') as f:
|
|
377 |
# print("/**", file=f)
|
|
378 |
print("\\subsubsection parameters ROS parameters", file=f)
|
|
379 |
print("", file=f)
|
|
380 |
print("Reads and maintains the following parameters on the ROS server", file=f)
|
|
381 |
print("", file=f)
|
|
382 |
for param in self.group.get_parameters():
|
|
383 |
print(Template("- \\b \"~$name\" : \\b [$type] $description min: $min, default: $default, max: $max").substitute(param), file=f)
|
|
384 |
print("", file=f)
|
|
385 |
# print("*/", file=f)
|
404 | 386 |
|
405 | 387 |
def generateusage(self):
|
406 | 388 |
self.mkdirabs("docs")
|
407 | |
f = open(os.path.join(self.binary_dir, "docs", self.msgname + "-usage.dox"), 'w')
|
408 | |
# print("/**", file=f)
|
409 | |
print("\\subsubsection usage Usage", file=f)
|
410 | |
print('\\verbatim', file=f)
|
411 | |
print(Template('<node name="$nodename" pkg="$pkgname" type="$nodename">').substitute(
|
412 | |
pkgname=self.pkgname, nodename=self.nodename), file=f)
|
413 | |
for param in self.group.get_parameters():
|
414 | |
print(Template(' <param name="$name" type="$type" value="$default" />').substitute(param), file=f)
|
415 | |
print('</node>', file=f)
|
416 | |
print('\\endverbatim', file=f)
|
417 | |
print("", file=f)
|
418 | |
# print("*/", file=f)
|
419 | |
f.close()
|
|
389 |
with open(os.path.join(self.binary_dir, "docs", self.msgname + "-usage.dox"), 'w') as f:
|
|
390 |
# print("/**", file=f)
|
|
391 |
print("\\subsubsection usage Usage", file=f)
|
|
392 |
print('\\verbatim', file=f)
|
|
393 |
print(Template('<node name="$nodename" pkg="$pkgname" type="$nodename">').substitute(
|
|
394 |
pkgname=self.pkgname, nodename=self.nodename), file=f)
|
|
395 |
for param in self.group.get_parameters():
|
|
396 |
print(Template(' <param name="$name" type="$type" value="$default" />').substitute(param), file=f)
|
|
397 |
print('</node>', file=f)
|
|
398 |
print('\\endverbatim', file=f)
|
|
399 |
print("", file=f)
|
|
400 |
# print("*/", file=f)
|
420 | 401 |
|
421 | 402 |
def crepr(self, param, val):
|
422 | 403 |
type = param["type"]
|
|
469 | 450 |
templatelines = []
|
470 | 451 |
templatefilesafe = templatefile.replace('\\', '\\\\') # line directive does backslash expansion.
|
471 | 452 |
curline = 1
|
472 | |
f = open(templatefile)
|
473 | |
for line in f:
|
474 | |
curline = curline + 1
|
475 | |
templatelines.append(Template(line).safe_substitute(linenum=curline, filename=templatefilesafe))
|
476 | |
f.close()
|
|
453 |
with open(templatefile) as f:
|
|
454 |
for line in f:
|
|
455 |
curline = curline + 1
|
|
456 |
templatelines.append(Template(line).safe_substitute(linenum=curline, filename=templatefilesafe))
|
|
457 |
|
477 | 458 |
template = ''.join(templatelines)
|
478 | 459 |
|
479 | 460 |
# Write the configuration manipulator.
|
480 | 461 |
self.mkdirabs(self.cpp_gen_dir)
|
481 | |
f = open(os.path.join(self.cpp_gen_dir, self.name + "Config.h"), 'w')
|
482 | 462 |
paramdescr = []
|
483 | 463 |
groups = []
|
484 | 464 |
members = []
|
|
523 | 503 |
members = '\n'.join(members)
|
524 | 504 |
constants = '\n'.join(constants)
|
525 | 505 |
groups = '\n'.join(groups)
|
526 | |
f.write(Template(template).substitute(
|
527 | |
uname=self.name.upper(),
|
528 | |
configname=self.name, pkgname=self.pkgname, paramdescr=paramdescr,
|
529 | |
members=members, groups=groups, doline=LINEDEBUG, constants=constants))
|
530 | |
f.close()
|
|
506 |
with open(os.path.join(self.cpp_gen_dir, self.name + "Config.h"), 'w') as f:
|
|
507 |
f.write(Template(template).substitute(
|
|
508 |
uname=self.name.upper(),
|
|
509 |
configname=self.name, pkgname=self.pkgname, paramdescr=paramdescr,
|
|
510 |
members=members, groups=groups, doline=LINEDEBUG, constants=constants))
|
531 | 511 |
print("Wrote header file in " + os.path.join(self.cpp_gen_dir, self.name + "Config.h"))
|
532 | 512 |
|
533 | 513 |
# def deleteoneobsolete(self, file):
|
|
601 | 581 |
def generatepy(self):
|
602 | 582 |
# Read the configuration manipulator template and insert line numbers and file name into template.
|
603 | 583 |
templatefile = os.path.join(self.dynconfpath, "templates", "ConfigType.py.template")
|
604 | |
f = open(templatefile)
|
605 | |
template = f.read()
|
606 | |
f.close()
|
|
584 |
with open(templatefile) as f:
|
|
585 |
template = f.read()
|
607 | 586 |
|
608 | 587 |
# Write the configuration manipulator.
|
609 | 588 |
self.mkdirabs(os.path.join(self.py_gen_dir, "cfg"))
|
610 | |
f = open(os.path.join(self.py_gen_dir, "cfg", self.name + "Config.py"), 'w')
|
611 | |
pycfgdata = self.replace_infinity(self.group.to_dict())
|
612 | |
f.write(Template(template).substitute(
|
613 | |
name=self.name,
|
614 | |
pkgname=self.pkgname, pycfgdata=pycfgdata))
|
615 | |
for const in self.constants:
|
616 | |
f.write(Template("${configname}_${name} = $v\n").substitute(
|
617 | |
const, v=repr(const['value']),
|
618 | |
configname=self.name))
|
619 | |
f.close()
|
620 | |
|
621 | |
f = open(os.path.join(self.py_gen_dir, "cfg", "__init__.py"), 'a')
|
622 | |
f.close()
|
|
589 |
with open(os.path.join(self.py_gen_dir, "cfg", self.name + "Config.py"), 'w') as f:
|
|
590 |
pycfgdata = self.replace_infinity(self.group.to_dict())
|
|
591 |
f.write(Template(template).substitute(
|
|
592 |
name=self.name,
|
|
593 |
pkgname=self.pkgname, pycfgdata=pycfgdata))
|
|
594 |
for const in self.constants:
|
|
595 |
f.write(Template("${configname}_${name} = $v\n").substitute(
|
|
596 |
const, v=repr(const['value']),
|
|
597 |
configname=self.name))
|
|
598 |
|
|
599 |
with open(os.path.join(self.py_gen_dir, "cfg", "__init__.py"), 'a'):
|
|
600 |
pass
|