Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle on waiting #1562

Merged
merged 22 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3fd6e45
Handle on waiting
Timple Apr 16, 2024
fa8a737
fixup! Handle on waiting
Timple Apr 16, 2024
14169dc
Ask for forgiveness, not permission
Timple Apr 16, 2024
3439e57
fixup! Ask for forgiveness, not permission Aligns spawner and unspawn…
Timple Apr 16, 2024
7445c41
Update docs on hardware_spawner
Timple Apr 16, 2024
f2c0297
Update controller_manager/doc/userdoc.rst
Timple Apr 16, 2024
7bfd6eb
Drop test which relied on timeout
Timple Apr 23, 2024
f267dc1
Bring back --controller-manager-timeout option
Timple Apr 29, 2024
d173d98
fixup! Bring back --controller-manager-timeout option
Timple Apr 29, 2024
f4cace0
Merge branch 'master' into fix/spawner-interrupt
bmagyar Jul 15, 2024
a61d7dd
add back arg handling
bmagyar Jul 15, 2024
3143f59
use the right cm timeout var
bmagyar Jul 15, 2024
0db65eb
debug to info
bmagyar Jul 15, 2024
ea7d4ca
maybe we don't need to remove that test
bmagyar Aug 9, 2024
383569b
Merge remote-tracking branch 'origin/master' into fix/spawner-interrupt
fmauch Aug 11, 2024
27f44d7
Modify test with missing CM to have a timeout
fmauch Aug 11, 2024
16396bd
Catch exception when CM services are not found
fmauch Aug 11, 2024
3d67dfa
Exit with code 1 on unreached CM
fmauch Aug 12, 2024
b032118
Merge branch 'master' into fix/spawner-interrupt
destogl Aug 14, 2024
d75073a
Match functinality of HW spawner to Ctrl Spawner
destogl Aug 14, 2024
4c1dc72
Merge branch 'master' into fix/spawner-interrupt
destogl Aug 14, 2024
921c106
Fix formatting.
destogl Aug 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@
import rclpy


def service_caller(node, service_name, service_type, request, service_timeout=10.0):
def service_caller(node, service_name, service_type, request, service_timeout=0.0):
cli = node.create_client(service_type, service_name)

if not cli.service_is_ready():
node.get_logger().debug(
f"waiting {service_timeout} seconds for service {service_name} to become available..."
)
if not cli.wait_for_service(service_timeout):
raise RuntimeError(f"Could not contact service {service_name}")
while not cli.service_is_ready():
node.get_logger().debug(f"waiting for service {service_name} to become available...")
bmagyar marked this conversation as resolved.
Show resolved Hide resolved
if service_timeout:
if not cli.wait_for_service(service_timeout):
node.get_logger().fatal(f"Could not contact service {service_name}")
raise RuntimeError(f"Could not contact service {service_name}")
destogl marked this conversation as resolved.
Show resolved Hide resolved
elif not cli.wait_for_service(10.0):
node.get_logger().warn(f"Could not contact service {service_name}")

