Skip to content

Commit 96aaf9a

Browse files
committed
add spwaner for hardware
1 parent 1c058d7 commit 96aaf9a

File tree

5 files changed

+271
-11
lines changed

5 files changed

+271
-11
lines changed

controller_manager/controller_manager/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
list_hardware_interfaces,
2121
load_controller,
2222
reload_controller_libraries,
23+
set_hardware_component_state,
2324
switch_controllers,
2425
unload_controller,
2526
)
@@ -32,6 +33,7 @@
3233
"list_hardware_interfaces",
3334
"load_controller",
3435
"reload_controller_libraries",
36+
"set_hardware_component_state",
3537
"switch_controllers",
3638
"unload_controller",
3739
]

controller_manager/controller_manager/controller_manager_services.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
ListHardwareInterfaces,
2121
LoadController,
2222
ReloadControllerLibraries,
23+
SetHardwareComponentState,
2324
SwitchController,
2425
UnloadController,
2526
)
@@ -107,6 +108,18 @@ def reload_controller_libraries(node, controller_manager_name, force_kill):
107108
)
108109

109110

111+
def set_hardware_component_state(node, controller_manager_name, component_name, lifecyle_state):
112+
request = SetHardwareComponentState.Request()
113+
request.name = component_name
114+
request.target_state = lifecyle_state
115+
return service_caller(
116+
node,
117+
f"{controller_manager_name}/set_hardware_component_state",
118+
SetHardwareComponentState,
119+
request,
120+
)
121+
122+
110123
def switch_controllers(
111124
node,
112125
controller_manager_name,
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2023 PAL Robotics S.L.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
import argparse
17+
import sys
18+
import time
19+
20+
from controller_manager import set_hardware_component_state
21+
22+
from lifecycle_msgs.msg import State
23+
import rclpy
24+
from rclpy.duration import Duration
25+
from rclpy.node import Node
26+
from rclpy.signals import SignalHandlerOptions
27+
28+
# from https://stackoverflow.com/a/287944
29+
30+
31+
class bcolors:
32+
HEADER = "\033[95m"
33+
OKBLUE = "\033[94m"
34+
OKCYAN = "\033[96m"
35+
OKGREEN = "\033[92m"
36+
WARNING = "\033[93m"
37+
FAIL = "\033[91m"
38+
ENDC = "\033[0m"
39+
BOLD = "\033[1m"
40+
UNDERLINE = "\033[4m"
41+
42+
43+
def first_match(iterable, predicate):
44+
return next((n for n in iterable if predicate(n)), None)
45+
46+
47+
def wait_for_value_or(function, node, timeout, default, description):
48+
while node.get_clock().now() < timeout:
49+
if result := function():
50+
return result
51+
node.get_logger().info(
52+
f"Waiting for {description}", throttle_duration_sec=2, skip_first=True
53+
)
54+
time.sleep(0.2)
55+
return default
56+
57+
58+
def combine_name_and_namespace(name_and_namespace):
59+
node_name, namespace = name_and_namespace
60+
return namespace + ("" if namespace.endswith("/") else "/") + node_name
61+
62+
63+
def find_node_and_namespace(node, full_node_name):
64+
node_names_and_namespaces = node.get_node_names_and_namespaces()
65+
return first_match(
66+
node_names_and_namespaces,
67+
lambda n: combine_name_and_namespace(n) == full_node_name,
68+
)
69+
70+
71+
def has_service_names(node, node_name, node_namespace, service_names):
72+
client_names_and_types = node.get_service_names_and_types_by_node(node_name, node_namespace)
73+
if not client_names_and_types:
74+
return False
75+
client_names, _ = zip(*client_names_and_types)
76+
return all(service in client_names for service in service_names)
77+
78+
79+
def wait_for_controller_manager(node, controller_manager, timeout_duration):
80+
# List of service names from controller_manager we wait for
81+
service_names = (
82+
f"{controller_manager}/list_hardware_components",
83+
f"{controller_manager}/set_hardware_component_state",
84+
)
85+
86+
# Wait for controller_manager
87+
timeout = node.get_clock().now() + Duration(seconds=timeout_duration)
88+
node_and_namespace = wait_for_value_or(
89+
lambda: find_node_and_namespace(node, controller_manager),
90+
node,
91+
timeout,
92+
None,
93+
f"'{controller_manager}' node to exist",
94+
)
95+
96+
# Wait for the services if the node was found
97+
if node_and_namespace:
98+
node_name, namespace = node_and_namespace
99+
return wait_for_value_or(
100+
lambda: has_service_names(node, node_name, namespace, service_names),
101+
node,
102+
timeout,
103+
False,
104+
f"'{controller_manager}' services to be available",
105+
)
106+
107+
return False
108+
109+
110+
def handle_set_component_state_service_call(
111+
node, controller_manager_name, component, target_state, action
112+
):
113+
response = set_hardware_component_state(node, controller_manager_name, component, target_state)
114+
if response.ok and response.state == target_state:
115+
node.get_logger().info(
116+
bcolors.OKGREEN
117+
+ f"{action} component '{component}'. Hardware now in state: {response.state}."
118+
)
119+
elif response.ok and not response.state == target_state:
120+
node.get_logger().warn(
121+
bcolors.WARNING
122+
+ f"Could not {action} component '{component}'. Service call returned ok=True, but state: {response.state} is not equal to target state '{target_state}'."
123+
)
124+
else:
125+
node.get_logger().warn(
126+
bcolors.WARNING
127+
+ f"Could not {action} component '{component}'. Service call failed. Wrong component name?"
128+
)
129+
130+
131+
def activate_components(node, controller_manager_name, components_to_activate):
132+
active_state = State()
133+
active_state.id = State.PRIMARY_STATE_ACTIVE
134+
active_state.label = "active"
135+
for component in components_to_activate:
136+
handle_set_component_state_service_call(
137+
node, controller_manager_name, component, active_state, "activated"
138+
)
139+
140+
141+
def configure_components(node, controller_manager_name, components_to_configure):
142+
inactive_state = State()
143+
inactive_state.id = State.PRIMARY_STATE_INACTIVE
144+
inactive_state.label = "inactive"
145+
for component in components_to_configure:
146+
handle_set_component_state_service_call(
147+
node, controller_manager_name, component, inactive_state, "configured"
148+
)
149+
150+
151+
def main(args=None):
152+
rclpy.init(args=args, signal_handler_options=SignalHandlerOptions.NO)
153+
parser = argparse.ArgumentParser()
154+
activate_or_confiigure_grp = parser.add_mutually_exclusive_group(required=True)
155+
156+
parser.add_argument(
157+
"hardware_component_name",
158+
help="The name of the hardware component which should be activated.",
159+
)
160+
161+
parser.add_argument(
162+
"-c",
163+
"--controller-manager",
164+
help="Name of the controller manager ROS node",
165+
default="controller_manager",
166+
required=False,
167+
)
168+
parser.add_argument(
169+
"--controller-manager-timeout",
170+
help="Time to wait for the controller manager",
171+
required=False,
172+
default=10,
173+
type=int,
174+
)
175+
176+
activate_or_confiigure_grp.add_argument(
177+
"--activate",
178+
help="Activates the given components. Note: Components are by default configured before activated. ",
179+
action="store_true",
180+
required=False,
181+
)
182+
183+
activate_or_confiigure_grp.add_argument(
184+
"--configure",
185+
help="Configures the given components.",
186+
action="store_true",
187+
required=False,
188+
)
189+
190+
command_line_args = rclpy.utilities.remove_ros_args(args=sys.argv)[1:]
191+
args = parser.parse_args(command_line_args)
192+
controller_manager_name = args.controller_manager
193+
controller_manager_timeout = args.controller_manager_timeout
194+
hardware_component = [args.hardware_component_name]
195+
activate = args.activate
196+
configure = args.configure
197+
198+
print(type(hardware_component))
199+
200+
node = Node("hardware_spawner")
201+
if not controller_manager_name.startswith("/"):
202+
spawner_namespace = node.get_namespace()
203+
if spawner_namespace != "/":
204+
controller_manager_name = f"{spawner_namespace}/{controller_manager_name}"
205+
else:
206+
controller_manager_name = f"/{controller_manager_name}"
207+
try:
208+
if not wait_for_controller_manager(
209+
node, controller_manager_name, controller_manager_timeout
210+
):
211+
node.get_logger().error("Controller manager not available")
212+
return 1
213+
214+
if activate:
215+
activate_components(node, controller_manager_name, hardware_component)
216+
elif configure:
217+
configure_components(node, controller_manager_name, hardware_component)
218+
else:
219+
node.get_logger().warn("Something went wrong.")
220+
parser.print_help()
221+
return 0
222+
223+
finally:
224+
rclpy.shutdown()
225+
226+
227+
if __name__ == "__main__":
228+
ret = main()
229+
sys.exit(ret)

controller_manager/setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
console_scripts =
33
spawner = controller_manager.spawner:main
44
unspawner = controller_manager.unspawner:main
5+
hardware_spawner = controller_manager.hardware_spawner:main

controller_manager/src/controller_manager.cpp

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -196,27 +196,42 @@ void ControllerManager::init_resource_manager(const std::string & robot_descript
196196
using lifecycle_msgs::msg::State;
197197

198198
std::vector<std::string> configure_components_on_start = std::vector<std::string>({});
199-
get_parameter("configure_components_on_start", configure_components_on_start);
200-
rclcpp_lifecycle::State inactive_state(
201-
State::PRIMARY_STATE_INACTIVE, hardware_interface::lifecycle_state_names::INACTIVE);
202-
for (const auto & component : configure_components_on_start)
199+
if (get_parameter("configure_components_on_start", configure_components_on_start))
203200
{
204-
resource_manager_->set_component_state(component, inactive_state);
201+
RCLCPP_WARN_STREAM(
202+
get_logger(),
203+
"[Deprecated]: Usage of parameter \"activate_components_on_start\" is deprecated. Use "
204+
"hardware_spawner instead.");
205+
rclcpp_lifecycle::State inactive_state(
206+
State::PRIMARY_STATE_INACTIVE, hardware_interface::lifecycle_state_names::INACTIVE);
207+
for (const auto & component : configure_components_on_start)
208+
{
209+
resource_manager_->set_component_state(component, inactive_state);
210+
}
205211
}
206212

207213
std::vector<std::string> activate_components_on_start = std::vector<std::string>({});
208-
get_parameter("activate_components_on_start", activate_components_on_start);
209-
rclcpp_lifecycle::State active_state(
210-
State::PRIMARY_STATE_ACTIVE, hardware_interface::lifecycle_state_names::ACTIVE);
211-
for (const auto & component : activate_components_on_start)
214+
if (get_parameter("activate_components_on_start", activate_components_on_start))
212215
{
213-
resource_manager_->set_component_state(component, active_state);
216+
RCLCPP_WARN_STREAM(
217+
get_logger(),
218+
"[Deprecated]: Usage of parameter \"activate_components_on_start\" is deprecated. Use "
219+
"hardware_spawner instead.");
220+
rclcpp_lifecycle::State active_state(
221+
State::PRIMARY_STATE_ACTIVE, hardware_interface::lifecycle_state_names::ACTIVE);
222+
for (const auto & component : activate_components_on_start)
223+
{
224+
resource_manager_->set_component_state(component, active_state);
225+
}
214226
}
215-
216227
// if both parameter are empty or non-existing preserve behavior where all components are
217228
// activated per default
218229
if (configure_components_on_start.empty() && activate_components_on_start.empty())
219230
{
231+
RCLCPP_WARN_STREAM(
232+
get_logger(),
233+
"[Deprecated]: Automatic activation of all components will not be supported in the future "
234+
"anymore. Use hardware_spawner instead.");
220235
resource_manager_->activate_all_components();
221236
}
222237
}

0 commit comments

Comments
 (0)