diff --git a/scripts/setup-robot-bringup.bash b/scripts/setup-robot-bringup.bash index 91b70432..1b4c464a 100755 --- a/scripts/setup-robot-bringup.bash +++ b/scripts/setup-robot-bringup.bash @@ -88,16 +88,20 @@ cp -n $ROS2_CONTROL_TEMPLATES/test_goal_publishers_config.yaml $ROBOT_FPC_PUB_YA for file_type in "${LAUNCH_FILE_TYPES[@]}"; do # Construct the file paths ROBOT_LAUNCH="launch/${ROBOT_NAME}.launch${file_type}" + ROBOT_CONTROL_LAUNCH="launch/${ROBOT_NAME}_control.launch${file_type}" TEST_FWD_POS_CTRL_LAUNCH="launch/test_forward_position_controller.launch${file_type}" TEST_JTC_LAUNCH="launch/test_joint_trajectory_controller.launch${file_type}" # Copy the templates to the destination with the specified file type - cp -n "$ROS2_CONTROL_TEMPLATES/robot_ros2_control.launch${file_type}" "${ROBOT_LAUNCH}" + if [ -f "$ROS2_CONTROL_TEMPLATES/robot.launch${file_type}" ]; then # only .xml for now + cp -n "$ROS2_CONTROL_TEMPLATES/robot.launch${file_type}" "${ROBOT_LAUNCH}" + fi + cp -n "$ROS2_CONTROL_TEMPLATES/robot_ros2_control.launch${file_type}" "${ROBOT_CONTROL_LAUNCH}" cp -n "$ROS2_CONTROL_TEMPLATES/test_forward_position_controller.launch${file_type}" "${TEST_FWD_POS_CTRL_LAUNCH}" cp -n "$ROS2_CONTROL_TEMPLATES/test_joint_trajectory_controller.launch${file_type}" "${TEST_JTC_LAUNCH}" # sed all needed files - FILES_TO_SED=($ROBOT_LAUNCH $TEST_FWD_POS_CTRL_LAUNCH $TEST_JTC_LAUNCH) + FILES_TO_SED=($ROBOT_LAUNCH $ROBOT_CONTROL_LAUNCH $TEST_FWD_POS_CTRL_LAUNCH $TEST_JTC_LAUNCH) for SED_FILE in "${FILES_TO_SED[@]}"; do sed -i "s/\\\$PKG_NAME\\\$/${PKG_NAME}/g" $SED_FILE @@ -123,14 +127,26 @@ done prepend_to_string="if(BUILD_TESTING)" sed -i "s/$prepend_to_string/install\(\\n DIRECTORY config launch\\n DESTINATION share\/\$\{PROJECT_NAME\}\\n\)\\n\\n$prepend_to_string/g" CMakeLists.txt -# extend README with general instructions +# extend main README with general instructions +if [ -f ../README.md ]; then + cat $ROS2_CONTROL_TEMPLATES/append_to_MAIN_README.md >> ../README.md + sed -i "s/\\\$PKG_NAME\\\$/${PKG_NAME}/g" ../README.md + sed -i "s/\\\$ROBOT_NAME\\\$/${ROBOT_NAME}/g" ../README.md + sed -i "s/\\\$DESCR_PKG_NAME\\\$/${DESCR_PKG_NAME}/g" ../README.md +else + echo "No README.md in parent directory found, skipping adding short bringup info." +fi + +# extend package README with general instructions if [ -f README.md ]; then - cat $ROS2_CONTROL_TEMPLATES/append_to_README.md >>README.md - sed -i "s/\\\$PKG_NAME\\\$/${PKG_NAME}/g" README.md - sed -i "s/\\\$ROBOT_NAME\\\$/${ROBOT_NAME}/g" README.md - sed -i "s/\\\$DESCR_PKG_NAME\\\$/${DESCR_PKG_NAME}/g" $SED_FILE + echo "#${PKG_NAME}\n\n" > README.md fi +cat $ROS2_CONTROL_TEMPLATES/append_to_README.md >>README.md +sed -i "s/\\\$PKG_NAME\\\$/${PKG_NAME}/g" README.md +sed -i "s/\\\$ROBOT_NAME\\\$/${ROBOT_NAME}/g" README.md +sed -i "s/\\\$DESCR_PKG_NAME\\\$/${DESCR_PKG_NAME}/g" README.md + # TODO: Add license checks # Compile and add new package the to the path diff --git a/scripts/setup-robot-description.bash b/scripts/setup-robot-description.bash index 9ac2530f..40d574f9 100755 --- a/scripts/setup-robot-description.bash +++ b/scripts/setup-robot-description.bash @@ -90,11 +90,15 @@ cp -n "$ROBOT_DESCRIPTION_TEMPLATES/materials.xacro" urdf/common/materials.xacro # Copy launch files for testing the description for file_type in "${LAUNCH_FILE_TYPES[@]}"; do mkdir -p launch + + ROBOT_DESCRIPTION_LAUNCH="launch/${ROBOT_NAME}_description.launch${file_type}" + cp -n "$ROBOT_DESCRIPTION_TEMPLATES/robot_description.launch${file_type}" $ROBOT_DESCRIPTION_LAUNCH + VIEW_ROBOT_LAUNCH="launch/view_${ROBOT_NAME}.launch${file_type}" cp -n "$ROBOT_DESCRIPTION_TEMPLATES/view_robot.launch${file_type}" $VIEW_ROBOT_LAUNCH # sed all needed files - FILES_TO_SED=($ROBOT_URDF_XACRO $ROBOT_MACRO $ROBOT_MACRO_ROS2_CONTROL $VIEW_ROBOT_LAUNCH) + FILES_TO_SED=($ROBOT_URDF_XACRO $ROBOT_MACRO $ROBOT_MACRO_ROS2_CONTROL $ROBOT_DESCRIPTION_LAUNCH $VIEW_ROBOT_LAUNCH) for SED_FILE in "${FILES_TO_SED[@]}"; do sed -i "s/\\\$PKG_NAME\\\$/${PKG_NAME}/g" $SED_FILE @@ -188,7 +192,7 @@ sed -i "/$pattern/a$lines_to_append" CMakeLists.txt # extend README with general instructions if [ ! -f README.md ]; then - echo "${PKG_NAME}\n\n" > README.md + echo "#${PKG_NAME}\n\n" > README.md fi cat $ROBOT_DESCRIPTION_TEMPLATES/append_to_README.md >> README.md diff --git a/templates/robot_description/robot_description.launch.py b/templates/robot_description/robot_description.launch.py new file mode 100644 index 00000000..a326a9a4 --- /dev/null +++ b/templates/robot_description/robot_description.launch.py @@ -0,0 +1,104 @@ +# Copyright (c) 2024, Stogl Robotics Consulting UG (haftungsbeschränkt) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Source of this file is https://github.com/StoglRobotics/ros_team_workspace repository. +# +# Author: Dr. Denis +# + +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution +from launch_ros.actions import Node +from launch_ros.substitutions import FindPackageShare +from launch.conditions import IfCondition + + +def generate_launch_description(): + # Declare arguments + declared_arguments = [] + declared_arguments.append( + DeclareLaunchArgument( + "description_package", + default_value="$PKG_NAME$", + description="Description package of the $ROBOT_NAME$. Usually the argument is not set, \ + it enables use of a custom description.", + ) + ) + declared_arguments.append( + DeclareLaunchArgument( + "prefix", + default_value='""', + description="Prefix of the joint names, useful for \ + multi-robot setup. If changed than also joint names in the controllers' configuration \ + have to be updated.", + ) + ) + declared_arguments.append( + DeclareLaunchArgument( + "launch_rviz", + default_value="false", + description="Launch Rviz?", + ) + ) + + # Initialize Arguments + description_package = LaunchConfiguration("description_package") + prefix = LaunchConfiguration("prefix") + launch_rviz = LaunchConfiguration("launch_rviz") + + # Get URDF via xacro + robot_description_content = Command( + [ + PathJoinSubstitution([FindExecutable(name="xacro")]), + " ", + PathJoinSubstitution( + [FindPackageShare(description_package), "urdf", "$ROBOT_NAME$.urdf.xacro"] + ), + " ", + "prefix:=", + prefix, + " ", + ] + ) + + robot_description = {"robot_description": robot_description_content} + + rviz_config_file = PathJoinSubstitution( + [FindPackageShare(description_package), "rviz", "$ROBOT_NAME$.rviz"] + ) + + robot_state_publisher_node = Node( + package="robot_state_publisher", + executable="robot_state_publisher", + output="both", + parameters=[robot_description], + ) + rviz_node = Node( + package="rviz2", + executable="rviz2", + name="rviz2", + output="log", + condition=IfCondition(launch_rviz), + arguments=["-d", rviz_config_file], + ) + + return LaunchDescription( + declared_arguments + + [ + robot_state_publisher_node, + rviz_node, + ] + ) diff --git a/templates/robot_description/robot_description.launch.xml b/templates/robot_description/robot_description.launch.xml new file mode 100644 index 00000000..d6f24cd6 --- /dev/null +++ b/templates/robot_description/robot_description.launch.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/robot_description/robot_macro.ros2_control.xacro b/templates/robot_description/robot_macro.ros2_control.xacro index e7c7d075..a6cfbe7b 100644 --- a/templates/robot_description/robot_macro.ros2_control.xacro +++ b/templates/robot_description/robot_macro.ros2_control.xacro @@ -16,6 +16,7 @@ mock_components/GenericSystem ${mock_sensor_commands} + true gazebo_ros2_control/GazeboSystem @@ -35,8 +36,12 @@ 0.0 - - + + 0.0 + + + 0.0 + @@ -46,8 +51,12 @@ 0.0 - - + + 0.0 + + + 0.0 + @@ -57,8 +66,12 @@ 0.0 - - + + 0.0 + + + 0.0 + @@ -68,8 +81,12 @@ 0.0 - - + + 0.0 + + + 0.0 + @@ -79,8 +96,12 @@ 0.0 - - + + 0.0 + + + 0.0 + @@ -90,16 +111,32 @@ 0.0 - - + + 0.0 + + + 0.0 + - - - - - - + + 0.0 + + + 0.0 + + + 0.0 + + + 0.0 + + + 0.0 + + + 0.0 + tool0 -100 100 diff --git a/templates/robot_description/robot_macro.xacro b/templates/robot_description/robot_macro.xacro index a99729eb..afd3ecd6 100644 --- a/templates/robot_description/robot_macro.xacro +++ b/templates/robot_description/robot_macro.xacro @@ -4,6 +4,10 @@ + + + + @@ -15,7 +19,7 @@ - + @@ -23,7 +27,7 @@ - + @@ -36,7 +40,7 @@ - + @@ -44,7 +48,7 @@ - + @@ -58,7 +62,7 @@ - + @@ -66,7 +70,7 @@ - + @@ -79,7 +83,7 @@ - + @@ -87,7 +91,7 @@ - + @@ -100,7 +104,7 @@ - + @@ -108,7 +112,7 @@ - + @@ -121,7 +125,7 @@ - + @@ -129,7 +133,7 @@ - + @@ -142,7 +146,7 @@ - + @@ -150,7 +154,7 @@ - + diff --git a/templates/robot_description/robot_moveit.launch.py b/templates/robot_description/robot_moveit.launch.py new file mode 100644 index 00000000..d03bd548 --- /dev/null +++ b/templates/robot_description/robot_moveit.launch.py @@ -0,0 +1,188 @@ +# Copyright (c) 2024, Stogl Robotics Consulting UG (haftungsbeschränkt) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Source of this file are templates in +# [RosTeamWorkspace](https://github.com/StoglRobotics/ros_team_workspace) repository. +# +# Authors: Nikola Banović, Dr. Denis +# +import os +import yaml +from ament_index_python.packages import get_package_share_directory + +from launch import LaunchDescription +from launch.actions import OpaqueFunction, DeclareLaunchArgument +from launch.conditions import IfCondition, UnlessCondition +from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution +from launch_ros.actions import Node +from launch_ros.substitutions import FindPackageShare + + +def load_yaml(package_name, file_path): + package_path = get_package_share_directory(package_name) + absolute_file_path = os.path.join(package_path, file_path) + + try: + with open(absolute_file_path) as file: + return yaml.safe_load(file) + except OSError: # parent of IOError, OSError *and* WindowsError where available + return None + + +def launch_setup(context, *args, **kwargs): + use_sim_time = LaunchConfiguration("use_sim_time") + launch_rviz = LaunchConfiguration("launch_rviz") + only_rviz = LaunchConfiguration("only_rviz") + + # Get SRDF via xacro + robot_srdf_content = Command( + [ + PathJoinSubstitution([FindExecutable(name="xacro")]), + " ", + PathJoinSubstitution( + [FindPackageShare("$PKG_NAME$"), "srdf", "$ROBOT_NAME$.srdf.xacro"] + ), + ] + ) + robot_description_semantic = { + "robot_description_semantic": robot_srdf_content.perform(context) + } + + moveit_config_package_name = "$ROBOT_NAME$_moveit_config" + + controllers_yaml = load_yaml( + moveit_config_package_name, "config/$ROBOT_NAME$/controllers.yaml" + ) + ompl_planning_yaml = load_yaml( + moveit_config_package_name, "config/$ROBOT_NAME$/ompl_planning.yaml" + ) + kinematics_yaml = load_yaml(moveit_config_package_name, "config/$ROBOT_NAME$/kinematics.yaml") + robot_description_kinematics = {"robot_description_kinematics": kinematics_yaml} + limits_yaml = load_yaml(moveit_config_package_name, "config/$ROBOT_NAME$/joint_limits.yaml") + robot_description_planning = {"robot_description_planning": limits_yaml} + + # Planning Configuration + ompl_planning_pipeline_config = { + "default_planning_pipeline": "ompl", + "planning_pipelines": ["ompl"], + "ompl": { + "planning_plugin": "ompl_interface/OMPLPlanner", + "request_adapters": """default_planner_request_adapters/AddTimeOptimalParameterization default_planner_request_adapters/FixWorkspaceBounds default_planner_request_adapters/FixStartStateBounds default_planner_request_adapters/FixStartStateCollision default_planner_request_adapters/FixStartStatePathConstraints""", + "start_state_max_bounds_error": 0.1, + }, + } + ompl_planning_pipeline_config["ompl"].update(ompl_planning_yaml) + + # Moveit controllers Configuration + moveit_controllers = { + "moveit_simple_controller_manager": controllers_yaml, + "moveit_controller_manager": "moveit_simple_controller_manager/MoveItSimpleControllerManager", + } + + # Trajectory Execution Configuration + trajectory_execution = { + "moveit_manage_controllers": True, + "trajectory_execution.allowed_execution_duration_scaling": 1.2, + "trajectory_execution.allowed_goal_duration_margin": 0.5, + "trajectory_execution.allowed_start_tolerance": 0.01, + "trajectory_execution.execution_duration_monitoring": False, + } + + plan_execution = { + "plan_execution.record_trajectory_state_frequency": 10.0, + } + + planning_scene_monitor_parameters = { + "publish_planning_scene": True, + "publish_geometry_updates": True, + "publish_state_updates": True, + "publish_transforms_updates": True, + } + + move_group_node = Node( + package="moveit_ros_move_group", + executable="move_group", + output="screen", + parameters=[ + robot_description_semantic, + robot_description_kinematics, + robot_description_planning, + ompl_planning_pipeline_config, + trajectory_execution, + plan_execution, + moveit_controllers, + planning_scene_monitor_parameters, + # PathJoinSubstitution( + # [FindPackageShare("$PKG_NAME$"), "$ROBOT_NAME$_moveit", "moveit_execution_and_control.yaml"] + # ), + # PathJoinSubstitution( + # [FindPackageShare("$PKG_NAME$"), "$ROBOT_NAME$_moveit", "planning_pipelines.yaml"] + # ), + {"use_sim_time": use_sim_time}, + ], + condition=UnlessCondition(only_rviz), + ) + + rviz_config_file = PathJoinSubstitution( + [FindPackageShare("$PKG_NAME$"), "rviz", "$ROBOT_NAME$_moveit.rviz"] + ) + rviz_node = Node( + package="rviz2", + executable="rviz2", + name="rviz2", + output="log", + arguments=["-d", rviz_config_file], + parameters=[ + robot_description_semantic, + robot_description_kinematics, + # PathJoinSubstitution( + # [FindPackageShare("$PKG_NAME$"), "$ROBOT_NAME$_moveit", "moveit_execution_and_control.yaml"] + # ), + # PathJoinSubstitution( + # [FindPackageShare("$PKG_NAME$"), "$ROBOT_NAME$_moveit", "planning_pipelines.yaml"] + # ), + {"use_sim_time": use_sim_time}, + ], + condition=IfCondition(launch_rviz or only_rviz), + ) + + return [move_group_node, rviz_node] + + +def generate_launch_description(): + declared_arguments = [] + declared_arguments.append( + DeclareLaunchArgument( + "use_sim_time", + default_value="false", + description="Use simulation clock instead of world clock", + ) + ) + declared_arguments.append( + DeclareLaunchArgument( + "launch_rviz", + default_value="false", + description="Launch Rviz?", + ) + ) + declared_arguments.append( + DeclareLaunchArgument( + "only_rviz", + default_value="false", + description="Start only rviz with MoveIt setup. This is useful when MoveIt is running on another machine, e.g., on the robot.", + ) + ) + + return LaunchDescription(declared_arguments + [OpaqueFunction(function=launch_setup)]) diff --git a/templates/robot_description/view_robot.launch.py b/templates/robot_description/view_robot.launch.py index d8274a87..55efbec9 100755 --- a/templates/robot_description/view_robot.launch.py +++ b/templates/robot_description/view_robot.launch.py @@ -19,81 +19,31 @@ # from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument -from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.actions import IncludeLaunchDescription +from launch.substitutions import PathJoinSubstitution from launch_ros.actions import Node from launch_ros.substitutions import FindPackageShare def generate_launch_description(): - # Declare arguments - declared_arguments = [] - declared_arguments.append( - DeclareLaunchArgument( - "description_package", - default_value="$PKG_NAME$", - description="Description package of the $ROBOT_NAME$. Usually the argument is not set, \ - it enables use of a custom description.", - ) - ) - declared_arguments.append( - DeclareLaunchArgument( - "prefix", - default_value='""', - description="Prefix of the joint names, useful for \ - multi-robot setup. If changed than also joint names in the controllers' configuration \ - have to be updated.", - ) - ) - - # Initialize Arguments - description_package = LaunchConfiguration("description_package") - prefix = LaunchConfiguration("prefix") - - # Get URDF via xacro - robot_description_content = Command( - [ - PathJoinSubstitution([FindExecutable(name="xacro")]), - " ", + robot_description_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource( PathJoinSubstitution( - [FindPackageShare(description_package), "urdf", "$ROBOT_NAME$.urdf.xacro"] - ), - " ", - "prefix:=", - prefix, - " ", - ] - ) - - robot_description = {"robot_description": robot_description_content} - - rviz_config_file = PathJoinSubstitution( - [FindPackageShare(description_package), "rviz", "$ROBOT_NAME$.rviz"] + [FindPackageShare("$PKG_NAME$"), "launch", "$ROBOT_NAME$_description.launch.py"] + ) + ), + launch_arguments={"launch_rviz": "true"}.items(), ) joint_state_publisher_node = Node( package="joint_state_publisher_gui", executable="joint_state_publisher_gui", ) - robot_state_publisher_node = Node( - package="robot_state_publisher", - executable="robot_state_publisher", - output="both", - parameters=[robot_description], - ) - rviz_node = Node( - package="rviz2", - executable="rviz2", - name="rviz2", - output="log", - arguments=["-d", rviz_config_file], - ) return LaunchDescription( - declared_arguments - + [ + [ + robot_description_launch, joint_state_publisher_node, - robot_state_publisher_node, - rviz_node, ] ) diff --git a/templates/robot_description/view_robot.launch.xml b/templates/robot_description/view_robot.launch.xml index d7076547..ba7a6162 100644 --- a/templates/robot_description/view_robot.launch.xml +++ b/templates/robot_description/view_robot.launch.xml @@ -18,20 +18,11 @@ Source of this file are templates in https://github.com/StoglRobotics/ros_team_w --> - - - + + + - - - - - diff --git a/templates/ros2_control/append_to_MAIN_README.md b/templates/ros2_control/append_to_MAIN_README.md index d459ea87..4b75a312 100644 --- a/templates/ros2_control/append_to_MAIN_README.md +++ b/templates/ros2_control/append_to_MAIN_README.md @@ -5,11 +5,11 @@ To test the mock hardware with standard controllers we first start the bringup with the mock hardware enabled: ``` -ros2 launch dte_ea_bringup dte_ea_1000.launch.xml use_mock_hardware:=true +ros2 launch $PKG_NAME$ $ROBOT_NAME$_control.launch.xml use_mock_hardware:=true ``` In a second terminal we then publish goals for the jtc: ``` -ros2 launch dte_ea_bringup test_joint_trajectory_controller.launch.xml +ros2 launch $PKG_NAME$ test_joint_trajectory_controller.launch.xml ``` After stopping the goal publisher, you can deactivate `JointTrajectoryController`, and activate `ForwardPositionController`: @@ -18,5 +18,5 @@ ros2 control switch_controllers --activate forward_position_controller --deactiv ``` After successful controller switch, start goal publisher for another controller: ``` -ros2 launch dte_ea_bringup test_forward_position_controller.launch.xml +ros2 launch $PKG_NAME$ test_forward_position_controller.launch.xml ``` diff --git a/templates/ros2_control/append_to_README.md b/templates/ros2_control/append_to_README.md index e21a1888..28c1fdf7 100644 --- a/templates/ros2_control/append_to_README.md +++ b/templates/ros2_control/append_to_README.md @@ -17,9 +17,9 @@ The general package structure is the following: │ ├── _forward_position_publisher.yaml # Setup test publisher for forward position controller │ └── _joint_trajectory_publisher.yaml # Setup test publisher for joint trajectory controller └── launch/ - ├── .launch.py # Robot's default launch file - ├── test_forward_position_controller.launch.py # Start test publisher for forward position controller - └── test_joint_trajectory_controller.launch.py # Start test publisher for joint trajectory controller + ├── .launch.xml # Robot's default launch file + ├── test_forward_position_controller.launch.xml # Start test publisher for forward position controller + └── test_joint_trajectory_controller.launch.xml # Start test publisher for joint trajectory controller ``` @@ -33,7 +33,7 @@ Consult the repository and [ros2_control documentation](https://ros-controls.git 1. Start robot's hardware and load controllers (default configuration starts mock hardware) ``` - ros2 launch $PKG_NAME$ $ROBOT_NAME$.launch.xml + ros2 launch $PKG_NAME$ $ROBOT_NAME$_control.launch.xml ``` 2. Open another terminal and check if your hardware is loaded properly: @@ -55,12 +55,37 @@ Consult the repository and [ros2_control documentation](https://ros-controls.git ## Loading Controllers To move the robot you should load and start controllers. -To get feedback about robot's state `JointStateController` is used. -to send command to the robot `ForwardCommandController` (direct goals) or `JointTrajectoryController` (interpolates trajectory). + +Check below the controller's setup for different components. + +### Mobile base + +To move the *differential drive* mobile robot [`DiffDriveController`](https://control.ros.org/master/doc/ros2_controllers/diff_drive_controller/doc/userdoc.html) controller is used. + +#### Differential Drive Controller +Diff Drive Controller provides calculation of inverse kinematics for differential drive kinematics, by receiving velocity commands in Cartesian space and sending them to the hardware in joint space. +In general it provides support for input velocities with (`geometry_msgs/TwistStamped`) and without (`geometry_msgs/Twist`) time stamp. + + **NOTE**: As of today, February 2024, when the controller is used with Nav2, the input without timestamp should be used and you should set `use_stamped_vel` parameter to false. (It is expected that from ROS 2 Jazzy stamped version and new message `VelocityStamped` will be used!). + +To test the configuration of the DiffDriveController and movement of the robot use either. + +1. `teleop twist keyboard`: + ``` + ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args --remap /cmd_vel:=/diff_drive_controller/cmd_vel_unstamped + ``` + +2. RQT-plugin `robot steering`. When you open it set the publish topic to `/diff_drive_controller/cmd_vel_unstamped` + + +### Arms + +To get feedback about robot's state [`JointStateBroadcaster`](https://control.ros.org/master/doc/ros2_controllers/joint_state_broadcaster/doc/userdoc.html) is used. +To send command to the robot [`ForwardCommandController`](https://control.ros.org/master/doc/ros2_controllers/forward_command_controller/doc/userdoc.html) (direct goals) or [`JointTrajectoryController`](https://control.ros.org/master/doc/ros2_controllers/joint_trajectory_controller/doc/userdoc.html) (interpolates trajectory). The sections below describe their usage. -### Joint State Controller -Joint state Controllers provides output of robot's internal states to `/joint_states` and `/dynamic_joint_states` ROS 2-topics. +#### Joint State Broadcaster +Joint State Broadcaster provides output of robot's internal states to `/joint_states` and `/dynamic_joint_states` ROS 2-topics. In a new terminal with sourced ROS 2 environment load, configure and start `joint_state_broadcaster`: ``` @@ -77,7 +102,7 @@ You should get similar response to: Now you should also see your robot represented correctly in the `rviz2`. -### Using Forward Command Controllers +#### Using Forward Command Controllers 1. If you want to test hardware with `ForwardCommandController` first load and configure it. Controller types are e.g., "position", "velocity", and depend on configured names in the [`config/$ROBOT_NAME$_controllers.yaml`](config/$ROBOT_NAME$_controllers.yaml): ``` @@ -123,7 +148,7 @@ Now you should also see your robot represented correctly in the `rviz2`. ``` b. Or you can start demo node which sends two goals every 5 seconds in a loop (**Only with position controller!**): ``` - ros2 launch $PKG_NAME$ test_forward_position_controller.launch.py + ros2 launch $PKG_NAME$ test_forward_position_controller.launch.xml ``` ### Using JointTrajectoryController (**Not working yet!**) @@ -158,7 +183,7 @@ Now you should also see your robot represented correctly in the `rviz2`. 3. Send command to the controller using test node: ``` - ros2 launch ros2_control_demo_robot test_joint_trajectory_controller.launch.py + ros2 launch ros2_control_demo_robot test_joint_trajectory_controller.launch.xml ``` **NOTE**: You can switch controllers (step 1 and 2) also with one command: diff --git a/templates/ros2_control/empty.sdf b/templates/ros2_control/empty.sdf new file mode 100644 index 00000000..5c95c05e --- /dev/null +++ b/templates/ros2_control/empty.sdf @@ -0,0 +1,67 @@ + + + + + 0.001 + 1.0 + + + + + + + + + + + + true + 0 0 10 0 0 0 + 0.8 0.8 0.8 1 + 0.2 0.2 0.2 1 + + 1000 + 0.9 + 0.01 + 0.001 + + -0.5 0.1 -0.9 + + + + true + + + + + 0 0 1 + 100 100 + + + + + + + 0 0 1 + 100 100 + + + + 0.8 0.8 0.8 1 + 0.8 0.8 0.8 1 + 0.8 0.8 0.8 1 + + + + + + + diff --git a/templates/ros2_control/robot.launch.xml b/templates/ros2_control/robot.launch.xml new file mode 100644 index 00000000..ab967cbc --- /dev/null +++ b/templates/ros2_control/robot.launch.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/ros2_control/robot_controllers.yaml b/templates/ros2_control/robot_controllers.yaml index 3c984fbd..d4b7d279 100644 --- a/templates/ros2_control/robot_controllers.yaml +++ b/templates/ros2_control/robot_controllers.yaml @@ -35,6 +35,10 @@ controller_manager: joint_trajectory_controller: # delete entry if controller is not applicable type: joint_trajectory_controller/JointTrajectoryController + diff_drive_controller: # delete entry if controller is not applicable + type: diff_drive_controller/DiffDriveController + + forward_position_controller: # delete entry if controller is not applicable ros__parameters: joints: @@ -46,6 +50,7 @@ forward_position_controller: # delete entry if controller is no - joint6 interface_name: position + forward_velocity_controller: # delete entry if controller is not applicable ros__parameters: joints: @@ -57,6 +62,7 @@ forward_velocity_controller: # delete entry if controller is no - joint6 interface_name: velocity + joint_trajectory_controller: # delete entry if controller is not applicable ros__parameters: joints: @@ -79,3 +85,52 @@ joint_trajectory_controller: # delete entry if controller is no constraints: stopped_velocity_tolerance: 0.01 # Defaults to 0.01 goal_time: 0.0 # Defaults to 0.0 (start immediately) + + +diff_drive_controller: + ros__parameters: + left_wheel_names: ["left_wheel_joint"] + right_wheel_names: ["right_wheel_joint"] + + wheel_separation: 0.20 + #wheels_per_side: 1 # actually 2, but both are controlled by 1 signal + wheel_radius: 0.035 + + wheel_separation_multiplier: 1.0 + left_wheel_radius_multiplier: 1.0 + right_wheel_radius_multiplier: 1.0 + + publish_rate: 50.0 + odom_frame_id: odom + base_frame_id: base_link + pose_covariance_diagonal : [0.001, 0.001, 0.001, 0.001, 0.001, 0.01] + twist_covariance_diagonal: [0.001, 0.001, 0.001, 0.001, 0.001, 0.01] + + open_loop: false + enable_odom_tf: true + + cmd_vel_timeout: 0.5 + #publish_limited_velocity: true + use_stamped_vel: false + #velocity_rolling_window_size: 10 + + # Velocity and acceleration limits + # Whenever a min_* is unspecified, default to -max_* + linear.x.has_velocity_limits: true + linear.x.has_acceleration_limits: true + linear.x.has_jerk_limits: false + linear.x.max_velocity: 1.0 + linear.x.min_velocity: -1.0 + linear.x.max_acceleration: 1.0 + linear.x.max_jerk: 0.0 + linear.x.min_jerk: 0.0 + + angular.z.has_velocity_limits: true + angular.z.has_acceleration_limits: true + angular.z.has_jerk_limits: false + angular.z.max_velocity: 1.0 + angular.z.min_velocity: -1.0 + angular.z.max_acceleration: 1.0 + angular.z.min_acceleration: -1.0 + angular.z.max_jerk: 0.0 + angular.z.min_jerk: 0.0 diff --git a/templates/ros2_control/robot_ros2_control.launch.py b/templates/ros2_control/robot_ros2_control.launch.py index b2506ca8..76e6d9a2 100644 --- a/templates/ros2_control/robot_ros2_control.launch.py +++ b/templates/ros2_control/robot_ros2_control.launch.py @@ -20,9 +20,15 @@ # from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, RegisterEventHandler, TimerAction +from launch.launch_description_sources import AnyLaunchDescriptionSource +from launch.actions import ( + DeclareLaunchArgument, + IncludeLaunchDescription, + RegisterEventHandler, + TimerAction, +) from launch.event_handlers import OnProcessExit, OnProcessStart -from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution +from launch.substitutions import LaunchConfiguration, PathJoinSubstitution from launch_ros.actions import Node from launch_ros.substitutions import FindPackageShare @@ -53,13 +59,6 @@ def generate_launch_description(): is not set, it enables use of a custom description.", ) ) - declared_arguments.append( - DeclareLaunchArgument( - "description_file", - default_value="$ROBOT_NAME$.urdf.xacro", - description="URDF/XACRO description file with the robot.", - ) - ) declared_arguments.append( DeclareLaunchArgument( "prefix", @@ -97,35 +96,29 @@ def generate_launch_description(): runtime_config_package = LaunchConfiguration("runtime_config_package") controllers_file = LaunchConfiguration("controllers_file") description_package = LaunchConfiguration("description_package") - description_file = LaunchConfiguration("description_file") prefix = LaunchConfiguration("prefix") use_mock_hardware = LaunchConfiguration("use_mock_hardware") mock_sensor_commands = LaunchConfiguration("mock_sensor_commands") robot_controller = LaunchConfiguration("robot_controller") - # Get URDF via xacro - robot_description_content = Command( - [ - PathJoinSubstitution([FindExecutable(name="xacro")]), - " ", + # Load robot description + robot_description_launch = IncludeLaunchDescription( + AnyLaunchDescriptionSource( PathJoinSubstitution( - [FindPackageShare(description_package), "urdf", description_file] - ), - " ", - "prefix:=", - prefix, - " ", - "use_mock_hardware:=", - use_mock_hardware, - " ", - "mock_sensor_commands:=", - mock_sensor_commands, - " ", - ] + [ + FindPackageShare(description_package), + "launch", + "$ROBOT_NAME$_description.launch.xml", + ] + ) + ), + launch_arguments={ + "prefix": prefix, + "use_mock_hardware": use_mock_hardware, + "mock_sensor_commands": mock_sensor_commands, + }.items(), ) - robot_description = {"robot_description": robot_description_content} - robot_controllers = PathJoinSubstitution( [FindPackageShare(runtime_config_package), "config", controllers_file] ) @@ -137,13 +130,8 @@ def generate_launch_description(): package="controller_manager", executable="ros2_control_node", output="both", - parameters=[robot_description, robot_controllers], - ) - robot_state_pub_node = Node( - package="robot_state_publisher", - executable="robot_state_publisher", - output="both", - parameters=[robot_description], + parameters=[robot_controllers], + remappings=[("~/robot_description", "/robot_description")], ) rviz_node = Node( package="rviz2", @@ -229,8 +217,8 @@ def generate_launch_description(): return LaunchDescription( declared_arguments + [ + robot_description_launch, control_node, - robot_state_pub_node, rviz_node, delay_joint_state_broadcaster_spawner_after_ros2_control_node, ] diff --git a/templates/ros2_control/robot_ros2_control.launch.xml b/templates/ros2_control/robot_ros2_control.launch.xml index ff75915a..2e152a11 100644 --- a/templates/ros2_control/robot_ros2_control.launch.xml +++ b/templates/ros2_control/robot_ros2_control.launch.xml @@ -42,6 +42,12 @@ Authors: Riyan Jose, Manuel Muth, Dr. Denis + + @@ -49,30 +55,45 @@ Authors: Riyan Jose, Manuel Muth, Dr. Denis default="forward_position_controller" description="Robot controller to start inactive. Choices are: [forward_position_controller, joint_trajectory_controller]."/> - + - - - - - + + + + + + + + - - - + + + + + - + + + + + - + + + - - + + + + - - + + + + diff --git a/templates/ros2_control/robot_ros2_control_sim.launch.py b/templates/ros2_control/robot_ros2_control_gazebo.launch.py similarity index 100% rename from templates/ros2_control/robot_ros2_control_sim.launch.py rename to templates/ros2_control/robot_ros2_control_gazebo.launch.py diff --git a/templates/ros2_control/robot_ros2_control_gazebo.launch.xml b/templates/ros2_control/robot_ros2_control_gazebo.launch.xml new file mode 100644 index 00000000..6f20475d --- /dev/null +++ b/templates/ros2_control/robot_ros2_control_gazebo.launch.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +