Skip to content

Commit 0f83e8e

Browse files
committed
param handler now generates python parameter files too
1 parent 2a71b33 commit 0f83e8e

File tree

3 files changed

+169
-6
lines changed

3 files changed

+169
-6
lines changed

cmake/rosparam_handler-macros.cmake

+44-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11

22
macro(generate_ros_parameter_files)
3-
43
set(CFG_FILES "${ARGN}")
54
set(ROSPARAM_HANDLER_ROOT_DIR "${ROSPARAM_HANDLER_CMAKE_DIR}/..")
65
if (${PROJECT_NAME}_CATKIN_PACKAGE)
@@ -33,6 +32,8 @@ macro(generate_ros_parameter_files)
3332
get_filename_component(_cfgonly ${_cfg} NAME_WE)
3433
set(_output_cfg ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_SHARE_DESTINATION}/cfg/${_cfgonly}.cfg)
3534
set(_output_cpp ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_INCLUDE_DESTINATION}/${_cfgonly}Parameters.h)
35+
set(_output_py ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}/param/${_cfgonly}Parameters.py)
36+
3637

3738
# Create command
3839
assert(CATKIN_ENV)
@@ -42,17 +43,18 @@ macro(generate_ros_parameter_files)
4243
${ROSPARAM_HANDLER_ROOT_DIR}
4344
${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_SHARE_DESTINATION}
4445
${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_INCLUDE_DESTINATION}
46+
${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}
4547
)
4648

4749
add_custom_command(OUTPUT
48-
${_output_cpp} ${_output_cfg}
50+
${_output_cpp} ${_output_cfg} ${_output_py}
4951
COMMAND ${_cmd}
5052
DEPENDS ${_input} ${genparam_build_files}
5153
COMMENT "Generating parameter files from ${_cfgonly}"
5254
)
5355

5456
list(APPEND ${PROJECT_NAME}_LOCAL_CFG_FILES "${_output_cfg}")
55-
list(APPEND ${PROJECT_NAME}_params_generated ${_output_cpp} ${_output_cfg})
57+
list(APPEND ${PROJECT_NAME}_params_generated ${_output_cpp} ${_output_cfg} ${_output_py})
5658

5759
install(
5860
FILES ${_output_cpp}
@@ -78,11 +80,13 @@ macro(generate_ros_parameter_files)
7880
list(APPEND ${PROJECT_NAME}_INCLUDE_DIRS ${CATKIN_DEVEL_PREFIX}/${CATKIN_GLOBAL_INCLUDE_DESTINATION})
7981
# ensure that the folder exists
8082
file(MAKE_DIRECTORY ${CATKIN_DEVEL_PREFIX}/${CATKIN_GLOBAL_INCLUDE_DESTINATION})
81-
8283
#Require C++11
8384
set_property(TARGET ${PROJECT_NAME}_genparam PROPERTY CXX_STANDARD 11)
8485
set_property(TARGET ${PROJECT_NAME}_genparam PROPERTY CXX_STANDARD_REQUIRED ON)
8586

87+
# install python files
88+
install_ros_python_parameter_files()
89+
8690
# generate dynamic reconfigure files
8791
if(dynamic_reconfigure_FOUND_CATKIN_PROJECT)
8892
if(${PROJECT_NAME}_LOCAL_CFG_FILES)
@@ -92,3 +96,39 @@ macro(generate_ros_parameter_files)
9296
message(WARNING "Dependency to dynamic_reconfigure is missing, or find_package(dynamic_reconfigure) was not called yet. Not building dynamic config files")
9397
endif()
9498
endmacro()
99+
100+
macro(install_ros_python_parameter_files)
101+
if(NOT install_ros_python_parameter_files_CALLED)
102+
set(install_ros_python_parameter_files_CALLED TRUE)
103+
104+
# mark that generate_dynamic_reconfigure_options() was called in order to detect wrong order of calling with catkin_python_setup()
105+
set(${PROJECT_NAME}_GENERATE_DYNAMIC_RECONFIGURE TRUE)
106+
107+
# check if catkin_python_setup() installs an __init__.py file for a package with the current project name
108+
# in order to skip the installation of a generated __init__.py file
109+
set(package_has_static_sources ${${PROJECT_NAME}_CATKIN_PYTHON_SETUP_HAS_PACKAGE_INIT})
110+
if(NOT EXISTS ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}/__init__.py)
111+
file(WRITE ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}/__init__.py "")
112+
endif()
113+
if(NOT package_has_static_sources)
114+
# install package __init__.py
115+
install(
116+
FILES ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}/__init__.py
117+
DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
118+
)
119+
endif()
120+
121+
# generate param module __init__.py
122+
if(NOT EXISTS ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}/param/__init__.py)
123+
file(WRITE ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}/param/__init__.py "")
124+
endif()
125+
126+
# compile python code before installing
127+
find_package(PythonInterp REQUIRED)
128+
install(CODE "execute_process(COMMAND \"${PYTHON_EXECUTABLE}\" -m compileall \"${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}/param\")")
129+
install(
130+
DIRECTORY ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}/param
131+
DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
132+
)
133+
endif()
134+
endmacro()

src/rosparam_handler/parameter_generator_catkin.py

+31-2
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,13 @@ def __init__(self, parent=None, group=""):
5858
self.group = "gen"
5959
self.group_variable = filter(str.isalnum, self.group)
6060

