diff --git a/controller_manager/doc/userdoc.rst b/controller_manager/doc/userdoc.rst index 745088c7bb..2e4694c468 100644 --- a/controller_manager/doc/userdoc.rst +++ b/controller_manager/doc/userdoc.rst @@ -597,3 +597,217 @@ The color output automatically adapts to the environment: * ``RCUTILS_COLORIZED_OUTPUT=1`` -> forces color output * Unset -> automatically detects TTY and enables color only in interactive terminals + +Utility scripts +--------------- + +In addition to the ``spawner``, ``unspawner``, and ``hardware_spawner`` helper scripts, the controller_manager package provides utility modules for programmatic interaction with the controller manager. + +Programmatic controller lifecycle (recommended) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Prefer the high-level service wrapper functions in +``controller_manager.controller_manager.controller_manager_services`` for lifecycle scripts +(e.g. ``load_controller``, ``configure_controller``, ``switch_controllers``, ``cleanup_controller``, ``unload_controller``) +instead of exposing internal service-client singletons. + +**Example lifecycle script:** + +.. code-block:: python + + import rclpy + from rclpy.node import Node + from controller_manager.controller_manager_services import ( + load_controller, configure_controller, switch_controllers, cleanup_controller, unload_controller + ) + from controller_manager_msgs.srv import LoadController + + def main(): + rclpy.init() + node = rclpy.create_node('controller_ops') + cm_name = 'controller_manager' + try: + resp = load_controller(node, cm_name, 'my_controller') + if getattr(resp, 'success', False): + node.get_logger().info("Controller loaded") + resp = configure_controller(node, cm_name, 'my_controller') + if getattr(resp, 'ok', getattr(resp, 'success', False)): + node.get_logger().info("Controller configured") + resp = switch_controllers( + node, cm_name, deactivate_controllers=[], activate_controllers=['my_controller'], + strictness=2, activate_asap=False, timeout=0.0 + ) + if getattr(resp, 'ok', False): + node.get_logger().info('Controller activated') + finally: + cleanup_controller(node, cm_name, 'my_controller') + unload_controller(node, cm_name, 'my_controller') + node.destroy_node() + rclpy.shutdown() + + if __name__ == '__main__': + main() + +Notes: +- Import path: controller_manager.controller_manager.controller_manager_services (matches repository layout). +- Response fields differ between services; examples use getattr to handle ``success`` vs ``ok``. + +launch_utils +^^^^^^^^^^^^ + +The ``launch_utils`` module provides helper functions to generate launch descriptions for loading controllers. These functions simplify launch file creation by reducing boilerplate code. + +**generate_controllers_spawner_launch_description()** + +Generates a launch description that automatically loads and activates controllers using the spawner utility. + +**Function Signature:** + +.. code-block:: python + + def generate_controllers_spawner_launch_description( + controller_names: list, + controller_params_files=None, + extra_spawner_args=[] + ) -> LaunchDescription + +**Python Usage Example:** + +.. code-block:: python + + from controller_manager.launch_utils import generate_controllers_spawner_launch_description + from launch import LaunchDescription + import os + from ament_index_python.packages import get_package_share_directory + + def generate_launch_description(): + config_dir = os.path.join( + get_package_share_directory('my_robot_bringup'), + 'config' + ) + # Example 1: Load with controller parameters already in controller_manager + spawner = generate_controllers_spawner_launch_description( + ['joint_state_broadcaster'] + ) + # Example 2: Load with parameter file and extra arguments + spawner = generate_controllers_spawner_launch_description( + ['joint_trajectory_controller'], + controller_params_files=[os.path.join(config_dir, 'controllers.yaml')], + extra_spawner_args=['--load-only'] + ) + return LaunchDescription([spawner]) + +**Parameters:** + +- ``controller_names`` (list[str]): Names of controllers to load. Example: ``['joint_state_broadcaster', 'joint_trajectory_controller']`` +- ``controller_params_files`` (list[str], optional): Paths to YAML parameter files for controllers. Defaults to ``None`` +- ``extra_spawner_args`` (list[str], optional): Additional arguments to pass to spawner (e.g., ``['--load-only', '--inactive']``). Defaults to ``[]`` + +**Return Value:** + +Returns a ``LaunchDescription`` containing: + +- Launch argument for ``controller_manager_name`` (default: "controller_manager") - allows overriding the controller manager node name +- Launch argument for ``unload_on_kill`` (default: "false") - waits for interrupt signal to unload controllers +- Spawner node that loads and activates the specified controllers + +**Complete Launch File Example:** + +.. code-block:: python + + from launch import LaunchDescription + from launch_ros.actions import Node + from controller_manager.launch_utils import generate_controllers_spawner_launch_description + import os + from ament_index_python.packages import get_package_share_directory + + def generate_launch_description(): + # Get configuration paths + config_dir = os.path.join( + get_package_share_directory('my_robot_bringup'), + 'config' + ) + # Create controller manager node + control_node = Node( + package='controller_manager', + executable='ros2_control_node', + parameters=[os.path.join(config_dir, 'ros2_controllers.yaml')], + output='both', + ) + # Create spawner for multiple controllers + controllers_spawner = generate_controllers_spawner_launch_description( + ['joint_state_broadcaster', 'joint_trajectory_controller'], + controller_params_files=[os.path.join(config_dir, 'controllers.yaml')], + ) + return LaunchDescription([ + control_node, + controllers_spawner, + ]) + +**Launching:** + +.. code-block:: console + + $ ros2 launch my_robot_bringup my_robot.launch.py + [controller_manager-1] [INFO] [ros2_control_node]: Loaded controller 'joint_state_broadcaster' + [spawner-1] [INFO] [spawner]: Configured and activated 'joint_state_broadcaster' + [spawner-1] [INFO] [spawner]: Configured and activated 'joint_trajectory_controller' + +**generate_controllers_spawner_launch_description_from_dict()** + +Provides an alternative way to specify controllers using a dictionary format, allowing different parameter files per controller. + +**Function Signature:** + +.. code-block:: python + + def generate_controllers_spawner_launch_description_from_dict( + controller_info_dict: dict, + extra_spawner_args=[] + ) -> LaunchDescription + +**Parameters:** + +- ``controller_info_dict`` (dict): Dictionary with controller names as keys and configuration dictionaries as values + - Each value should contain ``controller_params_file`` (str or list[str], optional): Path(s) to parameter file(s) +- ``extra_spawner_args`` (list[str], optional): Additional arguments to pass to spawner. Defaults to ``[]`` + +**Python Usage Example:** + +.. code-block:: python + + from controller_manager.launch_utils import generate_controllers_spawner_launch_description_from_dict + from launch import LaunchDescription + import os + from ament_index_python.packages import get_package_share_directory + + def generate_launch_description(): + config_dir = PathSubstitution(FindPackageShare('my_robot_bringup') / 'config') + ) + # Define controllers with per-controller configurations + controller_info_dict = { + 'joint_state_broadcaster': { + 'controller_params_file': os.path.join(config_dir, 'common_params.yaml') + }, + 'position_trajectory_controller': { + 'controller_params_file': [ + os.path.join(config_dir, 'common_params.yaml'), + os.path.join(config_dir, 'position_controller.yaml'), + ] + }, + 'velocity_trajectory_controller': { + 'controller_params_file': os.path.join(config_dir, 'velocity_controller.yaml') + }, + } + spawner = generate_controllers_spawner_launch_description_from_dict( + controller_info_dict, + extra_spawner_args=['--load-only'] + ) + return LaunchDescription([spawner]) + +**Use Cases:** + +- Define different parameter files for different controllers +- Apply common parameter files across multiple controllers +- Override controller parameters with multiple YAML files +- Simplify complex launch file configurations diff --git a/hardware_interface/doc/joint_limiting.rst b/hardware_interface/doc/joint_limiting.rst index 1dd0b85126..730b7f1153 100644 --- a/hardware_interface/doc/joint_limiting.rst +++ b/hardware_interface/doc/joint_limiting.rst @@ -49,8 +49,7 @@ If joint limits are active for a specific interface, the controller_manger will Configuration of Limits ######################## -tba -Description of the Limiter Algorithms -###################################### -tba + + +7. **Test Before Deployment**: Verify joint behavior with configured limits using simulation before real hardware