29 from __future__
import print_function
30 from string
import Template
37 print(
"************************************************", file=sys.stderr, **kwargs)
38 print(
"Error when setting up parameter '{}':".format(args[0]), file=sys.stderr, **kwargs)
39 print(*args[1:], file=sys.stderr, **kwargs)
40 print(
"************************************************", file=sys.stderr, **kwargs)
45 """Automatic config file and header generator""" 48 """Constructor for ParamGenerator""" 59 if len(sys.argv) != 5:
61 "ParameterGenerator: Unexpected amount of args, did you try to call this directly? You shouldn't do this!")
74 Add a new group in the dynamic reconfigure selection 75 :param name: name of the new group 76 :return: a new group object that you can add parameters to 79 eprint(
"You have added a group with an empty group name. This is not supported!")
81 self.childs.append(child)
84 def add_enum(self, name, description, entry_strings, default=None):
86 Add an enum to dynamic reconfigure 87 :param name: Name of enum parameter 88 :param description: Informative documentation string 89 :param entry_strings: Enum entries, must be strings! (will be numbered with increasing value) 90 :param default: Default value 94 entry_strings = [str(e)
for e
in entry_strings]
98 default = entry_strings.index(default)
99 self.
add(name=name, paramtype=
"int", description=description, edit_method=name, default=default,
101 for e
in entry_strings:
102 self.
add(name=name +
"_" + e, paramtype=
"int", description=
"Constant for enum {}".format(name),
103 default=entry_strings.index(e), constant=
True)
104 self.enums.append({
'name': name,
'description': description,
'values': entry_strings})
106 def add(self, name, paramtype, description, level=0, edit_method='""', default=None, min=None, max=None,
107 configurable=
False, global_scope=
False, constant=
False):
109 Add parameters to your parameter struct. Call this method from your .params file! 111 - If no default value is given, you need to specify one in your launch file 112 - Global parameters, vectors, maps and constant params can not be configurable 115 :param name: The Name of you new parameter 116 :param paramtype: The C++ type of this parameter. Can be any of ['std::string', 'int', 'bool', 'float', 117 'double'] or std::vector<...> or std::map<std::string, ...> 118 :param description: Choose an informative documentation string for this parameter. 119 :param level: (optional) Passed to dynamic_reconfigure 120 :param edit_method: (optional) Passed to dynamic_reconfigure 121 :param default: (optional) default value 122 :param min: (optional) 123 :param max: (optional) 124 :param configurable: (optional) Should this parameter be dynamic configurable 125 :param global_scope: (optional) If true, parameter is searched in global ('/') namespace instead of private ( 127 :param constant: (optional) If this is true, the parameter will not be fetched from param server, 128 but the default value is kept. 139 'edit_method': edit_method,
140 'description': description,
145 'configurable': configurable,
146 'constant': constant,
147 'global_scope': global_scope,
150 self.parameters.append(newparam)
154 Will test some logical constraints as well as correct types. 155 Throws Exception in case of error. 157 :param param: Dictionary of one param 161 in_type = param[
'type'].strip()
162 if param[
'max']
is not None or param[
'min']
is not None:
163 if in_type
in [
"std::string",
"bool"]:
164 eprint(param[
'name'],
"Max and min can not be specified for variable of type %s" % in_type)
166 if in_type.startswith(
'std::vector'):
167 param[
'is_vector'] =
True 168 if in_type.startswith(
'std::map'):
169 param[
'is_map'] =
True 171 if (param[
'is_vector']):
172 if (param[
'max']
is not None or param[
'min']
is not None):
173 ptype = in_type[12:-1].strip()
174 if ptype ==
"std::string":
175 eprint(param[
'name'],
"Max and min can not be specified for variable of type %s" % in_type)
177 if (param[
'is_map']):
178 if (param[
'max']
is not None or param[
'min']
is not None):
179 ptype = in_type[9:-1].split(
',')
182 "Wrong syntax used for setting up std::map<... , ...>: You provided '%s' with " 183 "parameter %s" % in_type)
184 ptype = ptype[1].strip()
185 if ptype ==
"std::string":
186 eprint(param[
'name'],
"Max and min can not be specified for variable of type %s" % in_type)
188 pattern =
r'^[a-zA-Z][a-zA-Z0-9_]*$' 189 if not re.match(pattern, param[
'name']):
190 eprint(param[
'name'],
"The name of field does not follow the ROS naming conventions, " 191 "see http://wiki.ros.org/ROS/Patterns/Conventions")
192 if param[
'configurable']
and (
193 param[
'global_scope']
or param[
'is_vector']
or param[
'is_map']
or param[
'constant']):
195 "Global Parameters, vectors, maps and constant params can not be declared configurable! ")
196 if param[
'global_scope']
and param[
'default']
is not None:
197 eprint(param[
'name'],
"Default values for global parameters should not be specified in node! ")
198 if param[
'constant']
and param[
'default']
is None:
199 eprint(param[
'name'],
"Constant parameters need a default value!")
200 if param[
'name']
in [p[
'name']
for p
in self.
parameters]:
201 eprint(param[
'name'],
"Parameter with the same name exists already")
202 if param[
'edit_method'] ==
'':
203 param[
'edit_method'] =
'""' 204 elif param[
'edit_method'] !=
'""':
205 param[
'configurable'] =
True 208 if param[
'is_vector']:
209 ptype = in_type[12:-1].strip()
211 param[
'type'] =
'std::vector<{}>'.format(ptype)
212 elif param[
'is_map']:
213 ptype = in_type[9:-1].split(
',')
215 eprint(param[
'name'],
"Wrong syntax used for setting up std::map<... , ...>: You provided '%s' with " 216 "parameter %s" % in_type)
217 ptype[0] = ptype[0].strip()
218 ptype[1] = ptype[1].strip()
219 if ptype[0] !=
"std::string":
220 eprint(param[
'name'],
"Can not setup map with %s as key type. Only std::map<std::string, " 221 "...> are allowed" % ptype[0])
224 param[
'type'] =
'std::map<{},{}>'.format(ptype[0], ptype[1])
228 param[
'pytype'] = self.
_pytype(in_type)
232 """Convert C++ type to python type""" 233 return {
'std::string':
"str",
'int':
"int",
'double':
"double",
'bool':
"bool"}[drtype]
238 Test whether parameter has one of the accepted C++ types 239 :param name: Parametername 240 :param drtype: Typestring 243 primitive_types = [
'std::string',
'int',
'bool',
'float',
'double']
244 if drtype
not in primitive_types:
245 raise TypeError(
"'%s' has type %s, but allowed are: %s" % (name, drtype, primitive_types))
250 Helper function to convert strings and booleans to correct C++ syntax 252 :return: C++ compatible representation 255 if param[
'type'] ==
'std::string':
256 value =
'"{}"'.format(param[field])
257 elif param[
'type'] ==
'bool':
258 value = str(param[field]).lower()
264 Helper function to convert strings and booleans to correct C++ syntax 266 :return: C++ compatible representation 269 if param[
'type'] ==
'std::string':
270 value =
'"{}"'.format(param[field])
271 elif param[
'type'] ==
'bool':
272 value = str(param[field]).capitalize()
278 Helper function to convert python list of strings and booleans to correct C++ syntax 280 :return: C++ compatible representation 282 values = param[field]
283 assert (isinstance(values, list))
286 if param[
'type'] ==
'std::vector<std::string>':
287 value =
'"{}"'.format(value)
288 elif param[
'type'] ==
'std::vector<bool>':
289 value = str(value).lower()
299 Helper function to convert python dict of strings and booleans to correct C++ syntax 301 :return: C++ compatible representation 303 values = param[field]
304 assert (isinstance(values, dict))
306 for key, value
in values.items():
307 if param[
'type'] ==
'std::map<std::string,std::string>':
308 pair =
'{{"{}","{}"}}'.format(key, value)
309 elif param[
'type'] ==
'std::map<std::string,bool>':
310 pair =
'{{"{}",{}}}'.format(key, str(value).lower())
312 pair =
'{{"{}",{}}}'.format(key, str(value))
319 Main working Function, call this at the end of your .params file! 321 :param pkgname: Name of the catkin package 322 :param nodename: Name of the Node that will hold these params 323 :param classname: This should match your file name, so that cmake will detect changes in config file. 331 eprint(
"You should not call generate on a group! Call it on the main parameter generator instead!")
337 Implementation level function. Can be overwritten by derived classes. 348 Generate .cfg file for dynamic reconfigure 352 templatefile = os.path.join(self.
dynconfpath,
"templates",
"ConfigType.h.template")
353 with open(templatefile,
'r') as f: 358 param_entries = "\n".join(param_entries)
359 template = Template(template).substitute(pkgname=self.
pkgname, nodename=self.
nodename,
360 classname=self.
classname, params=param_entries)
364 if not os.path.exists(os.path.dirname(cfg_file)):
365 os.makedirs(os.path.dirname(cfg_file))
369 with open(cfg_file,
'w')
as f:
371 os.chmod(cfg_file, 509)
375 Generate C++ Header file, holding the parameter struct. 381 templatefile = os.path.join(self.
dynconfpath,
"templates",
"Parameters.h.template")
382 with open(templatefile,
'r') as f: 386 string_representation = [] 389 non_default_params = [] 401 if param[
"global_scope"]:
402 namespace =
'globalNamespace' 404 namespace =
'privateNamespace' 405 full_name =
'{} + "{}"'.format(namespace, param[
"name"])
408 if param[
"default"]
is None:
410 non_default_params.append(Template(
' << "\t" << $namespace << "$name" << " ($type) ' 411 '\\n"\n').substitute(
412 namespace=namespace, name=name, type=param[
"type"]))
414 if param[
'is_vector']:
415 default =
', {}'.format(str(param[
'type']) +
"{" + self.
_get_cvaluelist(param,
"default") +
"}")
416 elif param[
'is_map']:
417 default =
', {}'.format(str(param[
'type']) +
"{" + self.
_get_cvaluedict(param,
"default") +
"}")
419 default =
', {}'.format(str(param[
'type']) +
"{" + self.
_get_cvalue(param,
"default") +
"}")
422 if param[
'constant']:
423 param_entries.append(Template(
' static constexpr auto ${name} = $default; /*!< ${description} ' 424 '*/').substitute(type=param[
'type'], name=name,
425 description=param[
'description'],
427 from_server.append(Template(
' rosparam_handler::testConstParam($paramname);').substitute(paramname=full_name))
429 param_entries.append(Template(
' ${type} ${name}; /*!< ${description} */').substitute(
430 type=param[
'type'], name=name, description=param[
'description']))
431 from_server.append(Template(
' success &= rosparam_handler::getParam($paramname, $name$default);').substitute(
432 paramname=full_name, name=name, default=default, description=param[
'description']))
434 Template(
' rosparam_handler::setParam(${paramname},${name});').substitute(paramname=full_name, name=name))
437 if param[
'configurable']:
438 from_config.append(Template(
' $name = config.$name;').substitute(name=name))
439 to_config.append(Template(
' config.$name = $name;').substitute(name=name))
442 if param[
'is_vector']:
443 ttype = param[
'type'][12:-1].strip()
444 elif param[
'is_map']:
445 ttype = param[
'type'][9:-1].strip()
447 ttype = param[
'type']
448 if param[
'min']
is not None:
449 test_limits.append(Template(
' rosparam_handler::testMin<$type>($paramname, $name, $min);').substitute(
450 paramname=full_name, name=name, min=param[
'min'], type=ttype))
451 if param[
'max']
is not None:
452 test_limits.append(Template(
' rosparam_handler::testMax<$type>($paramname, $name, $max);').substitute(
453 paramname=full_name, name=name, max=param[
'max'], type=ttype))
456 if param[
'is_vector']
or param[
'is_map']:
457 string_representation.append(Template(
' << "\t" << p.$namespace << "$name:" << rosparam_handler::to_string(p.$name) << ' 458 '"\\n"\n').substitute(namespace=namespace, name=name))
460 string_representation.append(Template(
' << "\t" << p.$namespace << "$name:" << p.$name << ' 461 '"\\n"\n').substitute(namespace=namespace, name=name))
463 param_entries =
"\n".join(param_entries)
464 string_representation =
"".join(string_representation)
465 non_default_params =
"".join(non_default_params)
466 from_server =
"\n".join(from_server)
467 to_server =
"\n".join(to_server)
468 from_config =
"\n".join(from_config)
469 to_config =
"\n".join(to_config)
470 test_limits =
"\n".join(test_limits)
472 content = Template(template).substitute(pkgname=self.
pkgname, ClassName=self.
classname,
473 parameters=param_entries, fromConfig=from_config,
474 fromParamServer=from_server,
475 string_representation=string_representation,
476 non_default_params=non_default_params, nodename=self.
nodename,
477 test_limits=test_limits, toParamServer=to_server,
482 if not os.path.exists(os.path.dirname(header_file)):
483 os.makedirs(os.path.dirname(header_file))
487 with open(header_file,
'w')
as f:
492 Generate Python parameter file 497 paramDescription = str(params)
500 templatefile = os.path.join(self.
dynconfpath,
"templates",
"Parameters.py.template")
501 with open(templatefile,
'r') as f: 504 content = Template(template).substitute(pkgname=self.pkgname, ClassName=self.classname, 505 paramDescription=paramDescription) 509 if not os.path.exists(os.path.dirname(py_file)):
510 os.makedirs(os.path.dirname(py_file))
514 with open(py_file,
'w')
as f:
516 init_file = os.path.join(self.
py_gen_dir,
"param",
"__init__.py")
517 with open(init_file,
'wa')
as f:
522 Generate .yaml file for roslaunch 528 content =
"### This file was generated using the rosparam_handler generate_yaml script.\n" 531 if not entry[
"constant"]:
533 content +=
"# Name:\t" + str(entry[
"name"]) +
"\n" 534 content +=
"# Desc:\t" + str(entry[
"description"]) +
"\n" 535 content +=
"# Type:\t" + str(entry[
"type"]) +
"\n" 536 if entry[
'min']
or entry[
'max']:
537 content +=
"# [min,max]:\t[" + str(entry[
"min"]) +
"/" + str(entry[
"max"]) +
"]" +
"\n" 538 if entry[
"global_scope"]:
539 content +=
"# Lives in global namespace!\n" 540 if entry[
"default"]
is not None:
541 content += str(entry[
"name"]) +
": " + str(entry[
"default"]) +
"\n" 543 content += str(entry[
"name"]) +
": \n" 545 yaml_file = os.path.join(os.getcwd(), self.
classname +
"Parameters.yaml")
547 with open(yaml_file,
'w')
as f:
552 Returns parameter of this and all childs 553 :return: list of all parameters 557 params.extend(child._get_parameters())
562 Generates the entries for the cfg-file 563 :return: list of param entries as string 566 dynamic_params = [p
for p
in self.
parameters if p[
"configurable"]]
569 param_entries.append(Template(
"$group_variable = $parent.add_group('$group')").substitute(
572 parent=self.parent.group_variable))
574 for enum
in self.
enums:
575 param_entries.append(Template(
"$name = gen.enum([").substitute(
579 for value
in enum[
'values']:
580 param_entries.append(
581 Template(
" gen.const(name='$name', type='$type', value=$value, descr='$descr'),")
582 .substitute(name=value, type=
"int", value=i, descr=
""))
584 param_entries.append(Template(
" ], '$description')").substitute(description=enum[
"description"]))
586 for param
in dynamic_params:
587 content_line = Template(
"$group_variable.add(name = '$name', paramtype = '$paramtype', level = $level, " 588 "description = '$description', edit_method=$edit_method").substitute(
591 paramtype=param[
'pytype'],
592 level=param[
'level'],
593 edit_method=param[
'edit_method'],
594 description=param[
'description'])
595 if param[
'default']
is not None:
596 content_line += Template(
", default=$default").substitute(default=self.
_get_pyvalue(param,
"default"))
597 if param[
'min']
is not None:
598 content_line += Template(
", min=$min").substitute(min=param[
'min'])
599 if param[
'max']
is not None:
600 content_line += Template(
", max=$max").substitute(max=param[
'max'])
602 param_entries.append(content_line)
605 param_entries.extend(child._generate_param_entries())
610 if isinstance(param, bool):
def _get_pyvalue(param, field)
def _get_cvalue(param, field)
def add(self, name, paramtype, description, level=0, edit_method='""', default=None, min=None, max=None, configurable=False, global_scope=False, constant=False)
def add_group(self, name)
def _test_primitive_type(name, drtype)
def _perform_checks(self, param)
def _get_cvaluedict(param, field)
def add_enum(self, name, description, entry_strings, default=None)
def generate(self, pkgname, nodename, classname)
def _get_cvaluelist(param, field)
def _get_parameters(self)
def _generate_param_entries(self)
def __init__(self, parent=None, group="")