0
$\begingroup$

Using ROS2 Humble. I have been stuck on a custom controller issue for a while now. I'm pretty sure some ROS2 updates broke my package, as running things using mock_components/GenericSystem doesn't seem to work anymore. Interestingly, rqt_joint_trajectory_controller doesn't work on my controller anymore either.

The error

When launching with ros2 launch linear_slider_bringup linear_slider.launch.py use_mock_hardware:=true, everything successfully launches. I then plan and execute a new pose using RViz, receiving the following error:

[move_group-4] [INFO] [1723589509.274056329] [moveit.ompl_planning.model_based_planning_context]: Planner configuration 'linear_slider__manipulator' will use planner 'geometric::RRTConnect'. Additional configuration parameters will be set when the planner is constructed.
[move_group-4] [INFO] [1723589509.300718025] [moveit.plugins.moveit_simple_controller_manager]: Returned 2 controllers in list
[move_group-4] [INFO] [1723589509.300744548] [moveit.plugins.moveit_simple_controller_manager]: Returned 2 controllers in list
[move_group-4] [INFO] [1723589509.300795754] [moveit_ros.trajectory_execution_manager]: Validating trajectory with allowed_start_tolerance 0.01
[move_group-4] [INFO] [1723589509.302286655] [moveit_ros.trajectory_execution_manager]: Starting trajectory execution ...
[move_group-4] [INFO] [1723589509.302339110] [moveit.plugins.moveit_simple_controller_manager]: Returned 2 controllers in list
[move_group-4] [INFO] [1723589509.302355776] [moveit.plugins.moveit_simple_controller_manager]: Returned 2 controllers in list
[move_group-4] [INFO] [1723589509.302475309] [moveit.simple_controller_manager.follow_joint_trajectory_controller_handle]: sending trajectory to linear_slider_controller
[ros2_control_node-2] [INFO] [1723589509.302950701] [linear_slider_controller]: Received new action goal
[ros2_control_node-2] [INFO] [1723589509.303008767] [linear_slider_controller]: Accepted new action goal
[move_group-4] [INFO] [1723589509.303150866] [moveit.simple_controller_manager.follow_joint_trajectory_controller_handle]: linear_slider_controller started execution
[move_group-4] [INFO] [1723589509.303176253] [moveit.simple_controller_manager.follow_joint_trajectory_controller_handle]: Goal request accepted!
`[move_group-4] [WARN] [1723589518.271080092] [moveit.simple_controller_manager.follow_joint_trajectory_controller_handle]: waitForExecution timed out
[move_group-4] [ERROR] [1723589518.271169625] [moveit_ros.trajectory_execution_manager]: Controller is taking too long to execute trajectory (the expected upper bound for the trajectory execution was 8.967761 seconds). Stopping trajectory.
[move_group-4] [INFO] [1723589518.271218034] [moveit.simple_controller_manager.follow_joint_trajectory_controller_handle]: Cancelling execution for linear_slider_controller
[ros2_control_node-2] [INFO] [1723589518.271686876] [linear_slider_controller]: Got request to cancel goal
[move_group-4] [INFO] [1723589518.272153542] [moveit_ros.trajectory_execution_manager]: Completed trajectory execution with status TIMED_OUT ...
[move_group-4] [INFO] [1723589518.272676190] [moveit_move_group_default_capabilities.move_action_capability]: Timeout reached
[rviz2-6] [INFO] [1723589518.274159933] [move_group_interface]: Plan and Execute request aborted
[rviz2-6] [ERROR] [1723589518.274772835] [move_group_interface]: MoveGroupInterface::move() failed or timeout reached

As detailed, the controller accepts the goal from MoveIt, but the controller completely times out. I used to be able to test the controller using rqt_joint_trajectory_controller, but this has also broken.

The files detailed below can also be found on GitHub here for better viewing. Much of the content was modeled after the Universal Robots ros2 drivers.

Disclaimer: I know this is a complicated question with many parts involved. Please let me know if you need more specific information in answering this question. I left out the URDF and base launch file to save on characters. I appreciate any help!

SRDF

The SRDF, generated using the same parameters as the URDF.

<?xml version="1.0" ?>
<robot name="linear_slider">
  <group name="linear_slider__manipulator">
    <chain base_link="linear_slider__base_link" tip_link="linear_slider__flange"/>
    <end_effector group="linear_slider__manipulator" name="linear_slider__eef" parent_link="linear_slider__tool0"/>
  </group>
  <group_state group="linear_slider__manipulator" name="linear_slider__center">
    <joint name="linear_slider__joint1" value="0.0"/>
  </group_state>
  <virtual_joint child_link="linear_slider__base" name="linear_slider__base_joint" parent_frame="world" type="fixed"/>
  <disable_collisions link1="linear_slider__base_link" link2="linear_slider__moving_base" reason="Adjacent"/>
</robot>

ros2_controllers.yaml

I am using a velocity-commanded joint_trajectory_controller implementation. Here is my ros2_controllers.yaml file:

controller_manager:
    ros__parameters:
        update_rate: 2000 # hz

        linear_slider_controller:
            type: joint_trajectory_controller/JointTrajectoryController
            # TODO: Therefore if the hardware provides only acceleration or velocity states they have to be integrated in the hardware-interface implementation of velocity and position to use these controllers.

        joint_state_broadcaster:
            type: joint_state_broadcaster/JointStateBroadcaster

        limit_switch_state_broadcaster:
            type: linear_slider_controllers/LimitSwitchStateBroadcaster

linear_slider_controller:
    ros__parameters:
        joints:
            - $(var prefix)joint1

        command_interfaces:
            - velocity

        state_interfaces:
            - position
            - velocity

        gains: # required for velocity interface
            $(var prefix)joint1:
                ff_velocity_scale: 0.0
                p: 6.0
                i: 2.0
                d: 2.0

                # p: 100.0
                # i: 2.0
                # d: 5.0
                # i_clamp: 1.0

        state_publish_rate: 100.0 # defaults to 50
        action_monitor_rate: 50.0 # defaults to 20

        allow_partial_joints_goal: false # defaults to false
        # stop_trajectory_duration: 0.1
        constraints:
            goal_time: 20.0 # defaults to 0.0 (start immediately)
            stopped_velocity_tolerance: 0.01 # Defaults to 0.01
            $(var prefix)joint1:
                goal: 0.05 # position tolerace (m)
                # trajectory: 0.20

        allow_nonzero_velocity_at_trajectory_end: false

limit_switch_state_broadcaster:
    ros__parameters:
        state_interfaces:
            - $(var prefix)lim_switch_neg/state
        - $(var prefix)lim_switch_pos/state

joint_state_broadcaster:
    ros__parameters:
        joints:
            - $(var prefix)joint1

moveit_controllers.yaml

The corresponding moveit_controllers file is below. Use of the MoveItSimpleControllerManager is detailed in the linear_slider_moveit.launch.py.

controller_names:
  - scaled_joint_trajectory_controller
  - linear_slider_controller

scaled_joint_trajectory_controller:
  action_ns: follow_joint_trajectory
  type: FollowJointTrajectory
  default: false
  joints:
    - $(var prefix)joint1

linear_slider_controller:
  action_ns: follow_joint_trajectory
  type: FollowJointTrajectory
  default: true
  joints:
    - $(var prefix)joint1

MoveIt launch file

from launch import LaunchDescription, LaunchContext
from launch.actions import DeclareLaunchArgument, OpaqueFunction, RegisterEventHandler
from launch.conditions import IfCondition
from launch.event_handlers import OnProcessExit, OnProcessStart
from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution, TextSubstitution
from launch.utilities import perform_substitutions
from launch_ros.actions import Node
from launch_ros.parameter_descriptions import ParameterFile
from launch_ros.substitutions import FindPackageShare
from ament_index_python.packages import get_package_share_directory

from linear_slider_moveit_config.launch_common import load_yaml
from moveit_configs_utils import MoveItConfigsBuilder

import rclpy
import rclpy.logging

import os
import yaml
import json

logger = rclpy.logging.get_logger("linear_slider_moveit_config.launch_logger")


def launch_setup(context: LaunchContext, *args, **kwargs):
    """Callback function for launch setup using runtime context for evaluation and debugging."""

    # Launch configurations
    launch_rviz = LaunchConfiguration("launch_rviz")
    launch_servo = LaunchConfiguration("launch_servo")
    description_file = LaunchConfiguration("description_file")
    description_pkg = LaunchConfiguration("description_pkg")
    mock_sensor_commands = LaunchConfiguration("mock_sensor_commands")
    moveit_semantic_description_file = LaunchConfiguration("moveit_semantic_description_file")
    moveit_runtime_config_pkg = LaunchConfiguration("moveit_runtime_config_pkg")
    moveit_joint_limits_file = LaunchConfiguration("moveit_joint_limits_file")
    prefix = LaunchConfiguration("prefix")
    use_mock_hardware = LaunchConfiguration("use_mock_hardware")
    use_sim_time = LaunchConfiguration("use_sim_time")
    warehouse_sqlite_path = LaunchConfiguration("warehouse_sqlite_path")    

    # Get URDF from xacro
    robot_description_content = Command(
        [
            PathJoinSubstitution([FindExecutable(name="xacro")]),
            " ",
            PathJoinSubstitution([FindPackageShare(description_pkg), "urdf", description_file]),
            " ",
            "prefix:=",
            prefix,
            " ",
            "use_mock_hardware:=",
            use_mock_hardware,
            " ",
            "mock_sensor_commands:=",
            mock_sensor_commands,
            " ",
        ]
    )
    robot_description = {"robot_description": robot_description_content}

    # Get SRDF from xacro
    robot_description_semantic_content = Command(
        [
            PathJoinSubstitution([FindExecutable(name="xacro")]),
            " ",
            PathJoinSubstitution([FindPackageShare(moveit_runtime_config_pkg), "srdf", moveit_semantic_description_file]),
            " ",
            "prefix:=",
            prefix,
            " ",
        ]
    )
    robot_description_semantic = {"robot_description_semantic": robot_description_semantic_content}

    # MoveIt Kinematics
    robot_description_kinematics_path = PathJoinSubstitution(
        [FindPackageShare(moveit_runtime_config_pkg), "config", "kinematics.yaml"]
    )
    robot_description_kinematics_evaluated_file = ParameterFile(robot_description_kinematics_path, allow_substs=True)
    robot_description_kinematics_evaluated_file.evaluate(context=context)
    robot_description_kinematics_content = load_yaml(
        package_name=str(moveit_runtime_config_pkg.perform(context=context)),
        file_path=os.path.join("config", str(robot_description_kinematics_evaluated_file.param_file)),
    )
    kinematics_tmp_path = PathJoinSubstitution(
        [  # For some reason, the temp file gets deleted pretty quickly, so rewrite the contents of that temp file to a slightly-more-permanent temp file in /linear_slider_moveit/config/tmp/
            FindPackageShare(moveit_runtime_config_pkg),
            "config",
            "tmp",
            "tmp_kinematics.yaml",
        ]
    )
    _sv_path = kinematics_tmp_path.perform(context=context)
    with open(_sv_path, "w") as file:
        yaml.safe_dump(robot_description_kinematics_content, file)

    # MoveIt Joint Limits
    moveit_joint_limits_path = PathJoinSubstitution(
        [FindPackageShare(moveit_runtime_config_pkg), "config", moveit_joint_limits_file]
    )
    moveit_joint_limits_evaluated_file = ParameterFile(moveit_joint_limits_path, allow_substs=True)
    moveit_joint_limits_evaluated_file.evaluate(context=context)
    robot_description_planning = {
        "robot_description_planning": load_yaml(
            package_name=str(moveit_runtime_config_pkg.perform(context=context)),
            file_path=os.path.join(
                "config", str(moveit_joint_limits_evaluated_file.param_file)
            ),  # .param_file gets name of temp yaml file created by ROS2
        )
    }

    # Planning configuration
    ompl_planning_pipeline_config = dict(
        move_group=dict(
            planning_plugins=["ompl_interface/OMPLPlanner"],
            request_adapters= "default_planner_request_adapters/ResolveConstraintFrames default_planner_request_adapters/FixStartStateBounds default_planner_request_adapters/FixStartStateCollision default_planner_request_adapters/AddRuckigTrajectorySmoothing default_planner_request_adapters/AddTimeOptimalParameterization default_planner_request_adapters/FixStartStatePathConstraints default_planner_request_adapters/FixWorkspaceBounds default_planner_request_adapters/Empty",
            
            response_adapters=[
                "default_planning_response_adapters/AddTimeOptimalParameterization",
                "default_planning_response_adapters/ValidateSolution",
                "default_planning_response_adapters/DisplayMotionPath",
            ],
        )
    )

    ompl_planning_content = load_yaml(
        package_name=str(moveit_runtime_config_pkg.perform(context=context)),
        file_path=os.path.join("config", "ompl_planning.yaml"),
    )
    ompl_planning_pipeline_config["move_group"].update(ompl_planning_content)

    # Trajectory execution configuration
    controllers_file = PathJoinSubstitution([FindPackageShare(moveit_runtime_config_pkg), "config", "controllers.yaml"])
    controllers_yaml_evaluated_file = ParameterFile(controllers_file, allow_substs=True)
    controllers_yaml_evaluated_file.evaluate(context=context)
    controllers_content = load_yaml(
        package_name=str(moveit_runtime_config_pkg.perform(context=context)),
        file_path=os.path.join("config", str(controllers_yaml_evaluated_file.param_file)),
    )

    # The scaled_joint_trajectory_controller does not work on mock_hardware, switch to regular joint_trajectory_controller
    change_controllers = context.perform_substitution(use_mock_hardware)
    if change_controllers.lower() == "true":
        controllers_content["scaled_joint_trajectory_controller"]["default"] = False
        controllers_content["linear_slider_controller"]["default"] = True

    moveit_controllers_content = dict(
        moveit_simple_controller_manager=controllers_content,
        moveit_controller_manager="moveit_simple_controller_manager/MoveItSimpleControllerManager",
    )

    """ The following parameters are used to compute the allowed trajectory execution duration by scaling the expected execution duration and adding the margin afterwards. If this duration is exceeded the trajectory will be cancelled. The controller-specific parameters can be set as follows TODO: put this in a yaml file..."""
    trajectory_execution = { 
        "moveit_manage_controllers": False,
        "trajectory_execution.allowed_execution_during_scaling": 10.2,
        "trajectory_execution.allowed_goal_duration_margin": 0.5,
        "trajectory_execution.allowed_start_tolerance": 0.01,
    }

    planning_scene_monitor_parameters = dict(
        publish_planning_scene=True,
        publish_geometry_updates=True,
        publish_state_updates=True,
        publish_transform_updates=True,
    )

    warehouse_ros_config = dict(
        warehouse_plugin="warehouse_ros_sqlite::DatabaseConnection", warehouse_host=warehouse_sqlite_path
    )


    mcb = MoveItConfigsBuilder(robot_name="linear_slider", package_name="linear_slider_moveit_config")
    mcb.robot_description(
        file_path=os.path.join(get_package_share_directory("linear_slider_description"), "urdf/linear_slider.urdf.xacro"),
        mappings=robot_description
    )
    mcb.robot_description_semantic(
        file_path=os.path.join(get_package_share_directory("linear_slider_moveit_config"), "srdf/linear_slider.srdf.xacro"),
        mappings=robot_description_semantic
    )
    mcb.robot_description_kinematics(
        file_path=os.path.join(get_package_share_directory("linear_slider_moveit_config"), "config/tmp/tmp_kinematics.yaml"),
    )
    mcb.planning_pipelines(
        default_planning_pipeline="ompl",
        pipelines=[
            "ompl", "pilz_industrial_motion_planner", "chomp"
        ]
    )

    moveit_config = mcb.to_moveit_configs()
    # logger.info(f"{robot_description_kinematics_content}")
    # logger.warn(f"{moveit_config.robot_description_kinematics}")
    # logger.info(f"{robot_description_semantic_content.perform(context=context)}")
    # logger.warn(f"{moveit_config.robot_description_semantic['robot_description_semantic']}")

    

    # Start the actual move_group node/action server
    node_move_group = Node(
        package="moveit_ros_move_group",
        executable="move_group",
        output="screen",
        parameters=[
            robot_description,
            robot_description_semantic,
            # robot_description_kinematics_path,
            moveit_config.robot_description_kinematics,
            robot_description_planning,
            ompl_planning_pipeline_config,
            trajectory_execution,
            moveit_controllers_content,
            planning_scene_monitor_parameters,
            {"use_sim_time": use_sim_time},
            warehouse_ros_config,
        ],
    )
 
    # RViz
    rviz_config_file = PathJoinSubstitution(
        [get_package_share_directory("linear_slider_moveit_config"), "rviz", "linear_slider_moveit.rviz"]
    )
    node_rviz = Node(
        package="rviz2",
        executable="rviz2",
        name="rviz2_moveit",
        output="log",
        arguments=["-d", rviz_config_file],
        parameters=[
            robot_description,
            robot_description_semantic,
            moveit_config.robot_description_kinematics,
            robot_description_planning,
            ompl_planning_pipeline_config,
            warehouse_ros_config,
        ],
        condition=IfCondition(launch_rviz),
    )

    # MoveIt Servo
    servo_yaml_path = PathJoinSubstitution(
        [FindPackageShare(moveit_runtime_config_pkg), "config", "linear_slider_servo.yaml"]
    )
    servo_yaml_evaluated_file = ParameterFile(servo_yaml_path, allow_substs=True)
    servo_yaml_evaluated_file.evaluate(context=context)
    servo_yaml_content = load_yaml(
        package_name=str(moveit_runtime_config_pkg.perform(context=context)),
        file_path=os.path.join("config", str(servo_yaml_evaluated_file.param_file)),
    )
    # logger.warn(f"{servo_yaml_content}")
    servo_params = dict(moveit_servo=servo_yaml_content)
    node_servo = Node(
        package="moveit_servo",
        executable="servo_node_main",
        output="screen",
        parameters=[servo_params, robot_description, robot_description_semantic, moveit_config.robot_description_kinematics],
        condition=IfCondition(launch_servo)
    )

    nodes_to_start = [node_move_group, node_servo, node_rviz]

    return nodes_to_start


def generate_launch_description():
    declared_args = []
    declared_args.append(
        DeclareLaunchArgument(
            "description_file",
            default_value="linear_slider.urdf.xacro",
            description="URDF/xacro description file of the robot.",
        )
    )
    declared_args.append(
        DeclareLaunchArgument(
            "description_pkg",
            default_value="linear_slider_description",
            description="Description package with the robot URDF/xacro files. Usually, the argument is not set; it enables the use of a custom setup.",
        )
    )
    declared_args.append(
        DeclareLaunchArgument(
            "launch_rviz",
            default_value="true", 
            description="Launch RViz window."
        )
    )
    declared_args.append(
        DeclareLaunchArgument(
            "launch_servo",
            default_value="true",
            description="Launch servoing node."
        )
    )
    declared_args.append(
        DeclareLaunchArgument(
            "mock_sensor_commands",
            default_value="false",
            choices=["true", "false"],
            description="Enable fake command interfaces for sensors for simple simulation. Use only if `use_mock_hardware` parameter is true.",
        )
    )
    declared_args.append(
        DeclareLaunchArgument(
            "moveit_semantic_description_file",
            default_value="linear_slider.srdf.xacro",  # TODO: Get srdf.xacro like UR drivers
            description="MoveIt SRDF/xacro description file with the robot.",
        )
    )
    declared_args.append(
        DeclareLaunchArgument(
            "moveit_runtime_config_pkg",
            default_value="linear_slider_moveit_config",
            description="MoveIt config package with robot SRDF/xacro files. Usually, the argument is not set; it enables the use of a custom setup.",
        )
    )
    declared_args.append(
        DeclareLaunchArgument(
            "moveit_joint_limits_file",
            default_value="joint_limits.yaml",
            description="MoveIt joint limits that augment or override the values from the URDF robot description.",
        )
    )
    declared_args.append(
        DeclareLaunchArgument(
            "prefix",
            default_value="linear_slider__",
            description="Prefix of the joint names. Useful for multi-robot setup. If changed, joint names in the controllers' configuration need to be updated, or dynamically configured in the launch file.",
        )
    )
    declared_args.append(
        DeclareLaunchArgument(
            "use_mock_hardware",
            default_value="false",
            choices=["true", "false"],
            description="Start robot with fake hardware mirroring command to its states.",
        )
    )
    declared_args.append(
        DeclareLaunchArgument(
            "use_sim_time",
            default_value="false",
            description="Make MoveIt to use simulation time. This is needed for the trajectory planing in simulation.",
        )
    )
    declared_args.append(
        DeclareLaunchArgument(
            "warehouse_sqlite_path",
            default_value=os.path.expanduser("~/.ros/warehouse_ros.sqlite"),
            description="Path to where the warehouse database should be stored.",
        )
    )

    ld = LaunchDescription(declared_args + [OpaqueFunction(function=launch_setup)])

    return ld
$\endgroup$

2 Answers 2

1
$\begingroup$

In using a velocity-controlled Joint Trajectory Controller, I had at one point a line in my hardware interface a line which integrated the position based on the period since the last read:

linear_slider_.state.pos += period.seconds() * linear_slider_.state.vel;

Somehow this got deleted. An annoying bug to track down, but fortunately not an issue with ROS2.

$\endgroup$
0
$\begingroup$

I suggest that you skip all the moveit parts, and try to use the default ros2_control_node with the GenericSystem. What is happening then?

$\endgroup$
4
  • $\begingroup$ When running without MoveIt, rqt_joint_trajectory_controller still doesn't work, erroring out with Traceback (most recent call last): File "/opt/ros/humble/lib/python3.10/site-packages/rqt_joint_trajectory_controller/joint_trajectory_controller.py", line 426, in _update_cmd_cb, pos = self._joint_pos[name]["position"]. KeyError: 'position' ``` $\endgroup$ Commented Aug 14 at 17:17
  • $\begingroup$ Also, sending ros2 action send_goal /linear_slider_controller/follow_joint_trajectory control_msgs/action/FollowJointTrajectory "{trajectory: {joint_names: ['linear_slider__joint1'], points: [{positions: [0.0]}]}}" through the CLI also times out. $\endgroup$ Commented Aug 14 at 17:37
  • $\begingroup$ The error of rqt_jtc indicates a different error than "timeout". Has the joint correct limits (position, velocity) set in the URDF? $\endgroup$ Commented Aug 16 at 5:36
  • $\begingroup$ I just reviewed my URDF, it looks like I had hardcoded joint limits in the URDF and was importing some different ones in the MoveIt launch as well. I've dynamically configured the URDF now, the prismatic joint has the tag <limit effort="0.2" lower="-0.4" upper="0.4" velocity="0.5"/>. This did not fix my error, however. $\endgroup$ Commented Aug 16 at 19:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.