61-
if len(sys.argv) != 4:
61+
if len(sys.argv) != 5:
6262
eprint("ParameterGenerator: Unexpected amount of args, did you try to call this directly? You shouldn't do this!")
6363

6464
self.dynconfpath = sys.argv[1]
6565
self.share_dir = sys.argv[2]
6666
self.cpp_gen_dir = sys.argv[3]
67+
self.py_gen_dir = sys.argv[4]
6768

6869
self.pkgname = None
6970
self.nodename = None
@@ -93,7 +94,7 @@ def add_enum(self, name, description, entry_strings, default=None):
9394

9495
entry_strings = [str(e) for e in entry_strings] # Make sure we only get strings
9596
if default is None:
96-
default = entry_strings[0]
97+
default = 0
9798
else:
9899
default = entry_strings.index(default)
99100
self.add(name=name, paramtype="int", description=description, edit_method=name, default=default,
@@ -330,6 +331,7 @@ def generate(self, pkgname, nodename, classname):
330331

331332
self._generatecfg()
332333
self._generatecpp()
334+
self._generatepy()
333335

334336
return 0
335337

@@ -459,6 +461,33 @@ def _generatecpp(self):
459461
with open(header_file, 'w') as f:
460462
f.write(content)
461463

464+
def _generatepy(self):
465+
"""
466+
Generate Python parameter file
467+
:param self:
468+
:return:
469+
"""
470+
params = self._get_parameters()
471+
paramDescription = str(params)
472+
473+
# Read in template file
474+
templatefile = os.path.join(self.dynconfpath, "templates", "Parameters.py.template")
475+
with open(templatefile, 'r') as f:
476+
template = f.read()
477+
478+
content = Template(template).substitute(pkgname=self.pkgname, ClassName=self.classname,
479+
paramDescription=paramDescription)
480+
481+
py_file = os.path.join(self.py_gen_dir, "param", self.classname + "Parameters.py")
482+
try:
483+
if not os.path.exists(os.path.dirname(py_file)):
484+
os.makedirs(os.path.dirname(py_file))
485+
except OSError:
486+
# Stupid error, sometimes the directory exists anyway
487+
pass
488+
with open(py_file, 'w') as f:
489+
f.write(content)
490+
462491
def _get_parameters(self):
463492
"""
464493
Returns parameter of this and all childs

templates/Parameters.py.template

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# *********************************************************
2+
#
3+
# File autogenerated for the ${pkgname} package
4+
# by the rosparam_handler package.
5+
# Please do not edit.
6+
#
7+
# *********************************************************
8+
import rospy
9+
10+
param_description = $paramDescription
11+
12+
defaults = {}
13+
14+
for param in param_description:
15+
defaults[param['name']] = param['default']
16+
17+
18+
class ${ClassName}Parameters(dict):
19+
def __init__(self):
20+
super(self.__class__, self).__init__(defaults)
21+
self.from_param_server()
22+
23+
__getattr__ = dict.__getitem__
24+
__setattr__ = dict.__setitem__
25+
26+
def from_param_server(self):
27+
"""
28+
Reads and initializes parameters with values from parameter server.
29+
Called automatically at initialization.
30+
"""
31+
for k, v in self.iteritems():
32+
config = next((item for item in param_description if item["name"] == k), None)
33+
if config['constant']:
34+
self.test_const_param(k)
35+
continue
36+
37+
self[k] = self.get_param(k, config)
38+
39+
def from_config(self, config):
40+
"""
41+
Reads parameter from a dynamic_reconfigure config file.
42+
Should be called in the callback of dynamic_reconfigure.
43+
:param config: config object from dynamic reconfigure
44+
"""
45+
for k, v in config.iteritems():
46+
# handle reserved name groups
47+
if k == "groups":
48+
continue
49+
if not k in self:
50+
raise TypeError("Element {} of config is not part of parameters.".format(k))
51+
self[k] = v
52+
53+
@staticmethod
54+
def test_const_param(param_name):
55+
if rospy.has_param("~" + param_name):
56+
rospy.logwarn(
57+
"Parameter {} was set on the parameter server even though it was defined to be constant.".format(
58+
param_name))
59+
60+
@staticmethod
61+
def get_param(param_name, config):
62+
full_name = "/" + param_name if config['global_scope'] else "~" + param_name
63+
val = rospy.get_param(full_name, config['default'])
64+
# test whether type is correct
65+
try:
66+
param_type = config['type']
67+
if config['is_vector']:
68+
val = list(val)
69+
elif config['is_map']:
70+
val = dict(val)
71+
elif param_type == 'std::string':
72+
val = str(val)
73+
elif param_type == 'int':
74+
val = int(val)
75+
elif param_type == 'bool':
76+
val = bool(val)
77+
elif param_type == 'float' or param_type == 'double':
78+
val = float(val)
79+
except ValueError:
80+
rospy.logerr(
81+
"Parameter {} is set, but has a different type. Using default value instead.".format(param_name))
82+
val = config['default']
83+
# test bounds
84+
if config['min'] is not None and config['min'] > val:
85+
rospy.logerr(
86+
"Value of {} for {} is smaller than minimal allowed value. "
87+
"Correcting value to min={}".format(val, param_name, config['min']))
88+
val = config['min']
89+
if config['max'] is not None and config['max'] < val:
90+
rospy.logerr(
91+
"Value of {} for {} is greater than maximal allowed value. "
92+
"Correcting value to max={}".format(val,param_name, config['max']))
93+
val = config['max']
94+
return val

0 commit comments

Comments
 (0)