node.get_logger().debug(f"requester: making request: {request}\n")
future = cli.call_async(request)
Expand All @@ -47,7 +49,7 @@ def service_caller(node, service_name, service_type, request, service_timeout=10
raise RuntimeError(f"Exception while calling service: {future.exception()}")


def configure_controller(node, controller_manager_name, controller_name, service_timeout=10.0):
def configure_controller(node, controller_manager_name, controller_name, service_timeout=0.0):
request = ConfigureController.Request()
request.name = controller_name
return service_caller(
Expand All @@ -59,7 +61,7 @@ def configure_controller(node, controller_manager_name, controller_name, service
)


def list_controllers(node, controller_manager_name, service_timeout=10.0):
def list_controllers(node, controller_manager_name, service_timeout=0.0):
request = ListControllers.Request()
return service_caller(
node,
Expand All @@ -70,7 +72,7 @@ def list_controllers(node, controller_manager_name, service_timeout=10.0):
)


def list_controller_types(node, controller_manager_name, service_timeout=10.0):
def list_controller_types(node, controller_manager_name, service_timeout=0.0):
request = ListControllerTypes.Request()
return service_caller(
node,
Expand All @@ -81,7 +83,7 @@ def list_controller_types(node, controller_manager_name, service_timeout=10.0):
)


def list_hardware_components(node, controller_manager_name, service_timeout=10.0):
def list_hardware_components(node, controller_manager_name, service_timeout=0.0):
request = ListHardwareComponents.Request()
return service_caller(
node,
Expand All @@ -92,7 +94,7 @@ def list_hardware_components(node, controller_manager_name, service_timeout=10.0
)


def list_hardware_interfaces(node, controller_manager_name, service_timeout=10.0):
def list_hardware_interfaces(node, controller_manager_name, service_timeout=0.0):
request = ListHardwareInterfaces.Request()
return service_caller(
node,
Expand All @@ -103,7 +105,7 @@ def list_hardware_interfaces(node, controller_manager_name, service_timeout=10.0
)


def load_controller(node, controller_manager_name, controller_name, service_timeout=10.0):
def load_controller(node, controller_manager_name, controller_name, service_timeout=0.0):
request = LoadController.Request()
request.name = controller_name
return service_caller(
Expand All @@ -115,7 +117,7 @@ def load_controller(node, controller_manager_name, controller_name, service_time
)


def reload_controller_libraries(node, controller_manager_name, force_kill, service_timeout=10.0):
def reload_controller_libraries(node, controller_manager_name, force_kill, service_timeout=0.0):
request = ReloadControllerLibraries.Request()
request.force_kill = force_kill
return service_caller(
Expand All @@ -128,7 +130,7 @@ def reload_controller_libraries(node, controller_manager_name, force_kill, servi


def set_hardware_component_state(
node, controller_manager_name, component_name, lifecyle_state, service_timeout=10.0
node, controller_manager_name, component_name, lifecyle_state, service_timeout=0.0
):
request = SetHardwareComponentState.Request()
request.name = component_name
Expand Down Expand Up @@ -165,7 +167,7 @@ def switch_controllers(
)


def unload_controller(node, controller_manager_name, controller_name, service_timeout=10.0):
def unload_controller(node, controller_manager_name, controller_name, service_timeout=0.0):
request = UnloadController.Request()
request.name = controller_name
return service_caller(
Expand Down
61 changes: 2 additions & 59 deletions controller_manager/controller_manager/hardware_spawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@

import argparse
import sys
import time

from controller_manager import set_hardware_component_state
destogl marked this conversation as resolved.
Show resolved Hide resolved

from lifecycle_msgs.msg import State
import rclpy
from rclpy.duration import Duration
from rclpy.node import Node
from rclpy.signals import SignalHandlerOptions

Expand All @@ -43,17 +41,6 @@ def first_match(iterable, predicate):
return next((n for n in iterable if predicate(n)), None)


def wait_for_value_or(function, node, timeout, default, description):
while node.get_clock().now() < timeout:
if result := function():
return result
node.get_logger().info(
f"Waiting for {description}", throttle_duration_sec=2, skip_first=True
)
time.sleep(0.2)
return default


def combine_name_and_namespace(name_and_namespace):
node_name, namespace = name_and_namespace
return namespace + ("" if namespace.endswith("/") else "/") + node_name
Expand All @@ -75,37 +62,6 @@ def has_service_names(node, node_name, node_namespace, service_names):
return all(service in client_names for service in service_names)


destogl marked this conversation as resolved.
Show resolved Hide resolved
def wait_for_controller_manager(node, controller_manager, timeout_duration):
# List of service names from controller_manager we wait for
service_names = (
f"{controller_manager}/list_hardware_components",
f"{controller_manager}/set_hardware_component_state",
)

# Wait for controller_manager
timeout = node.get_clock().now() + Duration(seconds=timeout_duration)
node_and_namespace = wait_for_value_or(
lambda: find_node_and_namespace(node, controller_manager),
node,
timeout,
None,
f"'{controller_manager}' node to exist",
)

# Wait for the services if the node was found
if node_and_namespace:
node_name, namespace = node_and_namespace
return wait_for_value_or(
lambda: has_service_names(node, node_name, namespace, service_names),
node,
timeout,
False,
f"'{controller_manager}' services to be available",
)

return False


def handle_set_component_state_service_call(
node, controller_manager_name, component, target_state, action
):
Expand Down Expand Up @@ -163,14 +119,6 @@ def main(args=None):
default="controller_manager",
required=False,
)
destogl marked this conversation as resolved.
Show resolved Hide resolved
parser.add_argument(
"--controller-manager-timeout",
help="Time to wait for the controller manager",
required=False,
default=10,
type=int,
)

# add arguments which are mutually exclusive
activate_or_confiigure_grp.add_argument(
"--activate",
Expand All @@ -188,7 +136,6 @@ def main(args=None):
command_line_args = rclpy.utilities.remove_ros_args(args=sys.argv)[1:]
args = parser.parse_args(command_line_args)
controller_manager_name = args.controller_manager
destogl marked this conversation as resolved.
Show resolved Hide resolved
controller_manager_timeout = args.controller_manager_timeout
hardware_component = [args.hardware_component_name]
activate = args.activate
configure = args.configure
Expand All @@ -202,12 +149,6 @@ def main(args=None):
controller_manager_name = f"/{controller_manager_name}"

try:
destogl marked this conversation as resolved.
Show resolved Hide resolved
if not wait_for_controller_manager(
node, controller_manager_name, controller_manager_timeout
):
node.get_logger().error("Controller manager not available")
return 1

if activate:
destogl marked this conversation as resolved.
Show resolved Hide resolved
activate_components(node, controller_manager_name, hardware_component)
elif configure:
Expand All @@ -218,6 +159,8 @@ def main(args=None):
)
parser.print_help()
return 0
except KeyboardInterrupt:
pass
destogl marked this conversation as resolved.
Show resolved Hide resolved
finally:
rclpy.shutdown()

Expand Down
73 changes: 9 additions & 64 deletions controller_manager/controller_manager/spawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@

import rclpy
from rcl_interfaces.msg import Parameter
from rclpy.duration import Duration
from rclpy.node import Node

# @note: The versions conditioning is added here to support the source-compatibility with Humble
Expand Down Expand Up @@ -62,17 +61,6 @@ def first_match(iterable, predicate):
return next((n for n in iterable if predicate(n)), None)


def wait_for_value_or(function, node, timeout, default, description):
while node.get_clock().now() < timeout:
if result := function():
return result
node.get_logger().info(
f"Waiting for {description}", throttle_duration_sec=2, skip_first=True
)
time.sleep(0.2)
return default


def combine_name_and_namespace(name_and_namespace):
node_name, namespace = name_and_namespace
return namespace + ("" if namespace.endswith("/") else "/") + node_name
Expand All @@ -93,46 +81,8 @@ def has_service_names(node, node_name, node_namespace, service_names):
return all(service in client_names for service in service_names)


def wait_for_controller_manager(node, controller_manager, timeout_duration):
# List of service names from controller_manager we wait for
service_names = (
f"{controller_manager}/configure_controller",
f"{controller_manager}/list_controllers",
f"{controller_manager}/list_controller_types",
f"{controller_manager}/list_hardware_components",
f"{controller_manager}/list_hardware_interfaces",
f"{controller_manager}/load_controller",
f"{controller_manager}/reload_controller_libraries",
f"{controller_manager}/switch_controller",
f"{controller_manager}/unload_controller",
)

# Wait for controller_manager
timeout = node.get_clock().now() + Duration(seconds=timeout_duration)
node_and_namespace = wait_for_value_or(
lambda: find_node_and_namespace(node, controller_manager),
node,
timeout,
None,
f"'{controller_manager}' node to exist",
)

# Wait for the services if the node was found
if node_and_namespace:
node_name, namespace = node_and_namespace
return wait_for_value_or(
lambda: has_service_names(node, node_name, namespace, service_names),
node,
timeout,
False,
f"'{controller_manager}' services to be available",
)

return False


def is_controller_loaded(node, controller_manager, controller_name):
controllers = list_controllers(node, controller_manager).controller
def is_controller_loaded(node, controller_manager, controller_name, service_timeout=0.0):
controllers = list_controllers(node, controller_manager, service_timeout).controller
return any(c.name == controller_name for c in controllers)


Expand Down Expand Up @@ -200,8 +150,8 @@ def main(args=None):
"--controller-manager-timeout",
help="Time to wait for the controller manager",
required=False,
default=10,
type=int,
default=0,
type=float,
)
parser.add_argument(
"--activate-as-group",
Expand All @@ -225,7 +175,6 @@ def main(args=None):
controller_manager_name = args.controller_manager
controller_namespace = args.namespace
param_file = args.param_file
bmagyar marked this conversation as resolved.
Show resolved Hide resolved
controller_manager_timeout = args.controller_manager_timeout
destogl marked this conversation as resolved.
Show resolved Hide resolved

if args.controller_type:
warnings.filterwarnings("always")
Expand All @@ -248,22 +197,16 @@ def main(args=None):
controller_manager_name = f"/{controller_manager_name}"

try:
if not wait_for_controller_manager(
node, controller_manager_name, controller_manager_timeout
):
node.get_logger().error(
bcolors.FAIL + "Controller manager not available" + bcolors.ENDC
)
return 1

for controller_name in controller_names:
fallback_controllers = args.fallback_controllers
controller_type = args.controller_type
prefixed_controller_name = controller_name
if controller_namespace:
prefixed_controller_name = controller_namespace + "/" + controller_name

if is_controller_loaded(node, controller_manager_name, prefixed_controller_name):
if is_controller_loaded(
node, controller_manager_name, prefixed_controller_name, timeout
bmagyar marked this conversation as resolved.
Show resolved Hide resolved
):
node.get_logger().warn(
bcolors.WARNING
+ "Controller already loaded, skipping load_controller"
Expand Down Expand Up @@ -466,6 +409,8 @@ def main(args=None):

node.get_logger().info("Unloaded controller")
return 0
except KeyboardInterrupt:
pass
finally:
rclpy.shutdown()

Expand Down
2 changes: 2 additions & 0 deletions controller_manager/controller_manager/unspawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ def main(args=None):
node.get_logger().info("Unloaded controller")

return 0
except KeyboardInterrupt:
pass
finally:
rclpy.shutdown()

Expand Down
21 changes: 21 additions & 0 deletions controller_manager/doc/userdoc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ There are two scripts to interact with controller manager from launch files:

1. ``spawner`` - loads, configures and start a controller on startup.
2. ``unspawner`` - stops and unloads a controller.
3. ``hardware_spawner`` - activates and configures a hardware component.


``spawner``
Expand Down Expand Up @@ -184,6 +185,26 @@ There are two scripts to interact with controller manager from launch files:
-c CONTROLLER_MANAGER, --controller-manager CONTROLLER_MANAGER
Name of the controller manager ROS node


``hardware_spawner``
^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: console

$ ros2 run controller_manager hardware_spawner -h
usage: hardware_spawner [-h] [-c CONTROLLER_MANAGER] (--activate | --configure) hardware_component_name

positional arguments:
hardware_component_name
The name of the hardware component which should be activated.

options:
-h, --help show this help message and exit
-c CONTROLLER_MANAGER, --controller-manager CONTROLLER_MANAGER
Name of the controller manager ROS node
--activate Activates the given components. Note: Components are by default configured before activated.
--configure Configures the given components.

Using the Controller Manager in a Process
-----------------------------------------

Expand Down
Loading
Loading