Skip to content

Commit 4472287

Browse files
authored
[ros2controlcli] add params file parsing to load_controller verb and add namespacing support (#1703)
1 parent 111321c commit 4472287

15 files changed

+223
-130
lines changed

controller_manager/controller_manager/controller_manager_services.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,23 @@ def service_caller(
8888
@return The service response
8989
9090
"""
91-
cli = node.create_client(service_type, service_name)
91+
namespace = "" if node.get_namespace() == "/" else node.get_namespace()
92+
fully_qualified_service_name = (
93+
f"{namespace}/{service_name}" if not service_name.startswith("/") else service_name
94+
)
95+
cli = node.create_client(service_type, fully_qualified_service_name)
9296

9397
while not cli.service_is_ready():
94-
node.get_logger().info(f"waiting for service {service_name} to become available...")
98+
node.get_logger().info(
99+
f"waiting for service {fully_qualified_service_name} to become available..."
100+
)
95101
if service_timeout:
96102
if not cli.wait_for_service(service_timeout):
97-
raise ServiceNotFoundError(f"Could not contact service {service_name}")
103+
raise ServiceNotFoundError(
104+
f"Could not contact service {fully_qualified_service_name}"
105+
)
98106
elif not cli.wait_for_service(10.0):
99-
node.get_logger().warn(f"Could not contact service {service_name}")
107+
node.get_logger().warn(f"Could not contact service {fully_qualified_service_name}")
100108

101109
node.get_logger().debug(f"requester: making request: {request}\n")
102110
future = None
@@ -105,13 +113,13 @@ def service_caller(
105113
rclpy.spin_until_future_complete(node, future, timeout_sec=call_timeout)
106114
if future.result() is None:
107115
node.get_logger().warning(
108-
f"Failed getting a result from calling {service_name} in "
116+
f"Failed getting a result from calling {fully_qualified_service_name} in "
109117
f"{service_timeout}. (Attempt {attempt+1} of {max_attempts}.)"
110118
)
111119
else:
112120
return future.result()
113121
raise RuntimeError(
114-
f"Could not successfully call service {service_name} after {max_attempts} attempts."
122+
f"Could not successfully call service {fully_qualified_service_name} after {max_attempts} attempts."
115123
)
116124

117125

doc/release_notes.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,15 @@ ros2controlcli
118118
.. code-block:: bash
119119
120120
ros2 control set_hardware_component_state <hardware_component_name> <state>
121+
122+
* The ``load_controller`` now supports parsing of the params file (`#1703 <https://github.com/ros-controls/ros2_control/pull/1703>`_).
123+
124+
.. code-block:: bash
125+
126+
ros2 control load_controller <controller_name> <realtive_or_absolute_file_path>
127+
128+
* All the ros2controlcli verbs now support the namespacing through the ROS 2 standard way (`#1703 <https://github.com/ros-controls/ros2_control/pull/1703>`_).
129+
130+
.. code-block:: bash
131+
132+
ros2 control <verb> <arguments> --ros-args -r __ns:=<namespace>

ros2controlcli/doc/userdoc.rst

Lines changed: 82 additions & 55 deletions
Large diffs are not rendered by default.

ros2controlcli/ros2controlcli/api/__init__.py

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,13 @@
1515

1616
from controller_manager import list_controllers, list_hardware_components
1717

18-
import rclpy
19-
2018
from ros2cli.node.direct import DirectNode
2119

2220
from ros2node.api import NodeNameCompleter
2321

2422
from ros2param.api import call_list_parameters
2523

26-
27-
def service_caller(service_name, service_type, request):
28-
try:
29-
rclpy.init()
30-
31-
node = rclpy.create_node(f"ros2controlcli_{service_name.replace('/', '')}_requester")
32-
33-
cli = node.create_client(service_type, service_name)
34-
35-
if not cli.service_is_ready():
36-
node.get_logger().debug(f"waiting for service {service_name} to become available...")
37-
38-
if not cli.wait_for_service(2.0):
39-
raise RuntimeError(f"Could not contact service {service_name}")
40-
41-
node.get_logger().debug(f"requester: making request: {repr(request)}\n")
42-
future = cli.call_async(request)
43-
rclpy.spin_until_future_complete(node, future)
44-
if future.result() is not None:
45-
return future.result()
46-
else:
47-
future_exception = future.exception()
48-
raise RuntimeError(f"Exception while calling service: {repr(future_exception)}")
49-
finally:
50-
node.destroy_node()
51-
rclpy.shutdown()
24+
import argparse
5225

5326

5427
class ControllerNameCompleter:
@@ -89,16 +62,28 @@ def __call__(self, prefix, parsed_args, **kwargs):
8962
return [c.name for c in hardware_components if c.state.label in self.valid_states]
9063

9164

65+
class ParserROSArgs(argparse.Action):
66+
def __call__(self, parser, namespace, values, option_string=None):
67+
values = [option_string] + values
68+
setattr(namespace, "argv", values)
69+
70+
9271
def add_controller_mgr_parsers(parser):
93-
"""Parser arguments to get controller manager node name, defaults to /controller_manager."""
72+
"""Parser arguments to get controller manager node name, defaults to controller_manager."""
9473
arg = parser.add_argument(
9574
"-c",
9675
"--controller-manager",
97-
help="Name of the controller manager ROS node",
98-
default="/controller_manager",
76+
help="Name of the controller manager ROS node (default: controller_manager)",
77+
default="controller_manager",
9978
required=False,
10079
)
10180
arg.completer = NodeNameCompleter(include_hidden_nodes_key="include_hidden_nodes")
10281
parser.add_argument(
10382
"--include-hidden-nodes", action="store_true", help="Consider hidden nodes as well"
10483
)
84+
parser.add_argument(
85+
"--ros-args",
86+
nargs=argparse.REMAINDER,
87+
help="Pass arbitrary arguments to the executable",
88+
action=ParserROSArgs,
89+
)

ros2controlcli/ros2controlcli/verb/list_controller_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def add_arguments(self, parser, cli_name):
2929
add_controller_mgr_parsers(parser)
3030

3131
def main(self, *, args):
32-
with NodeStrategy(args) as node:
32+
with NodeStrategy(args).direct_node as node:
3333
response = list_controller_types(node, args.controller_manager)
3434
types_and_classes = zip(response.types, response.base_classes)
3535
for c in types_and_classes:

ros2controlcli/ros2controlcli/verb/list_controllers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def add_arguments(self, parser, cli_name):
9595
add_controller_mgr_parsers(parser)
9696

9797
def main(self, *, args):
98-
with NodeStrategy(args) as node:
98+
with NodeStrategy(args).direct_node as node:
9999
response = list_controllers(node, args.controller_manager)
100100

101101
if not response.controller:

ros2controlcli/ros2controlcli/verb/list_hardware_components.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def add_arguments(self, parser, cli_name):
3636
add_controller_mgr_parsers(parser)
3737

3838
def main(self, *, args):
39-
with NodeStrategy(args) as node:
39+
with NodeStrategy(args).direct_node as node:
4040
hardware_components = list_hardware_components(node, args.controller_manager)
4141

4242
for idx, component in enumerate(hardware_components.component):

ros2controlcli/ros2controlcli/verb/list_hardware_interfaces.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def add_arguments(self, parser, cli_name):
2828
add_controller_mgr_parsers(parser)
2929

3030
def main(self, *, args):
31-
with NodeStrategy(args) as node:
31+
with NodeStrategy(args).direct_node as node:
3232
hardware_interfaces = list_hardware_interfaces(node, args.controller_manager)
3333
command_interfaces = sorted(
3434
hardware_interfaces.command_interfaces, key=lambda hwi: hwi.name

ros2controlcli/ros2controlcli/verb/load_controller.py

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,22 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from controller_manager import configure_controller, load_controller, switch_controllers
15+
from controller_manager import (
16+
configure_controller,
17+
load_controller,
18+
list_controllers,
19+
switch_controllers,
20+
set_controller_parameters_from_param_file,
21+
bcolors,
22+
)
1623

1724
from ros2cli.node.direct import add_arguments
1825
from ros2cli.node.strategy import NodeStrategy
1926
from ros2cli.verb import VerbExtension
2027

2128
from ros2controlcli.api import add_controller_mgr_parsers, ControllerNameCompleter
29+
import os
30+
from argparse import OPTIONAL
2231

2332

2433
class LoadControllerVerb(VerbExtension):
@@ -28,37 +37,79 @@ def add_arguments(self, parser, cli_name):
2837
add_arguments(parser)
2938
arg = parser.add_argument("controller_name", help="Name of the controller")
3039
arg.completer = ControllerNameCompleter()
40+
arg = parser.add_argument(
41+
"param_file",
42+
help="The YAML file with the controller parameters",
43+
nargs=OPTIONAL,
44+
default=None,
45+
)
3146
arg = parser.add_argument(
3247
"--set-state",
3348
choices=["inactive", "active"],
3449
help="Set the state of the loaded controller",
50+
default=None,
3551
)
3652
add_controller_mgr_parsers(parser)
3753

3854
def main(self, *, args):
39-
with NodeStrategy(args) as node:
40-
response = load_controller(node, args.controller_manager, args.controller_name)
41-
if not response.ok:
42-
return "Error loading controller, check controller_manager logs"
55+
with NodeStrategy(args).direct_node as node:
56+
controllers = list_controllers(node, args.controller_manager, 20.0).controller
57+
if any(c.name == args.controller_name for c in controllers):
58+
print(
59+
f"{bcolors.WARNING}Controller : {args.controller_name} already loaded, skipping load_controller!{bcolors.ENDC}"
60+
)
61+
else:
62+
if args.param_file:
63+
if not os.path.exists(args.param_file):
64+
print(
65+
f"{bcolors.FAIL}Controller parameter file : {args.param_file} does not exist, Aborting!{bcolors.ENDC}"
66+
)
67+
return 1
68+
if not os.path.isabs(args.param_file):
69+
args.param_file = os.path.join(os.getcwd(), args.param_file)
4370

44-
if not args.set_state:
45-
print(f"Successfully loaded controller {args.controller_name}")
46-
return 0
71+
if not set_controller_parameters_from_param_file(
72+
node,
73+
args.controller_manager,
74+
args.controller_name,
75+
args.param_file,
76+
node.get_namespace(),
77+
):
78+
return 1
4779

48-
# we in any case configure the controller
49-
response = configure_controller(node, args.controller_manager, args.controller_name)
50-
if not response.ok:
51-
return "Error configuring controller"
80+
ret = load_controller(node, args.controller_manager, args.controller_name)
81+
if not ret.ok:
82+
print(
83+
f"{bcolors.FAIL}Failed loading controller {args.controller_name} check controller_manager logs{bcolors.ENDC}"
84+
)
85+
return 1
86+
print(
87+
f"{bcolors.OKBLUE}Successfully loaded controller {args.controller_name}{bcolors.ENDC}"
88+
)
5289

53-
if args.set_state == "active":
54-
response = switch_controllers(
55-
node, args.controller_manager, [], [args.controller_name], True, True, 5.0
90+
if args.set_state:
91+
92+
# we in any case configure the controller
93+
response = configure_controller(
94+
node, args.controller_manager, args.controller_name
5695
)
5796
if not response.ok:
58-
return "Error activating controller, check controller_manager logs"
97+
print(
98+
f"{bcolors.FAIL}Error configuring controller : {args.controller_name}{bcolors.ENDC}"
99+
)
100+
return 1
101+
102+
if args.set_state == "active":
103+
response = switch_controllers(
104+
node, args.controller_manager, [], [args.controller_name], True, True, 5.0
105+
)
106+
if not response.ok:
107+
print(
108+
f"{bcolors.FAIL}Error activating controller : {args.controller_name}, check controller_manager logs{bcolors.ENDC}"
109+
)
110+
return 1
59111

60-
print(
61-
f"Successfully loaded controller {args.controller_name} into "
62-
f'state {"inactive" if args.set_state == "inactive" else "active"}'
63-
)
64-
return 0
112+
print(
113+
f"{bcolors.OKBLUE}Successfully loaded controller {args.controller_name} into state {args.set_state}{bcolors.ENDC}"
114+
)
115+
return 0

ros2controlcli/ros2controlcli/verb/reload_controller_libraries.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def add_arguments(self, parser, cli_name):
3232
add_controller_mgr_parsers(parser)
3333

3434
def main(self, *, args):
35-
with NodeStrategy(args) as node:
35+
with NodeStrategy(args).direct_node as node:
3636
response = reload_controller_libraries(
3737
node, args.controller_manager, force_kill=args.force_kill
3838
)

0 commit comments

Comments
 (0)