Skip to content

Commit 443451e

Browse files
authored
Merge branch 'master' into async_hardware_components
2 parents ff7ef87 + dd84414 commit 443451e

16 files changed

+556
-115
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ repos:
6363

6464
# CPP hooks
6565
- repo: https://github.com/pre-commit/mirrors-clang-format
66-
rev: v19.1.3
66+
rev: v19.1.4
6767
hooks:
6868
- id: clang-format
6969
args: ['-fallback-style=none', '-i']
@@ -133,7 +133,7 @@ repos:
133133
exclude: CHANGELOG\.rst|\.(svg|pyc|drawio)$
134134

135135
- repo: https://github.com/python-jsonschema/check-jsonschema
136-
rev: 0.29.4
136+
rev: 0.30.0
137137
hooks:
138138
- id: check-github-workflows
139139
args: ["--verbose"]

controller_manager/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ if(BUILD_TESTING)
210210
install(FILES test/test_controller_spawner_with_type.yaml
211211
DESTINATION test)
212212

213+
install(FILES test/test_controller_overriding_parameters.yaml
214+
DESTINATION test)
215+
213216
ament_add_gmock(test_hardware_management_srvs
214217
test/test_hardware_management_srvs.cpp
215218
)

controller_manager/controller_manager/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
set_hardware_component_state,
2424
switch_controllers,
2525
unload_controller,
26-
get_parameter_from_param_file,
26+
get_parameter_from_param_files,
2727
set_controller_parameters,
28-
set_controller_parameters_from_param_file,
28+
set_controller_parameters_from_param_files,
2929
bcolors,
3030
)
3131

@@ -40,8 +40,8 @@
4040
"set_hardware_component_state",
4141
"switch_controllers",
4242
"unload_controller",
43-
"get_parameter_from_param_file",
43+
"get_parameter_from_param_files",
4444
"set_controller_parameters",
45-
"set_controller_parameters_from_param_file",
45+
"set_controller_parameters_from_param_files",
4646
"bcolors",
4747
]

controller_manager/controller_manager/controller_manager_services.py

Lines changed: 104 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -284,57 +284,90 @@ def unload_controller(
284284
)
285285

286286

287-
def get_parameter_from_param_file(
288-
node, controller_name, namespace, parameter_file, parameter_name
287+
def get_params_files_with_controller_parameters(
288+
node, controller_name: str, namespace: str, parameter_files: list
289289
):
290-
with open(parameter_file) as f:
291-
namespaced_controller = (
292-
f"/{controller_name}" if namespace == "/" else f"{namespace}/{controller_name}"
293-
)
294-
WILDCARD_KEY = "/**"
295-
ROS_PARAMS_KEY = "ros__parameters"
296-
parameters = yaml.safe_load(f)
297-
controller_param_dict = None
298-
# check for the parameter in 'controller_name' or 'namespaced_controller' or '/**/namespaced_controller' or '/**/controller_name'
299-
for key in [
300-
controller_name,
301-
namespaced_controller,
302-
f"{WILDCARD_KEY}/{controller_name}",
303-
f"{WILDCARD_KEY}{namespaced_controller}",
304-
]:
305-
if key in parameters:
306-
if key == controller_name and namespace != "/":
307-
node.get_logger().fatal(
308-
f"{bcolors.FAIL}Missing namespace : {namespace} or wildcard in parameter file for controller : {controller_name}{bcolors.ENDC}"
290+
controller_parameter_files = []
291+
for parameter_file in parameter_files:
292+
if parameter_file in controller_parameter_files:
293+
continue
294+
with open(parameter_file) as f:
295+
namespaced_controller = (
296+
f"/{controller_name}" if namespace == "/" else f"{namespace}/{controller_name}"
297+
)
298+
WILDCARD_KEY = "/**"
299+
parameters = yaml.safe_load(f)
300+
# check for the parameter in 'controller_name' or 'namespaced_controller' or '/**/namespaced_controller' or '/**/controller_name'
301+
for key in [
302+
controller_name,
303+
namespaced_controller,
304+
f"{WILDCARD_KEY}/{controller_name}",
305+
f"{WILDCARD_KEY}{namespaced_controller}",
306+
]:
307+
if key in parameters:
308+
if key == controller_name and namespace != "/":
309+
node.get_logger().fatal(
310+
f"{bcolors.FAIL}Missing namespace : {namespace} or wildcard in parameter file for controller : {controller_name}{bcolors.ENDC}"
311+
)
312+
break
313+
controller_parameter_files.append(parameter_file)
314+
315+
if WILDCARD_KEY in parameters and key in parameters[WILDCARD_KEY]:
316+
controller_parameter_files.append(parameter_file)
317+
return controller_parameter_files
318+
319+
320+
def get_parameter_from_param_files(
321+
node, controller_name: str, namespace: str, parameter_files: list, parameter_name: str
322+
):
323+
for parameter_file in parameter_files:
324+
with open(parameter_file) as f:
325+
namespaced_controller = (
326+
f"/{controller_name}" if namespace == "/" else f"{namespace}/{controller_name}"
327+
)
328+
WILDCARD_KEY = "/**"
329+
ROS_PARAMS_KEY = "ros__parameters"
330+
parameters = yaml.safe_load(f)
331+
controller_param_dict = None
332+
# check for the parameter in 'controller_name' or 'namespaced_controller' or '/**/namespaced_controller' or '/**/controller_name'
333+
for key in [
334+
controller_name,
335+
namespaced_controller,
336+
f"{WILDCARD_KEY}/{controller_name}",
337+
f"{WILDCARD_KEY}{namespaced_controller}",
338+
]:
339+
if key in parameters:
340+
if key == controller_name and namespace != "/":
341+
node.get_logger().fatal(
342+
f"{bcolors.FAIL}Missing namespace : {namespace} or wildcard in parameter file for controller : {controller_name}{bcolors.ENDC}"
343+
)
344+
break
345+
controller_param_dict = parameters[key]
346+
347+
if WILDCARD_KEY in parameters and key in parameters[WILDCARD_KEY]:
348+
controller_param_dict = parameters[WILDCARD_KEY][key]
349+
350+
if controller_param_dict and (
351+
not isinstance(controller_param_dict, dict)
352+
or ROS_PARAMS_KEY not in controller_param_dict
353+
):
354+
raise RuntimeError(
355+
f"YAML file : {parameter_file} is not a valid ROS parameter file for controller node : {namespaced_controller}"
309356
)
357+
if (
358+
controller_param_dict
359+
and ROS_PARAMS_KEY in controller_param_dict
360+
and parameter_name in controller_param_dict[ROS_PARAMS_KEY]
361+
):
310362
break
311-
controller_param_dict = parameters[key]
312-
313-
if WILDCARD_KEY in parameters and key in parameters[WILDCARD_KEY]:
314-
controller_param_dict = parameters[WILDCARD_KEY][key]
315-
316-
if controller_param_dict and (
317-
not isinstance(controller_param_dict, dict)
318-
or ROS_PARAMS_KEY not in controller_param_dict
319-
):
320-
raise RuntimeError(
321-
f"YAML file : {parameter_file} is not a valid ROS parameter file for controller node : {namespaced_controller}"
322-
)
323-
if (
324-
controller_param_dict
325-
and ROS_PARAMS_KEY in controller_param_dict
326-
and parameter_name in controller_param_dict[ROS_PARAMS_KEY]
327-
):
328-
break
329-
330-
if controller_param_dict is None:
331-
node.get_logger().fatal(
332-
f"{bcolors.FAIL}Controller : {namespaced_controller} parameters not found in parameter file : {parameter_file}{bcolors.ENDC}"
333-
)
334-
if parameter_name in controller_param_dict[ROS_PARAMS_KEY]:
335-
return controller_param_dict[ROS_PARAMS_KEY][parameter_name]
336363

337-
return None
364+
if controller_param_dict and parameter_name in controller_param_dict[ROS_PARAMS_KEY]:
365+
return controller_param_dict[ROS_PARAMS_KEY][parameter_name]
366+
if controller_param_dict is None:
367+
node.get_logger().fatal(
368+
f"{bcolors.FAIL}Controller : {namespaced_controller} parameters not found in parameter files : {parameter_files}{bcolors.ENDC}"
369+
)
370+
return None
338371

339372

340373
def set_controller_parameters(
@@ -378,26 +411,36 @@ def set_controller_parameters(
378411
return True
379412

380413

381-
def set_controller_parameters_from_param_file(
382-
node, controller_manager_name, controller_name, parameter_file, namespace=None
414+
def set_controller_parameters_from_param_files(
415+
node, controller_manager_name: str, controller_name: str, parameter_files: list, namespace=None
383416
):
384-
if parameter_file:
385-
spawner_namespace = namespace if namespace else node.get_namespace()
417+
spawner_namespace = namespace if namespace else node.get_namespace()
418+
controller_parameter_files = get_params_files_with_controller_parameters(
419+
node, controller_name, spawner_namespace, parameter_files
420+
)
421+
if controller_parameter_files:
386422
set_controller_parameters(
387-
node, controller_manager_name, controller_name, "params_file", parameter_file
423+
node,
424+
controller_manager_name,
425+
controller_name,
426+
"params_file",
427+
controller_parameter_files,
388428
)
389429

390-
controller_type = get_parameter_from_param_file(
391-
node, controller_name, spawner_namespace, parameter_file, "type"
430+
controller_type = get_parameter_from_param_files(
431+
node, controller_name, spawner_namespace, controller_parameter_files, "type"
392432
)
393-
if controller_type:
394-
if not set_controller_parameters(
395-
node, controller_manager_name, controller_name, "type", controller_type
396-
):
397-
return False
433+
if controller_type and not set_controller_parameters(
434+
node, controller_manager_name, controller_name, "type", controller_type
435+
):
436+
return False
398437

399-
fallback_controllers = get_parameter_from_param_file(
400-
node, controller_name, spawner_namespace, parameter_file, "fallback_controllers"
438+
fallback_controllers = get_parameter_from_param_files(
439+
node,
440+
controller_name,
441+
spawner_namespace,
442+
controller_parameter_files,
443+
"fallback_controllers",
401444
)
402445
if fallback_controllers:
403446
if not set_controller_parameters(

controller_manager/controller_manager/launch_utils.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121

2222
def generate_controllers_spawner_launch_description(
23-
controller_names: list, controller_params_file=None, extra_spawner_args=[]
23+
controller_names: list, controller_params_files=None, extra_spawner_args=[]
2424
):
2525
"""
2626
Generate launch description for loading a controller using spawner.
@@ -37,8 +37,8 @@ def generate_controllers_spawner_launch_description(
3737
# Passing controller parameter file to load the controller (Controller type is retrieved from config file)
3838
generate_controllers_spawner_launch_description(
3939
['joint_state_broadcaster'],
40-
controller_params_file=os.path.join(get_package_share_directory('my_pkg'),
41-
'config', 'controller_params.yaml'),
40+
controller_params_files=[os.path.join(get_package_share_directory('my_pkg'),
41+
'config', 'controller_params.yaml')],
4242
extra_spawner_args=[--load-only]
4343
)
4444
@@ -62,8 +62,10 @@ def generate_controllers_spawner_launch_description(
6262
]
6363
)
6464

65-
if controller_params_file:
66-
spawner_arguments += ["--param-file", controller_params_file]
65+
if controller_params_files:
66+
for controller_params_file in controller_params_files:
67+
if controller_params_file:
68+
spawner_arguments += ["--param-file", controller_params_file]
6769

6870
# Setting --unload-on-kill if launch arg unload_on_kill is "true"
6971
# See https://github.com/ros2/launch/issues/290
@@ -98,11 +100,51 @@ def generate_controllers_spawner_launch_description(
98100
)
99101

100102

103+
def generate_controllers_spawner_launch_description_from_dict(
104+
controller_info_dict: dict, extra_spawner_args=[]
105+
):
106+
"""
107+
Generate launch description for loading a controller using spawner.
108+
109+
controller_info_dict: dict
110+
A dictionary with the following info:
111+
- controller_name: str
112+
The name of the controller to load as the key
113+
- controller_params_file: str or list or None
114+
The path to the controller parameter file or a list of paths to multiple parameter files
115+
or None if no parameter file is needed as the value of the key
116+
If a list is passed, the controller parameters will be overloaded in same order
117+
extra_spawner_args: list
118+
A list of extra arguments to pass to the controller spawner
119+
"""
120+
if not type(controller_info_dict) is dict:
121+
raise ValueError(f"Invalid controller_info_dict type parsed {controller_info_dict}")
122+
controller_names = controller_info_dict.keys()
123+
controller_params_files = []
124+
for controller_name in controller_names:
125+
controller_params_file = controller_info_dict[controller_name]
126+
if controller_params_file:
127+
if type(controller_params_file) is list:
128+
controller_params_files.extend(controller_params_file)
129+
elif type(controller_params_file) is str:
130+
controller_params_files.append(controller_params_file)
131+
else:
132+
raise ValueError(
133+
f"Invalid controller_params_file type parsed in the dict {controller_params_file}"
134+
)
135+
return generate_controllers_spawner_launch_description(
136+
controller_names=controller_names,
137+
controller_params_files=controller_params_files,
138+
extra_spawner_args=extra_spawner_args,
139+
)
140+
141+
101142
def generate_load_controller_launch_description(
102143
controller_name: str, controller_params_file=None, extra_spawner_args=[]
103144
):
145+
controller_params_files = [controller_params_file] if controller_params_file else None
104146
return generate_controllers_spawner_launch_description(
105147
controller_names=[controller_name],
106-
controller_params_file=controller_params_file,
148+
controller_params_file=controller_params_files,
107149
extra_spawner_args=extra_spawner_args,
108150
)

controller_manager/controller_manager/spawner.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
load_controller,
2727
switch_controllers,
2828
unload_controller,
29-
set_controller_parameters_from_param_file,
29+
set_controller_parameters_from_param_files,
3030
bcolors,
3131
)
3232
from controller_manager.controller_manager_services import ServiceNotFoundError
@@ -83,8 +83,11 @@ def main(args=None):
8383
parser.add_argument(
8484
"-p",
8585
"--param-file",
86-
help="Controller param file to be loaded into controller node before configure",
86+
help="Controller param file to be loaded into controller node before configure. "
87+
"Pass multiple times to load different files for different controllers or to "
88+
"override the parameters of the same controller.",
8789
default=None,
90+
action="append",
8891
required=False,
8992
)
9093
parser.add_argument(
@@ -147,13 +150,15 @@ def main(args=None):
147150
args = parser.parse_args(command_line_args)
148151
controller_names = args.controller_names
149152
controller_manager_name = args.controller_manager
150-
param_file = args.param_file
153+
param_files = args.param_file
151154
controller_manager_timeout = args.controller_manager_timeout
152155
service_call_timeout = args.service_call_timeout
153156
switch_timeout = args.switch_timeout
154157

155-
if param_file and not os.path.isfile(param_file):
156-
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), param_file)
158+
if param_files:
159+
for param_file in param_files:
160+
if not os.path.isfile(param_file):
161+
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), param_file)
157162

158163
node = Node("spawner_" + controller_names[0])
159164

@@ -198,12 +203,12 @@ def main(args=None):
198203
+ bcolors.ENDC
199204
)
200205
else:
201-
if param_file:
202-
if not set_controller_parameters_from_param_file(
206+
if param_files:
207+
if not set_controller_parameters_from_param_files(
203208
node,
204209
controller_manager_name,
205210
controller_name,
206-
param_file,
211+
param_files,
207212
spawner_namespace,
208213
):
209214
return 1

0 commit comments

Comments
 (0)